使用 ndk-stack 的时候需要你的 lib 编译为 debug版的,通常需要下面的修改:

1. 修改 android.mk,增加,为 LOCAL_CFLAGS 增加 -g 选项
2. 修改 application.mk,增加 APP_OPTIM := debug
3. nkd-build -B
4. 从 obj/local/armeabi/ 下面把包复制到 libs/armeabi/
5. 出现异常后,调用 adb logcat | ndk-stack -sym libs/armeabi/

可以在链接的时候生成一个mapfile,总览整个代码的地址列表:
You need to generate a map file. The map file contains the function address and memory locations in your executable. Have your build system modified to generate a map file.
From the map file, you can use a text editor and search for addresses. I once wrote a program to find the two symbols bounding a given address. Worked great for environments like yours.
For android, I used the following line in the Application.mk or Android.mk (note that LOCAL_LDFLAGS is semi-undocumented in the Android NDK docs). LOCAL_LDFLAGS := -Wl,-Map,app.map

http://blog.csdn.net/xyang81/article/details/42319789

在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

方式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的函数表信息:

/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 -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得到的结果一样。

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

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

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

  1. Android NDK开发Crash错误定位

    在Android开发中,程序Crash分三种情况:未捕获的异常.ANR(Application Not Responding)和闪退(NDK引发错误).其中未捕获的异常根据logcat打印的堆栈信息很 ...

  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. 由于Coinbase插件,BCH被5000万商家接受
  2. 【计算理论】计算复杂性 ( 多项式等价 | P 类 | 丘奇-图灵论题延伸 )
  3. 【数学基础】从零构建统计随机变量生成器之离散基础篇
  4. window安装swagger editor
  5. 禁用导航栏的右滑返回实现全屏手势返回
  6. 信号量与令牌桶_限流的4种方式令牌桶实战
  7. vueJs的简单入门以及基础语法
  8. 一 新开博客第一天,很开心哦!就写点简单的好了,那就数据类型、常量和运算符(c语言的)...
  9. RuntimeError: context has already been set(multiprocessing)
  10. js ajax 跨域上传文件,使用 Javascript 实现跨域上传文件到存储
  11. 接口测试用例设计 - 精简版
  12. 2018.9.8-9.8 统计学课程笔记(1)-从概率论到统计学
  13. 环形链表与快慢指针的关系
  14. 2020 年你读了哪些觉得比较好的计算机书籍?
  15. 32位计算机64位计算机实质,4G内存电脑安装32位还是64位win7系统?实际详细性能测试!...
  16. 【Android开发】微信精选,文章资讯类App开发记录总结
  17. Linux命令行下载大文件,下载Onedrive文件
  18. Centos 7 安装 ORACLE 11g
  19. 【剪映】基础剪辑 | 实用技巧
  20. 人机融合智能的哲学思考

热门文章

  1. Android开发技巧——ViewPager加View情况封装PagerAdapter的实现类
  2. 使用selector改变按钮状态
  3. LeetCode—221. 最大正方形
  4. 解决Spring下无法实时访问本地静态资源
  5. php session_regenerate_id,什么时候以及为什么我应该使用session_regenerate_id()?
  6. 全国计算机等级考试题库二级C操作题100套(第84套)
  7. 带你理解 只读事务(@Transactional(readOnly = true)
  8. 微信10个实用技巧,值得收藏!
  9. 学编程一定要掌握的186个关键单词!
  10. 程序人生:程序员成熟的标志