tombstones.py revision 4e180b6a0b4720a9b8e9e959a882386f690f08ff
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