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