Android native crash 日志分析

NDK的常见异常

NDK编译生成的.so文件作为程序的一部分,在运行发生异常时同样会造成程序崩溃。不同于Java代码异常造成的程序崩溃,在NDK的异常发生时,程序在Android设备上都会立即退出,即通常所说的闪退,而不会弹出“程序xxx无响应,是否立即关闭”之类的提示框。

NDK是使用C/ Cpp来进行开发的,熟悉C/Cpp的程序员都知道,指针和内存管理是最重要也是最容易出问题的地方,稍有不慎就会遇到诸如内存无效访问、无效对象、内存泄露、堆栈溢出等常见的问题,最后都是同一个结果:程序崩溃。例如我们常说的空指针错误,就是当一个内存指针被置为空(NULL)之后再次对其进行访问;另外一个经常出现的错误是,在程序的某个位置释放了某个内存空间,而后在程序的其他位置试图访问该内存地址,这就会产生一个无效地址错误。常见的错误类型如下:

  • 初始化错误
  • 访问错误
  • 数组索引访问越界
  • 指针对象访问越界
  • 访问空指针对象
  • 访问无效指针对象
  • 迭代器访问越界
  • 内存泄露
  • 参数错误
  • 堆栈溢出
  • 类型转换错误
  • 数字除0错误

NDK 异常如何分析

如下图所示:在JNI_OnLoad()的函数中,即so加载时,调用willCrash()函数,crash()函数中,string的这种赋值方法会产生一个空指针错误。这样,在hello-jni程序加载时就会闪退。我们记一下这两个行数:在61行调用了willCrash()函数;在69行发生了崩溃。

-60     JNIEXPORT jint JNI_OnLoad(javaVM* jvm, void *reserved){
-61         crash();
-62         return JNI_VERSION_1_4;
        }
        
-68     void crash(){
-69         string str = NULL;
-70     }

发生崩溃时的日志:

*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***  
 Build fingerprint: 'vivo/bbk89_cmcc_jb2/bbk89_cmcc_jb2:4.2.1/JOP40D/1372668680:user/test-keys'  
 pid: 32607, tid: 32607, name: xample.hellojni  >>> com.example.hellojni <<<  
 signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000000  
     r0 00000000  r1 beb123a8  r2 80808080  r3 00000000  
     r4 5d635f68  r5 5cdc3198  r6 41efcb18  r7 5d62df44  
     r8 4121b0c0  r9 00000001  sl 00000000  fp beb1238c  
     ip 5d635f7c  sp beb12380  lr 5d62ddec  pc 400e7438  cpsr 60000010  
   
 backtrace:  
     #00  pc 00023438  /system/lib/libc.so   
     #01  pc 00004de8  /data/app-lib/com.example.hellojni-2/libhello-jni.so  
     #02  pc 000056c8  /data/app-lib/com.example.hellojni-2/libhello-jni.so  
     #03  pc 00004fb4  /data/app-lib/com.example.hellojni-2/libhello-jni.so  
     #04  pc 00004f58  /data/app-lib/com.example.hellojni-2/libhello-jni.so  
     #05  pc 000505b9  /system/lib/libdvm.so  
     #06  pc 00068005  /system/lib/libdvm.so  

针对如上日志,是无法直接分析的,需要借助如下两种工具。
在使用任何一种工具之前,我们均需要动态库(.so)的符号表文件,比如这个例子中"libhello-jni.so". 符号表的动态库文件在Android studio中的位置(采用CMakeLists.txt的编译方式):

app/build/intermediates/cmake/debug(release)/obj/{ANDROID_ABI}/

ndk-stack

这个命令行工具包含在NDK工具的安装目录,和ndk-build和其他一些常用的NDK命令放在一起,比如在我的电脑上,其位置是/android-ndk-r9d/ndk-stack。根据Google官方文档,NDK从r6版本开始提供ndk-stack支持。

使用ndk-stack实时分析

adb shell logcat | ndk-stack -sym app/build/intermediates/cmake/debug(release)/obj/{ANDROID_ABI}/

此时的输出日志将会是:

********** Crash dump: **********  
Build fingerprint: 'vivo/bbk89_cmcc_jb2/bbk89_cmcc_jb2:4.2.1/JOP40D/1372668680:user/test-keys'  
pid: 32607, tid: 32607, name: xample.hellojni  >>> com.example.hellojni <<<  
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000000  
Stack frame #03  pc 00004fb4  /data/app-lib/com.example.hellojni-2/libhello-jni.so (willCrash()+68): Routine willCrash() at /home/testin/hello-jni/jni/hello-jni.cpp:69  
Stack frame #04  pc 00004f58  /data/app-lib/com.example.hellojni-2/libhello-jni.so (JNI_OnLoad+20): Routine JNI_OnLoad at /home/testin/hello-jni/jni/hello-jni.cpp:61  

可以看到,将会打印出具体出错的行数。

使用ndk-stack对日志分析

有时候我们无法复现问题时,则需要对crash的日志进行分析,假设保存下来的日志为 log_crash.txt
那么我们就可以采用如下命令结合符号表库文件进行定位:

dk-stack -sym app/build/intermediates/cmake/debug(release)/obj/{ANDROID_ABI}/ –dump log_crash.txt 

我们也会得到相似的结果。

addr2line 日志分析

addr2line实际上就是ndk-stack 的具体实现方式,同样可以在ndk的工具包中找到,需要针对自己机器的架构特点选择对应的执行程序。
在楼主的机器上,可以在如下路径找到:

C:/Users/jiache/AppData/Local/Android/Sdk/ndk-bundle/toolchains/x86_64-4.9/prebuilt/windows-x86_64/bin/x86_64-linux-android-addr2line.exe

使用该工具的步骤:

  • 根据back-trace的日志,找到so崩溃的具体地址,仍然以上述为例:
     #01  pc 00004de8  /data/app-lib/com.example.hellojni-2/libhello-jni.so  
     #02  pc 000056c8  /data/app-lib/com.example.hellojni-2/libhello-jni.so  
     #03  pc 00004fb4  /data/app-lib/com.example.hellojni-2/libhello-jni.so  
     #04  pc 00004f58  /data/app-lib/com.example.hellojni-2/libhello-jni.so  
  • 对于出错的地址,我们结合符号表即可以定位出具体的行号:
x86_64-linux-android-addr2line.exe -e app/build/intermediates/cmake/debug(release)/obj/{ANDROID_ABI}/libhello-jni.so 00004de8 000056c8 00004fb4 00004f58    
  • 不同于ndk-stack,addr2line需要制定具体的符号表文件,多个待解析的地址可以连续添加在行尾,执行完成将会得到如下结果:
/android-ndk-r9d/sources/cxx-stl/stlport/stlport/stl/char_traits.h:229  
/android-ndk-r9d/sources/cxx-stl/stlport/stlport/stl/_string.c:639  
/WordSpaces/hello-jni/jni/hello-jni.cpp:69  
/WordSpaces hello-jni/jni/hello-jni.cpp:61  

从addr2line的结果就能看到,我们拿到了我们自己的错误代码的调用关系和行数,在hello-jni.cpp的69行和61行(另外两行因为使用的是标准函数,可以忽略掉),结果和ndk-stack是一致的,说明ndk-stack也是通过addr2line来获取代码位置的。

你可能感兴趣的:(Android,java,ndk,工具开发)