1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/config/expandlibs_exec.py Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,362 @@ 1.4 +# This Source Code Form is subject to the terms of the Mozilla Public 1.5 +# License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 +# file, You can obtain one at http://mozilla.org/MPL/2.0/. 1.7 + 1.8 +'''expandlibs-exec.py applies expandlibs rules, and some more (see below) to 1.9 +a given command line, and executes that command line with the expanded 1.10 +arguments. 1.11 + 1.12 +With the --extract argument (useful for e.g. $(AR)), it extracts object files 1.13 +from static libraries (or use those listed in library descriptors directly). 1.14 + 1.15 +With the --uselist argument (useful for e.g. $(CC)), it replaces all object 1.16 +files with a list file. This can be used to avoid limitations in the length 1.17 +of a command line. The kind of list file format used depends on the 1.18 +EXPAND_LIBS_LIST_STYLE variable: 'list' for MSVC style lists (@file.list) 1.19 +or 'linkerscript' for GNU ld linker scripts. 1.20 +See https://bugzilla.mozilla.org/show_bug.cgi?id=584474#c59 for more details. 1.21 + 1.22 +With the --symbol-order argument, followed by a file name, it will add the 1.23 +relevant linker options to change the order in which the linker puts the 1.24 +symbols appear in the resulting binary. Only works for ELF targets. 1.25 +''' 1.26 +from __future__ import with_statement 1.27 +import sys 1.28 +import os 1.29 +from expandlibs import ( 1.30 + ExpandArgs, 1.31 + relativize, 1.32 + isDynamicLib, 1.33 + isObject, 1.34 + ensureParentDir, 1.35 + ExpandLibsDeps, 1.36 +) 1.37 +import expandlibs_config as conf 1.38 +from optparse import OptionParser 1.39 +import subprocess 1.40 +import tempfile 1.41 +import shutil 1.42 +import subprocess 1.43 +import re 1.44 +from mozbuild.makeutil import Makefile 1.45 + 1.46 +# The are the insert points for a GNU ld linker script, assuming a more 1.47 +# or less "standard" default linker script. This is not a dict because 1.48 +# order is important. 1.49 +SECTION_INSERT_BEFORE = [ 1.50 + ('.text', '.fini'), 1.51 + ('.rodata', '.rodata1'), 1.52 + ('.data.rel.ro', '.dynamic'), 1.53 + ('.data', '.data1'), 1.54 +] 1.55 + 1.56 +class ExpandArgsMore(ExpandArgs): 1.57 + ''' Meant to be used as 'with ExpandArgsMore(args) as ...: ''' 1.58 + def __enter__(self): 1.59 + self.tmp = [] 1.60 + return self 1.61 + 1.62 + def __exit__(self, type, value, tb): 1.63 + '''Automatically remove temporary files''' 1.64 + for tmp in self.tmp: 1.65 + if os.path.isdir(tmp): 1.66 + shutil.rmtree(tmp, True) 1.67 + else: 1.68 + os.remove(tmp) 1.69 + 1.70 + def extract(self): 1.71 + self[0:] = self._extract(self) 1.72 + 1.73 + def _extract(self, args): 1.74 + '''When a static library name is found, either extract its contents 1.75 + in a temporary directory or use the information found in the 1.76 + corresponding lib descriptor. 1.77 + ''' 1.78 + ar_extract = conf.AR_EXTRACT.split() 1.79 + newlist = [] 1.80 + for arg in args: 1.81 + if os.path.splitext(arg)[1] == conf.LIB_SUFFIX: 1.82 + if os.path.exists(arg + conf.LIBS_DESC_SUFFIX): 1.83 + newlist += self._extract(self._expand_desc(arg)) 1.84 + continue 1.85 + elif os.path.exists(arg) and (len(ar_extract) or conf.AR == 'lib'): 1.86 + tmp = tempfile.mkdtemp(dir=os.curdir) 1.87 + self.tmp.append(tmp) 1.88 + if conf.AR == 'lib': 1.89 + out = subprocess.check_output([conf.AR, '-NOLOGO', '-LIST', arg]) 1.90 + files = out.splitlines() 1.91 + # If lib -list returns a list full of dlls, it's an 1.92 + # import lib. 1.93 + if all(isDynamicLib(f) for f in files): 1.94 + newlist += [arg] 1.95 + continue 1.96 + for f in files: 1.97 + subprocess.call([conf.AR, '-NOLOGO', '-EXTRACT:%s' % f, os.path.abspath(arg)], cwd=tmp) 1.98 + else: 1.99 + subprocess.call(ar_extract + [os.path.abspath(arg)], cwd=tmp) 1.100 + objs = [] 1.101 + for root, dirs, files in os.walk(tmp): 1.102 + objs += [relativize(os.path.join(root, f)) for f in files if isObject(f)] 1.103 + newlist += sorted(objs) 1.104 + continue 1.105 + newlist += [arg] 1.106 + return newlist 1.107 + 1.108 + def makelist(self): 1.109 + '''Replaces object file names with a temporary list file, using a 1.110 + list format depending on the EXPAND_LIBS_LIST_STYLE variable 1.111 + ''' 1.112 + objs = [o for o in self if isObject(o)] 1.113 + if not len(objs): return 1.114 + fd, tmp = tempfile.mkstemp(suffix=".list",dir=os.curdir) 1.115 + if conf.EXPAND_LIBS_LIST_STYLE == "linkerscript": 1.116 + content = ['INPUT("%s")\n' % obj for obj in objs] 1.117 + ref = tmp 1.118 + elif conf.EXPAND_LIBS_LIST_STYLE == "filelist": 1.119 + content = ["%s\n" % obj for obj in objs] 1.120 + ref = "-Wl,-filelist," + tmp 1.121 + elif conf.EXPAND_LIBS_LIST_STYLE == "list": 1.122 + content = ["%s\n" % obj for obj in objs] 1.123 + ref = "@" + tmp 1.124 + else: 1.125 + os.close(fd) 1.126 + os.remove(tmp) 1.127 + return 1.128 + self.tmp.append(tmp) 1.129 + f = os.fdopen(fd, "w") 1.130 + f.writelines(content) 1.131 + f.close() 1.132 + idx = self.index(objs[0]) 1.133 + newlist = self[0:idx] + [ref] + [item for item in self[idx:] if item not in objs] 1.134 + self[0:] = newlist 1.135 + 1.136 + def _getFoldedSections(self): 1.137 + '''Returns a dict about folded sections. 1.138 + When section A and B are folded into section C, the dict contains: 1.139 + { 'A': 'C', 1.140 + 'B': 'C', 1.141 + 'C': ['A', 'B'] }''' 1.142 + if not conf.LD_PRINT_ICF_SECTIONS: 1.143 + return {} 1.144 + 1.145 + proc = subprocess.Popen(self + [conf.LD_PRINT_ICF_SECTIONS], stdout = subprocess.PIPE, stderr = subprocess.PIPE) 1.146 + (stdout, stderr) = proc.communicate() 1.147 + result = {} 1.148 + # gold's --print-icf-sections output looks like the following: 1.149 + # ld: ICF folding section '.section' in file 'file.o'into '.section' in file 'file.o' 1.150 + # In terms of words, chances are this will change in the future, 1.151 + # especially considering "into" is misplaced. Splitting on quotes 1.152 + # seems safer. 1.153 + for l in stderr.split('\n'): 1.154 + quoted = l.split("'") 1.155 + if len(quoted) > 5 and quoted[1] != quoted[5]: 1.156 + result[quoted[1]] = [quoted[5]] 1.157 + if quoted[5] in result: 1.158 + result[quoted[5]].append(quoted[1]) 1.159 + else: 1.160 + result[quoted[5]] = [quoted[1]] 1.161 + return result 1.162 + 1.163 + def _getOrderedSections(self, ordered_symbols): 1.164 + '''Given an ordered list of symbols, returns the corresponding list 1.165 + of sections following the order.''' 1.166 + if not conf.EXPAND_LIBS_ORDER_STYLE in ['linkerscript', 'section-ordering-file']: 1.167 + raise Exception('EXPAND_LIBS_ORDER_STYLE "%s" is not supported' % conf.EXPAND_LIBS_ORDER_STYLE) 1.168 + finder = SectionFinder([arg for arg in self if isObject(arg) or os.path.splitext(arg)[1] == conf.LIB_SUFFIX]) 1.169 + folded = self._getFoldedSections() 1.170 + sections = set() 1.171 + ordered_sections = [] 1.172 + for symbol in ordered_symbols: 1.173 + symbol_sections = finder.getSections(symbol) 1.174 + all_symbol_sections = [] 1.175 + for section in symbol_sections: 1.176 + if section in folded: 1.177 + if isinstance(folded[section], str): 1.178 + section = folded[section] 1.179 + all_symbol_sections.append(section) 1.180 + all_symbol_sections.extend(folded[section]) 1.181 + else: 1.182 + all_symbol_sections.append(section) 1.183 + for section in all_symbol_sections: 1.184 + if not section in sections: 1.185 + ordered_sections.append(section) 1.186 + sections.add(section) 1.187 + return ordered_sections 1.188 + 1.189 + def orderSymbols(self, order): 1.190 + '''Given a file containing a list of symbols, adds the appropriate 1.191 + argument to make the linker put the symbols in that order.''' 1.192 + with open(order) as file: 1.193 + sections = self._getOrderedSections([l.strip() for l in file.readlines() if l.strip()]) 1.194 + split_sections = {} 1.195 + linked_sections = [s[0] for s in SECTION_INSERT_BEFORE] 1.196 + for s in sections: 1.197 + for linked_section in linked_sections: 1.198 + if s.startswith(linked_section): 1.199 + if linked_section in split_sections: 1.200 + split_sections[linked_section].append(s) 1.201 + else: 1.202 + split_sections[linked_section] = [s] 1.203 + break 1.204 + content = [] 1.205 + # Order is important 1.206 + linked_sections = [s for s in linked_sections if s in split_sections] 1.207 + 1.208 + if conf.EXPAND_LIBS_ORDER_STYLE == 'section-ordering-file': 1.209 + option = '-Wl,--section-ordering-file,%s' 1.210 + content = sections 1.211 + for linked_section in linked_sections: 1.212 + content.extend(split_sections[linked_section]) 1.213 + content.append('%s.*' % linked_section) 1.214 + content.append(linked_section) 1.215 + 1.216 + elif conf.EXPAND_LIBS_ORDER_STYLE == 'linkerscript': 1.217 + option = '-Wl,-T,%s' 1.218 + section_insert_before = dict(SECTION_INSERT_BEFORE) 1.219 + for linked_section in linked_sections: 1.220 + content.append('SECTIONS {') 1.221 + content.append(' %s : {' % linked_section) 1.222 + content.extend(' *(%s)' % s for s in split_sections[linked_section]) 1.223 + content.append(' }') 1.224 + content.append('}') 1.225 + content.append('INSERT BEFORE %s' % section_insert_before[linked_section]) 1.226 + else: 1.227 + raise Exception('EXPAND_LIBS_ORDER_STYLE "%s" is not supported' % conf.EXPAND_LIBS_ORDER_STYLE) 1.228 + 1.229 + fd, tmp = tempfile.mkstemp(dir=os.curdir) 1.230 + f = os.fdopen(fd, "w") 1.231 + f.write('\n'.join(content)+'\n') 1.232 + f.close() 1.233 + self.tmp.append(tmp) 1.234 + self.append(option % tmp) 1.235 + 1.236 +class SectionFinder(object): 1.237 + '''Instances of this class allow to map symbol names to sections in 1.238 + object files.''' 1.239 + 1.240 + def __init__(self, objs): 1.241 + '''Creates an instance, given a list of object files.''' 1.242 + if not conf.EXPAND_LIBS_ORDER_STYLE in ['linkerscript', 'section-ordering-file']: 1.243 + raise Exception('EXPAND_LIBS_ORDER_STYLE "%s" is not supported' % conf.EXPAND_LIBS_ORDER_STYLE) 1.244 + self.mapping = {} 1.245 + for obj in objs: 1.246 + if not isObject(obj) and os.path.splitext(obj)[1] != conf.LIB_SUFFIX: 1.247 + raise Exception('%s is not an object nor a static library' % obj) 1.248 + for symbol, section in SectionFinder._getSymbols(obj): 1.249 + sym = SectionFinder._normalize(symbol) 1.250 + if sym in self.mapping: 1.251 + if not section in self.mapping[sym]: 1.252 + self.mapping[sym].append(section) 1.253 + else: 1.254 + self.mapping[sym] = [section] 1.255 + 1.256 + def getSections(self, symbol): 1.257 + '''Given a symbol, returns a list of sections containing it or the 1.258 + corresponding thunks. When the given symbol is a thunk, returns the 1.259 + list of sections containing its corresponding normal symbol and the 1.260 + other thunks for that symbol.''' 1.261 + sym = SectionFinder._normalize(symbol) 1.262 + if sym in self.mapping: 1.263 + return self.mapping[sym] 1.264 + return [] 1.265 + 1.266 + @staticmethod 1.267 + def _normalize(symbol): 1.268 + '''For normal symbols, return the given symbol. For thunks, return 1.269 + the corresponding normal symbol.''' 1.270 + if re.match('^_ZThn[0-9]+_', symbol): 1.271 + return re.sub('^_ZThn[0-9]+_', '_Z', symbol) 1.272 + return symbol 1.273 + 1.274 + @staticmethod 1.275 + def _getSymbols(obj): 1.276 + '''Returns a list of (symbol, section) contained in the given object 1.277 + file.''' 1.278 + proc = subprocess.Popen(['objdump', '-t', obj], stdout = subprocess.PIPE, stderr = subprocess.PIPE) 1.279 + (stdout, stderr) = proc.communicate() 1.280 + syms = [] 1.281 + for line in stdout.splitlines(): 1.282 + # Each line has the following format: 1.283 + # <addr> [lgu!][w ][C ][W ][Ii ][dD ][FfO ] <section>\t<length> <symbol> 1.284 + tmp = line.split(' ',1) 1.285 + # This gives us ["<addr>", "[lgu!][w ][C ][W ][Ii ][dD ][FfO ] <section>\t<length> <symbol>"] 1.286 + # We only need to consider cases where "<section>\t<length> <symbol>" is present, 1.287 + # and where the [FfO] flag is either F (function) or O (object). 1.288 + if len(tmp) > 1 and len(tmp[1]) > 6 and tmp[1][6] in ['O', 'F']: 1.289 + tmp = tmp[1][8:].split() 1.290 + # That gives us ["<section>","<length>", "<symbol>"] 1.291 + syms.append((tmp[-1], tmp[0])) 1.292 + return syms 1.293 + 1.294 +def print_command(out, args): 1.295 + print >>out, "Executing: " + " ".join(args) 1.296 + for tmp in [f for f in args.tmp if os.path.isfile(f)]: 1.297 + print >>out, tmp + ":" 1.298 + with open(tmp) as file: 1.299 + print >>out, "".join([" " + l for l in file.readlines()]) 1.300 + out.flush() 1.301 + 1.302 +def main(): 1.303 + parser = OptionParser() 1.304 + parser.add_option("--depend", dest="depend", metavar="FILE", 1.305 + help="generate dependencies for the given execution and store it in the given file") 1.306 + parser.add_option("--target", dest="target", metavar="FILE", 1.307 + help="designate the target for dependencies") 1.308 + parser.add_option("--extract", action="store_true", dest="extract", 1.309 + help="when a library has no descriptor file, extract it first, when possible") 1.310 + parser.add_option("--uselist", action="store_true", dest="uselist", 1.311 + help="use a list file for objects when executing a command") 1.312 + parser.add_option("--verbose", action="store_true", dest="verbose", 1.313 + help="display executed command and temporary files content") 1.314 + parser.add_option("--symbol-order", dest="symbol_order", metavar="FILE", 1.315 + help="use the given list of symbols to order symbols in the resulting binary when using with a linker") 1.316 + 1.317 + (options, args) = parser.parse_args() 1.318 + 1.319 + if not options.target: 1.320 + options.depend = False 1.321 + if options.depend: 1.322 + deps = ExpandLibsDeps(args) 1.323 + # Filter out common command wrappers 1.324 + while os.path.basename(deps[0]) in ['ccache', 'distcc']: 1.325 + deps.pop(0) 1.326 + # Remove command 1.327 + deps.pop(0) 1.328 + with ExpandArgsMore(args) as args: 1.329 + if options.extract: 1.330 + args.extract() 1.331 + if options.symbol_order: 1.332 + args.orderSymbols(options.symbol_order) 1.333 + if options.uselist: 1.334 + args.makelist() 1.335 + 1.336 + if options.verbose: 1.337 + print_command(sys.stderr, args) 1.338 + try: 1.339 + proc = subprocess.Popen(args, stdout = subprocess.PIPE, stderr = subprocess.STDOUT) 1.340 + except Exception, e: 1.341 + print >>sys.stderr, 'error: Launching', args, ':', e 1.342 + raise e 1.343 + (stdout, stderr) = proc.communicate() 1.344 + if proc.returncode and not options.verbose: 1.345 + print_command(sys.stderr, args) 1.346 + sys.stderr.write(stdout) 1.347 + sys.stderr.flush() 1.348 + if proc.returncode: 1.349 + exit(proc.returncode) 1.350 + if not options.depend: 1.351 + return 1.352 + ensureParentDir(options.depend) 1.353 + mk = Makefile() 1.354 + deps = [dep for dep in deps if os.path.isfile(dep) and dep != options.target 1.355 + and os.path.abspath(dep) != os.path.abspath(options.depend)] 1.356 + no_dynamic_lib = [dep for dep in deps if not isDynamicLib(dep)] 1.357 + mk.create_rule([options.target]).add_dependencies(no_dynamic_lib) 1.358 + if len(deps) != len(no_dynamic_lib): 1.359 + mk.create_rule(['%s_order_only' % options.target]).add_dependencies(dep for dep in deps if isDynamicLib(dep)) 1.360 + 1.361 + with open(options.depend, 'w') as depfile: 1.362 + mk.dump(depfile, removal_guard=True) 1.363 + 1.364 +if __name__ == '__main__': 1.365 + main()