OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/env python |
| 2 # Copyright 2013 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. |
| 5 |
| 6 """A tool to generate symbols for a binary suitable for breakpad. |
| 7 |
| 8 Currently, the tool only supports Linux, Android, and Mac. Support for other |
| 9 platforms is planned. |
| 10 """ |
| 11 |
| 12 import errno |
| 13 import optparse |
| 14 import os |
| 15 import Queue |
| 16 import re |
| 17 import shutil |
| 18 import subprocess |
| 19 import sys |
| 20 import threading |
| 21 |
| 22 |
| 23 CONCURRENT_TASKS=4 |
| 24 |
| 25 |
| 26 def GetCommandOutput(command): |
| 27 """Runs the command list, returning its output. |
| 28 |
| 29 Prints the given command (which should be a list of one or more strings), |
| 30 then runs it and returns its output (stdout) as a string. |
| 31 |
| 32 From chromium_utils. |
| 33 """ |
| 34 devnull = open(os.devnull, 'w') |
| 35 proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=devnull, |
| 36 bufsize=1) |
| 37 output = proc.communicate()[0] |
| 38 return output |
| 39 |
| 40 |
| 41 def GetDumpSymsBinary(build_dir=None): |
| 42 """Returns the path to the dump_syms binary.""" |
| 43 DUMP_SYMS = 'dump_syms' |
| 44 dump_syms_bin = os.path.join(os.path.expanduser(build_dir), DUMP_SYMS) |
| 45 if not os.access(dump_syms_bin, os.X_OK): |
| 46 print 'Cannot find %s.' % dump_syms_bin |
| 47 return None |
| 48 |
| 49 return dump_syms_bin |
| 50 |
| 51 |
| 52 def Resolve(path, exe_path, loader_path, rpaths): |
| 53 """Resolve a dyld path. |
| 54 |
| 55 @executable_path is replaced with |exe_path| |
| 56 @loader_path is replaced with |loader_path| |
| 57 @rpath is replaced with the first path in |rpaths| where the referenced file |
| 58 is found |
| 59 """ |
| 60 path = path.replace('@loader_path', loader_path) |
| 61 path = path.replace('@executable_path', exe_path) |
| 62 if path.find('@rpath') != -1: |
| 63 for rpath in rpaths: |
| 64 new_path = Resolve(path.replace('@rpath', rpath), exe_path, loader_path, |
| 65 []) |
| 66 if os.access(new_path, os.X_OK): |
| 67 return new_path |
| 68 return '' |
| 69 return path |
| 70 |
| 71 |
| 72 def GetSharedLibraryDependenciesLinux(binary): |
| 73 """Return absolute paths to all shared library dependecies of the binary. |
| 74 |
| 75 This implementation assumes that we're running on a Linux system.""" |
| 76 ldd = GetCommandOutput(['ldd', binary]) |
| 77 lib_re = re.compile('\t.* => (.+) \(.*\)$') |
| 78 result = [] |
| 79 for line in ldd.splitlines(): |
| 80 m = lib_re.match(line) |
| 81 if m: |
| 82 result.append(m.group(1)) |
| 83 return result |
| 84 |
| 85 |
| 86 def GetSharedLibraryDependenciesMac(binary, exe_path): |
| 87 """Return absolute paths to all shared library dependecies of the binary. |
| 88 |
| 89 This implementation assumes that we're running on a Mac system.""" |
| 90 loader_path = os.path.dirname(binary) |
| 91 otool = GetCommandOutput(['otool', '-l', binary]).splitlines() |
| 92 rpaths = [] |
| 93 for idx, line in enumerate(otool): |
| 94 if line.find('cmd LC_RPATH') != -1: |
| 95 m = re.match(' *path (.*) \(offset .*\)$', otool[idx+2]) |
| 96 rpaths.append(m.group(1)) |
| 97 |
| 98 otool = GetCommandOutput(['otool', '-L', binary]).splitlines() |
| 99 lib_re = re.compile('\t(.*) \(compatibility .*\)$') |
| 100 deps = [] |
| 101 for line in otool: |
| 102 m = lib_re.match(line) |
| 103 if m: |
| 104 dep = Resolve(m.group(1), exe_path, loader_path, rpaths) |
| 105 if dep: |
| 106 deps.append(os.path.normpath(dep)) |
| 107 return deps |
| 108 |
| 109 |
| 110 def GetSharedLibraryDependencies(options, binary, exe_path): |
| 111 """Return absolute paths to all shared library dependecies of the binary.""" |
| 112 deps = [] |
| 113 if sys.platform.startswith('linux'): |
| 114 deps = GetSharedLibraryDependenciesLinux(binary) |
| 115 elif sys.platform == 'darwin': |
| 116 deps = GetSharedLibraryDependenciesMac(binary, exe_path) |
| 117 else: |
| 118 print "Platform not supported." |
| 119 sys.exit(1) |
| 120 |
| 121 result = [] |
| 122 build_dir = os.path.abspath(options.build_dir) |
| 123 for dep in deps: |
| 124 if (os.access(dep, os.X_OK) and |
| 125 os.path.abspath(os.path.dirname(dep)).startswith(build_dir)): |
| 126 result.append(dep) |
| 127 return result |
| 128 |
| 129 |
| 130 def mkdir_p(path): |
| 131 """Simulates mkdir -p.""" |
| 132 try: |
| 133 os.makedirs(path) |
| 134 except OSError as e: |
| 135 if e.errno == errno.EEXIST and os.path.isdir(path): |
| 136 pass |
| 137 else: raise |
| 138 |
| 139 |
| 140 def GenerateSymbols(options, binaries): |
| 141 """Dumps the symbols of binary and places them in the given directory.""" |
| 142 |
| 143 queue = Queue.Queue() |
| 144 print_lock = threading.Lock() |
| 145 |
| 146 def _Worker(): |
| 147 while True: |
| 148 binary = queue.get() |
| 149 |
| 150 should_dump_syms = True |
| 151 reason = "no reason" |
| 152 |
| 153 output_path = os.path.join( |
| 154 options.symbols_dir, os.path.basename(binary)) |
| 155 if os.path.isdir(output_path): |
| 156 if os.path.getmtime(binary) < os.path.getmtime(output_path): |
| 157 should_dump_syms = False |
| 158 reason = "symbols are more current than binary" |
| 159 |
| 160 if not should_dump_syms: |
| 161 if options.verbose: |
| 162 with print_lock: |
| 163 print "Skipping %s (%s)" % (binary, reason) |
| 164 queue.task_done() |
| 165 continue |
| 166 |
| 167 if options.verbose: |
| 168 with print_lock: |
| 169 print "Generating symbols for %s" % binary |
| 170 |
| 171 if os.path.isdir(output_path): |
| 172 os.utime(output_path, None) |
| 173 |
| 174 syms = GetCommandOutput([GetDumpSymsBinary(options.build_dir), '-r', |
| 175 binary]) |
| 176 module_line = re.match("MODULE [^ ]+ [^ ]+ ([0-9A-F]+) (.*)\n", syms) |
| 177 output_path = os.path.join(options.symbols_dir, module_line.group(2), |
| 178 module_line.group(1)) |
| 179 mkdir_p(output_path) |
| 180 symbol_file = "%s.sym" % module_line.group(2) |
| 181 try: |
| 182 f = open(os.path.join(output_path, symbol_file), 'w') |
| 183 f.write(syms) |
| 184 f.close() |
| 185 except Exception, e: |
| 186 # Not much we can do about this. |
| 187 with print_lock: |
| 188 print e |
| 189 |
| 190 queue.task_done() |
| 191 |
| 192 for binary in binaries: |
| 193 queue.put(binary) |
| 194 |
| 195 for _ in range(options.jobs): |
| 196 t = threading.Thread(target=_Worker) |
| 197 t.daemon = True |
| 198 t.start() |
| 199 |
| 200 queue.join() |
| 201 |
| 202 |
| 203 def main(): |
| 204 parser = optparse.OptionParser() |
| 205 parser.add_option('', '--build-dir', default='', |
| 206 help='The build output directory.') |
| 207 parser.add_option('', '--symbols-dir', default='', |
| 208 help='The directory where to write the symbols file.') |
| 209 parser.add_option('', '--binary', default='', |
| 210 help='The path of the binary to generate symbols for.') |
| 211 parser.add_option('', '--clear', default=False, action='store_true', |
| 212 help='Clear the symbols directory before writing new ' |
| 213 'symbols.') |
| 214 parser.add_option('-j', '--jobs', default=CONCURRENT_TASKS, action='store', |
| 215 type='int', help='Number of parallel tasks to run.') |
| 216 parser.add_option('-v', '--verbose', action='store_true', |
| 217 help='Print verbose status output.') |
| 218 |
| 219 (options, _) = parser.parse_args() |
| 220 |
| 221 if not options.symbols_dir: |
| 222 print "Required option --symbols-dir missing." |
| 223 return 1 |
| 224 |
| 225 if not options.build_dir: |
| 226 print "Required option --build-dir missing." |
| 227 return 1 |
| 228 |
| 229 if not options.binary: |
| 230 print "Required option --binary missing." |
| 231 return 1 |
| 232 |
| 233 if not os.access(options.binary, os.X_OK): |
| 234 print "Cannot find %s." % options.binary |
| 235 return 1 |
| 236 |
| 237 if options.clear: |
| 238 try: |
| 239 shutil.rmtree(options.symbols_dir) |
| 240 except: |
| 241 pass |
| 242 |
| 243 if not GetDumpSymsBinary(options.build_dir): |
| 244 return 1 |
| 245 |
| 246 # Build the transitive closure of all dependencies. |
| 247 binaries = set([options.binary]) |
| 248 queue = [options.binary] |
| 249 exe_path = os.path.dirname(options.binary) |
| 250 while queue: |
| 251 deps = GetSharedLibraryDependencies(options, queue.pop(0), exe_path) |
| 252 new_deps = set(deps) - binaries |
| 253 binaries |= new_deps |
| 254 queue.extend(list(new_deps)) |
| 255 |
| 256 GenerateSymbols(options, binaries) |
| 257 |
| 258 return 0 |
| 259 |
| 260 |
| 261 if '__main__' == __name__: |
| 262 sys.exit(main()) |
OLD | NEW |