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 parser.add_argument('--mini-debug', 126 action='store_true', 127 default=False, 128 help='Add .gnu_debugdata section for stripped sofile') 129 args = parser.parse_args() 130 131 if args.sofile.endswith(".dll"): 132 rspfile = None 133 for a in args.command: 134 if a[0] == "@": 135 rspfile = a[1:] 136 break 137 if rspfile: 138 reformat_rsp_file(rspfile) 139 # Work-around for gold being slow-by-default. http://crbug.com/632230 140 fast_env = dict(os.environ) 141 fast_env['LC_ALL'] = 'C' 142 143 # First, run the actual link. 144 command = wrapper_utils.command_to_run(args.command) 145 result = wrapper_utils.run_link_with_optional_map_file( 146 command, env=fast_env, map_file=args.map_file) 147 148 if result != 0: 149 return result 150 151 # Next, generate the contents of the TOC file. 152 result, toc = collect_toc(args) 153 if result != 0: 154 return result 155 156 # If there is an existing TOC file with identical contents, leave it alone. 157 # Otherwise, write out the TOC file. 158 if args.tocfile: 159 update_toc(args.tocfile, toc) 160 161 # Finally, strip the linked shared object file (if desired). 162 if args.strip: 163 result = subprocess.call( 164 wrapper_utils.command_to_run( 165 [args.strip, '-o', args.output, args.sofile])) 166 if args.libfile: 167 libfile_name = os.path.basename(args.libfile) 168 sofile_output_dir = os.path.dirname(args.sofile) 169 unstripped_libfile = os.path.join(sofile_output_dir, libfile_name) 170 shutil.copy2(unstripped_libfile, args.libfile) 171 172 if args.mini_debug and not args.sofile.endswith(".dll"): 173 unstripped_libfile = os.path.abspath(args.sofile) 174 script_path = os.path.join( 175 os.path.dirname(__file__), 'mini_debug_info.py') 176 ohos_root_path = os.path.join(os.path.dirname(__file__), '../..') 177 result = subprocess.call( 178 wrapper_utils.command_to_run( 179 ['python3', script_path, '--unstripped-path', unstripped_libfile, '--stripped-path', args.output, 180 '--root-path', ohos_root_path])) 181 182 return result 183 184 185if __name__ == "__main__": 186 sys.exit(main()) 187