1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
|
#!/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'"(?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())
|