123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211 |
- #!/usr/bin/env python
- # Copyright 2015 The Chromium Authors. All rights reserved.
- # Use of this source code is governed by a BSD-style license that can be
- # found in the LICENSE file.
- """Runs 'ld -shared' and generates a .TOC file that's untouched when unchanged.
- This script exists to avoid using complex shell commands in
- gcc_toolchain.gni's tool("solink"), in case the host running the compiler
- does not have a POSIX-like shell (e.g. Windows).
- """
- import argparse
- import os
- import shlex
- import subprocess
- import sys
- import wrapper_utils
- def CollectSONAME(args):
- """Replaces: readelf -d $sofile | grep SONAME"""
- # TODO(crbug.com/1259067): Come up with a way to get this info without having
- # to bundle readelf in the toolchain package.
- toc = ''
- readelf = subprocess.Popen(wrapper_utils.CommandToRun(
- [args.readelf, '-d', args.sofile]),
- stdout=subprocess.PIPE,
- bufsize=-1,
- universal_newlines=True)
- for line in readelf.stdout:
- if 'SONAME' in line:
- toc += line
- return readelf.wait(), toc
- def CollectDynSym(args):
- """Replaces: nm --format=posix -g -D -p $sofile | cut -f1-2 -d' '"""
- toc = ''
- nm = subprocess.Popen(wrapper_utils.CommandToRun(
- [args.nm, '--format=posix', '-g', '-D', '-p', args.sofile]),
- stdout=subprocess.PIPE,
- bufsize=-1,
- universal_newlines=True)
- for line in nm.stdout:
- toc += ' '.join(line.split(' ', 2)[:2]) + '\n'
- return nm.wait(), toc
- def CollectTOC(args):
- result, toc = CollectSONAME(args)
- if result == 0:
- result, dynsym = CollectDynSym(args)
- toc += dynsym
- return result, toc
- def UpdateTOC(tocfile, toc):
- if os.path.exists(tocfile):
- old_toc = open(tocfile, 'r').read()
- else:
- old_toc = None
- if toc != old_toc:
- open(tocfile, 'w').write(toc)
- def CollectInputs(out, args):
- for x in args:
- if x.startswith('@'):
- with open(x[1:]) as rsp:
- CollectInputs(out, shlex.split(rsp.read()))
- elif not x.startswith('-') and (x.endswith('.o') or x.endswith('.a')):
- out.write(x)
- out.write('\n')
- def InterceptFlag(flag, command):
- ret = flag in command
- if ret:
- command.remove(flag)
- return ret
- def SafeDelete(path):
- try:
- os.unlink(path)
- except OSError:
- pass
- def main():
- parser = argparse.ArgumentParser(description=__doc__)
- parser.add_argument('--readelf',
- required=True,
- help='The readelf binary to run',
- metavar='PATH')
- parser.add_argument('--nm',
- required=True,
- help='The nm binary to run',
- metavar='PATH')
- parser.add_argument('--strip',
- help='The strip binary to run',
- metavar='PATH')
- parser.add_argument('--dwp', help='The dwp binary to run', metavar='PATH')
- parser.add_argument('--sofile',
- required=True,
- help='Shared object file produced by linking command',
- metavar='FILE')
- parser.add_argument('--tocfile',
- required=True,
- help='Output table-of-contents file',
- metavar='FILE')
- parser.add_argument('--map-file',
- help=('Use --Wl,-Map to generate a map file. Will be '
- 'gzipped if extension ends with .gz'),
- metavar='FILE')
- parser.add_argument('--output',
- required=True,
- help='Final output shared object file',
- metavar='FILE')
- parser.add_argument('command', nargs='+',
- help='Linking command')
- args = parser.parse_args()
- # Work-around for gold being slow-by-default. http://crbug.com/632230
- fast_env = dict(os.environ)
- fast_env['LC_ALL'] = 'C'
- # Extract flags passed through ldflags but meant for this script.
- # https://crbug.com/954311 tracks finding a better way to plumb these.
- partitioned_library = InterceptFlag('--partitioned-library', args.command)
- collect_inputs_only = InterceptFlag('--collect-inputs-only', args.command)
- # Partitioned .so libraries are used only for splitting apart in a subsequent
- # step.
- #
- # - The TOC file optimization isn't useful, because the partition libraries
- # must always be re-extracted if the combined library changes (and nothing
- # should be depending on the combined library's dynamic symbol table).
- # - Stripping isn't necessary, because the combined library is not used in
- # production or published.
- #
- # Both of these operations could still be done, they're needless work, and
- # tools would need to be updated to handle and/or not complain about
- # partitioned libraries. Instead, to keep Ninja happy, simply create dummy
- # files for the TOC and stripped lib.
- if collect_inputs_only or partitioned_library:
- open(args.output, 'w').close()
- open(args.tocfile, 'w').close()
- # Instead of linking, records all inputs to a file. This is used by
- # enable_resource_allowlist_generation in order to avoid needing to
- # link (which is slow) to build the resources allowlist.
- if collect_inputs_only:
- if args.map_file:
- open(args.map_file, 'w').close()
- if args.dwp:
- open(args.sofile + '.dwp', 'w').close()
- with open(args.sofile, 'w') as f:
- CollectInputs(f, args.command)
- return 0
- # First, run the actual link.
- command = wrapper_utils.CommandToRun(args.command)
- result = wrapper_utils.RunLinkWithOptionalMapFile(command,
- env=fast_env,
- map_file=args.map_file)
- if result != 0:
- return result
- # If dwp is set, then package debug info for this SO.
- dwp_proc = None
- if args.dwp:
- # Explicit delete to account for symlinks (when toggling between
- # debug/release).
- SafeDelete(args.sofile + '.dwp')
- # Suppress warnings about duplicate CU entries (https://crbug.com/1264130)
- dwp_proc = subprocess.Popen(wrapper_utils.CommandToRun(
- [args.dwp, '-e', args.sofile, '-o', args.sofile + '.dwp']),
- stderr=subprocess.DEVNULL)
- if not partitioned_library:
- # Next, generate the contents of the TOC file.
- result, toc = CollectTOC(args)
- if result != 0:
- return result
- # If there is an existing TOC file with identical contents, leave it alone.
- # Otherwise, write out the TOC file.
- UpdateTOC(args.tocfile, toc)
- # Finally, strip the linked shared object file (if desired).
- if args.strip:
- result = subprocess.call(
- wrapper_utils.CommandToRun(
- [args.strip, '-o', args.output, args.sofile]))
- if dwp_proc:
- dwp_result = dwp_proc.wait()
- if dwp_result != 0:
- sys.stderr.write('dwp failed with error code {}\n'.format(dwp_result))
- return dwp_result
- return result
- if __name__ == "__main__":
- sys.exit(main())
|