1. 程式人生 > >Android 除錯中 addr2line 工具的使用

Android 除錯中 addr2line 工具的使用

我們在解bug的時候經常能碰到一些段錯誤。下面是我從一個buglog中擷取的一個段錯誤:

//////////////////////////////////////////////////////////////////////////////////////////////////////
08-19 19:08:27.132  2105  2105 I DEBUG   : pid: 134, tid: 2104, name: OMXCallbackDisp  >>> /system/bin/mediaserver <<<
08-19 19:08:27.132  2105  2105 I DEBUG   : signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 44c86948
08-19 19:08:27.280  1803  1803 I SurfaceTextureClient: [STC::queueBuffer] (this:0x6162c9f8) fps:21.68, dur:1014.76, max:104.52, min:10.36
08-19 19:08:27.281   130  1018 I BufferQueue: [com.android.gallery3d/com.android.camera.Camera](this:0x41c1f008,api:1) [queue] fps:21.70, dur:1013.89, max:103.33, min:10.27
08-19 19:08:27.293   130   321 I SurfaceTextureClient: [STC::queueBuffer] (this:0x406df2e0) fps:34.70, dur:1037.44, max:75.25, min:14.19
08-19 19:08:27.294   130   321 I BufferQueue: [FramebufferSurface](this:0x406de810,api:1) [release] fps:34.70, dur:1037.44, max:75.23, min:14.19
08-19 19:08:27.294   130   321 I BufferQueue: [FramebufferSurface](this:0x406de810,api:1) [queue] fps:34.70, dur:1037.43, max:75.23, min:14.19
08-19 19:08:27.294   130   321 I SurfaceFlinger: [SurfaceFlinger] fps:34.701393,dur:1037.42,max:75.22,min:14.19
08-19 19:08:27.452   465   538 D MDLOGGER: pMDEngine->m_bTerminate is false.  busy for modem = 0,m_nM2ABufCnt = 8
08-19 19:08:27.452   465   538 D MDLOGGER: thrDetectFilter: Detecting catcher_filter.bin exist or not every 5 seconds!
08-19 19:08:27.462  2105  2105 I DEBUG   :     r0 00000000  r1 00000081  r2 00000001  r3 ffffffe8
08-19 19:08:27.462  2105  2105 I DEBUG   :     r4 46d4de60  r5 42671490  r6 40185d35  r7 00100000
08-19 19:08:27.462  2105  2105 I DEBUG   :     r8 45661240  r9 00000000  sl 40dd0159  fp 46c4d930
08-19 19:08:27.462  2105  2105 I DEBUG   :     ip ffffffea  sp 46d4de38  lr 4009915c  pc 44c86948  cpsr 60000010
08-19 19:08:27.462  2105  2105 I DEBUG   : 
08-19 19:08:27.462  2105  2105 I DEBUG   : backtrace:
08-19 19:08:27.462  2105  2105 I DEBUG   :     #00  pc 00042948  <unknown>
08-19 19:08:27.462  2105  2105 I DEBUG   :     #01  pc 0000d158  /system/lib/libc.so
08-19 19:08:27.462  2105  2105 I DEBUG   :     #02  pc 00032954  <unknown>
08-19 19:08:27.462  2105  2105 I DEBUG   : 
08-19 19:08:27.462  2105  2105 I DEBUG   : stack:
08-19 19:08:27.462  2105  2105 I DEBUG   :          46d4ddf8  00000000  
08-19 19:08:27.462  2105  2105 I DEBUG   :          46d4ddfc  00000020  
08-19 19:08:27.462  2105  2105 I DEBUG   :          46d4de00  00000000  
08-19 19:08:27.462  2105  2105 I DEBUG   :          46d4de04  00000000  
08-19 19:08:27.462  2105  2105 I DEBUG   :          46d4de08  00000000  
08-19 19:08:27.462  2105  2105 I DEBUG   :          46d4de0c  46d4de34  [stack:2104]
08-19 19:08:27.462  2105  2105 I DEBUG   :          46d4de10  44c86a04  
08-19 19:08:27.462  2105  2105 I DEBUG   :          46d4de14  46d4de60  [stack:2104]
08-19 19:08:27.462  2105  2105 I DEBUG   :          46d4de18  42671490  
08-19 19:08:27.462  2105  2105 I DEBUG   :          46d4de1c  40185d35  /system/lib/libutils.so
08-19 19:08:27.462  2105  2105 I DEBUG   :          46d4de20  00100000  
08-19 19:08:27.462  2105  2105 I DEBUG   :          46d4de24  45661240  
08-19 19:08:27.462  2105  2105 I DEBUG   :          46d4de28  00000000  
08-19 19:08:27.462  2105  2105 I DEBUG   :          46d4de2c  40dd0159  /system/lib/libstagefright.so
08-19 19:08:27.462  2105  2105 I DEBUG   :          46d4de30  df0027ad  
08-19 19:08:27.462  2105  2105 I DEBUG   :          46d4de34  00000000  
08-19 19:08:27.462  2105  2105 I DEBUG   :     #00  46d4de38  46d4de60  [stack:2104]
08-19 19:08:27.463  2105  2105 I DEBUG   :          ........  ........
08-19 19:08:27.463  2105  2105 I DEBUG   :     #01  46d4de38  46d4de60  [stack:2104]
08-19 19:08:27.463  2105  2105 I DEBUG   :          46d4de3c  46c4d958  
08-19 19:08:27.463  2105  2105 I DEBUG   :     #02  46d4de40  46d4de80  [stack:2104]
08-19 19:08:27.463  2105  2105 I DEBUG   :          46d4de44  40f01d33  /system/lib/libstagefright_omx.so (android::OMXNodeInstance::onMessage(android::omx_message const&)+50)
08-19 19:08:27.463  2105  2105 I DEBUG   :          46d4de48  46c4db48  
08-19 19:08:27.463  2105  2105 I DEBUG   :          46d4de4c  46c4db5c  
08-19 19:08:27.463  2105  2105 I DEBUG   :          46d4de50  46d4de80  [stack:2104]
08-19 19:08:27.463  2105  2105 I DEBUG   :          46d4de54  40f00347  /system/lib/libstagefright_omx.so (android::OMX::CallbackDispatcher::loop()+110)
08-19 19:08:27.463  2105  2105 I DEBUG   :          46d4de58  00000000  
08-19 19:08:27.463  2105  2105 I DEBUG   :          46d4de5c  46c4db50  
08-19 19:08:27.463  2105  2105 I DEBUG   :          46d4de60  00000000  
08-19 19:08:27.463  2105  2105 I DEBUG   :          46d4de64  00000019  
08-19 19:08:27.463  2105  2105 I DEBUG   :          46d4de68  00000000  
08-19 19:08:27.463  2105  2105 I DEBUG   :          46d4de6c  00000000  
08-19 19:08:27.463  2105  2105 I DEBUG   :          46d4de70  00000002  
08-19 19:08:27.463  2105  2105 I DEBUG   :          46d4de74  45def1ac  /system/lib/libMtkOmxVdec.so
08-19 19:08:27.463  2105  2105 I DEBUG   :          46d4de78  44c89f38  
08-19 19:08:27.463  2105  2105 I DEBUG   :          46d4de7c  45dedea1  /system/lib/libMtkOmxVdec.so (MtkOmxVdec::DeInitVideoDecodeHW()+84)
//////////////////////////////////////////////////////////////////////////////////////////////////////

