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 logging 14import multiprocessing 15import os 16import subprocess 17import sys 18import optparse 19 20from pylib import android_commands 21 22 23def _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 41def _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 55def _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 67def _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 76def _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 98def _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 110def _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 129def _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 164def 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 194if __name__ == '__main__': 195 sys.exit(main()) 196