tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

crashpad_stackwalker.py (6047B)


      1 #!/usr/bin/env vpython3
      2 #
      3 # Copyright 2019 The Chromium Authors
      4 # Use of this source code is governed by a BSD-style license that can be
      5 # found in the LICENSE file.
      6 
      7 # Fetches Crashpad dumps from a given device, walks and symbolizes the stacks.
      8 # All the non-trivial operations are performed by generate_breakpad_symbols.py,
      9 # dump_syms, minidump_dump and minidump_stackwalk.
     10 
     11 import argparse
     12 import logging
     13 import os
     14 import posixpath
     15 import re
     16 import sys
     17 import shutil
     18 import subprocess
     19 import tempfile
     20 
     21 _BUILD_ANDROID_PATH = os.path.abspath(
     22    os.path.join(os.path.dirname(__file__), '..'))
     23 sys.path.append(_BUILD_ANDROID_PATH)
     24 import devil_chromium
     25 from devil.android import device_utils
     26 from devil.utils import timeout_retry
     27 
     28 
     29 def _CreateSymbolsDir(build_path, dynamic_library_names):
     30  generator = os.path.normpath(
     31      os.path.join(_BUILD_ANDROID_PATH, '..', '..', 'components', 'crash',
     32                   'content', 'tools', 'generate_breakpad_symbols.py'))
     33  syms_dir = os.path.join(build_path, 'crashpad_syms')
     34  shutil.rmtree(syms_dir, ignore_errors=True)
     35  os.mkdir(syms_dir)
     36  for lib in dynamic_library_names:
     37    unstripped_library_path = os.path.join(build_path, 'lib.unstripped', lib)
     38    if not os.path.exists(unstripped_library_path):
     39      continue
     40    logging.info('Generating symbols for: %s', unstripped_library_path)
     41    cmd = [
     42        generator,
     43        '--symbols-dir',
     44        syms_dir,
     45        '--build-dir',
     46        build_path,
     47        '--binary',
     48        unstripped_library_path,
     49        '--platform',
     50        'android',
     51    ]
     52    return_code = subprocess.call(cmd)
     53    if return_code != 0:
     54      logging.error('Could not extract symbols, command failed: %s',
     55                    ' '.join(cmd))
     56  return syms_dir
     57 
     58 
     59 def _ChooseLatestCrashpadDump(device, crashpad_dump_path):
     60  if not device.PathExists(crashpad_dump_path):
     61    logging.warning('Crashpad dump directory does not exist: %s',
     62                    crashpad_dump_path)
     63    return None
     64  latest = None
     65  latest_timestamp = 0
     66  for crashpad_file in device.ListDirectory(crashpad_dump_path):
     67    if crashpad_file.endswith('.dmp'):
     68      stat = device.StatPath(posixpath.join(crashpad_dump_path, crashpad_file))
     69      current_timestamp = stat['st_mtime']
     70      if current_timestamp > latest_timestamp:
     71        latest_timestamp = current_timestamp
     72        latest = crashpad_file
     73  return latest
     74 
     75 
     76 def _ExtractLibraryNamesFromDump(build_path, dump_path):
     77  default_library_name = 'libmonochrome.so'
     78  dumper_path = os.path.join(build_path, 'minidump_dump')
     79  if not os.access(dumper_path, os.X_OK):
     80    logging.warning(
     81        'Cannot extract library name from dump because %s is not found, '
     82        'default to: %s', dumper_path, default_library_name)
     83    return [default_library_name]
     84  p = subprocess.Popen([dumper_path, dump_path],
     85                       stdout=subprocess.PIPE,
     86                       stderr=subprocess.PIPE)
     87  stdout, stderr = p.communicate()
     88  if p.returncode != 0:
     89    # Dumper errors often do not affect stack walkability, just a warning.
     90    logging.warning('Reading minidump failed with output:\n%s', stderr)
     91 
     92  library_names = []
     93  module_library_line_re = re.compile(r'[(]code_file[)]\s+= '
     94                                      r'"(?P<library_name>lib[^. ]+.so)"')
     95  in_module = False
     96  for line in stdout.splitlines():
     97    line = line.lstrip().rstrip('\n')
     98    if line == 'MDRawModule':
     99      in_module = True
    100      continue
    101    if line == '':
    102      in_module = False
    103      continue
    104    if in_module:
    105      m = module_library_line_re.match(line)
    106      if m:
    107        library_names.append(m.group('library_name'))
    108  if not library_names:
    109    logging.warning(
    110        'Could not find any library name in the dump, '
    111        'default to: %s', default_library_name)
    112    return [default_library_name]
    113  return library_names
    114 
    115 
    116 def main():
    117  logging.basicConfig(level=logging.INFO)
    118  parser = argparse.ArgumentParser(
    119      description='Fetches Crashpad dumps from a given device, '
    120      'walks and symbolizes the stacks.')
    121  parser.add_argument('--device', required=True, help='Device serial number')
    122  parser.add_argument('--adb-path', help='Path to the "adb" command')
    123  parser.add_argument(
    124      '--build-path',
    125      required=True,
    126      help='Build output directory, equivalent to CHROMIUM_OUTPUT_DIR')
    127  parser.add_argument(
    128      '--chrome-cache-path',
    129      required=True,
    130      help='Directory on the device where Chrome stores cached files,'
    131      ' crashpad stores dumps in a subdirectory of it')
    132  args = parser.parse_args()
    133 
    134  stackwalk_path = os.path.join(args.build_path, 'minidump_stackwalk')
    135  if not os.path.exists(stackwalk_path):
    136    logging.error('Missing minidump_stackwalk executable')
    137    return 1
    138 
    139  devil_chromium.Initialize(output_directory=args.build_path,
    140                            adb_path=args.adb_path)
    141  device = device_utils.DeviceUtils(args.device)
    142 
    143  device_crashpad_path = posixpath.join(args.chrome_cache_path, 'Crashpad',
    144                                        'pending')
    145 
    146  def CrashpadDumpExists():
    147    return _ChooseLatestCrashpadDump(device, device_crashpad_path)
    148 
    149  crashpad_file = timeout_retry.WaitFor(
    150      CrashpadDumpExists, wait_period=1, max_tries=9)
    151  if not crashpad_file:
    152    logging.error('Could not locate a crashpad dump')
    153    return 1
    154 
    155  dump_dir = tempfile.mkdtemp()
    156  symbols_dir = None
    157  try:
    158    device.PullFile(
    159        device_path=posixpath.join(device_crashpad_path, crashpad_file),
    160        host_path=dump_dir)
    161    dump_full_path = os.path.join(dump_dir, crashpad_file)
    162    library_names = _ExtractLibraryNamesFromDump(args.build_path,
    163                                                 dump_full_path)
    164    symbols_dir = _CreateSymbolsDir(args.build_path, library_names)
    165    stackwalk_cmd = [stackwalk_path, dump_full_path, symbols_dir]
    166    subprocess.call(stackwalk_cmd)
    167  finally:
    168    shutil.rmtree(dump_dir, ignore_errors=True)
    169    if symbols_dir:
    170      shutil.rmtree(symbols_dir, ignore_errors=True)
    171  return 0
    172 
    173 
    174 if __name__ == '__main__':
    175  sys.exit(main())