#!/usr/bin/env python # Copyright (C) 2009 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the 'License'); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an 'AS IS' BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os import re import string import sys ############################################################################### # match "#00 pc 0003f52e /system/lib/libdvm.so" for example ############################################################################### trace_line = re.compile("(.*)(\#[0-9]+) (..) ([0-9a-f]{8}) ([^\r\n \t]*)") # returns a list containing the function name and the file/lineno def CallAddr2Line(lib, addr): global symbols_dir global addr2line_cmd global cppfilt_cmd if lib != "": cmd = addr2line_cmd + \ " -f -e " + symbols_dir + lib + " 0x" + addr stream = os.popen(cmd) lines = stream.readlines() list = map(string.strip, lines) else: list = [] if list != []: # Name like "move_forward_type<JavaVMOption>" causes troubles mangled_name = re.sub('<', '\<', list[0]); mangled_name = re.sub('>', '\>', mangled_name); cmd = cppfilt_cmd + " " + mangled_name stream = os.popen(cmd) list[0] = stream.readline() stream.close() list = map(string.strip, list) else: list = [ "(unknown)", "(unknown)" ] return list ############################################################################### # similar to CallAddr2Line, but using objdump to find out the name of the # containing function of the specified address ############################################################################### def CallObjdump(lib, addr): global objdump_cmd global symbols_dir unknown = "(unknown)" uname = os.uname()[0] if uname == "Darwin": proc = os.uname()[-1] if proc == "i386": uname = "darwin-x86" else: uname = "darwin-ppc" elif uname == "Linux": uname = "linux-x86" if lib != "": next_addr = string.atoi(addr, 16) + 1 cmd = objdump_cmd \ + " -C -d --start-address=0x" + addr + " --stop-address=" \ + str(next_addr) \ + " " + symbols_dir + lib stream = os.popen(cmd) lines = stream.readlines() map(string.strip, lines) stream.close() else: return unknown # output looks like # # file format elf32-littlearm # # Disassembly of section .text: # # 0000833c <func+0x4>: # 833c: 701a strb r2, [r3, #0] # # we want to extract the "func" part num_lines = len(lines) if num_lines < 2: return unknown func_name = lines[num_lines-2] func_regexp = re.compile("(^.*\<)(.*)(\+.*\>:$)") components = func_regexp.match(func_name) if components is None: return unknown return components.group(2) ############################################################################### # determine the symbols directory in the local build ############################################################################### def FindSymbolsDir(): global symbols_dir try: path = os.environ['ANDROID_PRODUCT_OUT'] + "/symbols" except: cmd = "CALLED_FROM_SETUP=true BUILD_SYSTEM=build/core " \ + "SRC_TARGET_DIR=build/target make -f build/core/envsetup.mk " \ + "dumpvar-abs-TARGET_OUT_UNSTRIPPED" stream = os.popen(cmd) str = stream.read() stream.close() path = str.strip() if (not os.path.exists(path)): print path + " not found!" sys.exit(1) symbols_dir = path ############################################################################### # determine the path of binutils ############################################################################### def SetupToolsPath(): global addr2line_cmd global objdump_cmd global cppfilt_cmd global symbols_dir uname = os.uname()[0] if uname == "Darwin": proc = os.uname()[-1] if proc == "i386": uname = "darwin-x86" else: uname = "darwin-ppc" elif uname == "Linux": uname = "linux-x86" prefix = "./prebuilt/" + uname + "/toolchain/arm-eabi-4.4.0/bin/" addr2line_cmd = prefix + "arm-eabi-addr2line" if (not os.path.exists(addr2line_cmd)): try: prefix = os.environ['ANDROID_BUILD_TOP'] + "/prebuilt/" + uname + \ "/toolchain/arm-eabi-4.4.0/bin/" except: prefix = ""; addr2line_cmd = prefix + "arm-eabi-addr2line" if (not os.path.exists(addr2line_cmd)): print addr2line_cmd + " not found!" sys.exit(1) objdump_cmd = prefix + "arm-eabi-objdump" cppfilt_cmd = prefix + "arm-eabi-c++filt" ############################################################################### # look up the function and file/line number for a raw stack trace line # groups[0]: log tag # groups[1]: stack level # groups[2]: "pc" # groups[3]: code address # groups[4]: library name ############################################################################### def SymbolTranslation(groups): lib_name = groups[4] code_addr = groups[3] caller = CallObjdump(lib_name, code_addr) func_line_pair = CallAddr2Line(lib_name, code_addr) # If a callee is inlined to the caller, objdump will see the caller's # address but addr2line will report the callee's address. So the printed # format is desgined to be "caller<-callee file:line" if (func_line_pair[0] != caller): print groups[0] + groups[1] + " " + caller + "<-" + \ ' '.join(func_line_pair[:]) + " " else: print groups[0] + groups[1] + " " + ' '.join(func_line_pair[:]) + " " ############################################################################### if __name__ == '__main__': # pass the options to adb adb_cmd = "adb " + ' '.join(sys.argv[1:]) # setup addr2line_cmd and objdump_cmd SetupToolsPath() # setup the symbols directory FindSymbolsDir() # invoke the adb command and filter its output stream = os.popen(adb_cmd) while (True): line = stream.readline() # EOF reached if (line == ''): break # remove the trailing \n line = line.strip() # see if this is a stack trace line match = trace_line.match(line) if (match): groups = match.groups() # translate raw address into symbols SymbolTranslation(groups) else: print line sys.stdout.flush() # adb itself aborts stream.close()