Tue, 06 Jan 2015 21:39:09 +0100
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() |