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 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