summaryrefslogtreecommitdiff
path: root/deps/v8/build/android/stacktrace/crashpad_stackwalker.py
blob: a538105be45b51dd34cb7e2536c37d865ec47842 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
#!/usr/bin/env python
#
# Copyright 2019 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

# Fetches Crashpad dumps from a given device, walks and symbolizes the stacks.
# All the non-trivial operations are performed by generate_breakpad_symbols.py,
# dump_syms, minidump_dump and minidump_stackwalk.

import argparse
import logging
import os
import posixpath
import re
import sys
import shutil
import subprocess
import tempfile

_BUILD_ANDROID_PATH = os.path.abspath(
    os.path.join(os.path.dirname(__file__), '..'))
sys.path.append(_BUILD_ANDROID_PATH)
import devil_chromium
from devil.android import device_utils
from devil.utils import timeout_retry


def _CreateSymbolsDir(build_path, dynamic_library_names):
  generator = os.path.normpath(
      os.path.join(_BUILD_ANDROID_PATH, '..', '..', 'components', 'crash',
                   'content', 'tools', 'generate_breakpad_symbols.py'))
  syms_dir = os.path.join(build_path, 'crashpad_syms')
  shutil.rmtree(syms_dir, ignore_errors=True)
  os.mkdir(syms_dir)
  for lib in dynamic_library_names:
    unstripped_library_path = os.path.join(build_path, 'lib.unstripped', lib)
    if not os.path.exists(unstripped_library_path):
      continue
    logging.info('Generating symbols for: %s', unstripped_library_path)
    cmd = [
        generator,
        '--symbols-dir',
        syms_dir,
        '--build-dir',
        build_path,
        '--binary',
        unstripped_library_path,
    ]
    return_code = subprocess.call(cmd)
    if return_code != 0:
      logging.error('Could not extract symbols, command failed: %s',
                    ' '.join(cmd))
  return syms_dir


def _ChooseLatestCrashpadDump(device, crashpad_dump_path):
  if not device.PathExists(crashpad_dump_path):
    logging.warning('Crashpad dump directory does not exist: %s',
                    crashpad_dump_path)
    return None
  latest = None
  latest_timestamp = 0
  for crashpad_file in device.ListDirectory(crashpad_dump_path):
    if crashpad_file.endswith('.dmp'):
      stat = device.StatPath(posixpath.join(crashpad_dump_path, crashpad_file))
      current_timestamp = stat['st_mtime']
      if current_timestamp > latest_timestamp:
        latest_timestamp = current_timestamp
        latest = crashpad_file
  return latest


def _ExtractLibraryNamesFromDump(build_path, dump_path):
  default_library_name = 'libmonochrome.so'
  dumper_path = os.path.join(build_path, 'minidump_dump')
  if not os.access(dumper_path, os.X_OK):
    logging.warning(
        'Cannot extract library name from dump because %s is not found, '
        'default to: %s', dumper_path, default_library_name)
    return [default_library_name]
  p = subprocess.Popen([dumper_path, dump_path],
                       stdout=subprocess.PIPE,
                       stderr=subprocess.PIPE)
  stdout, stderr = p.communicate()
  if p.returncode != 0:
    # Dumper errors often do not affect stack walkability, just a warning.
    logging.warning('Reading minidump failed with output:\n%s', stderr)

  library_names = []
  module_library_line_re = re.compile(r'[(]code_file[)]\s+= '
                                      r'"(?P<library_name>lib[^. ]+.so)"')
  in_module = False
  for line in stdout.splitlines():
    line = line.lstrip().rstrip('\n')
    if line == 'MDRawModule':
      in_module = True
      continue
    if line == '':
      in_module = False
      continue
    if in_module:
      m = module_library_line_re.match(line)
      if m:
        library_names.append(m.group('library_name'))
  if not library_names:
    logging.warning(
        'Could not find any library name in the dump, '
        'default to: %s', default_library_name)
    return [default_library_name]
  return library_names


def main():
  logging.basicConfig(level=logging.INFO)
  parser = argparse.ArgumentParser(
      description='Fetches Crashpad dumps from a given device, '
      'walks and symbolizes the stacks.')
  parser.add_argument('--device', required=True, help='Device serial number')
  parser.add_argument(
      '--adb-path', required=True, help='Path to the "adb" command')
  parser.add_argument(
      '--build-path',
      required=True,
      help='Build output directory, equivalent to CHROMIUM_OUTPUT_DIR')
  parser.add_argument(
      '--chrome-cache-path',
      required=True,
      help='Directory on the device where Chrome stores cached files,'
      ' crashpad stores dumps in a subdirectory of it')
  args = parser.parse_args()

  stackwalk_path = os.path.join(args.build_path, 'minidump_stackwalk')
  if not os.path.exists(stackwalk_path):
    logging.error('Missing minidump_stackwalk executable')
    return 1

  devil_chromium.Initialize(adb_path=args.adb_path)
  device = device_utils.DeviceUtils(args.device)

  device_crashpad_path = posixpath.join(args.chrome_cache_path, 'Crashpad',
                                        'pending')

  def CrashpadDumpExists():
    return _ChooseLatestCrashpadDump(device, device_crashpad_path)

  crashpad_file = timeout_retry.WaitFor(
      CrashpadDumpExists, wait_period=1, max_tries=9)
  if not crashpad_file:
    logging.error('Could not locate a crashpad dump')
    return 1

  dump_dir = tempfile.mkdtemp()
  symbols_dir = None
  try:
    device.PullFile(
        device_path=posixpath.join(device_crashpad_path, crashpad_file),
        host_path=dump_dir)
    dump_full_path = os.path.join(dump_dir, crashpad_file)
    library_names = _ExtractLibraryNamesFromDump(args.build_path,
                                                 dump_full_path)
    symbols_dir = _CreateSymbolsDir(args.build_path, library_names)
    stackwalk_cmd = [stackwalk_path, dump_full_path, symbols_dir]
    subprocess.call(stackwalk_cmd)
  finally:
    shutil.rmtree(dump_dir, ignore_errors=True)
    if symbols_dir:
      shutil.rmtree(symbols_dir, ignore_errors=True)
  return 0


if __name__ == '__main__':
  sys.exit(main())