1#!/usr/bin/env python 2# 3# Copyright 2013 The Chromium Authors. All rights reserved. 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 datetime 13import multiprocessing 14import os 15import re 16import subprocess 17import sys 18import optparse 19 20from pylib import android_commands 21from pylib.device import device_utils 22 23 24def _ListTombstones(device): 25 """List the tombstone files on the device. 26 27 Args: 28 device: An instance of DeviceUtils. 29 30 Yields: 31 Tuples of (tombstone filename, date time of file on device). 32 """ 33 lines = device.RunShellCommand('TZ=UTC su -c ls -a -l /data/tombstones') 34 for line in lines: 35 if 'tombstone' in line and not 'No such file or directory' in line: 36 details = line.split() 37 t = datetime.datetime.strptime(details[-3] + ' ' + details[-2], 38 '%Y-%m-%d %H:%M') 39 yield details[-1], t 40 41 42def _GetDeviceDateTime(device): 43 """Determine the date time on the device. 44 45 Args: 46 device: An instance of DeviceUtils. 47 48 Returns: 49 A datetime instance. 50 """ 51 device_now_string = device.RunShellCommand('TZ=UTC date') 52 return datetime.datetime.strptime( 53 device_now_string[0], '%a %b %d %H:%M:%S %Z %Y') 54 55 56def _GetTombstoneData(device, tombstone_file): 57 """Retrieve the tombstone data from the device 58 59 Args: 60 device: An instance of DeviceUtils. 61 tombstone_file: the tombstone to retrieve 62 63 Returns: 64 A list of lines 65 """ 66 return device.ReadFile('/data/tombstones/' + tombstone_file, as_root=True) 67 68 69def _EraseTombstone(device, tombstone_file): 70 """Deletes a tombstone from the device. 71 72 Args: 73 device: An instance of DeviceUtils. 74 tombstone_file: the tombstone to delete. 75 """ 76 return device.RunShellCommand( 77 'rm /data/tombstones/' + tombstone_file, as_root=True) 78 79 80def _DeviceAbiToArch(device_abi): 81 # The order of this list is significant to find the more specific match (e.g., 82 # arm64) before the less specific (e.g., arm). 83 arches = ['arm64', 'arm', 'x86_64', 'x86_64', 'x86', 'mips'] 84 for arch in arches: 85 if arch in device_abi: 86 return arch 87 raise RuntimeError('Unknown device ABI: %s' % device_abi) 88 89def _ResolveSymbols(tombstone_data, include_stack, device_abi): 90 """Run the stack tool for given tombstone input. 91 92 Args: 93 tombstone_data: a list of strings of tombstone data. 94 include_stack: boolean whether to include stack data in output. 95 device_abi: the default ABI of the device which generated the tombstone. 96 97 Yields: 98 A string for each line of resolved stack output. 99 """ 100 # Check if the tombstone data has an ABI listed, if so use this in preference 101 # to the device's default ABI. 102 for line in tombstone_data: 103 found_abi = re.search('ABI: \'(.+?)\'', line) 104 if found_abi: 105 device_abi = found_abi.group(1) 106 arch = _DeviceAbiToArch(device_abi) 107 if not arch: 108 return 109 110 stack_tool = os.path.join(os.path.dirname(__file__), '..', '..', 111 'third_party', 'android_platform', 'development', 112 'scripts', 'stack') 113 proc = subprocess.Popen([stack_tool, '--arch', arch], stdin=subprocess.PIPE, 114 stdout=subprocess.PIPE) 115 output = proc.communicate(input='\n'.join(tombstone_data))[0] 116 for line in output.split('\n'): 117 if not include_stack and 'Stack Data:' in line: 118 break 119 yield line 120 121 122def _ResolveTombstone(tombstone): 123 lines = [] 124 lines += [tombstone['file'] + ' created on ' + str(tombstone['time']) + 125 ', about this long ago: ' + 126 (str(tombstone['device_now'] - tombstone['time']) + 127 ' Device: ' + tombstone['serial'])] 128 print '\n'.join(lines) 129 print 'Resolving...' 130 lines += _ResolveSymbols(tombstone['data'], tombstone['stack'], 131 tombstone['device_abi']) 132 return lines 133 134 135def _ResolveTombstones(jobs, tombstones): 136 """Resolve a list of tombstones. 137 138 Args: 139 jobs: the number of jobs to use with multiprocess. 140 tombstones: a list of tombstones. 141 """ 142 if not tombstones: 143 print 'No device attached? Or no tombstones?' 144 return 145 if len(tombstones) == 1: 146 data = _ResolveTombstone(tombstones[0]) 147 else: 148 pool = multiprocessing.Pool(processes=jobs) 149 data = pool.map(_ResolveTombstone, tombstones) 150 data = ['\n'.join(d) for d in data] 151 print '\n'.join(data) 152 153 154def _GetTombstonesForDevice(device, options): 155 """Returns a list of tombstones on a given device. 156 157 Args: 158 device: An instance of DeviceUtils. 159 options: command line arguments from OptParse 160 """ 161 ret = [] 162 all_tombstones = list(_ListTombstones(device)) 163 if not all_tombstones: 164 print 'No device attached? Or no tombstones?' 165 return ret 166 167 # Sort the tombstones in date order, descending 168 all_tombstones.sort(cmp=lambda a, b: cmp(b[1], a[1])) 169 170 # Only resolve the most recent unless --all-tombstones given. 171 tombstones = all_tombstones if options.all_tombstones else [all_tombstones[0]] 172 173 device_now = _GetDeviceDateTime(device) 174 for tombstone_file, tombstone_time in tombstones: 175 ret += [{'serial': str(device), 176 'device_abi': device.GetProp('ro.product.cpu.abi'), 177 'device_now': device_now, 178 'time': tombstone_time, 179 'file': tombstone_file, 180 'stack': options.stack, 181 'data': _GetTombstoneData(device, tombstone_file)}] 182 183 # Erase all the tombstones if desired. 184 if options.wipe_tombstones: 185 for tombstone_file, _ in all_tombstones: 186 _EraseTombstone(device, tombstone_file) 187 188 return ret 189 190 191def main(): 192 parser = optparse.OptionParser() 193 parser.add_option('--device', 194 help='The serial number of the device. If not specified ' 195 'will use all devices.') 196 parser.add_option('-a', '--all-tombstones', action='store_true', 197 help="""Resolve symbols for all tombstones, rather than just 198 the most recent""") 199 parser.add_option('-s', '--stack', action='store_true', 200 help='Also include symbols for stack data') 201 parser.add_option('-w', '--wipe-tombstones', action='store_true', 202 help='Erase all tombstones from device after processing') 203 parser.add_option('-j', '--jobs', type='int', 204 default=4, 205 help='Number of jobs to use when processing multiple ' 206 'crash stacks.') 207 options, _ = parser.parse_args() 208 209 if options.device: 210 devices = [options.device] 211 else: 212 devices = android_commands.GetAttachedDevices() 213 214 tombstones = [] 215 for device_serial in devices: 216 device = device_utils.DeviceUtils(device_serial) 217 tombstones += _GetTombstonesForDevice(device, options) 218 219 _ResolveTombstones(options.jobs, tombstones) 220 221if __name__ == '__main__': 222 sys.exit(main()) 223