1
# This Source Code Form is subject to the terms of the Mozilla Public
2
# License, v. 2.0. If a copy of the MPL was not distributed with this
3
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
'''expandlibs-exec.py applies expandlibs rules, and some more (see below) to
6
a given command line, and executes that command line with the expanded
9
With the --extract argument (useful for e.g. $(AR)), it extracts object files
10
from static libraries (or use those listed in library descriptors directly).
12
With the --uselist argument (useful for e.g. $(CC)), it replaces all object
13
files with a list file. This can be used to avoid limitations in the length
14
of a command line. The kind of list file format used depends on the
15
EXPAND_LIBS_LIST_STYLE variable: 'list' for MSVC style lists (@file.list)
16
or 'linkerscript' for GNU ld linker scripts.
17
See https://bugzilla.mozilla.org/show_bug.cgi?id=584474#c59 for more details.
19
With the --symbol-order argument, followed by a file name, it will add the
20
relevant linker options to change the order in which the linker puts the
21
symbols appear in the resulting binary. Only works for ELF targets.
23
from __future__ import with_statement
26
from expandlibs import ExpandArgs, relativize, isObject, ensureParentDir, ExpandLibsDeps
27
import expandlibs_config as conf
28
from optparse import OptionParser
35
# The are the insert points for a GNU ld linker script, assuming a more
36
# or less "standard" default linker script. This is not a dict because
38
SECTION_INSERT_BEFORE = [
40
('.rodata', '.rodata1'),
41
('.data.rel.ro', '.dynamic'),
45
class ExpandArgsMore(ExpandArgs):
46
''' Meant to be used as 'with ExpandArgsMore(args) as ...: '''
51
def __exit__(self, type, value, tb):
52
'''Automatically remove temporary files'''
54
if os.path.isdir(tmp):
55
shutil.rmtree(tmp, True)
60
self[0:] = self._extract(self)
62
def _extract(self, args):
63
'''When a static library name is found, either extract its contents
64
in a temporary directory or use the information found in the
65
corresponding lib descriptor.
67
ar_extract = conf.AR_EXTRACT.split()
70
if os.path.splitext(arg)[1] == conf.LIB_SUFFIX:
71
if os.path.exists(arg + conf.LIBS_DESC_SUFFIX):
72
newlist += self._extract(self._expand_desc(arg))
73
elif os.path.exists(arg) and len(ar_extract):
74
tmp = tempfile.mkdtemp(dir=os.curdir)
76
subprocess.call(ar_extract + [os.path.abspath(arg)], cwd=tmp)
78
for root, dirs, files in os.walk(tmp):
79
objs += [relativize(os.path.join(root, f)) for f in files if isObject(f)]
88
'''Replaces object file names with a temporary list file, using a
89
list format depending on the EXPAND_LIBS_LIST_STYLE variable
91
objs = [o for o in self if isObject(o)]
92
if not len(objs): return
93
fd, tmp = tempfile.mkstemp(suffix=".list",dir=os.curdir)
94
if conf.EXPAND_LIBS_LIST_STYLE == "linkerscript":
95
content = ['INPUT("%s")\n' % obj for obj in objs]
97
elif conf.EXPAND_LIBS_LIST_STYLE == "list":
98
content = ["%s\n" % obj for obj in objs]
105
f = os.fdopen(fd, "w")
106
f.writelines(content)
108
idx = self.index(objs[0])
109
newlist = self[0:idx] + [ref] + [item for item in self[idx:] if item not in objs]
112
def _getFoldedSections(self):
113
'''Returns a dict about folded sections.
114
When section A and B are folded into section C, the dict contains:
118
if not conf.LD_PRINT_ICF_SECTIONS:
121
proc = subprocess.Popen(self + [conf.LD_PRINT_ICF_SECTIONS], stdout = subprocess.PIPE, stderr = subprocess.PIPE)
122
(stdout, stderr) = proc.communicate()
124
# gold's --print-icf-sections output looks like the following:
125
# ld: ICF folding section '.section' in file 'file.o'into '.section' in file 'file.o'
126
# In terms of words, chances are this will change in the future,
127
# especially considering "into" is misplaced. Splitting on quotes
129
for l in stderr.split('\n'):
130
quoted = l.split("'")
131
if len(quoted) > 5 and quoted[1] != quoted[5]:
132
result[quoted[1]] = quoted[5]
133
if quoted[5] in result:
134
result[quoted[5]].append(quoted[1])
136
result[quoted[5]] = [quoted[1]]
139
def _getOrderedSections(self, ordered_symbols):
140
'''Given an ordered list of symbols, returns the corresponding list
141
of sections following the order.'''
142
if not conf.EXPAND_LIBS_ORDER_STYLE in ['linkerscript', 'section-ordering-file']:
143
raise Exception('EXPAND_LIBS_ORDER_STYLE "%s" is not supported' % conf.EXPAND_LIBS_ORDER_STYLE)
144
finder = SectionFinder([arg for arg in self if isObject(arg) or os.path.splitext(arg)[1] == conf.LIB_SUFFIX])
145
folded = self._getFoldedSections()
147
ordered_sections = []
148
for symbol in ordered_symbols:
149
symbol_sections = finder.getSections(symbol)
150
all_symbol_sections = []
151
for section in symbol_sections:
152
if section in folded:
153
if isinstance(folded[section], str):
154
section = folded[section]
155
all_symbol_sections.append(section)
156
all_symbol_sections.extend(folded[section])
158
all_symbol_sections.append(section)
159
for section in all_symbol_sections:
160
if not section in sections:
161
ordered_sections.append(section)
162
sections.add(section)
163
return ordered_sections
165
def orderSymbols(self, order):
166
'''Given a file containing a list of symbols, adds the appropriate
167
argument to make the linker put the symbols in that order.'''
168
with open(order) as file:
169
sections = self._getOrderedSections([l.strip() for l in file.readlines() if l.strip()])
171
linked_sections = [s[0] for s in SECTION_INSERT_BEFORE]
173
for linked_section in linked_sections:
174
if s.startswith(linked_section):
175
if linked_section in split_sections:
176
split_sections[linked_section].append(s)
178
split_sections[linked_section] = [s]
182
linked_sections = [s for s in linked_sections if s in split_sections]
184
if conf.EXPAND_LIBS_ORDER_STYLE == 'section-ordering-file':
185
option = '-Wl,--section-ordering-file,%s'
187
for linked_section in linked_sections:
188
content.extend(split_sections[linked_section])
189
content.append('%s.*' % linked_section)
190
content.append(linked_section)
192
elif conf.EXPAND_LIBS_ORDER_STYLE == 'linkerscript':
194
section_insert_before = dict(SECTION_INSERT_BEFORE)
195
for linked_section in linked_sections:
196
content.append('SECTIONS {')
197
content.append(' %s : {' % linked_section)
198
content.extend(' *(%s)' % s for s in split_sections[linked_section])
201
content.append('INSERT BEFORE %s' % section_insert_before[linked_section])
203
raise Exception('EXPAND_LIBS_ORDER_STYLE "%s" is not supported' % conf.EXPAND_LIBS_ORDER_STYLE)
205
fd, tmp = tempfile.mkstemp(dir=os.curdir)
206
f = os.fdopen(fd, "w")
207
f.write('\n'.join(content)+'\n')
210
self.append(option % tmp)
212
class SectionFinder(object):
213
'''Instances of this class allow to map symbol names to sections in
216
def __init__(self, objs):
217
'''Creates an instance, given a list of object files.'''
218
if not conf.EXPAND_LIBS_ORDER_STYLE in ['linkerscript', 'section-ordering-file']:
219
raise Exception('EXPAND_LIBS_ORDER_STYLE "%s" is not supported' % conf.EXPAND_LIBS_ORDER_STYLE)
222
if not isObject(obj) and os.path.splitext(obj)[1] != conf.LIB_SUFFIX:
223
raise Exception('%s is not an object nor a static library' % obj)
224
for symbol, section in SectionFinder._getSymbols(obj):
225
sym = SectionFinder._normalize(symbol)
226
if sym in self.mapping:
227
if not section in self.mapping[sym]:
228
self.mapping[sym].append(section)
230
self.mapping[sym] = [section]
232
def getSections(self, symbol):
233
'''Given a symbol, returns a list of sections containing it or the
234
corresponding thunks. When the given symbol is a thunk, returns the
235
list of sections containing its corresponding normal symbol and the
236
other thunks for that symbol.'''
237
sym = SectionFinder._normalize(symbol)
238
if sym in self.mapping:
239
return self.mapping[sym]
243
def _normalize(symbol):
244
'''For normal symbols, return the given symbol. For thunks, return
245
the corresponding normal symbol.'''
246
if re.match('^_ZThn[0-9]+_', symbol):
247
return re.sub('^_ZThn[0-9]+_', '_Z', symbol)
251
def _getSymbols(obj):
252
'''Returns a list of (symbol, section) contained in the given object
254
proc = subprocess.Popen(['objdump', '-t', obj], stdout = subprocess.PIPE, stderr = subprocess.PIPE)
255
(stdout, stderr) = proc.communicate()
257
for line in stdout.splitlines():
258
# Each line has the following format:
259
# <addr> [lgu!][w ][C ][W ][Ii ][dD ][FfO ] <section>\t<length> <symbol>
260
tmp = line.split(' ',1)
261
# This gives us ["<addr>", "[lgu!][w ][C ][W ][Ii ][dD ][FfO ] <section>\t<length> <symbol>"]
262
# We only need to consider cases where "<section>\t<length> <symbol>" is present,
263
# and where the [FfO] flag is either F (function) or O (object).
264
if len(tmp) > 1 and len(tmp[1]) > 6 and tmp[1][6] in ['O', 'F']:
265
tmp = tmp[1][8:].split()
266
# That gives us ["<section>","<length>", "<symbol>"]
267
syms.append((tmp[-1], tmp[0]))
271
parser = OptionParser()
272
parser.add_option("--depend", dest="depend", metavar="FILE",
273
help="generate dependencies for the given execution and store it in the given file")
274
parser.add_option("--target", dest="target", metavar="FILE",
275
help="designate the target for dependencies")
276
parser.add_option("--extract", action="store_true", dest="extract",
277
help="when a library has no descriptor file, extract it first, when possible")
278
parser.add_option("--uselist", action="store_true", dest="uselist",
279
help="use a list file for objects when executing a command")
280
parser.add_option("--verbose", action="store_true", dest="verbose",
281
help="display executed command and temporary files content")
282
parser.add_option("--symbol-order", dest="symbol_order", metavar="FILE",
283
help="use the given list of symbols to order symbols in the resulting binary when using with a linker")
285
(options, args) = parser.parse_args()
287
if not options.target:
288
options.depend = False
290
deps = ExpandLibsDeps(args)
291
# Filter out common command wrappers
292
while os.path.basename(deps[0]) in ['ccache', 'distcc']:
296
with ExpandArgsMore(args) as args:
299
if options.symbol_order:
300
args.orderSymbols(options.symbol_order)
305
print >>sys.stderr, "Executing: " + " ".join(args)
306
for tmp in [f for f in args.tmp if os.path.isfile(f)]:
307
print >>sys.stderr, tmp + ":"
308
with open(tmp) as file:
309
print >>sys.stderr, "".join([" " + l for l in file.readlines()])
311
ret = subprocess.call(args)
314
if not options.depend:
316
ensureParentDir(options.depend)
317
with open(options.depend, 'w') as depfile:
318
depfile.write("%s : %s\n" % (options.target, ' '.join(dep for dep in deps if os.path.isfile(dep) and dep != options.target)))
321
if __name__ == '__main__':