expandlibs_exec.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  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/.
  4. '''expandlibs-exec.py applies expandlibs rules, and some more (see below) to
  5. a given command line, and executes that command line with the expanded
  6. arguments.
  7. With the --extract argument (useful for e.g. $(AR)), it extracts object files
  8. from static libraries (or use those listed in library descriptors directly).
  9. With the --uselist argument (useful for e.g. $(CC)), it replaces all object
  10. files with a list file. This can be used to avoid limitations in the length
  11. of a command line. The kind of list file format used depends on the
  12. EXPAND_LIBS_LIST_STYLE variable: 'list' for MSVC style lists (@file.list)
  13. or 'linkerscript' for GNU ld linker scripts.
  14. See https://bugzilla.mozilla.org/show_bug.cgi?id=584474#c59 for more details.
  15. With the --symbol-order argument, followed by a file name, it will add the
  16. relevant linker options to change the order in which the linker puts the
  17. symbols appear in the resulting binary. Only works for ELF targets.
  18. '''
  19. from __future__ import with_statement
  20. import sys
  21. import os
  22. from expandlibs import (
  23. ExpandArgs,
  24. relativize,
  25. isDynamicLib,
  26. isObject,
  27. )
  28. import expandlibs_config as conf
  29. from optparse import OptionParser
  30. import subprocess
  31. import tempfile
  32. import shutil
  33. import subprocess
  34. import re
  35. from mozbuild.makeutil import Makefile
  36. # The are the insert points for a GNU ld linker script, assuming a more
  37. # or less "standard" default linker script. This is not a dict because
  38. # order is important.
  39. SECTION_INSERT_BEFORE = [
  40. ('.text', '.fini'),
  41. ('.rodata', '.rodata1'),
  42. ('.data.rel.ro', '.dynamic'),
  43. ('.data', '.data1'),
  44. ]
  45. class ExpandArgsMore(ExpandArgs):
  46. ''' Meant to be used as 'with ExpandArgsMore(args) as ...: '''
  47. def __enter__(self):
  48. self.tmp = []
  49. return self
  50. def __exit__(self, type, value, tb):
  51. '''Automatically remove temporary files'''
  52. for tmp in self.tmp:
  53. if os.path.isdir(tmp):
  54. shutil.rmtree(tmp, True)
  55. else:
  56. os.remove(tmp)
  57. def extract(self):
  58. self[0:] = self._extract(self)
  59. def _extract(self, args):
  60. '''When a static library name is found, either extract its contents
  61. in a temporary directory or use the information found in the
  62. corresponding lib descriptor.
  63. '''
  64. ar_extract = conf.AR_EXTRACT.split()
  65. newlist = []
  66. def lookup(base, f):
  67. for root, dirs, files in os.walk(base):
  68. if f in files:
  69. return os.path.join(root, f)
  70. for arg in args:
  71. if os.path.splitext(arg)[1] == conf.LIB_SUFFIX:
  72. if os.path.exists(arg + conf.LIBS_DESC_SUFFIX):
  73. newlist += self._extract(self._expand_desc(arg))
  74. continue
  75. elif os.path.exists(arg) and (len(ar_extract) or conf.AR == 'lib'):
  76. tmp = tempfile.mkdtemp(dir=os.curdir)
  77. self.tmp.append(tmp)
  78. if conf.AR == 'lib':
  79. out = subprocess.check_output([conf.AR, '-NOLOGO', '-LIST', arg])
  80. files = out.splitlines()
  81. # If lib -list returns a list full of dlls, it's an
  82. # import lib.
  83. if all(isDynamicLib(f) for f in files):
  84. newlist += [arg]
  85. continue
  86. for f in files:
  87. subprocess.call([conf.AR, '-NOLOGO', '-EXTRACT:%s' % f, os.path.abspath(arg)], cwd=tmp)
  88. else:
  89. subprocess.call(ar_extract + [os.path.abspath(arg)], cwd=tmp)
  90. objs = []
  91. basedir = os.path.dirname(arg)
  92. for root, dirs, files in os.walk(tmp):
  93. for f in files:
  94. if isObject(f):
  95. # If the file extracted from the library also
  96. # exists in the directory containing the
  97. # library, or one of its subdirectories, use
  98. # that instead.
  99. maybe_obj = lookup(os.path.join(basedir, os.path.relpath(root, tmp)), f)
  100. if maybe_obj:
  101. objs.append(relativize(maybe_obj))
  102. else:
  103. objs.append(relativize(os.path.join(root, f)))
  104. newlist += sorted(objs)
  105. continue
  106. newlist += [arg]
  107. return newlist
  108. def makelist(self):
  109. '''Replaces object file names with a temporary list file, using a
  110. list format depending on the EXPAND_LIBS_LIST_STYLE variable
  111. '''
  112. objs = [o for o in self if isObject(o)]
  113. if not len(objs): return
  114. fd, tmp = tempfile.mkstemp(suffix=".list",dir=os.curdir)
  115. if conf.EXPAND_LIBS_LIST_STYLE == "linkerscript":
  116. content = ['INPUT("%s")\n' % obj for obj in objs]
  117. ref = tmp
  118. elif conf.EXPAND_LIBS_LIST_STYLE == "filelist":
  119. content = ["%s\n" % obj for obj in objs]
  120. ref = "-Wl,-filelist," + tmp
  121. elif conf.EXPAND_LIBS_LIST_STYLE == "list":
  122. content = ["%s\n" % obj for obj in objs]
  123. ref = "@" + tmp
  124. else:
  125. os.close(fd)
  126. os.remove(tmp)
  127. return
  128. self.tmp.append(tmp)
  129. f = os.fdopen(fd, "w")
  130. f.writelines(content)
  131. f.close()
  132. idx = self.index(objs[0])
  133. newlist = self[0:idx] + [ref] + [item for item in self[idx:] if item not in objs]
  134. self[0:] = newlist
  135. def _getFoldedSections(self):
  136. '''Returns a dict about folded sections.
  137. When section A and B are folded into section C, the dict contains:
  138. { 'A': 'C',
  139. 'B': 'C',
  140. 'C': ['A', 'B'] }'''
  141. if not conf.LD_PRINT_ICF_SECTIONS:
  142. return {}
  143. proc = subprocess.Popen(self + [conf.LD_PRINT_ICF_SECTIONS], stdout = subprocess.PIPE, stderr = subprocess.PIPE)
  144. (stdout, stderr) = proc.communicate()
  145. result = {}
  146. # gold's --print-icf-sections output looks like the following:
  147. # ld: ICF folding section '.section' in file 'file.o'into '.section' in file 'file.o'
  148. # In terms of words, chances are this will change in the future,
  149. # especially considering "into" is misplaced. Splitting on quotes
  150. # seems safer.
  151. for l in stderr.split('\n'):
  152. quoted = l.split("'")
  153. if len(quoted) > 5 and quoted[1] != quoted[5]:
  154. result[quoted[1]] = [quoted[5]]
  155. if quoted[5] in result:
  156. result[quoted[5]].append(quoted[1])
  157. else:
  158. result[quoted[5]] = [quoted[1]]
  159. return result
  160. def _getOrderedSections(self, ordered_symbols):
  161. '''Given an ordered list of symbols, returns the corresponding list
  162. of sections following the order.'''
  163. if not conf.EXPAND_LIBS_ORDER_STYLE in ['linkerscript', 'section-ordering-file']:
  164. raise Exception('EXPAND_LIBS_ORDER_STYLE "%s" is not supported' % conf.EXPAND_LIBS_ORDER_STYLE)
  165. finder = SectionFinder([arg for arg in self if isObject(arg) or os.path.splitext(arg)[1] == conf.LIB_SUFFIX])
  166. folded = self._getFoldedSections()
  167. sections = set()
  168. ordered_sections = []
  169. for symbol in ordered_symbols:
  170. symbol_sections = finder.getSections(symbol)
  171. all_symbol_sections = []
  172. for section in symbol_sections:
  173. if section in folded:
  174. if isinstance(folded[section], str):
  175. section = folded[section]
  176. all_symbol_sections.append(section)
  177. all_symbol_sections.extend(folded[section])
  178. else:
  179. all_symbol_sections.append(section)
  180. for section in all_symbol_sections:
  181. if not section in sections:
  182. ordered_sections.append(section)
  183. sections.add(section)
  184. return ordered_sections
  185. def orderSymbols(self, order):
  186. '''Given a file containing a list of symbols, adds the appropriate
  187. argument to make the linker put the symbols in that order.'''
  188. with open(order) as file:
  189. sections = self._getOrderedSections([l.strip() for l in file.readlines() if l.strip()])
  190. split_sections = {}
  191. linked_sections = [s[0] for s in SECTION_INSERT_BEFORE]
  192. for s in sections:
  193. for linked_section in linked_sections:
  194. if s.startswith(linked_section):
  195. if linked_section in split_sections:
  196. split_sections[linked_section].append(s)
  197. else:
  198. split_sections[linked_section] = [s]
  199. break
  200. content = []
  201. # Order is important
  202. linked_sections = [s for s in linked_sections if s in split_sections]
  203. if conf.EXPAND_LIBS_ORDER_STYLE == 'section-ordering-file':
  204. option = '-Wl,--section-ordering-file,%s'
  205. content = sections
  206. for linked_section in linked_sections:
  207. content.extend(split_sections[linked_section])
  208. content.append('%s.*' % linked_section)
  209. content.append(linked_section)
  210. elif conf.EXPAND_LIBS_ORDER_STYLE == 'linkerscript':
  211. option = '-Wl,-T,%s'
  212. section_insert_before = dict(SECTION_INSERT_BEFORE)
  213. for linked_section in linked_sections:
  214. content.append('SECTIONS {')
  215. content.append(' %s : {' % linked_section)
  216. content.extend(' *(%s)' % s for s in split_sections[linked_section])
  217. content.append(' }')
  218. content.append('}')
  219. content.append('INSERT BEFORE %s' % section_insert_before[linked_section])
  220. else:
  221. raise Exception('EXPAND_LIBS_ORDER_STYLE "%s" is not supported' % conf.EXPAND_LIBS_ORDER_STYLE)
  222. fd, tmp = tempfile.mkstemp(dir=os.curdir)
  223. f = os.fdopen(fd, "w")
  224. f.write('\n'.join(content)+'\n')
  225. f.close()
  226. self.tmp.append(tmp)
  227. self.append(option % tmp)
  228. class SectionFinder(object):
  229. '''Instances of this class allow to map symbol names to sections in
  230. object files.'''
  231. def __init__(self, objs):
  232. '''Creates an instance, given a list of object files.'''
  233. if not conf.EXPAND_LIBS_ORDER_STYLE in ['linkerscript', 'section-ordering-file']:
  234. raise Exception('EXPAND_LIBS_ORDER_STYLE "%s" is not supported' % conf.EXPAND_LIBS_ORDER_STYLE)
  235. self.mapping = {}
  236. for obj in objs:
  237. if not isObject(obj) and os.path.splitext(obj)[1] != conf.LIB_SUFFIX:
  238. raise Exception('%s is not an object nor a static library' % obj)
  239. for symbol, section in SectionFinder._getSymbols(obj):
  240. sym = SectionFinder._normalize(symbol)
  241. if sym in self.mapping:
  242. if not section in self.mapping[sym]:
  243. self.mapping[sym].append(section)
  244. else:
  245. self.mapping[sym] = [section]
  246. def getSections(self, symbol):
  247. '''Given a symbol, returns a list of sections containing it or the
  248. corresponding thunks. When the given symbol is a thunk, returns the
  249. list of sections containing its corresponding normal symbol and the
  250. other thunks for that symbol.'''
  251. sym = SectionFinder._normalize(symbol)
  252. if sym in self.mapping:
  253. return self.mapping[sym]
  254. return []
  255. @staticmethod
  256. def _normalize(symbol):
  257. '''For normal symbols, return the given symbol. For thunks, return
  258. the corresponding normal symbol.'''
  259. if re.match('^_ZThn[0-9]+_', symbol):
  260. return re.sub('^_ZThn[0-9]+_', '_Z', symbol)
  261. return symbol
  262. @staticmethod
  263. def _getSymbols(obj):
  264. '''Returns a list of (symbol, section) contained in the given object
  265. file.'''
  266. proc = subprocess.Popen(['objdump', '-t', obj], stdout = subprocess.PIPE, stderr = subprocess.PIPE)
  267. (stdout, stderr) = proc.communicate()
  268. syms = []
  269. for line in stdout.splitlines():
  270. # Each line has the following format:
  271. # <addr> [lgu!][w ][C ][W ][Ii ][dD ][FfO ] <section>\t<length> <symbol>
  272. tmp = line.split(' ',1)
  273. # This gives us ["<addr>", "[lgu!][w ][C ][W ][Ii ][dD ][FfO ] <section>\t<length> <symbol>"]
  274. # We only need to consider cases where "<section>\t<length> <symbol>" is present,
  275. # and where the [FfO] flag is either F (function) or O (object).
  276. if len(tmp) > 1 and len(tmp[1]) > 6 and tmp[1][6] in ['O', 'F']:
  277. tmp = tmp[1][8:].split()
  278. # That gives us ["<section>","<length>", "<symbol>"]
  279. syms.append((tmp[-1], tmp[0]))
  280. return syms
  281. def print_command(out, args):
  282. print >>out, "Executing: " + " ".join(args)
  283. for tmp in [f for f in args.tmp if os.path.isfile(f)]:
  284. print >>out, tmp + ":"
  285. with open(tmp) as file:
  286. print >>out, "".join([" " + l for l in file.readlines()])
  287. out.flush()
  288. def main(args, proc_callback=None):
  289. parser = OptionParser()
  290. parser.add_option("--extract", action="store_true", dest="extract",
  291. help="when a library has no descriptor file, extract it first, when possible")
  292. parser.add_option("--uselist", action="store_true", dest="uselist",
  293. help="use a list file for objects when executing a command")
  294. parser.add_option("--verbose", action="store_true", dest="verbose",
  295. help="display executed command and temporary files content")
  296. parser.add_option("--symbol-order", dest="symbol_order", metavar="FILE",
  297. help="use the given list of symbols to order symbols in the resulting binary when using with a linker")
  298. (options, args) = parser.parse_args(args)
  299. with ExpandArgsMore(args) as args:
  300. if options.extract:
  301. args.extract()
  302. if options.symbol_order:
  303. args.orderSymbols(options.symbol_order)
  304. if options.uselist:
  305. args.makelist()
  306. if options.verbose:
  307. print_command(sys.stderr, args)
  308. try:
  309. proc = subprocess.Popen(args, stdout = subprocess.PIPE, stderr = subprocess.STDOUT)
  310. if proc_callback:
  311. proc_callback(proc)
  312. except Exception, e:
  313. print >>sys.stderr, 'error: Launching', args, ':', e
  314. raise e
  315. (stdout, stderr) = proc.communicate()
  316. if proc.returncode and not options.verbose:
  317. print_command(sys.stderr, args)
  318. sys.stderr.write(stdout)
  319. sys.stderr.flush()
  320. if proc.returncode:
  321. return proc.returncode
  322. return 0
  323. if __name__ == '__main__':
  324. exit(main(sys.argv[1:]))