123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355 |
- # This Source Code Form is subject to the terms of the Mozilla Public
- # License, v. 2.0. If a copy of the MPL was not distributed with this
- # file, You can obtain one at http://mozilla.org/MPL/2.0/.
- '''expandlibs-exec.py applies expandlibs rules, and some more (see below) to
- a given command line, and executes that command line with the expanded
- arguments.
- With the --extract argument (useful for e.g. $(AR)), it extracts object files
- from static libraries (or use those listed in library descriptors directly).
- With the --uselist argument (useful for e.g. $(CC)), it replaces all object
- files with a list file. This can be used to avoid limitations in the length
- of a command line. The kind of list file format used depends on the
- EXPAND_LIBS_LIST_STYLE variable: 'list' for MSVC style lists (@file.list)
- or 'linkerscript' for GNU ld linker scripts.
- See https://bugzilla.mozilla.org/show_bug.cgi?id=584474#c59 for more details.
- With the --symbol-order argument, followed by a file name, it will add the
- relevant linker options to change the order in which the linker puts the
- symbols appear in the resulting binary. Only works for ELF targets.
- '''
- from __future__ import with_statement
- import sys
- import os
- from expandlibs import (
- ExpandArgs,
- relativize,
- isDynamicLib,
- isObject,
- )
- import expandlibs_config as conf
- from optparse import OptionParser
- import subprocess
- import tempfile
- import shutil
- import subprocess
- import re
- from mozbuild.makeutil import Makefile
- # The are the insert points for a GNU ld linker script, assuming a more
- # or less "standard" default linker script. This is not a dict because
- # order is important.
- SECTION_INSERT_BEFORE = [
- ('.text', '.fini'),
- ('.rodata', '.rodata1'),
- ('.data.rel.ro', '.dynamic'),
- ('.data', '.data1'),
- ]
- class ExpandArgsMore(ExpandArgs):
- ''' Meant to be used as 'with ExpandArgsMore(args) as ...: '''
- def __enter__(self):
- self.tmp = []
- return self
-
- def __exit__(self, type, value, tb):
- '''Automatically remove temporary files'''
- for tmp in self.tmp:
- if os.path.isdir(tmp):
- shutil.rmtree(tmp, True)
- else:
- os.remove(tmp)
- def extract(self):
- self[0:] = self._extract(self)
- def _extract(self, args):
- '''When a static library name is found, either extract its contents
- in a temporary directory or use the information found in the
- corresponding lib descriptor.
- '''
- ar_extract = conf.AR_EXTRACT.split()
- newlist = []
- def lookup(base, f):
- for root, dirs, files in os.walk(base):
- if f in files:
- return os.path.join(root, f)
- for arg in args:
- if os.path.splitext(arg)[1] == conf.LIB_SUFFIX:
- if os.path.exists(arg + conf.LIBS_DESC_SUFFIX):
- newlist += self._extract(self._expand_desc(arg))
- continue
- elif os.path.exists(arg) and (len(ar_extract) or conf.AR == 'lib'):
- tmp = tempfile.mkdtemp(dir=os.curdir)
- self.tmp.append(tmp)
- if conf.AR == 'lib':
- out = subprocess.check_output([conf.AR, '-NOLOGO', '-LIST', arg])
- files = out.splitlines()
- # If lib -list returns a list full of dlls, it's an
- # import lib.
- if all(isDynamicLib(f) for f in files):
- newlist += [arg]
- continue
- for f in files:
- subprocess.call([conf.AR, '-NOLOGO', '-EXTRACT:%s' % f, os.path.abspath(arg)], cwd=tmp)
- else:
- subprocess.call(ar_extract + [os.path.abspath(arg)], cwd=tmp)
- objs = []
- basedir = os.path.dirname(arg)
- for root, dirs, files in os.walk(tmp):
- for f in files:
- if isObject(f):
- # If the file extracted from the library also
- # exists in the directory containing the
- # library, or one of its subdirectories, use
- # that instead.
- maybe_obj = lookup(os.path.join(basedir, os.path.relpath(root, tmp)), f)
- if maybe_obj:
- objs.append(relativize(maybe_obj))
- else:
- objs.append(relativize(os.path.join(root, f)))
- newlist += sorted(objs)
- continue
- newlist += [arg]
- return newlist
- def makelist(self):
- '''Replaces object file names with a temporary list file, using a
- list format depending on the EXPAND_LIBS_LIST_STYLE variable
- '''
- objs = [o for o in self if isObject(o)]
- if not len(objs): return
- fd, tmp = tempfile.mkstemp(suffix=".list",dir=os.curdir)
- if conf.EXPAND_LIBS_LIST_STYLE == "linkerscript":
- content = ['INPUT("%s")\n' % obj for obj in objs]
- ref = tmp
- elif conf.EXPAND_LIBS_LIST_STYLE == "filelist":
- content = ["%s\n" % obj for obj in objs]
- ref = "-Wl,-filelist," + tmp
- elif conf.EXPAND_LIBS_LIST_STYLE == "list":
- content = ["%s\n" % obj for obj in objs]
- ref = "@" + tmp
- else:
- os.close(fd)
- os.remove(tmp)
- return
- self.tmp.append(tmp)
- f = os.fdopen(fd, "w")
- f.writelines(content)
- f.close()
- idx = self.index(objs[0])
- newlist = self[0:idx] + [ref] + [item for item in self[idx:] if item not in objs]
- self[0:] = newlist
- def _getFoldedSections(self):
- '''Returns a dict about folded sections.
- When section A and B are folded into section C, the dict contains:
- { 'A': 'C',
- 'B': 'C',
- 'C': ['A', 'B'] }'''
- if not conf.LD_PRINT_ICF_SECTIONS:
- return {}
- proc = subprocess.Popen(self + [conf.LD_PRINT_ICF_SECTIONS], stdout = subprocess.PIPE, stderr = subprocess.PIPE)
- (stdout, stderr) = proc.communicate()
- result = {}
- # gold's --print-icf-sections output looks like the following:
- # ld: ICF folding section '.section' in file 'file.o'into '.section' in file 'file.o'
- # In terms of words, chances are this will change in the future,
- # especially considering "into" is misplaced. Splitting on quotes
- # seems safer.
- for l in stderr.split('\n'):
- quoted = l.split("'")
- if len(quoted) > 5 and quoted[1] != quoted[5]:
- result[quoted[1]] = [quoted[5]]
- if quoted[5] in result:
- result[quoted[5]].append(quoted[1])
- else:
- result[quoted[5]] = [quoted[1]]
- return result
- def _getOrderedSections(self, ordered_symbols):
- '''Given an ordered list of symbols, returns the corresponding list
- of sections following the order.'''
- if not conf.EXPAND_LIBS_ORDER_STYLE in ['linkerscript', 'section-ordering-file']:
- raise Exception('EXPAND_LIBS_ORDER_STYLE "%s" is not supported' % conf.EXPAND_LIBS_ORDER_STYLE)
- finder = SectionFinder([arg for arg in self if isObject(arg) or os.path.splitext(arg)[1] == conf.LIB_SUFFIX])
- folded = self._getFoldedSections()
- sections = set()
- ordered_sections = []
- for symbol in ordered_symbols:
- symbol_sections = finder.getSections(symbol)
- all_symbol_sections = []
- for section in symbol_sections:
- if section in folded:
- if isinstance(folded[section], str):
- section = folded[section]
- all_symbol_sections.append(section)
- all_symbol_sections.extend(folded[section])
- else:
- all_symbol_sections.append(section)
- for section in all_symbol_sections:
- if not section in sections:
- ordered_sections.append(section)
- sections.add(section)
- return ordered_sections
- def orderSymbols(self, order):
- '''Given a file containing a list of symbols, adds the appropriate
- argument to make the linker put the symbols in that order.'''
- with open(order) as file:
- sections = self._getOrderedSections([l.strip() for l in file.readlines() if l.strip()])
- split_sections = {}
- linked_sections = [s[0] for s in SECTION_INSERT_BEFORE]
- for s in sections:
- for linked_section in linked_sections:
- if s.startswith(linked_section):
- if linked_section in split_sections:
- split_sections[linked_section].append(s)
- else:
- split_sections[linked_section] = [s]
- break
- content = []
- # Order is important
- linked_sections = [s for s in linked_sections if s in split_sections]
- if conf.EXPAND_LIBS_ORDER_STYLE == 'section-ordering-file':
- option = '-Wl,--section-ordering-file,%s'
- content = sections
- for linked_section in linked_sections:
- content.extend(split_sections[linked_section])
- content.append('%s.*' % linked_section)
- content.append(linked_section)
- elif conf.EXPAND_LIBS_ORDER_STYLE == 'linkerscript':
- option = '-Wl,-T,%s'
- section_insert_before = dict(SECTION_INSERT_BEFORE)
- for linked_section in linked_sections:
- content.append('SECTIONS {')
- content.append(' %s : {' % linked_section)
- content.extend(' *(%s)' % s for s in split_sections[linked_section])
- content.append(' }')
- content.append('}')
- content.append('INSERT BEFORE %s' % section_insert_before[linked_section])
- else:
- raise Exception('EXPAND_LIBS_ORDER_STYLE "%s" is not supported' % conf.EXPAND_LIBS_ORDER_STYLE)
- fd, tmp = tempfile.mkstemp(dir=os.curdir)
- f = os.fdopen(fd, "w")
- f.write('\n'.join(content)+'\n')
- f.close()
- self.tmp.append(tmp)
- self.append(option % tmp)
- class SectionFinder(object):
- '''Instances of this class allow to map symbol names to sections in
- object files.'''
- def __init__(self, objs):
- '''Creates an instance, given a list of object files.'''
- if not conf.EXPAND_LIBS_ORDER_STYLE in ['linkerscript', 'section-ordering-file']:
- raise Exception('EXPAND_LIBS_ORDER_STYLE "%s" is not supported' % conf.EXPAND_LIBS_ORDER_STYLE)
- self.mapping = {}
- for obj in objs:
- if not isObject(obj) and os.path.splitext(obj)[1] != conf.LIB_SUFFIX:
- raise Exception('%s is not an object nor a static library' % obj)
- for symbol, section in SectionFinder._getSymbols(obj):
- sym = SectionFinder._normalize(symbol)
- if sym in self.mapping:
- if not section in self.mapping[sym]:
- self.mapping[sym].append(section)
- else:
- self.mapping[sym] = [section]
- def getSections(self, symbol):
- '''Given a symbol, returns a list of sections containing it or the
- corresponding thunks. When the given symbol is a thunk, returns the
- list of sections containing its corresponding normal symbol and the
- other thunks for that symbol.'''
- sym = SectionFinder._normalize(symbol)
- if sym in self.mapping:
- return self.mapping[sym]
- return []
- @staticmethod
- def _normalize(symbol):
- '''For normal symbols, return the given symbol. For thunks, return
- the corresponding normal symbol.'''
- if re.match('^_ZThn[0-9]+_', symbol):
- return re.sub('^_ZThn[0-9]+_', '_Z', symbol)
- return symbol
- @staticmethod
- def _getSymbols(obj):
- '''Returns a list of (symbol, section) contained in the given object
- file.'''
- proc = subprocess.Popen(['objdump', '-t', obj], stdout = subprocess.PIPE, stderr = subprocess.PIPE)
- (stdout, stderr) = proc.communicate()
- syms = []
- for line in stdout.splitlines():
- # Each line has the following format:
- # <addr> [lgu!][w ][C ][W ][Ii ][dD ][FfO ] <section>\t<length> <symbol>
- tmp = line.split(' ',1)
- # This gives us ["<addr>", "[lgu!][w ][C ][W ][Ii ][dD ][FfO ] <section>\t<length> <symbol>"]
- # We only need to consider cases where "<section>\t<length> <symbol>" is present,
- # and where the [FfO] flag is either F (function) or O (object).
- if len(tmp) > 1 and len(tmp[1]) > 6 and tmp[1][6] in ['O', 'F']:
- tmp = tmp[1][8:].split()
- # That gives us ["<section>","<length>", "<symbol>"]
- syms.append((tmp[-1], tmp[0]))
- return syms
- def print_command(out, args):
- print >>out, "Executing: " + " ".join(args)
- for tmp in [f for f in args.tmp if os.path.isfile(f)]:
- print >>out, tmp + ":"
- with open(tmp) as file:
- print >>out, "".join([" " + l for l in file.readlines()])
- out.flush()
- def main(args, proc_callback=None):
- parser = OptionParser()
- parser.add_option("--extract", action="store_true", dest="extract",
- help="when a library has no descriptor file, extract it first, when possible")
- parser.add_option("--uselist", action="store_true", dest="uselist",
- help="use a list file for objects when executing a command")
- parser.add_option("--verbose", action="store_true", dest="verbose",
- help="display executed command and temporary files content")
- parser.add_option("--symbol-order", dest="symbol_order", metavar="FILE",
- help="use the given list of symbols to order symbols in the resulting binary when using with a linker")
- (options, args) = parser.parse_args(args)
- with ExpandArgsMore(args) as args:
- if options.extract:
- args.extract()
- if options.symbol_order:
- args.orderSymbols(options.symbol_order)
- if options.uselist:
- args.makelist()
- if options.verbose:
- print_command(sys.stderr, args)
- try:
- proc = subprocess.Popen(args, stdout = subprocess.PIPE, stderr = subprocess.STDOUT)
- if proc_callback:
- proc_callback(proc)
- except Exception, e:
- print >>sys.stderr, 'error: Launching', args, ':', e
- raise e
- (stdout, stderr) = proc.communicate()
- if proc.returncode and not options.verbose:
- print_command(sys.stderr, args)
- sys.stderr.write(stdout)
- sys.stderr.flush()
- if proc.returncode:
- return proc.returncode
- return 0
- if __name__ == '__main__':
- exit(main(sys.argv[1:]))
|