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
))
74 elif os
.path
.exists(arg
) and (len(ar_extract
) or conf
.AR
== 'lib'):
75 tmp
= tempfile
.mkdtemp(dir=os
.curdir
)
78 out
= subprocess
.check_output([conf
.AR
, '-NOLOGO', '-LIST', arg
])
79 for l
in out
.splitlines():
80 subprocess
.call([conf
.AR
, '-NOLOGO', '-EXTRACT:%s' % l
, os
.path
.abspath(arg
)], cwd
=tmp
)
82 subprocess
.call(ar_extract
+ [os
.path
.abspath(arg
)], cwd
=tmp
)
84 for root
, dirs
, files
in os
.walk(tmp
):
85 objs
+= [relativize(os
.path
.join(root
, f
)) for f
in files
if isObject(f
)]
86 newlist
+= sorted(objs
)
92 '''Replaces object file names with a temporary list file, using a
93 list format depending on the EXPAND_LIBS_LIST_STYLE variable
95 objs
= [o
for o
in self
if isObject(o
)]
96 if not len(objs
): return
97 fd
, tmp
= tempfile
.mkstemp(suffix
=".list",dir=os
.curdir
)
98 if conf
.EXPAND_LIBS_LIST_STYLE
== "linkerscript":
99 content
= ['INPUT("%s")\n' % obj
for obj
in objs
]
101 elif conf
.EXPAND_LIBS_LIST_STYLE
== "filelist":
102 content
= ["%s\n" % obj
for obj
in objs
]
103 ref
= "-Wl,-filelist," + tmp
104 elif conf
.EXPAND_LIBS_LIST_STYLE
== "list":
105 content
= ["%s\n" % obj
for obj
in objs
]
112 f
= os
.fdopen(fd
, "w")
113 f
.writelines(content
)
115 idx
= self
.index(objs
[0])
116 newlist
= self
[0:idx
] + [ref
] + [item
for item
in self
[idx
:] if item
not in objs
]
119 def _getFoldedSections(self
):
120 '''Returns a dict about folded sections.
121 When section A and B are folded into section C, the dict contains:
125 if not conf
.LD_PRINT_ICF_SECTIONS
:
128 proc
= subprocess
.Popen(self
+ [conf
.LD_PRINT_ICF_SECTIONS
], stdout
= subprocess
.PIPE
, stderr
= subprocess
.PIPE
)
129 (stdout
, stderr
) = proc
.communicate()
131 # gold's --print-icf-sections output looks like the following:
132 # ld: ICF folding section '.section' in file 'file.o'into '.section' in file 'file.o'
133 # In terms of words, chances are this will change in the future,
134 # especially considering "into" is misplaced. Splitting on quotes
136 for l
in stderr
.split('\n'):
137 quoted
= l
.split("'")
138 if len(quoted
) > 5 and quoted
[1] != quoted
[5]:
139 result
[quoted
[1]] = [quoted
[5]]
140 if quoted
[5] in result
:
141 result
[quoted
[5]].append(quoted
[1])
143 result
[quoted
[5]] = [quoted
[1]]
146 def _getOrderedSections(self
, ordered_symbols
):
147 '''Given an ordered list of symbols, returns the corresponding list
148 of sections following the order.'''
149 if not conf
.EXPAND_LIBS_ORDER_STYLE
in ['linkerscript', 'section-ordering-file']:
150 raise Exception('EXPAND_LIBS_ORDER_STYLE "%s" is not supported' % conf
.EXPAND_LIBS_ORDER_STYLE
)
151 finder
= SectionFinder([arg
for arg
in self
if isObject(arg
) or os
.path
.splitext(arg
)[1] == conf
.LIB_SUFFIX
])
152 folded
= self
._getFoldedSections
()
154 ordered_sections
= []
155 for symbol
in ordered_symbols
:
156 symbol_sections
= finder
.getSections(symbol
)
157 all_symbol_sections
= []
158 for section
in symbol_sections
:
159 if section
in folded
:
160 if isinstance(folded
[section
], str):
161 section
= folded
[section
]
162 all_symbol_sections
.append(section
)
163 all_symbol_sections
.extend(folded
[section
])
165 all_symbol_sections
.append(section
)
166 for section
in all_symbol_sections
:
167 if not section
in sections
:
168 ordered_sections
.append(section
)
169 sections
.add(section
)
170 return ordered_sections
172 def orderSymbols(self
, order
):
173 '''Given a file containing a list of symbols, adds the appropriate
174 argument to make the linker put the symbols in that order.'''
175 with
open(order
) as file:
176 sections
= self
._getOrderedSections
([l
.strip() for l
in file.readlines() if l
.strip()])
178 linked_sections
= [s
[0] for s
in SECTION_INSERT_BEFORE
]
180 for linked_section
in linked_sections
:
181 if s
.startswith(linked_section
):
182 if linked_section
in split_sections
:
183 split_sections
[linked_section
].append(s
)
185 split_sections
[linked_section
] = [s
]
189 linked_sections
= [s
for s
in linked_sections
if s
in split_sections
]
191 if conf
.EXPAND_LIBS_ORDER_STYLE
== 'section-ordering-file':
192 option
= '-Wl,--section-ordering-file,%s'
194 for linked_section
in linked_sections
:
195 content
.extend(split_sections
[linked_section
])
196 content
.append('%s.*' % linked_section
)
197 content
.append(linked_section
)
199 elif conf
.EXPAND_LIBS_ORDER_STYLE
== 'linkerscript':
201 section_insert_before
= dict(SECTION_INSERT_BEFORE
)
202 for linked_section
in linked_sections
:
203 content
.append('SECTIONS {')
204 content
.append(' %s : {' % linked_section
)
205 content
.extend(' *(%s)' % s
for s
in split_sections
[linked_section
])
208 content
.append('INSERT BEFORE %s' % section_insert_before
[linked_section
])
210 raise Exception('EXPAND_LIBS_ORDER_STYLE "%s" is not supported' % conf
.EXPAND_LIBS_ORDER_STYLE
)
212 fd
, tmp
= tempfile
.mkstemp(dir=os
.curdir
)
213 f
= os
.fdopen(fd
, "w")
214 f
.write('\n'.join(content
)+'\n')
217 self
.append(option
% tmp
)
219 class SectionFinder(object):
220 '''Instances of this class allow to map symbol names to sections in
223 def __init__(self
, objs
):
224 '''Creates an instance, given a list of object files.'''
225 if not conf
.EXPAND_LIBS_ORDER_STYLE
in ['linkerscript', 'section-ordering-file']:
226 raise Exception('EXPAND_LIBS_ORDER_STYLE "%s" is not supported' % conf
.EXPAND_LIBS_ORDER_STYLE
)
229 if not isObject(obj
) and os
.path
.splitext(obj
)[1] != conf
.LIB_SUFFIX
:
230 raise Exception('%s is not an object nor a static library' % obj
)
231 for symbol
, section
in SectionFinder
._getSymbols
(obj
):
232 sym
= SectionFinder
._normalize
(symbol
)
233 if sym
in self
.mapping
:
234 if not section
in self
.mapping
[sym
]:
235 self
.mapping
[sym
].append(section
)
237 self
.mapping
[sym
] = [section
]
239 def getSections(self
, symbol
):
240 '''Given a symbol, returns a list of sections containing it or the
241 corresponding thunks. When the given symbol is a thunk, returns the
242 list of sections containing its corresponding normal symbol and the
243 other thunks for that symbol.'''
244 sym
= SectionFinder
._normalize
(symbol
)
245 if sym
in self
.mapping
:
246 return self
.mapping
[sym
]
250 def _normalize(symbol
):
251 '''For normal symbols, return the given symbol. For thunks, return
252 the corresponding normal symbol.'''
253 if re
.match('^_ZThn[0-9]+_', symbol
):
254 return re
.sub('^_ZThn[0-9]+_', '_Z', symbol
)
258 def _getSymbols(obj
):
259 '''Returns a list of (symbol, section) contained in the given object
261 proc
= subprocess
.Popen(['objdump', '-t', obj
], stdout
= subprocess
.PIPE
, stderr
= subprocess
.PIPE
)
262 (stdout
, stderr
) = proc
.communicate()
264 for line
in stdout
.splitlines():
265 # Each line has the following format:
266 # <addr> [lgu!][w ][C ][W ][Ii ][dD ][FfO ] <section>\t<length> <symbol>
267 tmp
= line
.split(' ',1)
268 # This gives us ["<addr>", "[lgu!][w ][C ][W ][Ii ][dD ][FfO ] <section>\t<length> <symbol>"]
269 # We only need to consider cases where "<section>\t<length> <symbol>" is present,
270 # and where the [FfO] flag is either F (function) or O (object).
271 if len(tmp
) > 1 and len(tmp
[1]) > 6 and tmp
[1][6] in ['O', 'F']:
272 tmp
= tmp
[1][8:].split()
273 # That gives us ["<section>","<length>", "<symbol>"]
274 syms
.append((tmp
[-1], tmp
[0]))
277 def print_command(out
, args
):
278 print >>out
, "Executing: " + " ".join(args
)
279 for tmp
in [f
for f
in args
.tmp
if os
.path
.isfile(f
)]:
280 print >>out
, tmp
+ ":"
281 with
open(tmp
) as file:
282 print >>out
, "".join([" " + l
for l
in file.readlines()])
286 parser
= OptionParser()
287 parser
.add_option("--depend", dest
="depend", metavar
="FILE",
288 help="generate dependencies for the given execution and store it in the given file")
289 parser
.add_option("--target", dest
="target", metavar
="FILE",
290 help="designate the target for dependencies")
291 parser
.add_option("--extract", action
="store_true", dest
="extract",
292 help="when a library has no descriptor file, extract it first, when possible")
293 parser
.add_option("--uselist", action
="store_true", dest
="uselist",
294 help="use a list file for objects when executing a command")
295 parser
.add_option("--verbose", action
="store_true", dest
="verbose",
296 help="display executed command and temporary files content")
297 parser
.add_option("--symbol-order", dest
="symbol_order", metavar
="FILE",
298 help="use the given list of symbols to order symbols in the resulting binary when using with a linker")
300 (options
, args
) = parser
.parse_args()
302 if not options
.target
:
303 options
.depend
= False
305 deps
= ExpandLibsDeps(args
)
306 # Filter out common command wrappers
307 while os
.path
.basename(deps
[0]) in ['ccache', 'distcc']:
311 with
ExpandArgsMore(args
) as args
:
314 if options
.symbol_order
:
315 args
.orderSymbols(options
.symbol_order
)
320 print_command(sys
.stderr
, args
)
321 proc
= subprocess
.Popen(args
, stdout
= subprocess
.PIPE
, stderr
= subprocess
.STDOUT
)
322 (stdout
, stderr
) = proc
.communicate()
323 if proc
.returncode
and not options
.verbose
:
324 print_command(sys
.stderr
, args
)
325 sys
.stderr
.write(stdout
)
328 exit(proc
.returncode
)
329 if not options
.depend
:
331 ensureParentDir(options
.depend
)
332 with
open(options
.depend
, 'w') as depfile
:
333 depfile
.write("%s : %s\n" % (options
.target
, ' '.join(dep
for dep
in deps
if os
.path
.isfile(dep
) and dep
!= options
.target
)))
336 if os
.path
.isfile(dep
) and dep
!= options
.target
:
337 depfile
.write("%s :\n" % dep
)
339 if __name__
== '__main__':