config/expandlibs_exec.py

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

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

mercurial