有沒有看起來很眼熟?

關於段錯誤的定義,我百科了一下:

所謂的段錯誤就是指訪問的記憶體超出了系統所給這個程式的記憶體空間,通常這個值是由gd tr來儲存的,他是一個48位的暫存器,其中的32位是儲存由它指向的 gdt表,後13位儲存相應於gdt的下標,最後3位包括了程式是否在記憶體中以及程式的在cpu中的執行級別,指向 的gdt是由以64位為一個單位的表,在這張表中就儲存著程式執行的程式碼段以及資料段的起始地址以及與此相應的段限和頁面交換還有程式執行級別還有記憶體粒度等等的資訊。【不甚理解】

在 Android 的工程當中,有一部分檔案會被編譯成 so 動態共享庫供系統執行時不同模組連結使用。若調用出錯的話,會報出如上面文字中貼出來的段錯誤。

出了這種錯誤,我們一般都會通過 add2line 工具解析具體出錯程式碼的位置,能精確到哪個檔案,哪一行!再去具體位置的程式碼檢視。

若你去找這個檔案的時候卻發現沒有,不必要大驚小怪,MTK沒有提供。在MTK 提供的 Android 的工程程式碼中,經常會出於一些保密方面的考慮,將一些核心檔案編譯成 so 庫提供給客戶。讓客戶看不到這一部分的程式碼,但是能夠正常使用。諸如此類問題,可以提給MTK客服讓其幫忙追查Bug!

1,在MTK的工程中,我們是這樣來解析段錯誤的:

我們將 backtrace 以下的段錯誤 copy 到 trace.txt中。(trace.txt放到工程根目錄下)

然後在linux 環境下面,進到工程根目錄,執行如下命令:

./adbs -sout/target/product/bbk82_wet_jb5/symbols/ -l trace.txt>a.txt

將會解析得到 a.txt 檔案,該檔案中就能追溯到具體哪一個檔案哪一行出錯了。

trace.txt內容:

08-19 19:08:27.462  2105  2105 I DEBUG   : backtrace:
08-19 19:08:27.462  2105  2105 I DEBUG   :     #00  pc 00042948  <unknown>
08-19 19:08:27.462  2105  2105 I DEBUG   :     #01  pc 0000d158  /system/lib/libc.so
08-19 19:08:27.462  2105  2105 I DEBUG   :     #02  pc 00032954  <unknown>

adbs 是一個指令碼檔案,它的作用是呼叫 arm-linux-androideabi-addr2line這個工具來解析 trace.txt 中的內容,並將解析的結果存到一個具體的檔案中去,譬如 a.txt

-s 和 -l 是引數

解析出來的結果 a.txt內容:

08-19 19:08:27.462  2105  2105 I DEBUG   : backtrace:

