摘抄自:http://zhidao.baidu.com/link?url=gOXPCxE4HSq9GOGH0QGNS5zLXUWkeeHrmWpD_W3DrUllNLgJF7OGV4RCAEQGDGQlQv6J_8b-7zXNBDtUnAzUr1vw5UCBmC16i8oMhgkrO8e Jave层的代码发生crash问题时,系统往往会打印出很详细的出错信息。比如上面这个例子,不但给出了出错的原因,还有出错的文件和行数。根据这些信息,我们会很容易的定位问题所在。native层的crash虽然也有栈log信息输出,但是就不那么容易看懂了。下面我们再看一个native层crash的例子: F/libc ( 2102): Fatal signal 11 (SIGSEGV) at 0x00000000 (code=1), thread2102 (testapp) D/dalvikvm(26630):GC_FOR_ALLOC freed 604K, 11% free 11980K/13368K, paused 36ms, total36ms I/dalvikvm-heap(26630):Grow heap (frag case) to 11.831MB for 102416-byteallocation D/dalvikvm(26630):GC_FOR_ALLOC freed 1K, 11% free 12078K/13472K, paused 34ms, total34ms I/DEBUG ( 127):*** *** *** *** *** *** *** *** *** *** *** *** *** *** ****** I/DEBUG ( 127):Build fingerprint: 'Android/full_maguro/maguro:4.2.2/JDQ39/eng.liuchao.20130619.201255:userdebug/test-keys' I/DEBUG ( 127):Revision: '9' I/DEBUG ( 127):pid: 2102, tid: 2102, name: testapp >>>./testapp <<< I/DEBUG ( 127):signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr00000000 I/DEBUG ( 127): r0 00000020 r173696874 r2 400ff520 r300000000 I/DEBUG ( 127): r4 400ff469 r5beb4ab24 r6 00000001 r7beb4ab2c I/DEBUG ( 127): r8 00000000 r900000000 sl 00000000 fpbeb4ab1c I/DEBUG ( 127): ip 4009b5dc spbeb4aae8 lr 400ff46f pc400ff45e cpsr 60000030 I/DEBUG ( 127): d0 000000004108dae8 d1 4108ced84108cec8 I/DEBUG ( 127): d2 4108cef84108cee8 d3 4108cf184108cf08 I/DEBUG ( 127): d4 4108c5a84108c598 d5 4108ca084108c5b8 I/DEBUG ( 127): d6 4108ce684108ce58 d7 4108ce884108ce78 I/DEBUG ( 127): d8 0000000000000000 d9 0000000000000000 I/DEBUG ( 127): d10 0000000000000000 d110000000000000000 I/DEBUG ( 127): d120000000000000000 d130000000000000000 I/DEBUG ( 127): d14 0000000000000000 d150000000000000000 I/DEBUG ( 127): d16 c1dcf7c087fec8b4 d173f50624dd2f1a9fc I/DEBUG ( 127): d18 41c7b1ac89800000 d190000000000000000 I/DEBUG ( 127): d20 0000000000000000 d210000000000000000 I/DEBUG ( 127): d22 0000000000000000 d230000000000000000 I/DEBUG ( 127): d24 0000000000000000 d250000000000000000 I/DEBUG ( 127): d26 0000000000000000 d270000000000000000 I/DEBUG ( 127): d28 0000000000000000 d290000000000000000 I/DEBUG ( 127): d30 0000000000000000 d310000000000000000 I/DEBUG ( 127): scr 00000010 I/DEBUG ( 127): I/DEBUG ( 127):backtrace: I/DEBUG ( 127): #00 pc0000045e /system/bin/testapp I/DEBUG ( 127): #01 pc0000046b /system/bin/testapp I/DEBUG ( 127): #02 pc0001271f /system/lib/libc.so (__libc_init+38) I/DEBUG ( 127): #03 pc00000400 /system/bin/testapp I/DEBUG ( 127): I/DEBUG ( 127):stack: I/DEBUG ( 127): beb4aaa8 000000c8 I/DEBUG ( 127): beb4aaac 00000000 I/DEBUG ( 127): beb4aab0 00000000 I/DEBUG ( 127): beb4aab4 401cbee0 /system/bin/linker I/DEBUG ( 127): beb4aab8 00001000 I/DEBUG ( 127): beb4aabc 4020191d /system/lib/libc.so (__libc_fini) I/DEBUG ( 127): beb4aac0 4020191d /system/lib/libc.so (__libc_fini) I/DEBUG ( 127): beb4aac4 40100eac /system/bin/testapp I/DEBUG ( 127): beb4aac8 00000000 I/DEBUG ( 127): beb4aacc 400ff469 /system/bin/testapp I/DEBUG ( 127): beb4aad0 beb4ab24 [stack] I/DEBUG ( 127): beb4aad4 00000001 I/DEBUG ( 127): beb4aad8 beb4ab2c [stack] I/DEBUG ( 127): beb4aadc 00000000 I/DEBUG ( 127): beb4aae0 df0027ad I/DEBUG ( 127): beb4aae4 00000000 I/DEBUG ( 127): #00 beb4aae8 00000000 I/DEBUG ( 127): ........ ........ I/DEBUG ( 127): #01 beb4aae8 00000000 I/DEBUG ( 127): beb4aaec 401e9721 /system/lib/libc.so (__libc_init+40) I/DEBUG ( 127): #02 beb4aaf0 beb4ab08 [stack] I/DEBUG ( 127): beb4aaf4 00000000 I/DEBUG ( 127): beb4aaf8 00000000 I/DEBUG ( 127): beb4aafc 00000000 I/DEBUG ( 127): beb4ab00 00000000 I/DEBUG ( 127): beb4ab04 400ff404 /system/bin/testapp I/DEBUG ( 127): 这个log就不那么容易懂了,但是还是能从中看出很多信息,让我们一起来学习如何分析这种log。首先看下面这行: pid: 2102, tid: 2102,name: testapp >>>./testapp <<< 从这一行我们可以知道crash进程的pid和tid,前文我们已经提到过,Android调用gettid函数得到的实际是进程Id号,所以这里的pid和tid相同。知道进程号后我们可以往前翻翻log,看看该进程最后一次打印的log是什么,这样能缩小一点范围。 接下来内容是进程名和启动参数。再接下来的一行比较重要了,它告诉了我们从系统角度看,出错的原因: signal 11 (SIGSEGV), code 1(SEGV_MAPERR), fault addr 00000000 signal11是Linux定义的信号之一,含义是Invalidmemory reference,无效的内存引用。加上后面的“faultaddr 00000000”我们基本可以判定这是一个空指针导致的crash。当然这是笔者为了讲解而特地制造的一个Crash的例子,比较容易判断,大部分实际的例子可能就没有那么容易了。 再接下来的log打印出了cpu的所有寄存器的信息和堆栈的信息,这里面最重要的是从堆栈中得到的backtrace信息: I/DEBUG ( 127):backtrace: I/DEBUG ( 127): #00 pc0000045e /system/bin/testapp I/DEBUG ( 127): #01 pc0000046b /system/bin/testapp I/DEBUG ( 127): #02 pc0001271f /system/lib/libc.so (__libc_init+38) I/DEBUG ( 127): #03 pc00000400 /system/bin/testapp 因为实际的运行系统里没有符号信息,所以打印出的log里看不出文件名和行数。这就需要我们借助编译时留下的符号信息表来翻译了。Android提供了一个工具可以来做这种翻译工作:arm-eabi-addr2line,位于prebuilts/gcc/linux-x86/arm/arm-eabi-4.6/bin目录下。用法很简单: #./arm-eabi-addr2line -f -eout/target/product/hammerhead/symbols/system/bin/testapp0x0000045e 参数-f表示打印函数名;参数-e表示带符号表的模块路径;最后是要转换的地址。这条命令在笔者的编译环境中得到的结果是: memcpy /home/rd/compile/android-4.4_r1.2/bionic/libc/include/string.h:108 剩余三个地址翻译如下: main /home/rd/compile/android-4.4_r1.2/packages/apps/testapp/app_main.cpp:38 out_vformat /home/rd/compile/android-4.4_r1.2/bionic/libc/bionic/libc_logging.cpp:361 _start libgcc2.c:0 利用这些信息我们很快就能定位问题了。不过这样手动一条一条的翻译比较麻烦,笔者使用的是从网上找到的一个脚本,可以一次翻译所有的行,有需要的读者可以在网上找一找。 了解了如何分析普通的Log文件。这两种方法都不是我发明了,都是网上一些高手公共出来的调试方法,无奈找不到出处的地方了,所以就在此总结一下,以方便android下的调试:
简要说明:
android系统中调试Java非常容易,一般遇到错误都在logcat中打印出错时函数的调用关系, 而C库中出错时只看到一些二进制信息,使用gdbserver调试环境搭建又比较复杂。
方法一: 下在介绍一个简单的调试库的方法,当然需要有so库的源代码 举例 a) 错误信息如下,它表示了出错时的函数调用关系(下面调上面的)
I/DEBUG ( 634): #00 pc 000078e6 /system/lib/libmultiplayerservice.so I/DEBUG ( 634): #01 pc 000087bc /system/lib/libmultiplayerservice.so I/DEBUG ( 634): #02 pc 0000e94e /system/lib/libsensorservice.so I/DEBUG ( 634): #03 pc 0000a790 /system/lib/libsensorservice.so I/DEBUG ( 634): #04 pc 0000d4b2 /system/lib/libsensorservice.so I/DEBUG ( 634): #05 pc 0000d852 /system/lib/libsensorservice.so I/DEBUG ( 634): #06 pc 00015ece /system/lib/libutils.so I/DEBUG ( 634): #07 pc 0000153a /system/lib/libsystem_server.so I/DEBUG ( 634): #08 pc 00001756 /system/lib/libsystem_server.so I/DEBUG ( 634): #09 pc 0000adb8 /system/lib/libandroid_servers.so I/DEBUG ( 634): #10 pc 00011cb4 /system/lib/libdvm.so
b)进入源码中带符号表的so库所在目录 $ cd out/target/product/generic/obj/SHARED_LIBRARIES/libmultiplayerservice_intermediates/LINKED
这个有个需要注意的地方:
对于可执行程序及动态库,一般在LINKED子目录中是带有符号的库(没有经过符号剥离),如果可执行文件中没有包括调试符号,您将获得??:0 作为响应。
c)用addr2line命令找到地址对应的程序位置,动态库为libmultiplayerservice.so arm-eabi-addr2line 000078e6 -e libmultiplayerservice.so 结果:,显示出对应的程序文件和行数,如果不是debug版本,可能有一两行偏差 frameworks/base/services/multiplayerservice/PlayerSocket.cpp 421 行 d)注意 arm-eabi_addr2line在prebuild/linux-x86/toolchain/arm-eabi-xxx/bin目录下, 运行build/envsetup.sh后即可直接使用它,同目录下的objdump, nm也是常用调试命令
方法二:
1、首先需要一个重要的脚本文件:
#!/usr/bin/python # stack symbol parser import os import string import sys #define android product name ANDROID_PRODUCT_NAME = 'generic' ANDROID_WORKSPACE = os.getcwd()+"/" # addr2line tool path and symbol path addr2line_tool = ANDROID_WORKSPACE + 'prebuilt/linux-x86/toolchain/arm-eabi-4.2.1/bin/arm-eabi-addr2line' symbol_dir = ANDROID_WORKSPACE + 'out/target/product/' + ANDROID_PRODUCT_NAME +'/symbols' symbol_bin = symbol_dir + '/system/bin/' symbol_lib = symbol_dir + '/system/lib/' class ReadLog: def __init__(self,filename): self.logname = filename def parse(self): f = file(self.logname,'r') lines = f.readlines() if lines != []: print 'read file ok' else: print 'read file failed' result =[] for line in lines: if line.find('stack') != -1: print 'stop search' break elif line.find('system') != -1: #print 'find one item' + line result.append(line) return result class ParseContent: def __init__(self,addr,lib): self.address = addr # pc address self.exename = lib # executable or shared library def addr2line(self): cmd = addr2line_tool + " -C -f -s -e " + symbol_dir + self.exename + " " + self.address #print cmd stream = os.popen(cmd) lines = stream.readlines(); list = map(string.strip,lines) return list inputarg = sys.argv if len(inputarg) < 2: print 'Please input panic log' exit() filename = inputarg[1] readlog = ReadLog(filename) inputlist = readlog.parse() for item in inputlist: itemsplit = item.split() test = ParseContent(itemsplit[-2],itemsplit[-1]) list = test.addr2line() print "%-30s%s" % (list[1],list[0])
将以上文件保存hy.panic.py
注意脚本里面两个地方: 1.脚本里面的ANDROID_PRODUCT_NAME = 'generic' 必须和源码生成路径对应。 2.编译环境工具也必须对应上# addr2line tool path and symbol path。
2、相关的死机堆栈信息保存 error.txt
例如:
I/DEBUG ( 634): #00 pc 000078e6 /system/lib/libmultiplayerservice.so I/DEBUG ( 634): #01 pc 000087bc /system/lib/libmultiplayerservice.so I/DEBUG ( 634): #02 pc 0000e94e /system/lib/libsensorservice.so I/DEBUG ( 634): #03 pc 0000a790 /system/lib/libsensorservice.so I/DEBUG ( 634): #04 pc 0000d4b2 /system/lib/libsensorservice.so I/DEBUG ( 634): #05 pc 0000d852 /system/lib/libsensorservice.so I/DEBUG ( 634): #06 pc 00015ece /system/lib/libutils.so I/DEBUG ( 634): #07 pc 0000153a /system/lib/libsystem_server.so I/DEBUG ( 634): #08 pc 00001756 /system/lib/libsystem_server.so I/DEBUG ( 634): #09 pc 0000adb8 /system/lib/libandroid_servers.so I/DEBUG ( 634): #10 pc 00011cb4 /system/lib/libdvm.so
3、将以上两个文件拷贝到android的编译根路径下面,执行
python hy.panic.py error.txt
方法2使用非常方便,相比于加打印效率大大提高。非常感谢提供脚本的同学。
http://blog.csdn.net/andyhuabing/article/details/7074979(我用这个脚本,好像不好使) http://blog.csdn.net/eustoma/article/details/6449156