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 glob
11import os
12import re
13import sys
14import string
15import subprocess
16
17pipes = {}
18load_addresses = {}
19next_inline_frameno = 0
20
21def patch_address(frameno, addr_s):
22  ''' Subtracts 1 or 2 from the top frame's address.
23  Top frame is normally the return address from asan_report*
24  call, which is not expected to return at all. Because of that, this
25  address often belongs to the next source code line, or even to a different
26  function. '''
27  if frameno == '0':
28    addr = int(addr_s, 16)
29    if os.uname()[4].startswith('arm'):
30      # Cancel the Thumb bit
31      addr = addr & (~1)
32    addr -= 1
33    return hex(addr)
34  return addr_s
35
36def android_get_load_address(path):
37  if load_addresses.has_key(path):
38    return load_addresses[path]
39  readelf_glob = os.path.join(os.environ['ANDROID_TOOLCHAIN'], '*-readelf')
40  readelf = glob.glob(readelf_glob)[0]
41  readelf_pipe = subprocess.Popen([readelf, "-l", path], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
42  for line in readelf_pipe.stdout:
43      if ('LOAD' in line) and (' E ' in line):
44          match = re.match(r'\s*LOAD\s+0x[01-9a-zA-Z]+\s+(0x[01-9a-zA-Z]+)', line, re.UNICODE)
45          if match:
46              load_addr = int(match.group(1), 16)
47              load_addresses[path] = load_addr
48              return load_addr
49          else: break
50  print 'Could not make sense of readelf output!'
51  sys.exit(1)
52
53def postprocess_file_name(file_name, paths_to_cut):
54  for path_to_cut in paths_to_cut:
55    file_name = re.sub(".*" + path_to_cut, "", file_name)
56  file_name = re.sub(".*asan_[a-z_]*.(cc|h):[0-9]*", "[asan_rtl]", file_name)
57  file_name = re.sub(".*crtstuff.c:0", "???:0", file_name)
58  return file_name
59
60# TODO(glider): need some refactoring here
61def symbolize_addr2line(line, binary_prefix, paths_to_cut):
62  global next_inline_frameno
63  # Strip the log prefix ("I/asanwrapper( 1196): ").
64  line = re.sub(r'^.*?: ', '', line)
65  #0 0x7f6e35cf2e45  (/blah/foo.so+0x11fe45)
66  match = re.match(r'^(\s*#)([0-9]+) *(0x[0-9a-f]+) *\((.*)\+(0x[0-9a-f]+)\)', line, re.UNICODE)
67  if match:
68    frameno = match.group(2)
69    binary = match.group(4)
70    addr = match.group(5)
71    addr = patch_address(frameno, addr)
72
73    if binary.startswith('/'):
74      binary = binary[1:]
75    binary = os.path.join(binary_prefix, binary)
76
77    if not os.path.exists(binary):
78      print line.rstrip().encode('utf-8')
79      return
80
81    load_addr = android_get_load_address(binary)
82    addr = hex(int(addr, 16) + load_addr)
83
84    if not pipes.has_key(binary):
85      pipes[binary] = subprocess.Popen(["addr2line", "-i", "-f", "-e", binary],
86                         stdin=subprocess.PIPE, stdout=subprocess.PIPE)
87    p = pipes[binary]
88    frames = []
89    try:
90      print >>p.stdin, addr
91      # This will trigger a "??" response from addr2line so we know when to stop
92      print >>p.stdin
93      while True:
94        function_name = p.stdout.readline().rstrip()
95        file_name     = p.stdout.readline().rstrip()
96        if function_name in ['??', '']:
97          break
98        file_name = postprocess_file_name(file_name, paths_to_cut)
99        frames.append((function_name, file_name))
100    except:
101      pass
102    if not frames:
103      frames.append(('', ''))
104      # Consume another pair of "??" lines
105      try:
106        p.stdout.readline()
107        p.stdout.readline()
108      except:
109        pass
110    for frame in frames:
111      inline_frameno = next_inline_frameno
112      next_inline_frameno += 1
113      print "%s%d" % (match.group(1).encode('utf-8'), inline_frameno), \
114          match.group(3).encode('utf-8'), "in", frame[0], frame[1]
115  else:
116    print line.rstrip().encode('utf-8')
117
118
119binary_prefix = os.path.join(os.environ['ANDROID_PRODUCT_OUT'], 'symbols')
120paths_to_cut = [os.getcwd() + '/', os.environ['ANDROID_BUILD_TOP'] + '/'] + sys.argv[1:]
121
122for line in sys.stdin:
123  line = line.decode('utf-8')
124  symbolize_addr2line(line, binary_prefix, paths_to_cut)
125