08-19 19:08:27.462  2105  2105 I DEBUG   : backtrace:
08-19 19:08:27.462  2105  2105 I DEBUG   :     #00  pc 00042948  <unknown>

08-19 19:08:27.462  2105  2105 I DEBUG   :     #00 (unknown)  (unknown) 
08-19 19:08:27.462  2105  2105 I DEBUG   :     #01  pc 0000d158  /system/lib/libc.so

08-19 19:08:27.462  2105  2105 I DEBUG   :     #01 __pthread_cond_pulse  /home/compiler/workspace/gphone/MT6582/PD1224CT/ALPS.JB5.MP.V1.6_WET_20130810_trunk_user/bionic/libc/bionic/pthread.c:1689 
08-19 19:08:27.462  2105  2105 I DEBUG   :     #02  pc 00032954  <unknown>

08-19 19:08:27.462  2105  2105 I DEBUG   :     #02 (unknown)  (unknown) 
08-19 19:08:27.462  2105  2105 I DEBUG   :

adbs 內容:
#!/usr/bin/env python

import os
import re
import string
import sys
import getopt

###############################################################################
# match "#00  pc 0003f52e  /system/lib/libdvm.so" for example
###############################################################################
trace_line = re.compile("(.*)(\#[0-9]+)  (..) ([0-9a-f]{8})  ([^\r\n \t]*)")


class Options(object):pass
OPTIONS = Options()
OPTIONS.symbols = ""
OPTIONS.log = ""

# 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 = "sourcecode/MT6575/ALPS.ICS.MP.V2_W_20120504/out/target/product/bbk75_cu_ics/symbols/"
    if (len(OPTIONS.symbols) == 0):
      path = os.environ['ANDROID_PRODUCT_OUT'] + "/symbols"
    else:
      path = OPTIONS.symbols
  except:
    cmd = "CALLED_FROM_SETUP=true BUILD_SYSTEM=build/core " \
      + "SRC_TARGET_DIR=build/target make -f build/core/config.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 = "./prebuilts/gcc/" + uname + "/arm/arm-linux-androideabi-4.6/bin/"
  addr2line_cmd = prefix + "arm-linux-androideabi-addr2line"

  if (not os.path.exists(addr2line_cmd)):
    try:
      prefix = os.environ['ANDROID_BUILD_TOP'] + "/prebuilt/gcc/" + uname + \
               "/arm/arm-linux-androideabi-4.6/bin/"
    except:
      prefix = "";

    addr2line_cmd = prefix + "arm-linux-androideabi-addr2line"
    if (not os.path.exists(addr2line_cmd)):
      print addr2line_cmd + " not found!"
      sys.exit(1)

  objdump_cmd = prefix + "arm-linux-androideabi-objdump"
  cppfilt_cmd = prefix + "arm-linux-androideabi-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[:]) + " "

###############################################################################

COMMON_DOCSTRING = """
 -s (--symbols) <symbols dir>
 -l (--log) <logcat file>

eg:
   adbs -s out/target/product/bbk75_cu_ics/symbols/ -l logcat.txt
 or
  adbs -s out/target/product/bbk75_cu_ics/symbols/ logcat
"""

def Usage():
  print COMMON_DOCSTRING

if __name__ == '__main__':

  try:
    opts, args = getopt.getopt(sys.argv[1:], "hs:l:", 
                  ["help", "symbols=", "log="])
  except getopt.GetOptError:
    Usage()

  for o, a in opts:
    if o in ("-h", "--help"):
       Usage()
       sys.exit()
    elif o in ("-s", "--symbols"):
       OPTIONS.symbols = a
    elif o in ("-l", "--log="):
       OPTIONS.log = a

  # pass the options to adb
  #adb_cmd  = "adb " + ' '.join(sys.argv[1:])
  if (len(OPTIONS.log) == 0):
    adb_cmd = "adb " + ' '.join(args)
  else:
    adb_cmd = "cat " + OPTIONS.log

  # 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()
    print line
    # 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()

通過分析,我們最終找到錯誤發生於 /home/compiler/workspace/gphone/MT6582/project_name/ALPS.JB5.MP.V1.6_WET_20130810_trunk_user/bionic/libc/bionic/pthread.c:1689

實際上libc.so 出錯對我們的幫助不大,需要找更上層的出錯的地方。或者通過log去分析app層的錯誤。

注意:

需要注意的是symbols目錄需要與 bug對應的版本號編譯出來的那個symbols。直接用本地目錄下的 symbols 解析出來的會與trace中的錯誤不一致!如果沒有相應的 symbols 目錄,那麼我們需要自己重新編譯一個軟體出來復現問題,再從頭來分析該段錯誤並解析。 symbols 下面的庫會多出來一些除錯資訊。

2,高通平臺下解析段錯誤就很簡單了。

同樣需要對應出問題版本下編譯出來的 symbols目錄解壓覆蓋原來的 out/product/project_name/symbols 目錄。通過如下方式來實現解析!

arm-eabi-addr2line-fe ./out-PD1225TMA/target/product/msm8960/symbols/system/lib/libc.so 0000e498

-fe 引數 libc.so 出問題的庫, 0000e498 函式地址。

參考資料: