123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175 |
- #!/usr/bin/env vpython3
- #
- # Copyright 2019 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.
- # Fetches Crashpad dumps from a given device, walks and symbolizes the stacks.
- # All the non-trivial operations are performed by generate_breakpad_symbols.py,
- # dump_syms, minidump_dump and minidump_stackwalk.
- import argparse
- import logging
- import os
- import posixpath
- import re
- import sys
- import shutil
- import subprocess
- import tempfile
- _BUILD_ANDROID_PATH = os.path.abspath(
- os.path.join(os.path.dirname(__file__), '..'))
- sys.path.append(_BUILD_ANDROID_PATH)
- import devil_chromium
- from devil.android import device_utils
- from devil.utils import timeout_retry
- def _CreateSymbolsDir(build_path, dynamic_library_names):
- generator = os.path.normpath(
- os.path.join(_BUILD_ANDROID_PATH, '..', '..', 'components', 'crash',
- 'content', 'tools', 'generate_breakpad_symbols.py'))
- syms_dir = os.path.join(build_path, 'crashpad_syms')
- shutil.rmtree(syms_dir, ignore_errors=True)
- os.mkdir(syms_dir)
- for lib in dynamic_library_names:
- unstripped_library_path = os.path.join(build_path, 'lib.unstripped', lib)
- if not os.path.exists(unstripped_library_path):
- continue
- logging.info('Generating symbols for: %s', unstripped_library_path)
- cmd = [
- generator,
- '--symbols-dir',
- syms_dir,
- '--build-dir',
- build_path,
- '--binary',
- unstripped_library_path,
- '--platform',
- 'android',
- ]
- return_code = subprocess.call(cmd)
- if return_code != 0:
- logging.error('Could not extract symbols, command failed: %s',
- ' '.join(cmd))
- return syms_dir
- def _ChooseLatestCrashpadDump(device, crashpad_dump_path):
- if not device.PathExists(crashpad_dump_path):
- logging.warning('Crashpad dump directory does not exist: %s',
- crashpad_dump_path)
- return None
- latest = None
- latest_timestamp = 0
- for crashpad_file in device.ListDirectory(crashpad_dump_path):
- if crashpad_file.endswith('.dmp'):
- stat = device.StatPath(posixpath.join(crashpad_dump_path, crashpad_file))
- current_timestamp = stat['st_mtime']
- if current_timestamp > latest_timestamp:
- latest_timestamp = current_timestamp
- latest = crashpad_file
- return latest
- def _ExtractLibraryNamesFromDump(build_path, dump_path):
- default_library_name = 'libmonochrome.so'
- dumper_path = os.path.join(build_path, 'minidump_dump')
- if not os.access(dumper_path, os.X_OK):
- logging.warning(
- 'Cannot extract library name from dump because %s is not found, '
- 'default to: %s', dumper_path, default_library_name)
- return [default_library_name]
- p = subprocess.Popen([dumper_path, dump_path],
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- stdout, stderr = p.communicate()
- if p.returncode != 0:
- # Dumper errors often do not affect stack walkability, just a warning.
- logging.warning('Reading minidump failed with output:\n%s', stderr)
- library_names = []
- module_library_line_re = re.compile(r'[(]code_file[)]\s+= '
- r'"(?P<library_name>lib[^. ]+.so)"')
- in_module = False
- for line in stdout.splitlines():
- line = line.lstrip().rstrip('\n')
- if line == 'MDRawModule':
- in_module = True
- continue
- if line == '':
- in_module = False
- continue
- if in_module:
- m = module_library_line_re.match(line)
- if m:
- library_names.append(m.group('library_name'))
- if not library_names:
- logging.warning(
- 'Could not find any library name in the dump, '
- 'default to: %s', default_library_name)
- return [default_library_name]
- return library_names
- def main():
- logging.basicConfig(level=logging.INFO)
- parser = argparse.ArgumentParser(
- description='Fetches Crashpad dumps from a given device, '
- 'walks and symbolizes the stacks.')
- parser.add_argument('--device', required=True, help='Device serial number')
- parser.add_argument('--adb-path', help='Path to the "adb" command')
- parser.add_argument(
- '--build-path',
- required=True,
- help='Build output directory, equivalent to CHROMIUM_OUTPUT_DIR')
- parser.add_argument(
- '--chrome-cache-path',
- required=True,
- help='Directory on the device where Chrome stores cached files,'
- ' crashpad stores dumps in a subdirectory of it')
- args = parser.parse_args()
- stackwalk_path = os.path.join(args.build_path, 'minidump_stackwalk')
- if not os.path.exists(stackwalk_path):
- logging.error('Missing minidump_stackwalk executable')
- return 1
- devil_chromium.Initialize(output_directory=args.build_path,
- adb_path=args.adb_path)
- device = device_utils.DeviceUtils(args.device)
- device_crashpad_path = posixpath.join(args.chrome_cache_path, 'Crashpad',
- 'pending')
- def CrashpadDumpExists():
- return _ChooseLatestCrashpadDump(device, device_crashpad_path)
- crashpad_file = timeout_retry.WaitFor(
- CrashpadDumpExists, wait_period=1, max_tries=9)
- if not crashpad_file:
- logging.error('Could not locate a crashpad dump')
- return 1
- dump_dir = tempfile.mkdtemp()
- symbols_dir = None
- try:
- device.PullFile(
- device_path=posixpath.join(device_crashpad_path, crashpad_file),
- host_path=dump_dir)
- dump_full_path = os.path.join(dump_dir, crashpad_file)
- library_names = _ExtractLibraryNamesFromDump(args.build_path,
- dump_full_path)
- symbols_dir = _CreateSymbolsDir(args.build_path, library_names)
- stackwalk_cmd = [stackwalk_path, dump_full_path, symbols_dir]
- subprocess.call(stackwalk_cmd)
- finally:
- shutil.rmtree(dump_dir, ignore_errors=True)
- if symbols_dir:
- shutil.rmtree(symbols_dir, ignore_errors=True)
- return 0
- if __name__ == '__main__':
- sys.exit(main())
|