1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3# Copyright 2015 The Chromium Authors. All rights reserved. 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 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 subprocess 16import sys 17import shutil 18 19import wrapper_utils 20 21 22def collect_soname(args): 23 """Replaces: readelf -d $sofile | grep SONAME""" 24 toc = '' 25 readelf = subprocess.Popen(wrapper_utils.command_to_run( 26 [args.readelf, '-d', args.sofile]), 27 stdout=subprocess.PIPE, 28 bufsize=-1) 29 for line in readelf.stdout: 30 if b'SONAME' in line: 31 toc += line.decode() 32 return readelf.wait(), toc 33 34 35def collect_dyn_sym(args): 36 """Replaces: nm --format=posix -g -D $sofile | cut -f1-2 -d' '""" 37 toc = '' 38 _command = [args.nm] 39 if args.sofile.endswith('.dll'): 40 _command.append('--extern-only') 41 else: 42 _command.extend(['--format=posix', '-g', '-D']) 43 _command.append(args.sofile) 44 nm = subprocess.Popen(wrapper_utils.command_to_run(_command), 45 stdout=subprocess.PIPE, 46 bufsize=-1) 47 for line in nm.stdout: 48 toc += '{}\n'.format(' '.join(line.decode().split(' ', 2)[:2])) 49 return nm.wait(), toc 50 51 52def collect_toc(args): 53 result, toc = collect_soname(args) 54 if result == 0: 55 result, dynsym = collect_dyn_sym(args) 56 toc += dynsym 57 return result, toc 58 59 60def update_toc(tocfile, toc): 61 if os.path.exists(tocfile): 62 with open(tocfile, 'r') as f: 63 old_toc = f.read() 64 else: 65 old_toc = None 66 if toc != old_toc: 67 with open(tocfile, 'w') as fp: 68 fp.write(toc) 69 70 71def reformat_rsp_file(rspfile): 72 """ Move all implibs from --whole-archive section""" 73 with open(rspfile, "r") as fi: 74 rspcontent = fi.read() 75 result = [] 76 implibs = [] 77 naflag = False 78 for arg in rspcontent.split(" "): 79 if naflag and arg.endswith(".lib"): 80 implibs.append(arg) 81 continue 82 result.append(arg) 83 if arg == "-Wl,--whole-archive": 84 naflag = True 85 continue 86 if arg == "-Wl,--no-whole-archive": 87 naflag = False 88 result.extend(implibs) 89 90 with open(rspfile, "w") as fo: 91 fo.write(" ".join(result)) 92 93 94def main(): 95 parser = argparse.ArgumentParser(description=__doc__) 96 parser.add_argument('--readelf', 97 required=True, 98 help='The readelf binary to run', 99 metavar='PATH') 100 parser.add_argument('--nm', 101 required=True, 102 help='The nm binary to run', 103 metavar='PATH') 104 parser.add_argument('--strip', 105 help='The strip binary to run', 106 metavar='PATH') 107 parser.add_argument('--sofile', 108 required=True, 109 help='Shared object file produced by linking command', 110 metavar='FILE') 111 parser.add_argument('--tocfile', 112 required=False, 113 help='Output table-of-contents file', 114 metavar='FILE') 115 parser.add_argument('--map-file', 116 help=('Use --Wl,-Map to generate a map file. Will be ' 117 'gzipped if extension ends with .gz'), 118 metavar='FILE') 119 parser.add_argument('--output', 120 required=True, 121 help='Final output shared object file', 122 metavar='FILE') 123 parser.add_argument('--libfile', required=False, metavar='FILE') 124 parser.add_argument('command', nargs='+', help='Linking command') 125 args = parser.parse_args() 126 127 if args.sofile.endswith(".dll"): 128 rspfile = None 129 for a in args.command: 130 if a[0] == "@": 131 rspfile = a[1:] 132 break 133 if rspfile: 134 reformat_rsp_file(rspfile) 135 # Work-around for gold being slow-by-default. http://crbug.com/632230 136 fast_env = dict(os.environ) 137 fast_env['LC_ALL'] = 'C' 138 139 # First, run the actual link. 140 command = wrapper_utils.command_to_run(args.command) 141 result = wrapper_utils.run_link_with_optional_map_file( 142 command, env=fast_env, map_file=args.map_file) 143 144 if result != 0: 145 return result 146 147 # Next, generate the contents of the TOC file. 148 result, toc = collect_toc(args) 149 if result != 0: 150 return result 151 152 # If there is an existing TOC file with identical contents, leave it alone. 153 # Otherwise, write out the TOC file. 154 if args.tocfile: 155 update_toc(args.tocfile, toc) 156 157 # Finally, strip the linked shared object file (if desired). 158 if args.strip: 159 result = subprocess.call( 160 wrapper_utils.command_to_run( 161 [args.strip, '-o', args.output, args.sofile])) 162 if args.libfile: 163 libfile_name = os.path.basename(args.libfile) 164 sofile_output_dir = os.path.dirname(args.sofile) 165 unstripped_libfile = os.path.join(sofile_output_dir, libfile_name) 166 shutil.copy2(unstripped_libfile, args.libfile) 167 168 return result 169 170 171if __name__ == "__main__": 172 sys.exit(main()) 173