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