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