1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
build / toolchain / gcc_solink_wrapper.py [blame]
#!/usr/bin/env python3
# Copyright 2015 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Runs 'ld -shared' and generates a .TOC file that's untouched when unchanged.
This script exists to avoid using complex shell commands in
gcc_toolchain.gni's tool("solink"), in case the host running the compiler
does not have a POSIX-like shell (e.g. Windows).
"""
import argparse
import os
import shlex
import subprocess
import sys
import wrapper_utils
def CollectSONAME(args):
"""Replaces: readelf -d $sofile | grep SONAME"""
# TODO(crbug.com/40797404): Come up with a way to get this info without having
# to bundle readelf in the toolchain package.
toc = ''
readelf = subprocess.Popen(wrapper_utils.CommandToRun(
[args.readelf, '-d', args.sofile]),
stdout=subprocess.PIPE,
bufsize=-1,
universal_newlines=True)
for line in readelf.stdout:
if 'SONAME' in line:
toc += line
return readelf.wait(), toc
def CollectDynSym(args):
"""Replaces: nm --format=posix -g -D -p $sofile | cut -f1-2 -d' '"""
toc = ''
nm = subprocess.Popen(wrapper_utils.CommandToRun(
[args.nm, '--format=posix', '-g', '-D', '-p', args.sofile]),
stdout=subprocess.PIPE,
bufsize=-1,
universal_newlines=True)
for line in nm.stdout:
toc += ' '.join(line.split(' ', 2)[:2]) + '\n'
return nm.wait(), toc
def CollectTOC(args):
result, toc = CollectSONAME(args)
if result == 0:
result, dynsym = CollectDynSym(args)
toc += dynsym
return result, toc
def UpdateTOC(tocfile, toc):
if os.path.exists(tocfile):
old_toc = open(tocfile, 'r').read()
else:
old_toc = None
if toc != old_toc:
open(tocfile, 'w').write(toc)
def CollectInputs(out, args):
for x in args:
if x.startswith('@'):
with open(x[1:]) as rsp:
CollectInputs(out, shlex.split(rsp.read()))
elif not x.startswith('-') and (x.endswith('.o') or x.endswith('.a')):
out.write(x)
out.write('\n')
def InterceptFlag(flag, command):
ret = flag in command
if ret:
command.remove(flag)
return ret
def SafeDelete(path):
try:
os.unlink(path)
except OSError:
pass
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--readelf',
required=True,
help='The readelf binary to run',
metavar='PATH')
parser.add_argument('--nm',
required=True,
help='The nm binary to run',
metavar='PATH')
parser.add_argument('--strip',
help='The strip binary to run',
metavar='PATH')
parser.add_argument('--dwp', help='The dwp binary to run', metavar='PATH')
parser.add_argument('--sofile',
required=True,
help='Shared object file produced by linking command',
metavar='FILE')
parser.add_argument('--tocfile',
required=True,
help='Output table-of-contents file',
metavar='FILE')
parser.add_argument('--map-file',
help=('Use --Wl,-Map to generate a map file. Will be '
'gzipped if extension ends with .gz'),
metavar='FILE')
parser.add_argument('--output',
required=True,
help='Final output shared object file',
metavar='FILE')
parser.add_argument('command', nargs='+',
help='Linking command')
args = parser.parse_args()
# Work-around for gold being slow-by-default. http://crbug.com/632230
fast_env = dict(os.environ)
fast_env['LC_ALL'] = 'C'
# Extract flags passed through ldflags but meant for this script.
# https://crbug.com/954311 tracks finding a better way to plumb these.
partitioned_library = InterceptFlag('--partitioned-library', args.command)
collect_inputs_only = InterceptFlag('--collect-inputs-only', args.command)
# Partitioned .so libraries are used only for splitting apart in a subsequent
# step.
#
# - The TOC file optimization isn't useful, because the partition libraries
# must always be re-extracted if the combined library changes (and nothing
# should be depending on the combined library's dynamic symbol table).
# - Stripping isn't necessary, because the combined library is not used in
# production or published.
#
# Both of these operations could still be done, they're needless work, and
# tools would need to be updated to handle and/or not complain about
# partitioned libraries. Instead, to keep Ninja happy, simply create dummy
# files for the TOC and stripped lib.
if collect_inputs_only or partitioned_library:
open(args.output, 'w').close()
open(args.tocfile, 'w').close()
# Instead of linking, records all inputs to a file. This is used by
# enable_resource_allowlist_generation in order to avoid needing to
# link (which is slow) to build the resources allowlist.
if collect_inputs_only:
if args.map_file:
open(args.map_file, 'w').close()
if args.dwp:
open(args.sofile + '.dwp', 'w').close()
with open(args.sofile, 'w') as f:
CollectInputs(f, args.command)
return 0
# First, run the actual link.
command = wrapper_utils.CommandToRun(args.command)
result = wrapper_utils.RunLinkWithOptionalMapFile(command,
env=fast_env,
map_file=args.map_file)
if result != 0:
return result
# If dwp is set, then package debug info for this SO.
dwp_proc = None
if args.dwp:
# Explicit delete to account for symlinks (when toggling between
# debug/release).
SafeDelete(args.sofile + '.dwp')
# Suppress warnings about duplicate CU entries (https://crbug.com/1264130)
dwp_proc = subprocess.Popen(wrapper_utils.CommandToRun(
[args.dwp, '-e', args.sofile, '-o', args.sofile + '.dwp']),
stderr=subprocess.DEVNULL)
if not partitioned_library:
# Next, generate the contents of the TOC file.
result, toc = CollectTOC(args)
if result != 0:
return result
# If there is an existing TOC file with identical contents, leave it alone.
# Otherwise, write out the TOC file.
UpdateTOC(args.tocfile, toc)
# Finally, strip the linked shared object file (if desired).
if args.strip:
result = subprocess.call(
wrapper_utils.CommandToRun(
[args.strip, '-o', args.output, args.sofile]))
if dwp_proc:
dwp_result = dwp_proc.wait()
if dwp_result != 0:
sys.stderr.write('dwp failed with error code {}\n'.format(dwp_result))
return dwp_result
return result
if __name__ == "__main__":
sys.exit(main())