1#!/usr/bin/env python3 2# Copyright 2015 The Chromium Authors 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 8This script exists to avoid using complex shell commands in 9gcc_toolchain.gni's tool("solink"), in case the host running the compiler 10does not have a POSIX-like shell (e.g. Windows). 11""" 12 13import argparse 14import os 15import shlex 16import subprocess 17import sys 18 19import wrapper_utils 20 21 22def CollectSONAME(args): 23 """Replaces: readelf -d $sofile | grep SONAME""" 24 # TODO(crbug.com/1259067): Come up with a way to get this info without having 25 # to bundle readelf in the toolchain package. 26 toc = '' 27 readelf = subprocess.Popen(wrapper_utils.CommandToRun( 28 [args.readelf, '-d', args.sofile]), 29 stdout=subprocess.PIPE, 30 bufsize=-1, 31 universal_newlines=True) 32 for line in readelf.stdout: 33 if 'SONAME' in line: 34 toc += line 35 return readelf.wait(), toc 36 37 38def CollectDynSym(args): 39 """Replaces: nm --format=posix -g -D -p $sofile | cut -f1-2 -d' '""" 40 toc = '' 41 nm = subprocess.Popen(wrapper_utils.CommandToRun( 42 [args.nm, '--format=posix', '-g', '-D', '-p', args.sofile]), 43 stdout=subprocess.PIPE, 44 bufsize=-1, 45 universal_newlines=True) 46 for line in nm.stdout: 47 toc += ' '.join(line.split(' ', 2)[:2]) + '\n' 48 return nm.wait(), toc 49 50 51def CollectTOC(args): 52 result, toc = CollectSONAME(args) 53 if result == 0: 54 result, dynsym = CollectDynSym(args) 55 toc += dynsym 56 return result, toc 57 58 59def UpdateTOC(tocfile, toc): 60 if os.path.exists(tocfile): 61 old_toc = open(tocfile, 'r').read() 62 else: 63 old_toc = None 64 if toc != old_toc: 65 open(tocfile, 'w').write(toc) 66 67 68def CollectInputs(out, args): 69 for x in args: 70 if x.startswith('@'): 71 with open(x[1:]) as rsp: 72 CollectInputs(out, shlex.split(rsp.read())) 73 elif not x.startswith('-') and (x.endswith('.o') or x.endswith('.a')): 74 out.write(x) 75 out.write('\n') 76 77 78def InterceptFlag(flag, command): 79 ret = flag in command 80 if ret: 81 command.remove(flag) 82 return ret 83 84 85def SafeDelete(path): 86 try: 87 os.unlink(path) 88 except OSError: 89 pass 90 91 92def main(): 93 parser = argparse.ArgumentParser(description=__doc__) 94 parser.add_argument('--readelf', 95 required=True, 96 help='The readelf binary to run', 97 metavar='PATH') 98 parser.add_argument('--nm', 99 required=True, 100 help='The nm binary to run', 101 metavar='PATH') 102 parser.add_argument('--strip', 103 help='The strip binary to run', 104 metavar='PATH') 105 parser.add_argument('--dwp', help='The dwp binary to run', metavar='PATH') 106 parser.add_argument('--sofile', 107 required=True, 108 help='Shared object file produced by linking command', 109 metavar='FILE') 110 parser.add_argument('--tocfile', 111 required=True, 112 help='Output table-of-contents file', 113 metavar='FILE') 114 parser.add_argument('--map-file', 115 help=('Use --Wl,-Map to generate a map file. Will be ' 116 'gzipped if extension ends with .gz'), 117 metavar='FILE') 118 parser.add_argument('--output', 119 required=True, 120 help='Final output shared object file', 121 metavar='FILE') 122 parser.add_argument('command', nargs='+', 123 help='Linking command') 124 args = parser.parse_args() 125 126 # Work-around for gold being slow-by-default. http://crbug.com/632230 127 fast_env = dict(os.environ) 128 fast_env['LC_ALL'] = 'C' 129 130 # Extract flags passed through ldflags but meant for this script. 131 # https://crbug.com/954311 tracks finding a better way to plumb these. 132 partitioned_library = InterceptFlag('--partitioned-library', args.command) 133 collect_inputs_only = InterceptFlag('--collect-inputs-only', args.command) 134 135 # Partitioned .so libraries are used only for splitting apart in a subsequent 136 # step. 137 # 138 # - The TOC file optimization isn't useful, because the partition libraries 139 # must always be re-extracted if the combined library changes (and nothing 140 # should be depending on the combined library's dynamic symbol table). 141 # - Stripping isn't necessary, because the combined library is not used in 142 # production or published. 143 # 144 # Both of these operations could still be done, they're needless work, and 145 # tools would need to be updated to handle and/or not complain about 146 # partitioned libraries. Instead, to keep Ninja happy, simply create dummy 147 # files for the TOC and stripped lib. 148 if collect_inputs_only or partitioned_library: 149 open(args.output, 'w').close() 150 open(args.tocfile, 'w').close() 151 152 # Instead of linking, records all inputs to a file. This is used by 153 # enable_resource_allowlist_generation in order to avoid needing to 154 # link (which is slow) to build the resources allowlist. 155 if collect_inputs_only: 156 if args.map_file: 157 open(args.map_file, 'w').close() 158 if args.dwp: 159 open(args.sofile + '.dwp', 'w').close() 160 161 with open(args.sofile, 'w') as f: 162 CollectInputs(f, args.command) 163 return 0 164 165 # First, run the actual link. 166 command = wrapper_utils.CommandToRun(args.command) 167 result = wrapper_utils.RunLinkWithOptionalMapFile(command, 168 env=fast_env, 169 map_file=args.map_file) 170 171 if result != 0: 172 return result 173 174 # If dwp is set, then package debug info for this SO. 175 dwp_proc = None 176 if args.dwp: 177 # Explicit delete to account for symlinks (when toggling between 178 # debug/release). 179 SafeDelete(args.sofile + '.dwp') 180 # Suppress warnings about duplicate CU entries (https://crbug.com/1264130) 181 dwp_proc = subprocess.Popen(wrapper_utils.CommandToRun( 182 [args.dwp, '-e', args.sofile, '-o', args.sofile + '.dwp']), 183 stderr=subprocess.DEVNULL) 184 185 if not partitioned_library: 186 # Next, generate the contents of the TOC file. 187 result, toc = CollectTOC(args) 188 if result != 0: 189 return result 190 191 # If there is an existing TOC file with identical contents, leave it alone. 192 # Otherwise, write out the TOC file. 193 UpdateTOC(args.tocfile, toc) 194 195 # Finally, strip the linked shared object file (if desired). 196 if args.strip: 197 result = subprocess.call( 198 wrapper_utils.CommandToRun( 199 [args.strip, '-o', args.output, args.sofile])) 200 201 if dwp_proc: 202 dwp_result = dwp_proc.wait() 203 if dwp_result != 0: 204 sys.stderr.write('dwp failed with error code {}\n'.format(dwp_result)) 205 return dwp_result 206 207 return result 208 209 210if __name__ == "__main__": 211 sys.exit(main()) 212