summaryrefslogtreecommitdiffstats
path: root/components/crash/content/tools/generate_breakpad_symbols.py
diff options
context:
space:
mode:
Diffstat (limited to 'components/crash/content/tools/generate_breakpad_symbols.py')
-rwxr-xr-xcomponents/crash/content/tools/generate_breakpad_symbols.py262
1 files changed, 262 insertions, 0 deletions
diff --git a/components/crash/content/tools/generate_breakpad_symbols.py b/components/crash/content/tools/generate_breakpad_symbols.py
new file mode 100755
index 0000000..5f8fb85
--- /dev/null
+++ b/components/crash/content/tools/generate_breakpad_symbols.py
@@ -0,0 +1,262 @@
+#!/usr/bin/env python
+# Copyright 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""A tool to generate symbols for a binary suitable for breakpad.
+
+Currently, the tool only supports Linux, Android, and Mac. Support for other
+platforms is planned.
+"""
+
+import errno
+import optparse
+import os
+import Queue
+import re
+import shutil
+import subprocess
+import sys
+import threading
+
+
+CONCURRENT_TASKS=4
+
+
+def GetCommandOutput(command):
+ """Runs the command list, returning its output.
+
+ Prints the given command (which should be a list of one or more strings),
+ then runs it and returns its output (stdout) as a string.
+
+ From chromium_utils.
+ """
+ devnull = open(os.devnull, 'w')
+ proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=devnull,
+ bufsize=1)
+ output = proc.communicate()[0]
+ return output
+
+
+def GetDumpSymsBinary(build_dir=None):
+ """Returns the path to the dump_syms binary."""
+ DUMP_SYMS = 'dump_syms'
+ dump_syms_bin = os.path.join(os.path.expanduser(build_dir), DUMP_SYMS)
+ if not os.access(dump_syms_bin, os.X_OK):
+ print 'Cannot find %s.' % dump_syms_bin
+ return None
+
+ return dump_syms_bin
+
+
+def Resolve(path, exe_path, loader_path, rpaths):
+ """Resolve a dyld path.
+
+ @executable_path is replaced with |exe_path|
+ @loader_path is replaced with |loader_path|
+ @rpath is replaced with the first path in |rpaths| where the referenced file
+ is found
+ """
+ path = path.replace('@loader_path', loader_path)
+ path = path.replace('@executable_path', exe_path)
+ if path.find('@rpath') != -1:
+ for rpath in rpaths:
+ new_path = Resolve(path.replace('@rpath', rpath), exe_path, loader_path,
+ [])
+ if os.access(new_path, os.X_OK):
+ return new_path
+ return ''
+ return path
+
+
+def GetSharedLibraryDependenciesLinux(binary):
+ """Return absolute paths to all shared library dependecies of the binary.
+
+ This implementation assumes that we're running on a Linux system."""
+ ldd = GetCommandOutput(['ldd', binary])
+ lib_re = re.compile('\t.* => (.+) \(.*\)$')
+ result = []
+ for line in ldd.splitlines():
+ m = lib_re.match(line)
+ if m:
+ result.append(m.group(1))
+ return result
+
+
+def GetSharedLibraryDependenciesMac(binary, exe_path):
+ """Return absolute paths to all shared library dependecies of the binary.
+
+ This implementation assumes that we're running on a Mac system."""
+ loader_path = os.path.dirname(binary)
+ otool = GetCommandOutput(['otool', '-l', binary]).splitlines()
+ rpaths = []
+ for idx, line in enumerate(otool):
+ if line.find('cmd LC_RPATH') != -1:
+ m = re.match(' *path (.*) \(offset .*\)$', otool[idx+2])
+ rpaths.append(m.group(1))
+
+ otool = GetCommandOutput(['otool', '-L', binary]).splitlines()
+ lib_re = re.compile('\t(.*) \(compatibility .*\)$')
+ deps = []
+ for line in otool:
+ m = lib_re.match(line)
+ if m:
+ dep = Resolve(m.group(1), exe_path, loader_path, rpaths)
+ if dep:
+ deps.append(os.path.normpath(dep))
+ return deps
+
+
+def GetSharedLibraryDependencies(options, binary, exe_path):
+ """Return absolute paths to all shared library dependecies of the binary."""
+ deps = []
+ if sys.platform.startswith('linux'):
+ deps = GetSharedLibraryDependenciesLinux(binary)
+ elif sys.platform == 'darwin':
+ deps = GetSharedLibraryDependenciesMac(binary, exe_path)
+ else:
+ print "Platform not supported."
+ sys.exit(1)
+
+ result = []
+ build_dir = os.path.abspath(options.build_dir)
+ for dep in deps:
+ if (os.access(dep, os.X_OK) and
+ os.path.abspath(os.path.dirname(dep)).startswith(build_dir)):
+ result.append(dep)
+ return result
+
+
+def mkdir_p(path):
+ """Simulates mkdir -p."""
+ try:
+ os.makedirs(path)
+ except OSError as e:
+ if e.errno == errno.EEXIST and os.path.isdir(path):
+ pass
+ else: raise
+
+
+def GenerateSymbols(options, binaries):
+ """Dumps the symbols of binary and places them in the given directory."""
+
+ queue = Queue.Queue()
+ print_lock = threading.Lock()
+
+ def _Worker():
+ while True:
+ binary = queue.get()
+
+ should_dump_syms = True
+ reason = "no reason"
+
+ output_path = os.path.join(
+ options.symbols_dir, os.path.basename(binary))
+ if os.path.isdir(output_path):
+ if os.path.getmtime(binary) < os.path.getmtime(output_path):
+ should_dump_syms = False
+ reason = "symbols are more current than binary"
+
+ if not should_dump_syms:
+ if options.verbose:
+ with print_lock:
+ print "Skipping %s (%s)" % (binary, reason)
+ queue.task_done()
+ continue
+
+ if options.verbose:
+ with print_lock:
+ print "Generating symbols for %s" % binary
+
+ if os.path.isdir(output_path):
+ os.utime(output_path, None)
+
+ syms = GetCommandOutput([GetDumpSymsBinary(options.build_dir), '-r',
+ binary])
+ module_line = re.match("MODULE [^ ]+ [^ ]+ ([0-9A-F]+) (.*)\n", syms)
+ output_path = os.path.join(options.symbols_dir, module_line.group(2),
+ module_line.group(1))
+ mkdir_p(output_path)
+ symbol_file = "%s.sym" % module_line.group(2)
+ try:
+ f = open(os.path.join(output_path, symbol_file), 'w')
+ f.write(syms)
+ f.close()
+ except Exception, e:
+ # Not much we can do about this.
+ with print_lock:
+ print e
+
+ queue.task_done()
+
+ for binary in binaries:
+ queue.put(binary)
+
+ for _ in range(options.jobs):
+ t = threading.Thread(target=_Worker)
+ t.daemon = True
+ t.start()
+
+ queue.join()
+
+
+def main():
+ parser = optparse.OptionParser()
+ parser.add_option('', '--build-dir', default='',
+ help='The build output directory.')
+ parser.add_option('', '--symbols-dir', default='',
+ help='The directory where to write the symbols file.')
+ parser.add_option('', '--binary', default='',
+ help='The path of the binary to generate symbols for.')
+ parser.add_option('', '--clear', default=False, action='store_true',
+ help='Clear the symbols directory before writing new '
+ 'symbols.')
+ parser.add_option('-j', '--jobs', default=CONCURRENT_TASKS, action='store',
+ type='int', help='Number of parallel tasks to run.')
+ parser.add_option('-v', '--verbose', action='store_true',
+ help='Print verbose status output.')
+
+ (options, _) = parser.parse_args()
+
+ if not options.symbols_dir:
+ print "Required option --symbols-dir missing."
+ return 1
+
+ if not options.build_dir:
+ print "Required option --build-dir missing."
+ return 1
+
+ if not options.binary:
+ print "Required option --binary missing."
+ return 1
+
+ if not os.access(options.binary, os.X_OK):
+ print "Cannot find %s." % options.binary
+ return 1
+
+ if options.clear:
+ try:
+ shutil.rmtree(options.symbols_dir)
+ except:
+ pass
+
+ if not GetDumpSymsBinary(options.build_dir):
+ return 1
+
+ # Build the transitive closure of all dependencies.
+ binaries = set([options.binary])
+ queue = [options.binary]
+ exe_path = os.path.dirname(options.binary)
+ while queue:
+ deps = GetSharedLibraryDependencies(options, queue.pop(0), exe_path)
+ new_deps = set(deps) - binaries
+ binaries |= new_deps
+ queue.extend(list(new_deps))
+
+ GenerateSymbols(options, binaries)
+
+ return 0
+
+
+if '__main__' == __name__:
+ sys.exit(main())