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 18import json 19 20import wrapper_utils 21 22 23def collect_soname(args): 24 """Replaces: readelf -d $sofile | grep SONAME""" 25 toc = '' 26 readelf = subprocess.Popen(wrapper_utils.command_to_run( 27 [args.readelf, '-d', args.sofile]), 28 stdout=subprocess.PIPE, 29 bufsize=-1) 30 for line in readelf.stdout: 31 if b'SONAME' in line: 32 toc += line.decode() 33 return readelf.wait(), toc 34 35 36def collect_dyn_sym(args): 37 """Replaces: nm --format=posix -g -D $sofile | cut -f1-2 -d' '""" 38 toc = '' 39 _command = [args.nm] 40 if args.sofile.endswith('.dll'): 41 _command.append('--extern-only') 42 else: 43 _command.extend(['--format=posix', '-g', '-D']) 44 _command.append(args.sofile) 45 nm = subprocess.Popen(wrapper_utils.command_to_run(_command), 46 stdout=subprocess.PIPE, 47 bufsize=-1) 48 for line in nm.stdout: 49 toc += '{}\n'.format(' '.join(line.decode().split(' ', 2)[:2])) 50 return nm.wait(), toc 51 52 53def collect_toc(args): 54 result, toc = collect_soname(args) 55 if result == 0: 56 result, dynsym = collect_dyn_sym(args) 57 toc += dynsym 58 return result, toc 59 60 61def update_toc(tocfile, toc): 62 if os.path.exists(tocfile): 63 with open(tocfile, 'r') as f: 64 old_toc = f.read() 65 else: 66 old_toc = None 67 if toc != old_toc: 68 with open(tocfile, 'w') as fp: 69 fp.write(toc) 70 71 72def reformat_rsp_file(rspfile): 73 """ Move all implibs from --whole-archive section""" 74 with open(rspfile, "r") as fi: 75 rspcontent = fi.read() 76 result = [] 77 implibs = [] 78 naflag = False 79 for arg in rspcontent.split(" "): 80 if naflag and arg.endswith(".lib"): 81 implibs.append(arg) 82 continue 83 result.append(arg) 84 if arg == "-Wl,--whole-archive": 85 naflag = True 86 continue 87 if arg == "-Wl,--no-whole-archive": 88 naflag = False 89 result.extend(implibs) 90 91 with open(rspfile, "w") as fo: 92 fo.write(" ".join(result)) 93 94 95def get_install_dest_dir(args): 96 file_list = os.listdir(args.target_out_dir) 97 target_name = args.target_name 98 match_file = f'{target_name}_module_info.json' 99 if not f'{target_name}_module_info.json' in file_list: 100 return None 101 with open(os.path.join(args.target_out_dir, match_file), "r") as f: 102 module_info = json.load(f) 103 dest_dirs = module_info.get("dest") 104 for dest_dir in dest_dirs: 105 if dest_dir.startswith("system"): 106 return dest_dir 107 return None 108 109 110def main(): 111 parser = argparse.ArgumentParser(description=__doc__) 112 parser.add_argument('--readelf', 113 required=True, 114 help='The readelf binary to run', 115 metavar='PATH') 116 parser.add_argument('--nm', 117 required=True, 118 help='The nm binary to run', 119 metavar='PATH') 120 parser.add_argument('--strip', 121 help='The strip binary to run', 122 metavar='PATH') 123 parser.add_argument('--strip-debug-whitelist', 124 help='The strip debug whitelist, lines of which are names of shared objects with .symtab kept.', 125 metavar='PATH') 126 parser.add_argument('--sofile', 127 required=True, 128 help='Shared object file produced by linking command', 129 metavar='FILE') 130 parser.add_argument('--tocfile', 131 required=False, 132 help='Output table-of-contents file', 133 metavar='FILE') 134 parser.add_argument('--map-file', 135 help=('Use --Wl,-Map to generate a map file. Will be ' 136 'gzipped if extension ends with .gz'), 137 metavar='FILE') 138 parser.add_argument('--output', 139 required=True, 140 help='Final output shared object file', 141 metavar='FILE') 142 parser.add_argument('--libfile', required=False, metavar='FILE') 143 parser.add_argument('command', nargs='+', help='Linking command') 144 parser.add_argument('--mini-debug', 145 action='store_true', 146 default=False, 147 help='Add .gnu_debugdata section for stripped sofile') 148 parser.add_argument('--target-name', help='') 149 parser.add_argument('--target-out-dir', help='') 150 parser.add_argument('--allowed-lib-list', help='') 151 parser.add_argument('--clang-base-dir', help='') 152 args = parser.parse_args() 153 154 if args.sofile.endswith(".dll"): 155 rspfile = None 156 for a in args.command: 157 if a[0] == "@": 158 rspfile = a[1:] 159 break 160 if rspfile: 161 reformat_rsp_file(rspfile) 162 # Work-around for gold being slow-by-default. http://crbug.com/632230 163 fast_env = dict(os.environ) 164 fast_env['LC_ALL'] = 'C' 165 166 # First, run the actual link. 167 command = wrapper_utils.command_to_run(args.command) 168 result = wrapper_utils.run_link_with_optional_map_file( 169 command, env=fast_env, map_file=args.map_file) 170 171 if result != 0: 172 return result 173 174 # Next, generate the contents of the TOC file. 175 result, toc = collect_toc(args) 176 if result != 0: 177 return result 178 179 # If there is an existing TOC file with identical contents, leave it alone. 180 # Otherwise, write out the TOC file. 181 if args.tocfile: 182 update_toc(args.tocfile, toc) 183 184 # Finally, strip the linked shared object file (if desired). 185 if args.strip: 186 strip_option = [] 187 strip_command = [args.strip, '-o', args.output] 188 189 #ADLT so should not be stripped 190 if args.target_out_dir and os.path.exists(args.target_out_dir): 191 install_dest = get_install_dest_dir(args) 192 else: 193 install_dest = None 194 if install_dest: 195 with open(args.allowed_lib_list, 'r') as f: 196 lines = f.readlines() 197 lines = [line.strip()[1:] for line in lines] 198 if install_dest in lines: 199 strip_option.extend(['-S']) 200 if args.strip_debug_whitelist: 201 with open(args.strip_debug_whitelist, 'r') as whitelist: 202 for strip_debug_sofile in whitelist.readlines(): 203 if args.sofile.endswith(strip_debug_sofile.strip()): 204 strip_option.extend(['--strip-debug', '-R', '.comment']) 205 break 206 strip_command.extend(strip_option) 207 208 strip_command.append(args.sofile) 209 result = subprocess.call( 210 wrapper_utils.command_to_run(strip_command)) 211 if args.libfile: 212 libfile_name = os.path.basename(args.libfile) 213 sofile_output_dir = os.path.dirname(args.sofile) 214 unstripped_libfile = os.path.join(sofile_output_dir, libfile_name) 215 shutil.copy2(unstripped_libfile, args.libfile) 216 217 if args.mini_debug and not args.sofile.endswith(".dll"): 218 unstripped_libfile = os.path.abspath(args.sofile) 219 script_path = os.path.join( 220 os.path.dirname(__file__), 'mini_debug_info.py') 221 ohos_root_path = os.path.join(os.path.dirname(__file__), '../..') 222 result = subprocess.call( 223 wrapper_utils.command_to_run( 224 ['python3', script_path, '--unstripped-path', unstripped_libfile, '--stripped-path', args.output, 225 '--root-path', ohos_root_path, '--clang-base-dir', args.clang_base_dir])) 226 227 return result 228 229 230if __name__ == "__main__": 231 sys.exit(main()) 232