xref: /aosp_15_r20/external/angle/build/android/stacktrace/crashpad_stackwalker.py (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
1*8975f5c5SAndroid Build Coastguard Worker#!/usr/bin/env vpython3
2*8975f5c5SAndroid Build Coastguard Worker#
3*8975f5c5SAndroid Build Coastguard Worker# Copyright 2019 The Chromium Authors
4*8975f5c5SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be
5*8975f5c5SAndroid Build Coastguard Worker# found in the LICENSE file.
6*8975f5c5SAndroid Build Coastguard Worker
7*8975f5c5SAndroid Build Coastguard Worker# Fetches Crashpad dumps from a given device, walks and symbolizes the stacks.
8*8975f5c5SAndroid Build Coastguard Worker# All the non-trivial operations are performed by generate_breakpad_symbols.py,
9*8975f5c5SAndroid Build Coastguard Worker# dump_syms, minidump_dump and minidump_stackwalk.
10*8975f5c5SAndroid Build Coastguard Worker
11*8975f5c5SAndroid Build Coastguard Workerimport argparse
12*8975f5c5SAndroid Build Coastguard Workerimport logging
13*8975f5c5SAndroid Build Coastguard Workerimport os
14*8975f5c5SAndroid Build Coastguard Workerimport posixpath
15*8975f5c5SAndroid Build Coastguard Workerimport re
16*8975f5c5SAndroid Build Coastguard Workerimport sys
17*8975f5c5SAndroid Build Coastguard Workerimport shutil
18*8975f5c5SAndroid Build Coastguard Workerimport subprocess
19*8975f5c5SAndroid Build Coastguard Workerimport tempfile
20*8975f5c5SAndroid Build Coastguard Worker
21*8975f5c5SAndroid Build Coastguard Worker_BUILD_ANDROID_PATH = os.path.abspath(
22*8975f5c5SAndroid Build Coastguard Worker    os.path.join(os.path.dirname(__file__), '..'))
23*8975f5c5SAndroid Build Coastguard Workersys.path.append(_BUILD_ANDROID_PATH)
24*8975f5c5SAndroid Build Coastguard Workerimport devil_chromium
25*8975f5c5SAndroid Build Coastguard Workerfrom devil.android import device_utils
26*8975f5c5SAndroid Build Coastguard Workerfrom devil.utils import timeout_retry
27*8975f5c5SAndroid Build Coastguard Worker
28*8975f5c5SAndroid Build Coastguard Worker
29*8975f5c5SAndroid Build Coastguard Workerdef _CreateSymbolsDir(build_path, dynamic_library_names):
30*8975f5c5SAndroid Build Coastguard Worker  generator = os.path.normpath(
31*8975f5c5SAndroid Build Coastguard Worker      os.path.join(_BUILD_ANDROID_PATH, '..', '..', 'components', 'crash',
32*8975f5c5SAndroid Build Coastguard Worker                   'content', 'tools', 'generate_breakpad_symbols.py'))
33*8975f5c5SAndroid Build Coastguard Worker  syms_dir = os.path.join(build_path, 'crashpad_syms')
34*8975f5c5SAndroid Build Coastguard Worker  shutil.rmtree(syms_dir, ignore_errors=True)
35*8975f5c5SAndroid Build Coastguard Worker  os.mkdir(syms_dir)
36*8975f5c5SAndroid Build Coastguard Worker  for lib in dynamic_library_names:
37*8975f5c5SAndroid Build Coastguard Worker    unstripped_library_path = os.path.join(build_path, 'lib.unstripped', lib)
38*8975f5c5SAndroid Build Coastguard Worker    if not os.path.exists(unstripped_library_path):
39*8975f5c5SAndroid Build Coastguard Worker      continue
40*8975f5c5SAndroid Build Coastguard Worker    logging.info('Generating symbols for: %s', unstripped_library_path)
41*8975f5c5SAndroid Build Coastguard Worker    cmd = [
42*8975f5c5SAndroid Build Coastguard Worker        generator,
43*8975f5c5SAndroid Build Coastguard Worker        '--symbols-dir',
44*8975f5c5SAndroid Build Coastguard Worker        syms_dir,
45*8975f5c5SAndroid Build Coastguard Worker        '--build-dir',
46*8975f5c5SAndroid Build Coastguard Worker        build_path,
47*8975f5c5SAndroid Build Coastguard Worker        '--binary',
48*8975f5c5SAndroid Build Coastguard Worker        unstripped_library_path,
49*8975f5c5SAndroid Build Coastguard Worker        '--platform',
50*8975f5c5SAndroid Build Coastguard Worker        'android',
51*8975f5c5SAndroid Build Coastguard Worker    ]
52*8975f5c5SAndroid Build Coastguard Worker    return_code = subprocess.call(cmd)
53*8975f5c5SAndroid Build Coastguard Worker    if return_code != 0:
54*8975f5c5SAndroid Build Coastguard Worker      logging.error('Could not extract symbols, command failed: %s',
55*8975f5c5SAndroid Build Coastguard Worker                    ' '.join(cmd))
56*8975f5c5SAndroid Build Coastguard Worker  return syms_dir
57*8975f5c5SAndroid Build Coastguard Worker
58*8975f5c5SAndroid Build Coastguard Worker
59*8975f5c5SAndroid Build Coastguard Workerdef _ChooseLatestCrashpadDump(device, crashpad_dump_path):
60*8975f5c5SAndroid Build Coastguard Worker  if not device.PathExists(crashpad_dump_path):
61*8975f5c5SAndroid Build Coastguard Worker    logging.warning('Crashpad dump directory does not exist: %s',
62*8975f5c5SAndroid Build Coastguard Worker                    crashpad_dump_path)
63*8975f5c5SAndroid Build Coastguard Worker    return None
64*8975f5c5SAndroid Build Coastguard Worker  latest = None
65*8975f5c5SAndroid Build Coastguard Worker  latest_timestamp = 0
66*8975f5c5SAndroid Build Coastguard Worker  for crashpad_file in device.ListDirectory(crashpad_dump_path):
67*8975f5c5SAndroid Build Coastguard Worker    if crashpad_file.endswith('.dmp'):
68*8975f5c5SAndroid Build Coastguard Worker      stat = device.StatPath(posixpath.join(crashpad_dump_path, crashpad_file))
69*8975f5c5SAndroid Build Coastguard Worker      current_timestamp = stat['st_mtime']
70*8975f5c5SAndroid Build Coastguard Worker      if current_timestamp > latest_timestamp:
71*8975f5c5SAndroid Build Coastguard Worker        latest_timestamp = current_timestamp
72*8975f5c5SAndroid Build Coastguard Worker        latest = crashpad_file
73*8975f5c5SAndroid Build Coastguard Worker  return latest
74*8975f5c5SAndroid Build Coastguard Worker
75*8975f5c5SAndroid Build Coastguard Worker
76*8975f5c5SAndroid Build Coastguard Workerdef _ExtractLibraryNamesFromDump(build_path, dump_path):
77*8975f5c5SAndroid Build Coastguard Worker  default_library_name = 'libmonochrome.so'
78*8975f5c5SAndroid Build Coastguard Worker  dumper_path = os.path.join(build_path, 'minidump_dump')
79*8975f5c5SAndroid Build Coastguard Worker  if not os.access(dumper_path, os.X_OK):
80*8975f5c5SAndroid Build Coastguard Worker    logging.warning(
81*8975f5c5SAndroid Build Coastguard Worker        'Cannot extract library name from dump because %s is not found, '
82*8975f5c5SAndroid Build Coastguard Worker        'default to: %s', dumper_path, default_library_name)
83*8975f5c5SAndroid Build Coastguard Worker    return [default_library_name]
84*8975f5c5SAndroid Build Coastguard Worker  p = subprocess.Popen([dumper_path, dump_path],
85*8975f5c5SAndroid Build Coastguard Worker                       stdout=subprocess.PIPE,
86*8975f5c5SAndroid Build Coastguard Worker                       stderr=subprocess.PIPE)
87*8975f5c5SAndroid Build Coastguard Worker  stdout, stderr = p.communicate()
88*8975f5c5SAndroid Build Coastguard Worker  if p.returncode != 0:
89*8975f5c5SAndroid Build Coastguard Worker    # Dumper errors often do not affect stack walkability, just a warning.
90*8975f5c5SAndroid Build Coastguard Worker    logging.warning('Reading minidump failed with output:\n%s', stderr)
91*8975f5c5SAndroid Build Coastguard Worker
92*8975f5c5SAndroid Build Coastguard Worker  library_names = []
93*8975f5c5SAndroid Build Coastguard Worker  module_library_line_re = re.compile(r'[(]code_file[)]\s+= '
94*8975f5c5SAndroid Build Coastguard Worker                                      r'"(?P<library_name>lib[^. ]+.so)"')
95*8975f5c5SAndroid Build Coastguard Worker  in_module = False
96*8975f5c5SAndroid Build Coastguard Worker  for line in stdout.splitlines():
97*8975f5c5SAndroid Build Coastguard Worker    line = line.lstrip().rstrip('\n')
98*8975f5c5SAndroid Build Coastguard Worker    if line == 'MDRawModule':
99*8975f5c5SAndroid Build Coastguard Worker      in_module = True
100*8975f5c5SAndroid Build Coastguard Worker      continue
101*8975f5c5SAndroid Build Coastguard Worker    if line == '':
102*8975f5c5SAndroid Build Coastguard Worker      in_module = False
103*8975f5c5SAndroid Build Coastguard Worker      continue
104*8975f5c5SAndroid Build Coastguard Worker    if in_module:
105*8975f5c5SAndroid Build Coastguard Worker      m = module_library_line_re.match(line)
106*8975f5c5SAndroid Build Coastguard Worker      if m:
107*8975f5c5SAndroid Build Coastguard Worker        library_names.append(m.group('library_name'))
108*8975f5c5SAndroid Build Coastguard Worker  if not library_names:
109*8975f5c5SAndroid Build Coastguard Worker    logging.warning(
110*8975f5c5SAndroid Build Coastguard Worker        'Could not find any library name in the dump, '
111*8975f5c5SAndroid Build Coastguard Worker        'default to: %s', default_library_name)
112*8975f5c5SAndroid Build Coastguard Worker    return [default_library_name]
113*8975f5c5SAndroid Build Coastguard Worker  return library_names
114*8975f5c5SAndroid Build Coastguard Worker
115*8975f5c5SAndroid Build Coastguard Worker
116*8975f5c5SAndroid Build Coastguard Workerdef main():
117*8975f5c5SAndroid Build Coastguard Worker  logging.basicConfig(level=logging.INFO)
118*8975f5c5SAndroid Build Coastguard Worker  parser = argparse.ArgumentParser(
119*8975f5c5SAndroid Build Coastguard Worker      description='Fetches Crashpad dumps from a given device, '
120*8975f5c5SAndroid Build Coastguard Worker      'walks and symbolizes the stacks.')
121*8975f5c5SAndroid Build Coastguard Worker  parser.add_argument('--device', required=True, help='Device serial number')
122*8975f5c5SAndroid Build Coastguard Worker  parser.add_argument('--adb-path', help='Path to the "adb" command')
123*8975f5c5SAndroid Build Coastguard Worker  parser.add_argument(
124*8975f5c5SAndroid Build Coastguard Worker      '--build-path',
125*8975f5c5SAndroid Build Coastguard Worker      required=True,
126*8975f5c5SAndroid Build Coastguard Worker      help='Build output directory, equivalent to CHROMIUM_OUTPUT_DIR')
127*8975f5c5SAndroid Build Coastguard Worker  parser.add_argument(
128*8975f5c5SAndroid Build Coastguard Worker      '--chrome-cache-path',
129*8975f5c5SAndroid Build Coastguard Worker      required=True,
130*8975f5c5SAndroid Build Coastguard Worker      help='Directory on the device where Chrome stores cached files,'
131*8975f5c5SAndroid Build Coastguard Worker      ' crashpad stores dumps in a subdirectory of it')
132*8975f5c5SAndroid Build Coastguard Worker  args = parser.parse_args()
133*8975f5c5SAndroid Build Coastguard Worker
134*8975f5c5SAndroid Build Coastguard Worker  stackwalk_path = os.path.join(args.build_path, 'minidump_stackwalk')
135*8975f5c5SAndroid Build Coastguard Worker  if not os.path.exists(stackwalk_path):
136*8975f5c5SAndroid Build Coastguard Worker    logging.error('Missing minidump_stackwalk executable')
137*8975f5c5SAndroid Build Coastguard Worker    return 1
138*8975f5c5SAndroid Build Coastguard Worker
139*8975f5c5SAndroid Build Coastguard Worker  devil_chromium.Initialize(output_directory=args.build_path,
140*8975f5c5SAndroid Build Coastguard Worker                            adb_path=args.adb_path)
141*8975f5c5SAndroid Build Coastguard Worker  device = device_utils.DeviceUtils(args.device)
142*8975f5c5SAndroid Build Coastguard Worker
143*8975f5c5SAndroid Build Coastguard Worker  device_crashpad_path = posixpath.join(args.chrome_cache_path, 'Crashpad',
144*8975f5c5SAndroid Build Coastguard Worker                                        'pending')
145*8975f5c5SAndroid Build Coastguard Worker
146*8975f5c5SAndroid Build Coastguard Worker  def CrashpadDumpExists():
147*8975f5c5SAndroid Build Coastguard Worker    return _ChooseLatestCrashpadDump(device, device_crashpad_path)
148*8975f5c5SAndroid Build Coastguard Worker
149*8975f5c5SAndroid Build Coastguard Worker  crashpad_file = timeout_retry.WaitFor(
150*8975f5c5SAndroid Build Coastguard Worker      CrashpadDumpExists, wait_period=1, max_tries=9)
151*8975f5c5SAndroid Build Coastguard Worker  if not crashpad_file:
152*8975f5c5SAndroid Build Coastguard Worker    logging.error('Could not locate a crashpad dump')
153*8975f5c5SAndroid Build Coastguard Worker    return 1
154*8975f5c5SAndroid Build Coastguard Worker
155*8975f5c5SAndroid Build Coastguard Worker  dump_dir = tempfile.mkdtemp()
156*8975f5c5SAndroid Build Coastguard Worker  symbols_dir = None
157*8975f5c5SAndroid Build Coastguard Worker  try:
158*8975f5c5SAndroid Build Coastguard Worker    device.PullFile(
159*8975f5c5SAndroid Build Coastguard Worker        device_path=posixpath.join(device_crashpad_path, crashpad_file),
160*8975f5c5SAndroid Build Coastguard Worker        host_path=dump_dir)
161*8975f5c5SAndroid Build Coastguard Worker    dump_full_path = os.path.join(dump_dir, crashpad_file)
162*8975f5c5SAndroid Build Coastguard Worker    library_names = _ExtractLibraryNamesFromDump(args.build_path,
163*8975f5c5SAndroid Build Coastguard Worker                                                 dump_full_path)
164*8975f5c5SAndroid Build Coastguard Worker    symbols_dir = _CreateSymbolsDir(args.build_path, library_names)
165*8975f5c5SAndroid Build Coastguard Worker    stackwalk_cmd = [stackwalk_path, dump_full_path, symbols_dir]
166*8975f5c5SAndroid Build Coastguard Worker    subprocess.call(stackwalk_cmd)
167*8975f5c5SAndroid Build Coastguard Worker  finally:
168*8975f5c5SAndroid Build Coastguard Worker    shutil.rmtree(dump_dir, ignore_errors=True)
169*8975f5c5SAndroid Build Coastguard Worker    if symbols_dir:
170*8975f5c5SAndroid Build Coastguard Worker      shutil.rmtree(symbols_dir, ignore_errors=True)
171*8975f5c5SAndroid Build Coastguard Worker  return 0
172*8975f5c5SAndroid Build Coastguard Worker
173*8975f5c5SAndroid Build Coastguard Worker
174*8975f5c5SAndroid Build Coastguard Workerif __name__ == '__main__':
175*8975f5c5SAndroid Build Coastguard Worker  sys.exit(main())
176