1#!/usr/bin/env python3 2# Copyright 2022 The Chromium Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6import collections 7import os 8import re 9import subprocess 10import sys 11 12 13# Run a command and symbolize anything that looks like a stacktrace in the 14# stdout/stderr. This will return with the same error code as the command. 15def main(basedir, cmd): 16 logs = collections.deque(maxlen=500) 17 18 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, 19 stderr=subprocess.STDOUT, 20 encoding='ISO-8859-1') 21 for line in iter(proc.stdout.readline, ''): 22 sys.stdout.write(line) 23 logs.append(line) 24 proc.wait() 25 print('Command exited with code %s' % proc.returncode) 26 27 try: 28 subprocess.check_call(['addr2line', '--help'], 29 stdout=subprocess.DEVNULL, 30 stderr=subprocess.DEVNULL) 31 except OSError: 32 print('addr2line not found on PATH. Skipping symbolization.') 33 return 34 35 36 # Stacktraces generally look like: 37 # /lib/x86_64-linux-gnu/libc.so.6(abort+0x16a) [0x7fa90e8d0c62] 38 # /b/s/w/irISUIyA/linux_vulkan_intel_driver_debug/./libvulkan_intel.so(+0x1f4d0a) [0x7fa909eead0a] 39 # /b/s/w/irISUIyA/out/Debug/dm() [0x17c3c5f] 40 # The stack_line regex splits those into three parts. Experimentation has 41 # shown that the address in () works best for external libraries, but our code 42 # doesn't have that. So, we capture both addresses and prefer using the first 43 # over the second, unless the first is blank or invalid. Relative offsets 44 # like abort+0x16a are ignored. 45 stack_line = r'^(?P<path>.+)\(\+?(?P<addr>.*)\) ?\[(?P<addr2>.+)\]' 46 # After performing addr2line, the result can be something obnoxious like: 47 # foo(bar) at /b/s/w/a39kd/Skia/out/Clang/../../src/gpu/Frobulator.cpp:13 48 # The extra_path strips off the not-useful prefix and leaves just the 49 # important src/gpu/Frobulator.cpp:13 bit. 50 extra_path = r'/.*\.\./' 51 is_first = True 52 last = "" 53 for line in logs: 54 line = line.strip() 55 # For unknown reasons, sometimes lines are duplicated in the stacktrace. 56 # This removes the duplication 57 if line == last: 58 continue 59 last = line 60 61 m = re.search(stack_line, line) 62 if m: 63 if is_first: 64 print('#######################################') 65 print('symbolized stacktrace follows') 66 print('#######################################') 67 is_first = False 68 69 path = m.group('path') 70 addr = m.group('addr') 71 addr2 = m.group('addr2') 72 if os.path.exists(path): 73 if not addr or not addr.startswith('0x'): 74 addr = addr2 75 try: 76 sym = subprocess.check_output([ 77 'addr2line', '--demangle', '--pretty-print', '--functions', 78 '--exe='+path, addr 79 ]).decode('utf-8') 80 except subprocess.CalledProcessError: 81 sym = '' 82 sym = sym.strip() 83 # If addr2line doesn't return anything useful, we don't replace the 84 # original address, so the human can see it. 85 if sym and not sym.startswith('?'): 86 if path.startswith(basedir): 87 path = path[len(basedir) + 1:] 88 sym = re.sub(extra_path, '', sym) 89 line = path + ' ' + sym 90 print(line) 91 92 sys.exit(proc.returncode) 93 94 95if __name__ == '__main__': 96 if len(sys.argv) < 3: 97 print('USAGE: %s working_dir cmd_and_args...' % sys.argv[0], 98 file=sys.stderr) 99 sys.exit(1) 100 main(sys.argv[1], sys.argv[2:]) 101