#!/usr/bin/env vpython3 # # Copyright 2019 The Chromium Authors # 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'"(?Plib[^. ]+.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())