From 1d2de6d1fbc730ef644afe8bf1d24d756686a89c Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 15 Nov 2013 22:52:08 +0800 Subject: [PATCH] mac: Add target to dump breakpad symbols. --- atom.gyp | 39 +++++- common.gypi | 6 +- tools/mac/generate_breakpad_symbols.py | 233 +++++++++++++++++++++++++++++++++ 3 files changed, 274 insertions(+), 4 deletions(-) create mode 100755 tools/mac/generate_breakpad_symbols.py diff --git a/atom.gyp b/atom.gyp index e374c1d..83b3627 100644 --- a/atom.gyp +++ b/atom.gyp @@ -306,7 +306,7 @@ ], }], # OS=="win" ], - }, + }, # target <(project_name) { 'target_name': '<(project_name)_lib', 'type': 'static_library', @@ -350,7 +350,7 @@ ], }], ], - }, + }, # target <(product_name)_lib { 'target_name': 'generated_sources', 'type': 'none', @@ -390,7 +390,40 @@ ], }, ], - }, + }, # target generated_sources + { + 'target_name': '<(project_name)_dump_symbols', + 'type': 'none', + 'dependencies': [ + '<(project_name)', + ], + 'conditions': [ + ['OS=="mac"', { + 'dependencies': [ + 'vendor/breakpad/breakpad.gyp:dump_syms', + ], + 'actions': [ + { + 'action_name': 'Dump Symbols', + 'inputs': [ + '<(PRODUCT_DIR)/<(product_name).app/Contents/MacOS/<(product_name)', + ], + 'outputs': [ + '<(PRODUCT_DIR)/<(product_name).breakpad.syms', + ], + 'action': [ + 'tools/mac/generate_breakpad_symbols.py', + '--build-dir=<(PRODUCT_DIR)', + '--binary=<(PRODUCT_DIR)/<(product_name).app/Contents/MacOS/<(product_name)', + '--symbols-dir=<(PRODUCT_DIR)/<(product_name).breakpad.syms', + '--clear', + '--jobs=16', + ], + }, + ], + }], # OS=="mac" + ], + }, # target <(project_name>_dump_symbols ], 'conditions': [ ['OS=="mac"', { diff --git a/common.gypi b/common.gypi index 7902864..9861a1b 100644 --- a/common.gypi +++ b/common.gypi @@ -82,10 +82,11 @@ }], # OS=="win" ], }], - ['_target_name.startswith("breakpad") or _target_name in ["crash_report_sender"]', { + ['_target_name.startswith("breakpad") or _target_name in ["crash_report_sender", "dump_syms"]', { 'xcode_settings': { 'WARNING_CFLAGS': [ '-Wno-deprecated-declarations', + '-Wno-unused-private-field', '-Wno-unused-function', ], }, @@ -125,6 +126,9 @@ ], }, }, + 'xcode_settings': { + 'DEBUG_INFORMATION_FORMAT': 'dwarf-with-dsym', + }, }, 'conditions': [ # Settings to compile with clang under OS X. diff --git a/tools/mac/generate_breakpad_symbols.py b/tools/mac/generate_breakpad_symbols.py new file mode 100755 index 0000000..f7d3ca4 --- /dev/null +++ b/tools/mac/generate_breakpad_symbols.py @@ -0,0 +1,233 @@ +#!/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 + sys.exit(1) + + 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.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(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 + + 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) + 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 = 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 + + # 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()) -- 2.7.4