1#!/usr/bin/env python 2# Copyright 2017 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# pylint: disable=line-too-long 6 7 8from __future__ import print_function 9import collections 10import os 11import re 12import subprocess 13import sys 14 15 16# Run a command and symbolize anything that looks like a stacktrace in the 17# stdout/stderr. This will return with the same error code as the command. 18 19# First parameter is the current working directory, which will be stripped 20# out of stacktraces. The rest of the parameters will be fed to 21# subprocess.check_output() and should be the command and arguments that 22# will be fed in. If any environment variables are set when running this 23# script, they will be automatically used by the call to 24# subprocess.check_output(). 25 26# This wrapper function is needed to make sure stdout and stderr stay properly 27# interleaved, to assist in debugging. There are no clean ways to achieve 28# this with recipes. For example, running the dm step with parameters like 29# stdout=api.raw_io.output(), stderr=api.raw_io.output() ended up with 30# stderr and stdout being separate files, which eliminated the interwoven logs. 31# Aside from specifying stdout/stderr, there are no ways to capture or reason 32# about the logs of previous steps without using a wrapper like this. 33 34 35def main(basedir, cmd): 36 logs = collections.deque(maxlen=500) 37 38 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, 39 stderr=subprocess.STDOUT) 40 for line in iter(proc.stdout.readline, ''): 41 line = line.decode('utf-8') 42 sys.stdout.write(line) 43 logs.append(line) 44 proc.wait() 45 print('Command exited with code %s' % proc.returncode) 46 # Stacktraces generally look like: 47 # /lib/x86_64-linux-gnu/libc.so.6(abort+0x16a) [0x7fa90e8d0c62] 48 # /b/s/w/irISUIyA/linux_vulkan_intel_driver_debug/./libvulkan_intel.so(+0x1f4d0a) [0x7fa909eead0a] 49 # /b/s/w/irISUIyA/out/Debug/dm() [0x17c3c5f] 50 # The stack_line regex splits those into three parts. Experimentation has 51 # shown that the address in () works best for external libraries, but our code 52 # doesn't have that. So, we capture both addresses and prefer using the first 53 # over the second, unless the first is blank or invalid. Relative offsets 54 # like abort+0x16a are ignored. 55 stack_line = r'^(?P<path>.+)\(\+?(?P<addr>.*)\) ?\[(?P<addr2>.+)\]' 56 # After performing addr2line, the result can be something obnoxious like: 57 # foo(bar) at /b/s/w/a39kd/Skia/out/Clang/../../src/gpu/Frobulator.cpp:13 58 # The extra_path strips off the not-useful prefix and leaves just the 59 # important src/gpu/Frobulator.cpp:13 bit. 60 extra_path = r'/.*\.\./' 61 is_first = True 62 for line in logs: 63 line = line.strip() 64 65 m = re.search(stack_line, line) 66 if m: 67 if is_first: 68 print('#######################################') 69 print('symbolized stacktrace follows') 70 print('#######################################') 71 is_first = False 72 73 path = m.group('path') 74 addr = m.group('addr') 75 addr2 = m.group('addr2') 76 if os.path.exists(path): 77 if not addr or not addr.startswith('0x'): 78 addr = addr2 79 try: 80 sym = subprocess.check_output(['addr2line', '-Cfpe', path, addr]) 81 except subprocess.CalledProcessError: 82 sym = '' 83 sym = sym.strip() 84 # If addr2line doesn't return anything useful, we don't replace the 85 # original address, so the human can see it. 86 if sym and not sym.startswith('?'): 87 if path.startswith(basedir): 88 path = path[len(basedir)+1:] 89 sym = re.sub(extra_path, '', sym) 90 line = path + ' ' + sym 91 print(line) 92 93 sys.exit(proc.returncode) 94 95 96if __name__ == '__main__': 97 if len(sys.argv) < 3: 98 print('USAGE: %s working_dir cmd_and_args...' % sys.argv[0], 99 file=sys.stderr) 100 sys.exit(1) 101 main(sys.argv[1], sys.argv[2:]) 102