• 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
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