tombstones.py (9164B)
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 12 import argparse 13 import datetime 14 import logging 15 import os 16 import sys 17 18 from multiprocessing.pool import ThreadPool 19 20 import devil_chromium 21 22 from devil.android import device_denylist 23 from devil.android import device_errors 24 from devil.android import device_utils 25 from devil.utils import run_tests_helper 26 from pylib import constants 27 from pylib.symbols import stack_symbolizer 28 29 30 _TZ_UTC = {'TZ': 'UTC'} 31 32 33 def _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 58 def _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 73 def _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 87 def _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 99 def _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 116 def _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 141 def _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 188 def 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 202 def 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 226 def 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 279 if __name__ == '__main__': 280 sys.exit(main())