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/config.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     uname = "darwin-x86"
    141   elif uname == "Linux":
    142     uname = "linux-x86"
    143   gcc_version = os.environ["TARGET_GCC_VERSION"]
    144   prefix = "./prebuilts/gcc/" + uname + "/arm/arm-linux-androideabi-" + \
    145            gcc_version + "/bin/"
    146   addr2line_cmd = prefix + "arm-linux-androideabi-addr2line"
    147 
    148   if (not os.path.exists(addr2line_cmd)):
    149     try:
    150       prefix = os.environ['ANDROID_BUILD_TOP'] + "/prebuilts/gcc/" + \
    151                uname + "/arm/arm-linux-androideabi-" + gcc_version + "/bin/"
    152     except:
    153       prefix = "";
    154 
    155     addr2line_cmd = prefix + "arm-linux-androideabi-addr2line"
    156     if (not os.path.exists(addr2line_cmd)):
    157       print addr2line_cmd + " not found!"
    158       sys.exit(1)
    159 
    160   objdump_cmd = prefix + "arm-linux-androideabi-objdump"
    161   cppfilt_cmd = prefix + "arm-linux-androideabi-c++filt"
    162 
    163 ###############################################################################
    164 # look up the function and file/line number for a raw stack trace line
    165 # groups[0]: log tag
    166 # groups[1]: stack level
    167 # groups[2]: "pc"
    168 # groups[3]: code address
    169 # groups[4]: library name
    170 ###############################################################################
    171 def SymbolTranslation(groups):
    172   lib_name = groups[4]
    173   code_addr = groups[3]
    174   caller = CallObjdump(lib_name, code_addr)
    175   func_line_pair = CallAddr2Line(lib_name, code_addr)
    176 
    177   # If a callee is inlined to the caller, objdump will see the caller's
    178   # address but addr2line will report the callee's address. So the printed
    179   # format is desgined to be "caller<-callee  file:line"
    180   if (func_line_pair[0] != caller):
    181     print groups[0] + groups[1] + " " + caller + "<-" + \
    182           '  '.join(func_line_pair[:]) + " "
    183   else:
    184     print groups[0] + groups[1] + " " + '  '.join(func_line_pair[:]) + " "
    185 
    186 ###############################################################################
    187 
    188 if __name__ == '__main__':
    189   # pass the options to adb
    190   adb_cmd  = "adb " + ' '.join(sys.argv[1:])
    191 
    192   # setup addr2line_cmd and objdump_cmd
    193   SetupToolsPath()
    194 
    195   # setup the symbols directory
    196   FindSymbolsDir()
    197 
    198   # invoke the adb command and filter its output
    199   stream = os.popen(adb_cmd)
    200   while (True):
    201     line = stream.readline()
    202 
    203     # EOF reached
    204     if (line == ''):
    205       break
    206 
    207     # remove the trailing \n
    208     line = line.strip()
    209 
    210     # see if this is a stack trace line
    211     match = trace_line.match(line)
    212     if (match):
    213       groups = match.groups()
    214       # translate raw address into symbols
    215       SymbolTranslation(groups)
    216     else:
    217       print line
    218       sys.stdout.flush()
    219 
    220   # adb itself aborts
    221   stream.close()
    222