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 |