• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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