1#!/usr/bin/env vpython3 2# 3# Copyright 2013 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# Find the most recent tombstone file(s) on all connected devices 8# and prints their stacks. 9# 10# Assumes tombstone file was created with current symbols. 11 12import argparse 13import datetime 14import logging 15import os 16import sys 17 18from multiprocessing.pool import ThreadPool 19 20import devil_chromium 21 22from devil.android import device_denylist 23from devil.android import device_errors 24from devil.android import device_utils 25from devil.utils import run_tests_helper 26from pylib import constants 27from pylib.symbols import stack_symbolizer 28 29 30_TZ_UTC = {'TZ': 'UTC'} 31 32 33def _ListTombstones(device): 34 """List the tombstone files on the device. 35 36 Args: 37 device: An instance of DeviceUtils. 38 39 Yields: 40 Tuples of (tombstone filename, date time of file on device). 41 """ 42 try: 43 if not device.PathExists('/data/tombstones', as_root=True): 44 return 45 entries = device.StatDirectory('/data/tombstones', as_root=True) 46 for entry in entries: 47 if 'tombstone' in entry['filename']: 48 yield (entry['filename'], 49 datetime.datetime.fromtimestamp(entry['st_mtime'])) 50 except device_errors.CommandFailedError: 51 logging.exception('Could not retrieve tombstones.') 52 except device_errors.DeviceUnreachableError: 53 logging.exception('Device unreachable retrieving tombstones.') 54 except device_errors.CommandTimeoutError: 55 logging.exception('Timed out retrieving tombstones.') 56 57 58def _GetDeviceDateTime(device): 59 """Determine the date time on the device. 60 61 Args: 62 device: An instance of DeviceUtils. 63 64 Returns: 65 A datetime instance. 66 """ 67 device_now_string = device.RunShellCommand( 68 ['date'], check_return=True, env=_TZ_UTC) 69 return datetime.datetime.strptime( 70 device_now_string[0], '%a %b %d %H:%M:%S %Z %Y') 71 72 73def _GetTombstoneData(device, tombstone_file): 74 """Retrieve the tombstone data from the device 75 76 Args: 77 device: An instance of DeviceUtils. 78 tombstone_file: the tombstone to retrieve 79 80 Returns: 81 A list of lines 82 """ 83 return device.ReadFile( 84 '/data/tombstones/' + tombstone_file, as_root=True).splitlines() 85 86 87def _EraseTombstone(device, tombstone_file): 88 """Deletes a tombstone from the device. 89 90 Args: 91 device: An instance of DeviceUtils. 92 tombstone_file: the tombstone to delete. 93 """ 94 return device.RunShellCommand( 95 ['rm', '/data/tombstones/' + tombstone_file], 96 as_root=True, check_return=True) 97 98 99def _ResolveTombstone(args): 100 tombstone = args[0] 101 tombstone_symbolizer = args[1] 102 lines = [] 103 lines += [tombstone['file'] + ' created on ' + str(tombstone['time']) + 104 ', about this long ago: ' + 105 (str(tombstone['device_now'] - tombstone['time']) + 106 ' Device: ' + tombstone['serial'])] 107 logging.info('\n'.join(lines)) 108 logging.info('Resolving...') 109 lines += tombstone_symbolizer.ExtractAndResolveNativeStackTraces( 110 tombstone['data'], 111 tombstone['device_abi'], 112 tombstone['stack']) 113 return lines 114 115 116def _ResolveTombstones(jobs, tombstones, tombstone_symbolizer): 117 """Resolve a list of tombstones. 118 119 Args: 120 jobs: the number of jobs to use with multithread. 121 tombstones: a list of tombstones. 122 """ 123 if not tombstones: 124 logging.warning('No tombstones to resolve.') 125 return [] 126 if len(tombstones) == 1: 127 data = [_ResolveTombstone([tombstones[0], tombstone_symbolizer])] 128 else: 129 pool = ThreadPool(jobs) 130 data = pool.map( 131 _ResolveTombstone, 132 [[tombstone, tombstone_symbolizer] for tombstone in tombstones]) 133 pool.close() 134 pool.join() 135 resolved_tombstones = [] 136 for tombstone in data: 137 resolved_tombstones.extend(tombstone) 138 return resolved_tombstones 139 140 141def _GetTombstonesForDevice(device, resolve_all_tombstones, 142 include_stack_symbols, 143 wipe_tombstones): 144 """Returns a list of tombstones on a given device. 145 146 Args: 147 device: An instance of DeviceUtils. 148 resolve_all_tombstone: Whether to resolve every tombstone. 149 include_stack_symbols: Whether to include symbols for stack data. 150 wipe_tombstones: Whether to wipe tombstones. 151 """ 152 ret = [] 153 all_tombstones = list(_ListTombstones(device)) 154 if not all_tombstones: 155 logging.warning('No tombstones.') 156 return ret 157 158 # Sort the tombstones in date order, descending 159 all_tombstones.sort(key=lambda a: a[1], reverse=True) 160 161 # Only resolve the most recent unless --all-tombstones given. 162 tombstones = all_tombstones if resolve_all_tombstones else [all_tombstones[0]] 163 164 device_now = _GetDeviceDateTime(device) 165 try: 166 for tombstone_file, tombstone_time in tombstones: 167 ret += [{'serial': str(device), 168 'device_abi': device.product_cpu_abi, 169 'device_now': device_now, 170 'time': tombstone_time, 171 'file': tombstone_file, 172 'stack': include_stack_symbols, 173 'data': _GetTombstoneData(device, tombstone_file)}] 174 except device_errors.CommandFailedError: 175 for entry in device.StatDirectory( 176 '/data/tombstones', as_root=True, timeout=60): 177 logging.info('%s: %s', str(device), entry) 178 raise 179 180 # Erase all the tombstones if desired. 181 if wipe_tombstones: 182 for tombstone_file, _ in all_tombstones: 183 _EraseTombstone(device, tombstone_file) 184 185 return ret 186 187 188def ClearAllTombstones(device): 189 """Clear all tombstones in the device. 190 191 Args: 192 device: An instance of DeviceUtils. 193 """ 194 all_tombstones = list(_ListTombstones(device)) 195 if not all_tombstones: 196 logging.warning('No tombstones to clear.') 197 198 for tombstone_file, _ in all_tombstones: 199 _EraseTombstone(device, tombstone_file) 200 201 202def ResolveTombstones(device, resolve_all_tombstones, include_stack_symbols, 203 wipe_tombstones, jobs=4, apk_under_test=None, 204 tombstone_symbolizer=None): 205 """Resolve tombstones in the device. 206 207 Args: 208 device: An instance of DeviceUtils. 209 resolve_all_tombstone: Whether to resolve every tombstone. 210 include_stack_symbols: Whether to include symbols for stack data. 211 wipe_tombstones: Whether to wipe tombstones. 212 jobs: Number of jobs to use when processing multiple crash stacks. 213 214 Returns: 215 A list of resolved tombstones. 216 """ 217 return _ResolveTombstones(jobs, 218 _GetTombstonesForDevice(device, 219 resolve_all_tombstones, 220 include_stack_symbols, 221 wipe_tombstones), 222 (tombstone_symbolizer 223 or stack_symbolizer.Symbolizer(apk_under_test))) 224 225 226def main(): 227 custom_handler = logging.StreamHandler(sys.stdout) 228 custom_handler.setFormatter(run_tests_helper.CustomFormatter()) 229 logging.getLogger().addHandler(custom_handler) 230 logging.getLogger().setLevel(logging.INFO) 231 232 parser = argparse.ArgumentParser() 233 parser.add_argument('--device', 234 help='The serial number of the device. If not specified ' 235 'will use all devices.') 236 parser.add_argument('--denylist-file', help='Device denylist JSON file.') 237 parser.add_argument('-a', '--all-tombstones', action='store_true', 238 help='Resolve symbols for all tombstones, rather than ' 239 'just the most recent.') 240 parser.add_argument('-s', '--stack', action='store_true', 241 help='Also include symbols for stack data') 242 parser.add_argument('-w', '--wipe-tombstones', action='store_true', 243 help='Erase all tombstones from device after processing') 244 parser.add_argument('-j', '--jobs', type=int, 245 default=4, 246 help='Number of jobs to use when processing multiple ' 247 'crash stacks.') 248 parser.add_argument('--output-directory', 249 help='Path to the root build directory.') 250 parser.add_argument('--adb-path', type=os.path.abspath, 251 help='Path to the adb binary.') 252 args = parser.parse_args() 253 254 if args.output_directory: 255 constants.SetOutputDirectory(args.output_directory) 256 257 devil_chromium.Initialize(output_directory=constants.GetOutDirectory(), 258 adb_path=args.adb_path) 259 260 denylist = (device_denylist.Denylist(args.denylist_file) 261 if args.denylist_file else None) 262 263 if args.device: 264 devices = [device_utils.DeviceUtils(args.device)] 265 else: 266 devices = device_utils.DeviceUtils.HealthyDevices(denylist) 267 268 # This must be done serially because strptime can hit a race condition if 269 # used for the first time in a multithreaded environment. 270 # http://bugs.python.org/issue7980 271 for device in devices: 272 resolved_tombstones = ResolveTombstones( 273 device, args.all_tombstones, 274 args.stack, args.wipe_tombstones, args.jobs) 275 for line in resolved_tombstones: 276 logging.info(line) 277 278 279if __name__ == '__main__': 280 sys.exit(main()) 281