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