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__':