在Android开发中,程序Crash分三种情况:未捕获的异常、ANR(Application Not Responding)和闪退(NDK引发错误)。其中未捕获的异常根据logcat打印的堆栈信息很容易定位错误。ANR错误也好查,Android规定,应用与用户进行交互时,如果5秒内没有响应用户的操作,则会引发ANR错误,并弹出一个系统提示框,让用户选择继续等待或立即关闭程序。并会在/data/anr目录下生成一个traces.txt文件,记录系统产生anr异常的堆栈和线程信息。如果是闪退,这问题比较难查,通常是项目中用到了NDK引发某类致命的错误导致闪退。因为NDK是使用C/C++来进行开发,熟悉C/C++的程序员都知道,指针和内存管理是最重要也是最容易出问题的地方,稍有不慎就会遇到诸如内存地址访问错误、使用野针对、内存泄露、堆栈溢出、初始化错误、类型转换错误、数字除0等常见的问题,导致最后都是同一个结果:程序崩溃。不会像在Java层产生的异常时弹出“xxx程序无响应,是否立即关闭”之类的提示框。当发生NDK错误后,logcat打印出来的那堆日志根据看不懂,更别想从日志当中定位错误的根源。当时尝试过在各个jni函数中打印日志来跟踪问题,那效率实在是太低了,而且还定位不到问题。后来找到了NDK提供的几款调试工具,能够精确的定位到产生错误的根源。

NDK安装包中提供了三个调试工具:addr2line、objdump和ndk-stack,其中ndk-stack放在$NDK_HOME目录下,与ndk-build同级目录。addr2line和objdump在ndk的交叉编译器工具链目录下,下面是我本机NDK交叉编译器工具链的目录结构:

从上图的目录结构中可以看出来,NDK针对不同的CPU架构实现了多套相同的工具。所以在选择addr2line和objdump工具的时候,要根据你目标机器的CPU架构来选择。如果是arm架构,选择arm-linux-androidabi-4.6/4.8(一般选择高版本)。x86架构,选择x86-4.6/4.8。mipsel架构,选择mipsel-linux-android-4.6/4.8。如果不知道目标机器的CPU架构,把手机连上电脑,用adb shell cat /proc/cpuinfo可以查看手机的CPU信息。

下图是我本机的arm架构工具链目录结构:

下面通过NDK自带的例子hello-jni项目来演示一下如何精确的定位错误:

#include <string.h>
#include <jni.h>
// hell-jni.c
#ifdef __cplusplus
extern "C" {
#endif  void willCrash()  {  int i = 10;  int y = i / 0;  }  JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)  {  willCrash();  return JNI_VERSION_1_4;  }  jstring  Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,  jobject thiz )  {  // 此处省略实现逻辑。。。  }  #ifdef __cplusplus
}
#endif  

第7行定义了一个willCrash函数,函数中有一个除0的非法操作,会造成程序崩溃。第13行JNI_OnLoad函数中调用了willCrash,这个函数会在Java加载完.so文件之后回调,也就是说程序一启动就会崩溃。下面是运行程序后打印的log:

