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 argparse
13import datetime
14import logging
15import os
16import sys
17
18from multiprocessing.pool import ThreadPool
19
20import devil_chromium
21
22from devil.android import device_blacklist
23from devil.android import device_errors
24from devil.android import device_utils
25from devil.utils import run_tests_helper
26from pylib import constants
27from pylib.symbols import stack_symbolizer
28
29
30_TZ_UTC = {'TZ': 'UTC'}
31
32
33def _ListTombstones(device):
34  """List the tombstone files on the device.
35
36  Args:
37    device: An instance of DeviceUtils.
38
39  Yields:
40    Tuples of (tombstone filename, date time of file on device).
41  """
42  try:
43    if not device.PathExists('/data/tombstones', as_root=True):
44      return
45    entries = device.StatDirectory('/data/tombstones', as_root=True)
46    for entry in entries:
47      if 'tombstone' in entry['filename']:
48        yield (entry['filename'],
49               datetime.datetime.fromtimestamp(entry['st_mtime']))
50  except device_errors.CommandFailedError:
51    logging.exception('Could not retrieve tombstones.')
52  except device_errors.DeviceUnreachableError:
53    logging.exception('Device unreachable retrieving tombstones.')
54  except device_errors.CommandTimeoutError:
55    logging.exception('Timed out retrieving tombstones.')
56
57
58def _GetDeviceDateTime(device):
59  """Determine the date time on the device.
60
61  Args:
62    device: An instance of DeviceUtils.
63
64  Returns:
65    A datetime instance.
66  """
67  device_now_string = device.RunShellCommand(
68      ['date'], check_return=True, env=_TZ_UTC)
69  return datetime.datetime.strptime(
70      device_now_string[0], '%a %b %d %H:%M:%S %Z %Y')
71
72
73def _GetTombstoneData(device, tombstone_file):
74  """Retrieve the tombstone data from the device
75
76  Args:
77    device: An instance of DeviceUtils.
78    tombstone_file: the tombstone to retrieve
79
80  Returns:
81    A list of lines
82  """
83  return device.ReadFile(
84      '/data/tombstones/' + tombstone_file, as_root=True).splitlines()
85
86
87def _EraseTombstone(device, tombstone_file):
88  """Deletes a tombstone from the device.
89
90  Args:
91    device: An instance of DeviceUtils.
92    tombstone_file: the tombstone to delete.
93  """
94  return device.RunShellCommand(
95      ['rm', '/data/tombstones/' + tombstone_file],
96      as_root=True, check_return=True)
97
98
99def _ResolveTombstone(args):
100  tombstone = args[0]
101  tombstone_symbolizer = args[1]
102  lines = []
103  lines += [tombstone['file'] + ' created on ' + str(tombstone['time']) +
104            ', about this long ago: ' +
105            (str(tombstone['device_now'] - tombstone['time']) +
106            ' Device: ' + tombstone['serial'])]
107  logging.info('\n'.join(lines))
108  logging.info('Resolving...')
109  lines += tombstone_symbolizer.ExtractAndResolveNativeStackTraces(
110      tombstone['data'],
111      tombstone['device_abi'],
112      tombstone['stack'])
113  return lines
114
115
116def _ResolveTombstones(jobs, tombstones, tombstone_symbolizer):
117  """Resolve a list of tombstones.
118
119  Args:
120    jobs: the number of jobs to use with multithread.
121    tombstones: a list of tombstones.
122  """
123  if not tombstones:
124    logging.warning('No tombstones to resolve.')
125    return []
126  if len(tombstones) == 1:
127    data = [_ResolveTombstone([tombstones[0], tombstone_symbolizer])]
128  else:
129    pool = ThreadPool(jobs)
130    data = pool.map(
131        _ResolveTombstone,
132        [[tombstone, tombstone_symbolizer] for tombstone in tombstones])
133    pool.close()
134    pool.join()
135  resolved_tombstones = []
136  for tombstone in data:
137    resolved_tombstones.extend(tombstone)
138  return resolved_tombstones
139
140
141def _GetTombstonesForDevice(device, resolve_all_tombstones,
142                            include_stack_symbols,
143                            wipe_tombstones):
144  """Returns a list of tombstones on a given device.
145
146  Args:
147    device: An instance of DeviceUtils.
148    resolve_all_tombstone: Whether to resolve every tombstone.
149    include_stack_symbols: Whether to include symbols for stack data.
150    wipe_tombstones: Whether to wipe tombstones.
151  """
152  ret = []
153  all_tombstones = list(_ListTombstones(device))
154  if not all_tombstones:
155    logging.warning('No tombstones.')
156    return ret
157
158  # Sort the tombstones in date order, descending
159  all_tombstones.sort(cmp=lambda a, b: cmp(b[1], a[1]))
160
161  # Only resolve the most recent unless --all-tombstones given.
162  tombstones = all_tombstones if resolve_all_tombstones else [all_tombstones[0]]
163
164  device_now = _GetDeviceDateTime(device)
165  try:
166    for tombstone_file, tombstone_time in tombstones:
167      ret += [{'serial': str(device),
168               'device_abi': device.product_cpu_abi,
169               'device_now': device_now,
170               'time': tombstone_time,
171               'file': tombstone_file,
172               'stack': include_stack_symbols,
173               'data': _GetTombstoneData(device, tombstone_file)}]
174  except device_errors.CommandFailedError:
175    for entry in device.StatDirectory(
176        '/data/tombstones', as_root=True, timeout=60):
177      logging.info('%s: %s', str(device), entry)
178    raise
179
180  # Erase all the tombstones if desired.
181  if wipe_tombstones:
182    for tombstone_file, _ in all_tombstones:
183      _EraseTombstone(device, tombstone_file)
184
185  return ret
186
187
188def ClearAllTombstones(device):
189  """Clear all tombstones in the device.
190
191  Args:
192    device: An instance of DeviceUtils.
193  """
194  all_tombstones = list(_ListTombstones(device))
195  if not all_tombstones:
196    logging.warning('No tombstones to clear.')
197
198  for tombstone_file, _ in all_tombstones:
199    _EraseTombstone(device, tombstone_file)
200
201
202def ResolveTombstones(device, resolve_all_tombstones, include_stack_symbols,
203                      wipe_tombstones, jobs=4, apk_under_test=None,
204                      tombstone_symbolizer=None):
205  """Resolve tombstones in the device.
206
207  Args:
208    device: An instance of DeviceUtils.
209    resolve_all_tombstone: Whether to resolve every tombstone.
210    include_stack_symbols: Whether to include symbols for stack data.
211    wipe_tombstones: Whether to wipe tombstones.
212    jobs: Number of jobs to use when processing multiple crash stacks.
213
214  Returns:
215    A list of resolved tombstones.
216  """
217  return _ResolveTombstones(jobs,
218                            _GetTombstonesForDevice(device,
219                                                    resolve_all_tombstones,
220                                                    include_stack_symbols,
221                                                    wipe_tombstones),
222                            (tombstone_symbolizer
223                             or stack_symbolizer.Symbolizer(apk_under_test)))
224
225
226def main():
227  custom_handler = logging.StreamHandler(sys.stdout)
228  custom_handler.setFormatter(run_tests_helper.CustomFormatter())
229  logging.getLogger().addHandler(custom_handler)
230  logging.getLogger().setLevel(logging.INFO)
231
232  parser = argparse.ArgumentParser()
233  parser.add_argument('--device',
234                      help='The serial number of the device. If not specified '
235                           'will use all devices.')
236  parser.add_argument('--blacklist-file', help='Device blacklist JSON file.')
237  parser.add_argument('-a', '--all-tombstones', action='store_true',
238                      help='Resolve symbols for all tombstones, rather than '
239                           'just the most recent.')
240  parser.add_argument('-s', '--stack', action='store_true',
241                      help='Also include symbols for stack data')
242  parser.add_argument('-w', '--wipe-tombstones', action='store_true',
243                      help='Erase all tombstones from device after processing')
244  parser.add_argument('-j', '--jobs', type=int,
245                      default=4,
246                      help='Number of jobs to use when processing multiple '
247                           'crash stacks.')
248  parser.add_argument('--output-directory',
249                      help='Path to the root build directory.')
250  parser.add_argument('--adb-path', type=os.path.abspath,
251                      help='Path to the adb binary.')
252  args = parser.parse_args()
253
254  devil_chromium.Initialize(adb_path=args.adb_path)
255
256  blacklist = (device_blacklist.Blacklist(args.blacklist_file)
257               if args.blacklist_file
258               else None)
259
260  if args.output_directory:
261    constants.SetOutputDirectory(args.output_directory)
262  # Do an up-front test that the output directory is known.
263  constants.CheckOutputDirectory()
264
265  if args.device:
266    devices = [device_utils.DeviceUtils(args.device)]
267  else:
268    devices = device_utils.DeviceUtils.HealthyDevices(blacklist)
269
270  # This must be done serially because strptime can hit a race condition if
271  # used for the first time in a multithreaded environment.
272  # http://bugs.python.org/issue7980
273  for device in devices:
274    resolved_tombstones = ResolveTombstones(
275        device, args.all_tombstones,
276        args.stack, args.wipe_tombstones, args.jobs)
277    for line in resolved_tombstones:
278      logging.info(line)
279
280
281if __name__ == '__main__':
282  sys.exit(main())
283