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 |