123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269 |
- #!/usr/bin/env python
- # Copyright (c) 2013 GitHub, Inc.
- # Copyright (c) 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 argparse
- 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
- sys.exit(1)
- return dump_syms_bin
- def FindBundlePart(full_path):
- if full_path.endswith(('.dylib', '.framework', '.app')):
- return os.path.basename(full_path)
- elif full_path != '' and full_path != '/':
- return FindBundlePart(os.path.dirname(full_path))
- else:
- return ''
- def GetDSYMBundle(options, binary_path):
- """Finds the .dSYM bundle to the binary."""
- if os.path.isabs(binary_path):
- dsym_path = binary_path + '.dSYM'
- if os.path.exists(dsym_path):
- return dsym_path
- filename = FindBundlePart(binary_path)
- search_dirs = [options.build_dir, options.libchromiumcontent_dir]
- if filename.endswith(('.dylib', '.framework', '.app')):
- for directory in search_dirs:
- dsym_path = os.path.join(directory, filename) + '.dSYM'
- if os.path.exists(dsym_path):
- return dsym_path
- return binary_path
- def GetSymbolPath(options, binary_path):
- """Finds the .dbg to the binary."""
- filename = os.path.basename(binary_path)
- dbg_path = os.path.join(options.libchromiumcontent_dir, filename) + '.dbg'
- if os.path.exists(dbg_path):
- return dbg_path
- return binary_path
- 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.F_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(os.path.realpath(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.F_OK)):
- 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()
- if options.verbose:
- with print_lock:
- print "Generating symbols for %s" % binary
- if sys.platform == 'darwin':
- binary = GetDSYMBundle(options, binary)
- elif sys.platform == 'linux2':
- binary = GetSymbolPath(options, binary)
- syms = GetCommandOutput([GetDumpSymsBinary(options.build_dir), '-r', '-c',
- 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)
- f = open(os.path.join(output_path, symbol_file), 'w')
- f.write(syms)
- f.close()
- 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 = argparse.ArgumentParser(description='Generate Breakpad Symbols Project')
- parser.add_argument('--build-dir', required=True,
- help='The build output directory.')
- parser.add_argument('--symbols-dir', required=True,
- help='The directory where to write the symbols file.')
- parser.add_argument('--libchromiumcontent-dir', required=True,
- help='The directory where libchromiumcontent is downloaded.')
- parser.add_argument('--binary', action='append', required=True,
- help='The path of the binary to generate symbols for.')
- parser.add_argument('--clear', default=False, action='store_true',
- help='Clear the symbols directory before writing new '
- 'symbols.')
- parser.add_argument('-j', '--jobs', default=CONCURRENT_TASKS, action='store',
- type=int, help='Number of parallel tasks to run.')
- parser.add_argument('-v', '--verbose', action='store_true',
- help='Print verbose status output.')
- options = parser.parse_args()
- for bin_file in options.binary:
- if not os.access(bin_file, os.X_OK):
- print "Cannot find %s." % options.binary
- return 1
- if options.clear:
- try:
- shutil.rmtree(options.symbols_dir)
- except:
- pass
- # Build the transitive closure of all dependencies.
- binaries = set(options.binary)
- queue = options.binary
- while queue:
- current_bin = queue.pop(0)
- exe_path = os.path.dirname(current_bin)
- deps = GetSharedLibraryDependencies(options, current_bin, 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())
|