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
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.
34 devnull = open(os.devnull, 'w')
35 proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=devnull,
37 output = proc.communicate()[0]
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
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
60 path = path.replace('@loader_path', loader_path)
61 path = path.replace('@executable_path', exe_path)
62 if path.find('@rpath') != -1:
64 new_path = Resolve(path.replace('@rpath', rpath), exe_path, loader_path,
66 if os.access(new_path, os.X_OK):
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.* => (.+) \(.*\)$')
79 for line in ldd.splitlines():
80 m = lib_re.match(line)
82 result.append(m.group(1))
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()
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 .*\)$')
102 m = lib_re.match(line)
104 dep = Resolve(m.group(1), exe_path, loader_path, rpaths)
106 deps.append(os.path.normpath(dep))
110 def GetSharedLibraryDependencies(options, binary, exe_path):
111 """Return absolute paths to all shared library dependecies of the binary."""
113 if sys.platform.startswith('linux'):
114 deps = GetSharedLibraryDependenciesLinux(binary)
115 elif sys.platform == 'darwin':
116 deps = GetSharedLibraryDependenciesMac(binary, exe_path)
118 print "Platform not supported."
122 build_dir = os.path.abspath(options.build_dir)
124 if (os.access(dep, os.X_OK) and
125 os.path.abspath(os.path.dirname(dep)).startswith(build_dir)):
131 """Simulates mkdir -p."""
135 if e.errno == errno.EEXIST and os.path.isdir(path):
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()
150 should_dump_syms = True
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:
163 print "Skipping %s (%s)" % (binary, reason)
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',
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))
180 symbol_file = "%s.sym" % module_line.group(2)
181 f = open(os.path.join(output_path, symbol_file), 'w')
187 for binary in binaries:
190 for _ in range(options.jobs):
191 t = threading.Thread(target=_Worker)
199 parser = optparse.OptionParser()
200 parser.add_option('', '--build-dir', default='',
201 help='The build output directory.')
202 parser.add_option('', '--symbols-dir', default='',
203 help='The directory where to write the symbols file.')
204 parser.add_option('', '--binary', default='',
205 help='The path of the binary to generate symbols for.')
206 parser.add_option('', '--clear', default=False, action='store_true',
207 help='Clear the symbols directory before writing new '
209 parser.add_option('-j', '--jobs', default=CONCURRENT_TASKS, action='store',
210 type='int', help='Number of parallel tasks to run.')
211 parser.add_option('-v', '--verbose', action='store_true',
212 help='Print verbose status output.')
214 (options, _) = parser.parse_args()
216 if not options.symbols_dir:
217 print "Required option --symbols-dir missing."
220 if not options.build_dir:
221 print "Required option --build-dir missing."
224 if not options.binary:
225 print "Required option --binary missing."
228 if not os.access(options.binary, os.X_OK):
229 print "Cannot find %s." % options.binary
234 shutil.rmtree(options.symbols_dir)
238 # Build the transitive closure of all dependencies.
239 binaries = set([options.binary])
240 queue = [options.binary]
241 exe_path = os.path.dirname(options.binary)
243 deps = GetSharedLibraryDependencies(options, queue.pop(0), exe_path)
244 new_deps = set(deps) - binaries
246 queue.extend(list(new_deps))
248 GenerateSymbols(options, binaries)
253 if '__main__' == __name__: