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 multiprocessing
14import os
15import re
16import subprocess
17import sys
18import optparse
19
20from pylib import android_commands
21from pylib.device import device_utils
22
23
24def _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
42def _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
56def _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
69def _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
80def _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
89def _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
122def _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
135def _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
154def _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
191def 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
221if __name__ == '__main__':
222  sys.exit(main())
223