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