| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """A tool to generate symbols for a binary suitable for breakpad. | 6 """A tool to generate symbols for a binary suitable for breakpad. |
| 7 | 7 |
| 8 Currently, the tool only supports Linux, Android, and Mac. Support for other | 8 Currently, the tool only supports Linux, Android, and Mac. Support for other |
| 9 platforms is planned. | 9 platforms is planned. |
| 10 """ | 10 """ |
| (...skipping 20 matching lines...) Expand all Loading... |
| 31 | 31 |
| 32 From chromium_utils. | 32 From chromium_utils. |
| 33 """ | 33 """ |
| 34 devnull = open(os.devnull, 'w') | 34 devnull = open(os.devnull, 'w') |
| 35 proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=devnull, | 35 proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=devnull, |
| 36 bufsize=1) | 36 bufsize=1) |
| 37 output = proc.communicate()[0] | 37 output = proc.communicate()[0] |
| 38 return output | 38 return output |
| 39 | 39 |
| 40 | 40 |
| 41 def GetDumpSymsBinary(dump_syms_dir=None): | 41 def GetDumpSymsBinary(build_dir=None): |
| 42 """Returns the path to the dump_syms binary.""" | 42 """Returns the path to the dump_syms binary.""" |
| 43 DUMP_SYMS = 'dump_syms' | 43 DUMP_SYMS = 'dump_syms' |
| 44 dump_syms_bin = None | 44 dump_syms_bin = os.path.join(os.path.expanduser(build_dir), DUMP_SYMS) |
| 45 if dump_syms_dir: | 45 if not os.access(dump_syms_bin, os.X_OK): |
| 46 bin = os.path.join(os.path.expanduser(dump_syms_dir), DUMP_SYMS) | |
| 47 if os.access(bin, os.X_OK): | |
| 48 dump_syms_bin = bin | |
| 49 else: | |
| 50 for path in os.environ['PATH'].split(':'): | |
| 51 bin = os.path.join(path, DUMP_SYMS) | |
| 52 if os.access(bin, os.X_OK): | |
| 53 dump_syms_bin = bin | |
| 54 break | |
| 55 if not dump_syms_bin: | |
| 56 print 'Cannot find %s.' % DUMP_SYMS | 46 print 'Cannot find %s.' % DUMP_SYMS |
| 57 sys.exit(1) | 47 sys.exit(1) |
| 58 | 48 |
| 59 return dump_syms_bin | 49 return dump_syms_bin |
| 60 | 50 |
| 61 | 51 |
| 62 def Resolve(path, exe_path, loader_path, rpaths): | 52 def Resolve(path, exe_path, loader_path, rpaths): |
| 63 """Resolve a dyld path. | 53 """Resolve a dyld path. |
| 64 | 54 |
| 65 @executable_path is replaced with |exe_path| | 55 @executable_path is replaced with |exe_path| |
| 66 @loader_path is replaced with |loader_path| | 56 @loader_path is replaced with |loader_path| |
| 67 @rpath is replaced with the first path in |rpaths| where the referenced file | 57 @rpath is replaced with the first path in |rpaths| where the referenced file |
| 68 is found | 58 is found |
| 69 """ | 59 """ |
| 70 path.replace('@loader_path', loader_path) | 60 path = path.replace('@loader_path', loader_path) |
| 71 path.replace('@executable_path', exe_path) | 61 path = path.replace('@executable_path', exe_path) |
| 72 if path.find('@rpath') != -1: | 62 if path.find('@rpath') != -1: |
| 73 for rpath in rpaths: | 63 for rpath in rpaths: |
| 74 new_path = Resolve(path.replace('@rpath', rpath), exe_path, loader_path, | 64 new_path = Resolve(path.replace('@rpath', rpath), exe_path, loader_path, |
| 75 []) | 65 []) |
| 76 if os.access(new_path, os.X_OK): | 66 if os.access(new_path, os.X_OK): |
| 77 return new_path | 67 return new_path |
| 78 return '' | 68 return '' |
| 79 return path | 69 return path |
| 80 | 70 |
| 81 | 71 |
| (...skipping 23 matching lines...) Expand all Loading... |
| 105 m = re.match(' *path (.*) \(offset .*\)$', otool[idx+2]) | 95 m = re.match(' *path (.*) \(offset .*\)$', otool[idx+2]) |
| 106 rpaths.append(m.group(1)) | 96 rpaths.append(m.group(1)) |
| 107 | 97 |
| 108 otool = GetCommandOutput(['otool', '-L', binary]).splitlines() | 98 otool = GetCommandOutput(['otool', '-L', binary]).splitlines() |
| 109 lib_re = re.compile('\t(.*) \(compatibility .*\)$') | 99 lib_re = re.compile('\t(.*) \(compatibility .*\)$') |
| 110 deps = [] | 100 deps = [] |
| 111 for line in otool: | 101 for line in otool: |
| 112 m = lib_re.match(line) | 102 m = lib_re.match(line) |
| 113 if m: | 103 if m: |
| 114 dep = Resolve(m.group(1), exe_path, loader_path, rpaths) | 104 dep = Resolve(m.group(1), exe_path, loader_path, rpaths) |
| 115 if dep and os.access(dep, os.X_OK): | 105 if dep: |
| 116 deps.append(os.path.normpath(dep)) | 106 deps.append(os.path.normpath(dep)) |
| 117 return deps | 107 return deps |
| 118 | 108 |
| 119 | 109 |
| 120 def GetSharedLibraryDependencies(binary, exe_path): | 110 def GetSharedLibraryDependencies(options, binary, exe_path): |
| 121 """Return absolute paths to all shared library dependecies of the binary.""" | 111 """Return absolute paths to all shared library dependecies of the binary.""" |
| 112 deps = [] |
| 122 if sys.platform.startswith('linux'): | 113 if sys.platform.startswith('linux'): |
| 123 return GetSharedLibraryDependenciesLinux(binary) | 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) |
| 124 | 120 |
| 125 if sys.platform == 'darwin': | 121 result = [] |
| 126 return GetSharedLibraryDependenciesMac(binary, exe_path) | 122 build_dir = os.path.abspath(options.build_dir) |
| 127 | 123 for dep in deps: |
| 128 print "Platform not supported." | 124 if (os.access(dep, os.X_OK) and |
| 129 sys.exit(1) | 125 os.path.abspath(os.path.dirname(dep)).startswith(build_dir)): |
| 126 result.append(dep) |
| 127 return result |
| 130 | 128 |
| 131 | 129 |
| 132 def mkdir_p(path): | 130 def mkdir_p(path): |
| 133 """Simulates mkdir -p.""" | 131 """Simulates mkdir -p.""" |
| 134 try: | 132 try: |
| 135 os.makedirs(path) | 133 os.makedirs(path) |
| 136 except OSError as e: | 134 except OSError as e: |
| 137 if e.errno == errno.EEXIST and os.path.isdir(path): | 135 if e.errno == errno.EEXIST and os.path.isdir(path): |
| 138 pass | 136 pass |
| 139 else: raise | 137 else: raise |
| 140 | 138 |
| 141 | 139 |
| 142 def GenerateSymbols(options, binaries): | 140 def GenerateSymbols(options, binaries): |
| 143 """Dumps the symbols of binary and places them in the given directory.""" | 141 """Dumps the symbols of binary and places them in the given directory.""" |
| 144 | 142 |
| 145 queue = Queue.Queue() | 143 queue = Queue.Queue() |
| 146 | 144 |
| 147 def _Worker(): | 145 def _Worker(): |
| 148 while True: | 146 while True: |
| 149 binary = queue.get() | 147 binary = queue.get() |
| 150 | 148 |
| 151 syms = GetCommandOutput([GetDumpSymsBinary(options.dump_syms_dir), | 149 syms = GetCommandOutput([GetDumpSymsBinary(options.build_dir), |
| 152 binary]) | 150 binary]) |
| 153 module_line = re.match("MODULE [^ ]+ [^ ]+ ([0-9A-F]+) (.*)\n", syms) | 151 module_line = re.match("MODULE [^ ]+ [^ ]+ ([0-9A-F]+) (.*)\n", syms) |
| 154 output_path = os.path.join(options.symbols_dir, module_line.group(2), | 152 output_path = os.path.join(options.symbols_dir, module_line.group(2), |
| 155 module_line.group(1)) | 153 module_line.group(1)) |
| 156 mkdir_p(output_path) | 154 mkdir_p(output_path) |
| 157 symbol_file = "%s.sym" % module_line.group(2) | 155 symbol_file = "%s.sym" % module_line.group(2) |
| 158 f = open(os.path.join(output_path, symbol_file), 'w') | 156 f = open(os.path.join(output_path, symbol_file), 'w') |
| 159 f.write(syms) | 157 f.write(syms) |
| 160 f.close() | 158 f.close() |
| 161 | 159 |
| 162 queue.task_done() | 160 queue.task_done() |
| 163 | 161 |
| 164 for binary in binaries: | 162 for binary in binaries: |
| 165 queue.put(binary) | 163 queue.put(binary) |
| 166 | 164 |
| 167 for _ in range(options.jobs): | 165 for _ in range(options.jobs): |
| 168 t = threading.Thread(target=_Worker) | 166 t = threading.Thread(target=_Worker) |
| 169 t.daemon = True | 167 t.daemon = True |
| 170 t.start() | 168 t.start() |
| 171 | 169 |
| 172 queue.join() | 170 queue.join() |
| 173 | 171 |
| 174 | 172 |
| 175 def main(): | 173 def main(): |
| 176 parser = optparse.OptionParser() | 174 parser = optparse.OptionParser() |
| 177 parser.add_option('', '--dump-syms-dir', default='', | 175 parser.add_option('', '--build-dir', default='', |
| 178 help='The directory where dump_syms is installed. ' | 176 help='The build output directory.') |
| 179 'Searches $PATH by default') | |
| 180 parser.add_option('', '--symbols-dir', default='', | 177 parser.add_option('', '--symbols-dir', default='', |
| 181 help='The directory where to write the symbols file.') | 178 help='The directory where to write the symbols file.') |
| 182 parser.add_option('', '--binary', default='', | 179 parser.add_option('', '--binary', default='', |
| 183 help='The path of the binary to generate symbols for.') | 180 help='The path of the binary to generate symbols for.') |
| 184 parser.add_option('', '--clear', default=False, action='store_true', | 181 parser.add_option('', '--clear', default=False, action='store_true', |
| 185 help='Clear the symbols directory before writing new ' | 182 help='Clear the symbols directory before writing new ' |
| 186 'symbols.') | 183 'symbols.') |
| 187 parser.add_option('-j', '--jobs', default=CONCURRENT_TASKS, action='store', | 184 parser.add_option('-j', '--jobs', default=CONCURRENT_TASKS, action='store', |
| 188 type='int', help='Number of parallel tasks to run.') | 185 type='int', help='Number of parallel tasks to run.') |
| 189 | 186 |
| 190 (options, _) = parser.parse_args() | 187 (options, _) = parser.parse_args() |
| 191 | 188 |
| 192 if not options.symbols_dir: | 189 if not options.symbols_dir: |
| 193 print "Required option --symbols-dir missing." | 190 print "Required option --symbols-dir missing." |
| 194 return 1 | 191 return 1 |
| 195 | 192 |
| 193 if not options.build_dir: |
| 194 print "Required option --build-dir missing." |
| 195 return 1 |
| 196 |
| 196 if not options.binary: | 197 if not options.binary: |
| 197 print "Required option --binary missing." | 198 print "Required option --binary missing." |
| 198 return 1 | 199 return 1 |
| 199 | 200 |
| 200 if not os.access(options.binary, os.X_OK): | 201 if not os.access(options.binary, os.X_OK): |
| 201 print "Cannot find %s." % options.binary | 202 print "Cannot find %s." % options.binary |
| 202 return 1 | 203 return 1 |
| 203 | 204 |
| 204 if options.clear: | 205 if options.clear: |
| 205 try: | 206 try: |
| 206 shutil.rmtree(options.symbols_dir) | 207 shutil.rmtree(options.symbols_dir) |
| 207 except: | 208 except: |
| 208 pass | 209 pass |
| 209 | 210 |
| 210 # Build the transitive closure of all dependencies. | 211 # Build the transitive closure of all dependencies. |
| 211 binaries = set([options.binary]) | 212 binaries = set([options.binary]) |
| 212 queue = [options.binary] | 213 queue = [options.binary] |
| 213 exe_path = os.path.dirname(options.binary) | 214 exe_path = os.path.dirname(options.binary) |
| 214 while queue: | 215 while queue: |
| 215 deps = GetSharedLibraryDependencies(queue.pop(0), exe_path) | 216 deps = GetSharedLibraryDependencies(options, queue.pop(0), exe_path) |
| 216 new_deps = set(deps) - binaries | 217 new_deps = set(deps) - binaries |
| 217 binaries |= new_deps | 218 binaries |= new_deps |
| 218 queue.extend(list(new_deps)) | 219 queue.extend(list(new_deps)) |
| 219 | 220 |
| 220 GenerateSymbols(options, binaries) | 221 GenerateSymbols(options, binaries) |
| 221 | 222 |
| 222 return 0 | 223 return 0 |
| 223 | 224 |
| 224 | 225 |
| 225 if '__main__' == __name__: | 226 if '__main__' == __name__: |
| 226 sys.exit(main()) | 227 sys.exit(main()) |
| OLD | NEW |