• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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