|  | # 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:])) |