Add abhijeet.k@samsung.com to AUTHORS list.
[chromium-blink-merge.git] / components / crash / tools / generate_breakpad_symbols.py
blob5f8fb8565140d49f3cc022b3bb99f9fd2c6610cf
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.
6 """A tool to generate symbols for a binary suitable for breakpad.
8 Currently, the tool only supports Linux, Android, and Mac. Support for other
9 platforms is planned.
10 """
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
23 CONCURRENT_TASKS=4
26 def GetCommandOutput(command):
27 """Runs the command list, returning its output.
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.
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
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
49 return dump_syms_bin
52 def Resolve(path, exe_path, loader_path, rpaths):
53 """Resolve a dyld path.
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
72 def GetSharedLibraryDependenciesLinux(binary):
73 """Return absolute paths to all shared library dependecies of the binary.
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
86 def GetSharedLibraryDependenciesMac(binary, exe_path):
87 """Return absolute paths to all shared library dependecies of the binary.
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))
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
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)
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
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
140 def GenerateSymbols(options, binaries):
141 """Dumps the symbols of binary and places them in the given directory."""
143 queue = Queue.Queue()
144 print_lock = threading.Lock()
146 def _Worker():
147 while True:
148 binary = queue.get()
150 should_dump_syms = True
151 reason = "no reason"
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"
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
167 if options.verbose:
168 with print_lock:
169 print "Generating symbols for %s" % binary
171 if os.path.isdir(output_path):
172 os.utime(output_path, None)
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
190 queue.task_done()
192 for binary in binaries:
193 queue.put(binary)
195 for _ in range(options.jobs):
196 t = threading.Thread(target=_Worker)
197 t.daemon = True
198 t.start()
200 queue.join()
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.')
219 (options, _) = parser.parse_args()
221 if not options.symbols_dir:
222 print "Required option --symbols-dir missing."
223 return 1
225 if not options.build_dir:
226 print "Required option --build-dir missing."
227 return 1
229 if not options.binary:
230 print "Required option --binary missing."
231 return 1
233 if not os.access(options.binary, os.X_OK):
234 print "Cannot find %s." % options.binary
235 return 1
237 if options.clear:
238 try:
239 shutil.rmtree(options.symbols_dir)
240 except:
241 pass
243 if not GetDumpSymsBinary(options.build_dir):
244 return 1
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))
256 GenerateSymbols(options, binaries)
258 return 0
261 if '__main__' == __name__:
262 sys.exit(main())