01-01 17:59:38.246: D/dalvikvm(20794): Late-enabling CheckJNI
01-01 17:59:38.246: I/ActivityManager(1185):
Start proc com.example.hellojni for activity com.example.hellojni/.HelloJni: pid=20794 uid=10351 gids={50351, 1028, 1015}
01-01 17:59:38.296: I/dalvikvm(20794): Enabling JNI app bug workarounds for target SDK version 3...
01-01 17:59:38.366: D/dalvikvm(20794): Trying to load lib /data/app-lib/com.example.hellojni-1/libhello-jni.so 0x422a4f58
01-01 17:59:38.366: D/dalvikvm(20794): Added shared lib /data/app-lib/com.example.hellojni-1/libhello-jni.so 0x422a4f58
01-01 17:59:38.366: A/libc(20794): Fatal signal 8 (SIGFPE) at 0x0000513a (code=-6), thread 20794 (xample.hellojni)
01-01 17:59:38.476: I/DEBUG(253): pid: 20794, tid: 20794, name: xample.hellojni  >>> com.example.hellojni <<<
01-01 17:59:38.476: I/DEBUG(253): signal 8 (SIGFPE), code -6 (SI_TKILL), fault addr 0000513a
01-01 17:59:38.586: I/DEBUG(253):     r0 00000000  r1 0000513a  r2 00000008  r3 00000000
01-01 17:59:38.586: I/DEBUG(253):     r4 00000008  r5 0000000d  r6 0000513a  r7 0000010c
01-01 17:59:38.586: I/DEBUG(253):     r8 75226d08  r9 00000000  sl 417c5c38  fp bedbf134
01-01 17:59:38.586: I/DEBUG(253):     ip 41705910  sp bedbf0f0  lr 4012e169  pc 4013d10c  cpsr 000f0010  // 省略部份日志 。。。。。。
01-01 17:59:38.596: I/DEBUG(253): backtrace:
01-01 17:59:38.596: I/DEBUG(253):     #00  pc 0002210c  /system/lib/libc.so (tgkill+12)
01-01 17:59:38.596: I/DEBUG(253):     #01  pc 00013165  /system/lib/libc.so (pthread_kill+48)
01-01 17:59:38.596: I/DEBUG(253):     #02  pc 00013379  /system/lib/libc.so (raise+10)
01-01 17:59:38.596: I/DEBUG(253):     #03  pc 00000e80  /data/app-lib/com.example.hellojni-1/libhello-jni.so (__aeabi_idiv0+8)
01-01 17:59:38.596: I/DEBUG(253):     #04  pc 00000cf4  /data/app-lib/com.example.hellojni-1/libhello-jni.so (willCrash+32)
01-01 17:59:38.596: I/DEBUG(253):     #05  pc 00000d1c  /data/app-lib/com.example.hellojni-1/libhello-jni.so (JNI_OnLoad+20)
01-01 17:59:38.596: I/DEBUG(253):     #06  pc 00052eb1  /system/lib/libdvm.so (dvmLoadNativeCode(char const*, Object*, char**)+468)
01-01 17:59:38.596: I/DEBUG(253):     #07  pc 0006a62d  /system/lib/libdvm.so
01-01 17:59:38.596: I/DEBUG(253):          // 省略部份日志 。。。。。。
01-01 17:59:38.596: I/DEBUG(253): stack:
01-01 17:59:38.596: I/DEBUG(253):          bedbf0b0  71b17034  /system/lib/libsechook.so
01-01 17:59:38.596: I/DEBUG(253):          bedbf0b4  7521ce28
01-01 17:59:38.596: I/DEBUG(253):          bedbf0b8  71b17030  /system/lib/libsechook.so
01-01 17:59:38.596: I/DEBUG(253):          bedbf0bc  4012c3cf  /system/lib/libc.so (dlfree+50)
01-01 17:59:38.596: I/DEBUG(253):          bedbf0c0  40165000  /system/lib/libc.so
01-01 17:59:38.596: I/DEBUG(253):          // 省略部份日志 。。。。。。
01-01 17:59:38.736: W/ActivityManager(1185):   Force finishing activity com.example.hellojni/.HelloJni  

日志分析:

第3行开始启动应用,第5行尝试加载应用数据目录下的so,第6行在加载so文件的时候产生了一个致命的错误,第7行的Fatal signal 8提示这是一个致命的错误,这个信号是由linux内核发出来的,信号8的意思是浮点数运算异常,应该是在willCrash函数中做除0操作所产生的。下面重点看第15行backtrace的日志,backtrace日志可以看作是JNI调用的堆栈信息,以“#两位数字 pc”开头的都是backtrace日志。注意看第20行和21行,是我们自己编译的so文件和定义的两个函数,在这里引发了异常,导致程序崩溃。

01-01 17:59:38.596: I/DEBUG(253):     #04  pc 00000cf4  /data/app-lib/com.example.hellojni-1/libhello-jni.so (willCrash+32)
01-01 17:59:38.596: I/DEBUG(253):     #05  pc 00000d1c  /data/app-lib/com.example.hellojni-1/libhello-jni.so (JNI_OnLoad+20)  

开始有些眉目了,但具体崩在这两个函数的哪个位置,我们是不确定的,如果函数代码比较少还好查,如果比较复杂的话,查起来也费劲。这时候就需要靠NDK为我们提供的工具来精确定位了。在这之前,我们先记录下让程序崩溃的汇编指令地址,willCrash:00000cf4,JNI_OnLoad:00000d1c

