Home | History | Annotate | Download | only in android
      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