Home | History | Annotate | Download | only in tools
      1 #!/usr/bin/env python
      2 
      3 # Copyright (C) 2009 The Android Open Source Project
      4 #
      5 # Licensed under the Apache License, Version 2.0 (the 'License');
      6 # you may not use this file except in compliance with the License.
      7 # You may obtain a copy of the License at
      8 #
      9 #      http://www.apache.org/licenses/LICENSE-2.0
     10 #
     11 # Unless required by applicable law or agreed to in writing, software
     12 # distributed under the License is distributed on an 'AS IS' BASIS,
     13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14 # See the License for the specific language governing permissions and
     15 # limitations under the License.
     16 
     17 import os
     18 import re
     19 import string
     20 import sys
     21 
     22 ###############################################################################
     23 # match "#00  pc 0003f52e  /system/lib/libdvm.so" for example
     24 ###############################################################################
     25 trace_line = re.compile("(.*)(\#[0-9]+)  (..) ([0-9a-f]{8})  ([^\r\n \t]*)")
     26 
     27 # returns a list containing the function name and the file/lineno
     28 def CallAddr2Line(lib, addr):
     29   global symbols_dir
     30   global addr2line_cmd
     31   global cppfilt_cmd
     32 
     33   if lib != "":
     34     cmd = addr2line_cmd + \
     35         " -f -e " + symbols_dir + lib + " 0x" + addr
     36     stream = os.popen(cmd)
     37     lines = stream.readlines()
     38     list = map(string.strip, lines)
     39   else:
     40     list = []
     41   if list != []:
     42     # Name like "move_forward_type<JavaVMOption>" causes troubles
     43     mangled_name = re.sub('<', '\<', list[0]);
     44     mangled_name = re.sub('>', '\>', mangled_name);
     45     cmd = cppfilt_cmd + " " + mangled_name
     46     stream = os.popen(cmd)
     47     list[0] = stream.readline()
     48     stream.close()
     49     list = map(string.strip, list)
     50   else:
     51     list = [ "(unknown)", "(unknown)" ]
     52   return list
     53 
     54 
     55 ###############################################################################
     56 # similar to CallAddr2Line, but using objdump to find out the name of the
     57 # containing function of the specified address
     58 ###############################################################################
     59 def CallObjdump(lib, addr):
     60   global objdump_cmd
     61   global symbols_dir
     62 
     63   unknown = "(unknown)"
     64   uname = os.uname()[0]
     65   if uname == "Darwin":
     66     proc = os.uname()[-1]
     67     if proc == "i386":
     68       uname = "darwin-x86"
     69     else:
     70       uname = "darwin-ppc"
     71   elif uname == "Linux":
     72     uname = "linux-x86"
     73   if lib != "":
     74     next_addr = string.atoi(addr, 16) + 1
     75     cmd = objdump_cmd \
     76         + " -C -d --start-address=0x" + addr + " --stop-address=" \
     77         + str(next_addr) \
     78         + " " + symbols_dir + lib
     79     stream = os.popen(cmd)
     80     lines = stream.readlines()
     81     map(string.strip, lines)
     82     stream.close()
     83   else:
     84     return unknown
     85 
     86   # output looks like
     87   #
     88   # file format elf32-littlearm
     89   #
     90   # Disassembly of section .text:
     91   #
     92   # 0000833c <func+0x4>:
     93   #        833c:       701a            strb    r2, [r3, #0]
     94   #
     95   # we want to extract the "func" part
     96   num_lines = len(lines)
     97   if num_lines < 2:
     98     return unknown
     99   func_name = lines[num_lines-2]
    100   func_regexp = re.compile("(^.*\<)(.*)(\+.*\>:$)")
    101   components = func_regexp.match(func_name)
    102   if components is None:
    103     return unknown
    104   return components.group(2)
    105 
    106 ###############################################################################
    107 # determine the symbols directory in the local build
    108 ###############################################################################
    109 def FindSymbolsDir():
    110   global symbols_dir
    111 
    112   try:
    113     path = os.environ['ANDROID_PRODUCT_OUT'] + "/symbols"
    114   except:
    115     cmd = "CALLED_FROM_SETUP=true BUILD_SYSTEM=build/core " \
    116       + "SRC_TARGET_DIR=build/target make -f build/core/envsetup.mk " \
    117       + "dumpvar-abs-TARGET_OUT_UNSTRIPPED"
    118     stream = os.popen(cmd)
    119     str = stream.read()
    120     stream.close()
    121     path = str.strip()
    122 
    123   if (not os.path.exists(path)):
    124     print path + " not found!"
    125     sys.exit(1)
    126 
    127   symbols_dir = path
    128 
    129 ###############################################################################
    130 # determine the path of binutils
    131 ###############################################################################
    132 def SetupToolsPath():
    133   global addr2line_cmd
    134   global objdump_cmd
    135   global cppfilt_cmd
    136   global symbols_dir
    137 
    138   uname = os.uname()[0]
    139   if uname == "Darwin":
    140     proc = os.uname()[-1]
    141     if proc == "i386":
    142       uname = "darwin-x86"
    143     else:
    144       uname = "darwin-ppc"
    145   elif uname == "Linux":
    146     uname = "linux-x86"
    147   prefix = "./prebuilt/" + uname + "/toolchain/arm-eabi-4.4.0/bin/"
    148   addr2line_cmd = prefix + "arm-eabi-addr2line"
    149 
    150   if (not os.path.exists(addr2line_cmd)):
    151     try:
    152       prefix = os.environ['ANDROID_BUILD_TOP'] + "/prebuilt/" + uname + \
    153                "/toolchain/arm-eabi-4.4.0/bin/"
    154     except:
    155       prefix = "";
    156 
    157     addr2line_cmd = prefix + "arm-eabi-addr2line"
    158     if (not os.path.exists(addr2line_cmd)):
    159       print addr2line_cmd + " not found!"
    160       sys.exit(1)
    161 
    162   objdump_cmd = prefix + "arm-eabi-objdump"
    163   cppfilt_cmd = prefix + "arm-eabi-c++filt"
    164 
    165 ###############################################################################
    166 # look up the function and file/line number for a raw stack trace line
    167 # groups[0]: log tag
    168 # groups[1]: stack level
    169 # groups[2]: "pc"
    170 # groups[3]: code address
    171 # groups[4]: library name
    172 ###############################################################################
    173 def SymbolTranslation(groups):
    174   lib_name = groups[4]
    175   code_addr = groups[3]
    176   caller = CallObjdump(lib_name, code_addr)
    177   func_line_pair = CallAddr2Line(lib_name, code_addr)
    178 
    179   # If a callee is inlined to the caller, objdump will see the caller's
    180   # address but addr2line will report the callee's address. So the printed
    181   # format is desgined to be "caller<-callee  file:line"
    182   if (func_line_pair[0] != caller):
    183     print groups[0] + groups[1] + " " + caller + "<-" + \
    184           '  '.join(func_line_pair[:]) + " "
    185   else:
    186     print groups[0] + groups[1] + " " + '  '.join(func_line_pair[:]) + " "
    187 
    188 ###############################################################################
    189 
    190 if __name__ == '__main__':
    191   # pass the options to adb
    192   adb_cmd  = "adb " + ' '.join(sys.argv[1:])
    193 
    194   # setup addr2line_cmd and objdump_cmd
    195   SetupToolsPath()
    196 
    197   # setup the symbols directory
    198   FindSymbolsDir()
    199 
    200   # invoke the adb command and filter its output
    201   stream = os.popen(adb_cmd)
    202   while (True):
    203     line = stream.readline()
    204 
    205     # EOF reached
    206     if (line == ''):
    207       break
    208 
    209     # remove the trailing \n
    210     line = line.strip()
    211 
    212     # see if this is a stack trace line
    213     match = trace_line.match(line)
    214     if (match):
    215       groups = match.groups()
    216       # translate raw address into symbols
    217       SymbolTranslation(groups)
    218     else:
    219       print line
    220       sys.stdout.flush()
    221 
    222   # adb itself aborts
    223   stream.close()
    224