Ben Murdoch | 109988c | 2016-05-18 11:27:45 +0100 | [diff] [blame^] | 1 | #!/usr/bin/env python |
| 2 | # Copyright 2015 The Chromium Authors. All rights reserved. |
| 3 | # Use of this source code is governed by a BSD-style license that can be |
| 4 | # found in the LICENSE file. |
| 5 | |
| 6 | """Runs 'ld -shared' and generates a .TOC file that's untouched when unchanged. |
| 7 | |
| 8 | This script exists to avoid using complex shell commands in |
| 9 | gcc_toolchain.gni's tool("solink"), in case the host running the compiler |
| 10 | does not have a POSIX-like shell (e.g. Windows). |
| 11 | """ |
| 12 | |
| 13 | import argparse |
| 14 | import os |
| 15 | import re |
| 16 | import subprocess |
| 17 | import sys |
| 18 | |
| 19 | |
| 20 | # When running on a Windows host and using a toolchain whose tools are |
| 21 | # actually wrapper scripts (i.e. .bat files on Windows) rather than binary |
| 22 | # executables, the "command" to run has to be prefixed with this magic. |
| 23 | # The GN toolchain definitions take care of that for when GN/Ninja is |
| 24 | # running the tool directly. When that command is passed in to this |
| 25 | # script, it appears as a unitary string but needs to be split up so that |
| 26 | # just 'cmd' is the actual command given to Python's subprocess module. |
| 27 | BAT_PREFIX = 'cmd /c call ' |
| 28 | |
| 29 | def CommandToRun(command): |
| 30 | if command[0].startswith(BAT_PREFIX): |
| 31 | command = command[0].split(None, 3) + command[1:] |
| 32 | return command |
| 33 | |
| 34 | |
| 35 | def CollectSONAME(args): |
| 36 | """Replaces: readelf -d $sofile | grep SONAME""" |
| 37 | toc = '' |
| 38 | readelf = subprocess.Popen(CommandToRun([args.readelf, '-d', args.sofile]), |
| 39 | stdout=subprocess.PIPE, bufsize=-1) |
| 40 | for line in readelf.stdout: |
| 41 | if 'SONAME' in line: |
| 42 | toc += line |
| 43 | return readelf.wait(), toc |
| 44 | |
| 45 | |
| 46 | def CollectDynSym(args): |
| 47 | """Replaces: nm --format=posix -g -D $sofile | cut -f1-2 -d' '""" |
| 48 | toc = '' |
| 49 | nm = subprocess.Popen(CommandToRun([ |
| 50 | args.nm, '--format=posix', '-g', '-D', args.sofile]), |
| 51 | stdout=subprocess.PIPE, bufsize=-1) |
| 52 | for line in nm.stdout: |
| 53 | toc += ' '.join(line.split(' ', 2)[:2]) + '\n' |
| 54 | return nm.wait(), toc |
| 55 | |
| 56 | |
| 57 | def CollectTOC(args): |
| 58 | result, toc = CollectSONAME(args) |
| 59 | if result == 0: |
| 60 | result, dynsym = CollectDynSym(args) |
| 61 | toc += dynsym |
| 62 | return result, toc |
| 63 | |
| 64 | |
| 65 | def UpdateTOC(tocfile, toc): |
| 66 | if os.path.exists(tocfile): |
| 67 | old_toc = open(tocfile, 'r').read() |
| 68 | else: |
| 69 | old_toc = None |
| 70 | if toc != old_toc: |
| 71 | open(tocfile, 'w').write(toc) |
| 72 | |
| 73 | |
| 74 | def main(): |
| 75 | parser = argparse.ArgumentParser(description=__doc__) |
| 76 | parser.add_argument('--readelf', |
| 77 | required=True, |
| 78 | help='The readelf binary to run', |
| 79 | metavar='PATH') |
| 80 | parser.add_argument('--nm', |
| 81 | required=True, |
| 82 | help='The nm binary to run', |
| 83 | metavar='PATH') |
| 84 | parser.add_argument('--strip', |
| 85 | help='The strip binary to run', |
| 86 | metavar='PATH') |
| 87 | parser.add_argument('--sofile', |
| 88 | required=True, |
| 89 | help='Shared object file produced by linking command', |
| 90 | metavar='FILE') |
| 91 | parser.add_argument('--tocfile', |
| 92 | required=True, |
| 93 | help='Output table-of-contents file', |
| 94 | metavar='FILE') |
| 95 | parser.add_argument('--output', |
| 96 | required=True, |
| 97 | help='Final output shared object file', |
| 98 | metavar='FILE') |
| 99 | parser.add_argument('command', nargs='+', |
| 100 | help='Linking command') |
| 101 | args = parser.parse_args() |
| 102 | |
| 103 | # First, run the actual link. |
| 104 | result = subprocess.call(CommandToRun(args.command)) |
| 105 | if result != 0: |
| 106 | return result |
| 107 | |
| 108 | # Next, generate the contents of the TOC file. |
| 109 | result, toc = CollectTOC(args) |
| 110 | if result != 0: |
| 111 | return result |
| 112 | |
| 113 | # If there is an existing TOC file with identical contents, leave it alone. |
| 114 | # Otherwise, write out the TOC file. |
| 115 | UpdateTOC(args.tocfile, toc) |
| 116 | |
| 117 | # Finally, strip the linked shared object file (if desired). |
| 118 | if args.strip: |
| 119 | result = subprocess.call(CommandToRun([args.strip, '--strip-unneeded', |
| 120 | '-o', args.output, args.sofile])) |
| 121 | |
| 122 | return result |
| 123 | |
| 124 | |
| 125 | if __name__ == "__main__": |
| 126 | sys.exit(main()) |