1#!/usr/bin/env python
2#===- lib/asan/scripts/asan_symbolize.py -----------------------------------===#
3#
4#                     The LLVM Compiler Infrastructure
5#
6# This file is distributed under the University of Illinois Open Source
7# License. See LICENSE.TXT for details.
8#
9#===------------------------------------------------------------------------===#
10import os
11import re
12import sys
13import string
14import subprocess
15
16pipes = {}
17filetypes = {}
18DEBUG=False
19
20def patch_address(frameno, addr_s):
21  ''' Subtracts 1 or 2 from the top frame's address.
22  Top frame is normally the return address from asan_report*
23  call, which is not expected to return at all. Because of that, this
24  address often belongs to the next source code line, or even to a different
25  function. '''
26  if frameno == '0':
27    addr = int(addr_s, 16)
28    if os.uname()[4].startswith('arm'):
29      # Cancel the Thumb bit
30      addr = addr & (~1)
31    addr -= 1
32    return hex(addr)
33  return addr_s
34
35
36def fix_filename(file_name):
37  for path_to_cut in sys.argv[1:]:
38    file_name = re.sub(".*" + path_to_cut, "", file_name)
39  file_name = re.sub(".*asan_[a-z_]*.cc:[0-9]*", "_asan_rtl_", file_name)
40  file_name = re.sub(".*crtstuff.c:0", "???:0", file_name)
41  return file_name
42
43
44# TODO(glider): need some refactoring here
45def symbolize_addr2line(line):
46  #0 0x7f6e35cf2e45  (/blah/foo.so+0x11fe45)
47  match = re.match('^( *#([0-9]+) *0x[0-9a-f]+) *\((.*)\+(0x[0-9a-f]+)\)', line)
48  if match:
49    frameno = match.group(2)
50    binary = match.group(3)
51    addr = match.group(4)
52    addr = patch_address(frameno, addr)
53    if not pipes.has_key(binary):
54      pipes[binary] = subprocess.Popen(["addr2line", "-f", "-e", binary],
55                         stdin=subprocess.PIPE, stdout=subprocess.PIPE)
56    p = pipes[binary]
57    try:
58      print >>p.stdin, addr
59      function_name = p.stdout.readline().rstrip()
60      file_name     = p.stdout.readline().rstrip()
61    except:
62      function_name = ""
63      file_name = ""
64    file_name = fix_filename(file_name)
65
66    print match.group(1), "in", function_name, file_name
67  else:
68    print line.rstrip()
69
70
71def get_macho_filetype(binary):
72  if not filetypes.has_key(binary):
73    otool_pipe = subprocess.Popen(["otool", "-Vh",  binary],
74      stdin=subprocess.PIPE, stdout=subprocess.PIPE)
75    otool_line = "".join(otool_pipe.stdout.readlines())
76    for t in ["DYLIB", "EXECUTE"]:
77      if t in otool_line:
78        filetypes[binary] = t
79    otool_pipe.stdin.close()
80  return filetypes[binary]
81
82
83def symbolize_atos(line):
84  #0 0x7f6e35cf2e45  (/blah/foo.so+0x11fe45)
85  match = re.match('^( *#([0-9]+) *)(0x[0-9a-f]+) *\((.*)\+(0x[0-9a-f]+)\)', line)
86  if match:
87    #print line
88    prefix = match.group(1)
89    frameno = match.group(2)
90    orig_addr = match.group(3)
91    binary = match.group(4)
92    offset = match.group(5)
93    addr = patch_address(frameno, orig_addr)
94    load_addr = hex(int(orig_addr, 16) - int(offset, 16))
95    filetype = get_macho_filetype(binary)
96
97    if not pipes.has_key(binary):
98      # Guess which arch we're running. 10 = len("0x") + 8 hex digits.
99      if len(addr) > 10:
100        arch = "x86_64"
101      else:
102        arch = "i386"
103
104    if filetype == "DYLIB":
105      load_addr = "0x0"
106    if DEBUG:
107      print "atos -o %s -arch %s -l %s" % (binary, arch, load_addr)
108    pipes[binary] = subprocess.Popen(["atos", "-o", binary, "-arch", arch, "-l", load_addr],
109                         stdin=subprocess.PIPE, stdout=subprocess.PIPE,)
110    p = pipes[binary]
111    if filetype == "DYLIB":
112      print >>p.stdin, "%s" % offset
113    else:
114      print >>p.stdin, "%s" % addr
115    # TODO(glider): it's more efficient to make a batch atos run for each binary.
116    p.stdin.close()
117    atos_line = p.stdout.readline().rstrip()
118    # A well-formed atos response looks like this:
119    #   foo(type1, type2) (in object.name) (filename.cc:80)
120    match = re.match('^(.*) \(in (.*)\) \((.*:\d*)\)$', atos_line)
121    #print "atos_line: ", atos_line
122    if match:
123      function_name = match.group(1)
124      function_name = re.sub("\(.*?\)", "", function_name)
125      file_name = fix_filename(match.group(3))
126      print "%s%s in %s %s" % (prefix, addr, function_name, file_name)
127    else:
128      print "%s%s in %s" % (prefix, addr, atos_line)
129    del pipes[binary]
130  else:
131    print line.rstrip()
132
133system = os.uname()[0]
134if system in ['Linux', 'Darwin']:
135  for line in sys.stdin:
136    if system == 'Linux':
137      symbolize_addr2line(line)
138    elif system == 'Darwin':
139      symbolize_atos(line)
140else:
141  print 'Unknown system: ', system
142