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 12 import datetime 13 import multiprocessing 14 import os 15 import re 16 import subprocess 17 import sys 18 import optparse 19 20 from pylib import android_commands 21 from pylib.device import device_utils 22 23 24 def _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 42 def _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 56 def _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 69 def _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 80 def _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 89 def _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 122 def _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 135 def _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 154 def _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 191 def 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 221 if __name__ == '__main__': 222 sys.exit(main()) 223