Android native crash 日志分析

14 篇文章 0 订阅
14 篇文章 0 订阅

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来获取代码位置的。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是一些分析 Android 崩溃的步骤案例: 步骤一:获取崩溃日志 获取崩溃日志是分析 Android 崩溃的第一步。您可以在开发者选项中启用 USB 调试,并使用 ADB 工具连接到设备,然后使用以下命令获取崩溃日志: ``` adb logcat -d > crash_log.txt ``` 步骤二:查看崩溃日志 崩溃日志包含有关崩溃的详细信息,例如异常类型、堆栈跟踪和线程信息。您可以使用文本编辑器或日志查看器查看崩溃日志并查找关键信息。以下是一个示例崩溃日志: ``` FATAL EXCEPTION: main Process: com.example.app, PID: 12345 java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String com.example.app.User.getName()' on a null object reference at com.example.app.MainActivity.onCreate(MainActivity.java:25) at android.app.Activity.performCreate(Activity.java:6975) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1213) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2770) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2892) at android.app.ActivityThread.-wrap11(Unknown Source:0) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1593) at android.os.Handler.dispatchMessage(Handler.java:105) at android.os.Looper.loop(Looper.java:164) at android.app.ActivityThread.main(ActivityThread.java:6540) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767) ``` 步骤三:确定异常类型和位置 崩溃日志中的第一部分(例如“java.lang.NullPointerException”)指示发生的异常类型。异常类型有助于确定崩溃的原因和所需的修复操作。接下来,您需要查看堆栈跟踪以确定崩溃的位置。在上面的示例中,堆栈跟踪指示在 MainActivity.java 的第 25 发生了空指针异常。 步骤四:分析代码 一旦您确定了崩溃的位置,您需要分析代码以找出问题的根本原因。在上面的示例中,问题可能是在 MainActivity.java 中使用了空对象。您可以检查代码并添加必要的 Null 检查,例如: ```java User user = getUser(); if (user != null) { String name = user.getName(); // do something with name } ``` 步骤五:测试修复 一旦您修改了代码并重新编译应用程序,您需要进测试以确保崩溃已解决。您可以重新安装应用程序并尝试重现崩溃。如果崩溃没有再次发生,则修复已成功。如果崩溃仍然存在,则需要进一步分析和修复。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值