(ps:对于可执行程序及动态库,一般在LINKED子目录中是带有符号的库(没有经过符号剥离,如混淆等),如果可执行文件中没有包括调试符号,您将获得??:0 作为响应。

方式1:使用arm-linux-androideabi-addr2line 定位出错位置

以arm架构的CPU为例,执行如下命令:

/Users/yangxin/Documents/devToos/java/android-ndk-r9d/toolchains/arm-linux-androideabi-4.8/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-addr2line -e /Users/yangxin/Documents/devToos/java/android-ndk-r9d/samples/hello-jni/obj/local/armeabi-v7a/libhello-jni.so 00000cf4 00000d1c  

-e:指定so文件路径

0000cf4 0000d1c:出错的汇编指令地址

结果如下:

是不是惊喜的看到我们想要的结果了,分别在hello-jni.c的10和15行的出的错,再回去看看hello-jni.c的源码,15行的Jni_OnLoad函内调用了willCrash函数,第10行做了除0的操作引发的crash。

方式2:使用arm-linux-androideabi-objdump 定位出错的函数信息

在第一种方式中,通过addr2lin已经获取到了代码出错的位置,但是不知道函数的上下文信息,显得有点不是那么的“完美”,对于追求极致的我来说,这显然是不够的,下面我们来看一下怎么来定位函数的信息。
首先使用如下命令导出so的函数表信息:

[cpp] view plain copy
/Users/yangxin/Documents/devToos/java/android-ndk-r9d/toolchains/arm-linux-androideabi-4.8/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-objdump -S -D /Users/yangxin/Documents/devToos/java/android-ndk-r9d/samples/hello-jni/obj/local/armeabi-v7a/libhello-jni.so > Users/yangxin/Desktop/dump.log  

在生成的asm文件中,找出我们开始定位到的那两个出错的汇编指令地址(在文件中搜索cf4或willCrash可以找到),如下图所示:

通过这种方式,也可以查出这两个出错的指针地址分别位于哪个函数中。

方式3:ndk-stack

如果你觉得上面的方法太麻烦的话,ndk-stack可以帮你减轻操作步聚,直接定位到代码出错的位置。

实时分析日志:

使用adb获取logcat的日志,并通过管道输出给ndk-stack分析,并指定包含符号表的so文件位置。如果程序包含多种CPU架构,需要根据手机的CPU类型,来选择不同的CPU架构目录。以armv7架构为例,执行如下命令:

adb logcat | ndk-stack -sym /Users/yangxin/Documents/devToos/java/android-ndk-r9d/samples/hello-jni/obj/local/armeabi-v7a 

当程序发生crash时,会输出如下信息:

pid: 22654, tid: 22654, name: xample.hellojni  >>> com.example.hellojni <<<
signal 8 (SIGFPE), code -6 (SI_TKILL), fault addr 0000587e
Stack frame #00  pc 0002210c  /system/lib/libc.so (tgkill+12)
Stack frame #01  pc 00013165  /system/lib/libc.so (pthread_kill+48)
Stack frame #02  pc 00013379  /system/lib/libc.so (raise+10)
Stack frame #03  pc 00000e80  /data/app-lib/com.example.hellojni-1/libhello-jni.so (__aeabi_idiv0+8): Routine __aeabi_idiv0 at /s/ndk-toolchain/src/build/../gcc/gcc-4.6/libgcc/../gcc/config/arm/lib1funcs.asm:1270
Stack frame #04  pc 00000cf4  /data/app-lib/com.example.hellojni-1/libhello-jni.so (willCrash+32): Routine willCrash at /Users/yangxin/Documents/devToos/java/android-ndk-r9d/samples/hello-jni/jni/hello-jni.c:10
Stack frame #05  pc 00000d1c  /data/app-lib/com.example.hellojni-1/libhello-jni.so (JNI_OnLoad+20): Routine JNI_OnLoad at /Users/yangxin/Documents/devToos/java/android-ndk-r9d/samples/hello-jni/jni/hello-jni.c:15
Stack frame #06  pc 00052eb1  /system/lib/libdvm.so (dvmLoadNativeCode(char const*, Object*, char**)+468)
Stack frame #07  pc 0006a62d  /system/lib/libdvm.so  

第7行和第8行分别打印出了在源文件中出错的位置,和addr2line得到的结果一样。

先获取日志再分析:

这种方式和上面的方法差不多,只是获取log的来源不一样。适用于应用或游戏给测试部们测试的时候,测试人员发现crash,用adb logcat保存日志文件,然后发给程序员通过ndk-stack命令分析。操作流程如下:

adb logcat > crash.log
ndk-stack -sym /Users/yangxin/Documents/devToos/java/android-ndk-r9d/samples/hello-jni/obj/local/armeabi-v7a -dump crash.log  

得到的结果和上面的方式是一样的。

转自:Android NDK开发Crash错误定位

Android NDK开发Crash错误定位相关推荐

  1. Android NDK开发Crash错误定位[转]

    使用 ndk-stack 的时候需要你的 lib 编译为 debug版的,通常需要下面的修改: 1. 修改 android.mk,增加,为 LOCAL_CFLAGS 增加 -g 选项 2. 修改 ap ...

  2. Android NDK开发(三)——常见错误集锦以及LOG使用,androidndk

    Android NDK开发(三)--常见错误集锦以及LOG使用,androidndk 转载请注明出处:http://blog.csdn.net/allen315410/article/details/ ...

  3. 【Android】NDK开发Crash分析

    NDK开发Crash问题分析 手机user版本还是userdebug或是eng版本:adb shell getprop ro.build.type 因为使用的user版本的手机,所有没有权限读取到/d ...

  4. Android NDK开发之旅(2):一篇文章搞定Android Studio中使用CMake进行NDK/JNI开发

    Android NDK开发之旅(2):一篇文章搞定android Studio中使用CMake进行NDK/JNI开发 (码字不易,转载请声明出处:http://blog.csdn.NET/andrex ...

  5. 【Android NDK 开发】Android.mk 配置动态库 ( Android Studio 配置动态库 | 动态库加载版本限制 | 本章仅做参考推荐使用 CMake 配置动态库 )

    文章目录 I . Android Studio 中使用 Android.mk 配置动态库 总结 II . 第三方动态库来源 III . 配置 Android.mk 构建脚本路径 IV . 预编译 第三 ...

  6. Android NDK开发method GetStringUTFChars’could not be resolved

    Android NDK开发method GetStringUTFChars'could not be resolved 图1 最近用到android的ndk,但在eclipse中提示method Ge ...

  7. OpenCV android sdk配置OpenCV android NDK开发实例

    OpenCV android sdk配置OpenCV android NDK开发实例 [尊重原创,转载请注明出处]http://blog.csdn.net/guyuealian/article/det ...

  8. 安卓逆向_15( 三 ) --- Android NDK 开发【 jni 静态注册、JNI_OnLoad 动态注册】

    Android Studio开发JNI示例:https://blog.csdn.net/wzhseu/article/details/79683045 JNI_动态注册_静态注册.zip : http ...

  9. Android NDK开发: 通过C/C++调用第三方so库

    文章目录 一.编写so库代码 二.安装Android NDK 三.编译so库 3.1 编辑Android.mk 3.2 编辑Application.mk 3.3 编译 四.集成到Android工程中 ...

最新文章

  1. 你一笑,AI就变聪明了?微软最新研究提出“微笑训练法”
  2. 【MFC】固定按钮工具栏
  3. c语言运行后没生成exe,这个程序怎么运行?为什么显示没有exe??
  4. pat 乙级 1028 人口普查(C++)
  5. .NET开源的背后:是无奈,还是顺应潮流?
  6. 选课_ctsc1997_ssl1606_树形dp
  7. ichartjs android,在android上动态实现ichartjs的3D柱形图
  8. 【C#】通过正则表达式对TextBox进行校验
  9. sublime跳转到函数定义
  10. arcgis数据导入mysql_ArcGIS Geodatabase教程:将数据导入到地理数据库
  11. 滑动平均滤波c语言_9种简单的数字滤波算法(C语言源程序)
  12. 【GIS开发】批量下载和拼接地图瓦片(Python)
  13. java毕业生设计新生报到管理系统计算机源码+系统+mysql+调试部署+lw
  14. 推荐一本老外给初学者学习ABAP的书籍,2012年12月上市。
  15. phpadmin安装到mysql中_phpadmin安装教程
  16. 洛谷P3987 我永远喜欢珂朵莉~(set 树状数组)
  17. [NewStarCTF] Word-For-You
  18. 【RPA学习天地:版本解读】艺赛旗iS-RPA2021.2版本亮点
  19. PCB走线和过孔通流能力的标准、影响因素及其计算软件
  20. 空调器制冷系统故障-蒸发器反面脏堵

热门文章

  1. 李想发布了一款怎样的智能电动车?
  2. 一只初学者,如何登顶野生动物识别挑战赛?| 附代码
  3. 围观人类弹琴后,Facebook的AI学会了假装演奏
  4. 叮铃哐当,一大波机器人开始入侵厨房
  5. Cookie、Session和自定义分页
  6. [20190312]视图v$datafile字段OFFLINE_CHANGE#, ONLINE_CHANGE#.txt
  7. mysql xtrabackup安装与原理
  8. 孩子春节猛吃零食怎么办?专家支招:先用蔬菜填饱肚子
  9. CIO感悟:IT人转型之“势、道、术”
  10. 通过VMName获取VM IP