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 (
32 import expandlibs_config
as conf
33 from optparse
import OptionParser
39 from mozbuild
.makeutil
import Makefile
41 # The are the insert points for a GNU ld linker script, assuming a more
42 # or less "standard" default linker script. This is not a dict because
44 SECTION_INSERT_BEFORE
= [
46 ('.rodata', '.rodata1'),
47 ('.data.rel.ro', '.dynamic'),
51 class ExpandArgsMore(ExpandArgs
):
52 ''' Meant to be used as 'with ExpandArgsMore(args) as ...: '''
57 def __exit__(self
, type, value
, tb
):
58 '''Automatically remove temporary files'''
60 if os
.path
.isdir(tmp
):
61 shutil
.rmtree(tmp
, True)
66 self
[0:] = self
._extract
(self
)
68 def _extract(self
, args
):
69 '''When a static library name is found, either extract its contents
70 in a temporary directory or use the information found in the
71 corresponding lib descriptor.
73 ar_extract
= conf
.AR_EXTRACT
.split()
77 for root
, dirs
, files
in os
.walk(base
):
79 return os
.path
.join(root
, f
)
82 if os
.path
.splitext(arg
)[1] == conf
.LIB_SUFFIX
:
83 if os
.path
.exists(arg
+ conf
.LIBS_DESC_SUFFIX
):
84 newlist
+= self
._extract
(self
._expand
_desc
(arg
))
86 elif os
.path
.exists(arg
) and (len(ar_extract
) or conf
.AR
== 'lib'):
87 tmp
= tempfile
.mkdtemp(dir=os
.curdir
)
90 out
= subprocess
.check_output([conf
.AR
, '-NOLOGO', '-LIST', arg
])
91 files
= out
.splitlines()
92 # If lib -list returns a list full of dlls, it's an
94 if all(isDynamicLib(f
) for f
in files
):
98 subprocess
.call([conf
.AR
, '-NOLOGO', '-EXTRACT:%s' % f
, os
.path
.abspath(arg
)], cwd
=tmp
)
100 subprocess
.call(ar_extract
+ [os
.path
.abspath(arg
)], cwd
=tmp
)
102 basedir
= os
.path
.dirname(arg
)
103 for root
, dirs
, files
in os
.walk(tmp
):
106 # If the file extracted from the library also
107 # exists in the directory containing the
108 # library, or one of its subdirectories, use
110 maybe_obj
= lookup(os
.path
.join(basedir
, os
.path
.relpath(root
, tmp
)), f
)
112 objs
.append(relativize(maybe_obj
))
114 objs
.append(relativize(os
.path
.join(root
, f
)))
115 newlist
+= sorted(objs
)
121 '''Replaces object file names with a temporary list file, using a
122 list format depending on the EXPAND_LIBS_LIST_STYLE variable
124 objs
= [o
for o
in self
if isObject(o
)]
125 if not len(objs
): return
126 fd
, tmp
= tempfile
.mkstemp(suffix
=".list",dir=os
.curdir
)
127 if conf
.EXPAND_LIBS_LIST_STYLE
== "linkerscript":
128 content
= ['INPUT("%s")\n' % obj
for obj
in objs
]
130 elif conf
.EXPAND_LIBS_LIST_STYLE
== "filelist":
131 content
= ["%s\n" % obj
for obj
in objs
]
132 ref
= "-Wl,-filelist," + tmp
133 elif conf
.EXPAND_LIBS_LIST_STYLE
== "list":
134 content
= ["%s\n" % obj
for obj
in objs
]
141 f
= os
.fdopen(fd
, "w")
142 f
.writelines(content
)
144 idx
= self
.index(objs
[0])
145 newlist
= self
[0:idx
] + [ref
] + [item
for item
in self
[idx
:] if item
not in objs
]
148 def _getFoldedSections(self
):
149 '''Returns a dict about folded sections.
150 When section A and B are folded into section C, the dict contains:
154 if not conf
.LD_PRINT_ICF_SECTIONS
:
157 proc
= subprocess
.Popen(self
+ [conf
.LD_PRINT_ICF_SECTIONS
], stdout
= subprocess
.PIPE
, stderr
= subprocess
.PIPE
)
158 (stdout
, stderr
) = proc
.communicate()
160 # gold's --print-icf-sections output looks like the following:
161 # ld: ICF folding section '.section' in file 'file.o'into '.section' in file 'file.o'
162 # In terms of words, chances are this will change in the future,
163 # especially considering "into" is misplaced. Splitting on quotes
165 for l
in stderr
.split('\n'):
166 quoted
= l
.split("'")
167 if len(quoted
) > 5 and quoted
[1] != quoted
[5]:
168 result
[quoted
[1]] = [quoted
[5]]
169 if quoted
[5] in result
:
170 result
[quoted
[5]].append(quoted
[1])
172 result
[quoted
[5]] = [quoted
[1]]
175 def _getOrderedSections(self
, ordered_symbols
):
176 '''Given an ordered list of symbols, returns the corresponding list
177 of sections following the order.'''
178 if not conf
.EXPAND_LIBS_ORDER_STYLE
in ['linkerscript', 'section-ordering-file']:
179 raise Exception('EXPAND_LIBS_ORDER_STYLE "%s" is not supported' % conf
.EXPAND_LIBS_ORDER_STYLE
)
180 finder
= SectionFinder([arg
for arg
in self
if isObject(arg
) or os
.path
.splitext(arg
)[1] == conf
.LIB_SUFFIX
])
181 folded
= self
._getFoldedSections
()
183 ordered_sections
= []
184 for symbol
in ordered_symbols
:
185 symbol_sections
= finder
.getSections(symbol
)
186 all_symbol_sections
= []
187 for section
in symbol_sections
:
188 if section
in folded
:
189 if isinstance(folded
[section
], str):
190 section
= folded
[section
]
191 all_symbol_sections
.append(section
)
192 all_symbol_sections
.extend(folded
[section
])
194 all_symbol_sections
.append(section
)
195 for section
in all_symbol_sections
:
196 if not section
in sections
:
197 ordered_sections
.append(section
)
198 sections
.add(section
)
199 return ordered_sections
201 def orderSymbols(self
, order
):
202 '''Given a file containing a list of symbols, adds the appropriate
203 argument to make the linker put the symbols in that order.'''
204 with
open(order
) as file:
205 sections
= self
._getOrderedSections
([l
.strip() for l
in file.readlines() if l
.strip()])
207 linked_sections
= [s
[0] for s
in SECTION_INSERT_BEFORE
]
209 for linked_section
in linked_sections
:
210 if s
.startswith(linked_section
):
211 if linked_section
in split_sections
:
212 split_sections
[linked_section
].append(s
)
214 split_sections
[linked_section
] = [s
]
218 linked_sections
= [s
for s
in linked_sections
if s
in split_sections
]
220 if conf
.EXPAND_LIBS_ORDER_STYLE
== 'section-ordering-file':
221 option
= '-Wl,--section-ordering-file,%s'
223 for linked_section
in linked_sections
:
224 content
.extend(split_sections
[linked_section
])
225 content
.append('%s.*' % linked_section
)
226 content
.append(linked_section
)
228 elif conf
.EXPAND_LIBS_ORDER_STYLE
== 'linkerscript':
230 section_insert_before
= dict(SECTION_INSERT_BEFORE
)
231 for linked_section
in linked_sections
:
232 content
.append('SECTIONS {')
233 content
.append(' %s : {' % linked_section
)
234 content
.extend(' *(%s)' % s
for s
in split_sections
[linked_section
])
237 content
.append('INSERT BEFORE %s' % section_insert_before
[linked_section
])
239 raise Exception('EXPAND_LIBS_ORDER_STYLE "%s" is not supported' % conf
.EXPAND_LIBS_ORDER_STYLE
)
241 fd
, tmp
= tempfile
.mkstemp(dir=os
.curdir
)
242 f
= os
.fdopen(fd
, "w")
243 f
.write('\n'.join(content
)+'\n')
246 self
.append(option
% tmp
)
248 class SectionFinder(object):
249 '''Instances of this class allow to map symbol names to sections in
252 def __init__(self
, objs
):
253 '''Creates an instance, given a list of object files.'''
254 if not conf
.EXPAND_LIBS_ORDER_STYLE
in ['linkerscript', 'section-ordering-file']:
255 raise Exception('EXPAND_LIBS_ORDER_STYLE "%s" is not supported' % conf
.EXPAND_LIBS_ORDER_STYLE
)
258 if not isObject(obj
) and os
.path
.splitext(obj
)[1] != conf
.LIB_SUFFIX
:
259 raise Exception('%s is not an object nor a static library' % obj
)
260 for symbol
, section
in SectionFinder
._getSymbols
(obj
):
261 sym
= SectionFinder
._normalize
(symbol
)
262 if sym
in self
.mapping
:
263 if not section
in self
.mapping
[sym
]:
264 self
.mapping
[sym
].append(section
)
266 self
.mapping
[sym
] = [section
]
268 def getSections(self
, symbol
):
269 '''Given a symbol, returns a list of sections containing it or the
270 corresponding thunks. When the given symbol is a thunk, returns the
271 list of sections containing its corresponding normal symbol and the
272 other thunks for that symbol.'''
273 sym
= SectionFinder
._normalize
(symbol
)
274 if sym
in self
.mapping
:
275 return self
.mapping
[sym
]
279 def _normalize(symbol
):
280 '''For normal symbols, return the given symbol. For thunks, return
281 the corresponding normal symbol.'''
282 if re
.match('^_ZThn[0-9]+_', symbol
):
283 return re
.sub('^_ZThn[0-9]+_', '_Z', symbol
)
287 def _getSymbols(obj
):
288 '''Returns a list of (symbol, section) contained in the given object
290 proc
= subprocess
.Popen(['objdump', '-t', obj
], stdout
= subprocess
.PIPE
, stderr
= subprocess
.PIPE
)
291 (stdout
, stderr
) = proc
.communicate()
293 for line
in stdout
.splitlines():
294 # Each line has the following format:
295 # <addr> [lgu!][w ][C ][W ][Ii ][dD ][FfO ] <section>\t<length> <symbol>
296 tmp
= line
.split(' ',1)
297 # This gives us ["<addr>", "[lgu!][w ][C ][W ][Ii ][dD ][FfO ] <section>\t<length> <symbol>"]
298 # We only need to consider cases where "<section>\t<length> <symbol>" is present,
299 # and where the [FfO] flag is either F (function) or O (object).
300 if len(tmp
) > 1 and len(tmp
[1]) > 6 and tmp
[1][6] in ['O', 'F']:
301 tmp
= tmp
[1][8:].split()
302 # That gives us ["<section>","<length>", "<symbol>"]
303 syms
.append((tmp
[-1], tmp
[0]))
306 def print_command(out
, args
):
307 print >>out
, "Executing: " + " ".join(args
)
308 for tmp
in [f
for f
in args
.tmp
if os
.path
.isfile(f
)]:
309 print >>out
, tmp
+ ":"
310 with
open(tmp
) as file:
311 print >>out
, "".join([" " + l
for l
in file.readlines()])
314 def main(args
, proc_callback
=None):
315 parser
= OptionParser()
316 parser
.add_option("--extract", action
="store_true", dest
="extract",
317 help="when a library has no descriptor file, extract it first, when possible")
318 parser
.add_option("--uselist", action
="store_true", dest
="uselist",
319 help="use a list file for objects when executing a command")
320 parser
.add_option("--verbose", action
="store_true", dest
="verbose",
321 help="display executed command and temporary files content")
322 parser
.add_option("--symbol-order", dest
="symbol_order", metavar
="FILE",
323 help="use the given list of symbols to order symbols in the resulting binary when using with a linker")
325 (options
, args
) = parser
.parse_args(args
)
327 with
ExpandArgsMore(args
) as args
:
330 if options
.symbol_order
:
331 args
.orderSymbols(options
.symbol_order
)
336 print_command(sys
.stderr
, args
)
338 proc
= subprocess
.Popen(args
, stdout
= subprocess
.PIPE
, stderr
= subprocess
.STDOUT
)
342 print >>sys
.stderr
, 'error: Launching', args
, ':', e
344 (stdout
, stderr
) = proc
.communicate()
345 if proc
.returncode
and not options
.verbose
:
346 print_command(sys
.stderr
, args
)
347 sys
.stderr
.write(stdout
)
350 return proc
.returncode
353 if __name__
== '__main__':
354 exit(main(sys
.argv
[1:]))