mac: Add target to dump breakpad symbols.
authorCheng Zhao <zcbenz@gmail.com>
Fri, 15 Nov 2013 14:52:08 +0000 (22:52 +0800)
committerCheng Zhao <zcbenz@gmail.com>
Fri, 15 Nov 2013 14:52:08 +0000 (22:52 +0800)
atom.gyp
common.gypi
tools/mac/generate_breakpad_symbols.py [new file with mode: 0755]

index e374c1d..83b3627 100644 (file)
--- a/atom.gyp
+++ b/atom.gyp
           ],
         }],  # OS=="win"
       ],
-    },
+    },  # target <(project_name)
     {
       'target_name': '<(project_name)_lib',
       'type': 'static_library',
           ],
         }],
       ],
-    },
+    },  # target <(product_name)_lib
     {
       'target_name': 'generated_sources',
       'type': 'none',
           ],
         },
       ],
-    },
+    },  # 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"', {
index 7902864..9861a1b 100644 (file)
           }],  # 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',
           ],
         },
         ],
       },
     },
+    '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 (executable)
index 0000000..f7d3ca4
--- /dev/null
@@ -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())