人间观察
你有多久没有十点之前睡过觉了。
假期ing~~~
在Android中进行JNI的开发的当然也会发生crash,而发生crash后比较难定位。
因为jni是使用C/C++来进行开发的,熟悉C/C++语言的同学都知道,指针和内存申请的使用时需要自己申请和释放的,它不像java那样有jvm有垃圾回收管理机制gc,稍微管理不当就会导致问题。比如:内存地址访问错误、堆栈溢出、指针使用错误等等,最后都会导致程序崩溃。
幸好Android NDK提供了一些工具来帮助精确定位到出问题的代码。
我们模拟一下crash,模拟一个指针未初始化访问的case。
#include "common.h"
#include
extern "C"
JNIEXPORT void JNICALL
Java_com_bj_gxz_jniapp_crash_JNICrash_crash(JNIEnv *env, jobject thiz) {
// int *p = static_cast(malloc(sizeof(int)));
int *p;
*p = 1;
LOG_D("*P=%d", *p);
free(p);
}
*p没有初始化
崩溃的日志如下:
2020-09-28 14:36:49.737 19047-19047/com.bj.gxz.jniapp A/libc: Fatal signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0x6fcabb4d34 in tid 19047 (m.bj.gxz.jniapp), pid 19047 (m.bj.gxz.jniapp)
2020-09-28 14:36:49.856 19167-19167/? A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
2020-09-28 14:36:49.856 19167-19167/? A/DEBUG: Build fingerprint: 'HONOR/STF-AL00/HWSTF:9/HUAWEISTF-AL00/9.1.0.219C00:user/release-keys'
2020-09-28 14:36:49.856 19167-19167/? A/DEBUG: Revision: '0'
2020-09-28 14:36:49.856 19167-19167/? A/DEBUG: ABI: 'arm64'
2020-09-28 14:36:49.857 19167-19167/? A/DEBUG: Happend: 'Mon Sep 28 14:36:49 2020
'
2020-09-28 14:36:49.857 19167-19167/? A/DEBUG: SYSVMTYPE: Art
APPVMTYPE: Art
2020-09-28 14:36:49.857 19167-19167/? A/DEBUG: pid: 19047, tid: 19047, name: m.bj.gxz.jniapp >>> com.bj.gxz.jniapp <<<
2020-09-28 14:36:49.857 19167-19167/? A/DEBUG: signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0x6fcabb4d34
2020-09-28 14:36:49.857 19167-19167/? A/DEBUG: x0 0000006fcb0c5460 x1 0000007fdd935134 x2 0000006fac4b2b84 x3 0000000070cfc600
2020-09-28 14:36:49.857 19167-19167/? A/DEBUG: x4 0000000000000000 x5 0000000000000000 x6 0000000000000000 x7 0000000000000000
2020-09-28 14:36:49.857 19167-19167/? A/DEBUG: x8 0000000000000001 x9 0000000000000003 x10 0000006fac4b2b80 x11 0000006fcabb4d34
2020-09-28 14:36:49.857 19167-19167/? A/DEBUG: x12 0000007051586630 x13 0f2fce861ea823e3 x14 000000705118b000 x15 ffffffffffffffff
2020-09-28 14:36:49.857 19167-19167/? A/DEBUG: x16 0000006fac4990bc x17 00000070507c5d48 x18 0000000000000001 x19 0000006fcb015c00
2020-09-28 14:36:49.857 19167-19167/? A/DEBUG: x20 0000000000000000 x21 0000006fcb015c00 x22 0000007fdd935400 x23 0000006faeae68f1
2020-09-28 14:36:49.857 19167-19167/? A/DEBUG: x24 0000000000000004 x25 00000070518f65e0 x26 0000006fcb015ca0 x27 0000000000000001
2020-09-28 14:36:49.857 19167-19167/? A/DEBUG: x28 0000007fdd935130 x29 0000007fdd935100
2020-09-28 14:36:49.857 19167-19167/? A/DEBUG: sp 0000007fdd9350e0 lr 0000006fcaeb6fe4 pc 0000006fac4990ec
2020-09-28 14:36:49.891 744-2617/? E/LOGSERVER_UTILS: [Erecovery]readEvent: eRecEventManager readEvent 0
2020-09-28 14:36:49.998 19167-19167/? A/DEBUG: backtrace:
2020-09-28 14:36:49.998 19167-19167/? A/DEBUG: #00 pc 000000000000e0ec /data/app/com.bj.gxz.jniapp-wTViTmvyh8_za4_TB4rqtQ==/lib/arm64/libnative-lib.so (Java_com_bj_gxz_jniapp_crash_JNICrash_crash+48)
2020-09-28 14:36:49.998 19167-19167/? A/DEBUG: #01 pc 0000000000588fe0 /system/lib64/libart.so (art_quick_generic_jni_trampoline+144)
2020-09-28 14:36:49.998 19167-19167/? A/DEBUG: #02 pc 000000000057ff88 /system/lib64/libart.so (art_quick_invoke_stub+584)
2020-09-28 14:36:49.998 19167-19167/? A/DEBUG: #03 pc 00000000000d8608 /system/lib64/libart.so (art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)+200)
2020-09-28 14:36:49.998 19167-19167/? A/DEBUG: #04 pc 000000000028cec8 /system/lib64/libart.so (art::interpreter::ArtInterpreterToCompiledCodeBridge(art::Thread*, art::ArtMethod*, art::ShadowFrame*, unsigned short, art::JValue*)+344)
2020-09-28 14:36:49.998 19167-19167/? A/DEBUG: #05 pc 0000000000286ed0 /system/lib64/libart.so (bool art::interpreter::DoCall(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+968)
2020-09-28 14:36:49.998 19167-19167/? A/DEBUG: #06 pc 000000000054747c /system/lib64/libart.so (MterpInvokeVirtual+588)
2020-09-28 14:36:49.998 19167-19167/? A/DEBUG: #07 pc 0000000000572514 /system/lib64/libart.so (ExecuteMterpImpl+14228)
2020-09-28 14:36:49.998 19167-19167/? A/DEBUG: #08 pc 00000000001678da /dev/ashmem/dalvik-classes.dex extracted in memory from /data/app/com.bj.gxz.jniapp-wTViTmvyh8_za4_TB4rqtQ==/base.apk (deleted) (com.bj.gxz.jniapp.MainActivity.onJniCrash+10)
2020-09-28 14:36:49.998 19167-19167/? A/DEBUG: #09 pc 0000000000260bd4 /system/lib64/libart.so (_ZN3art11interpreterL7ExecuteEPNS_6ThreadERKNS_20CodeItemDataAccessorERNS_11ShadowFrameENS_6JValueEb.llvm.2850692617+488)
2
// ...省略其它日志不重要的日志
crash日志里 最有用的backtrace信息。 backtrace描述了crash发生时函数的调用关系及其它地址信息等。
在backtrace中,从#00到#04 共五行信息代表是crash时函数调用关系,从下往上倒着看,#04行的方法调用了#03行的方法;#03行的方法调用了#02行的方法;向上类推,最后就是#01行的方法调用了#00行的方法。而最终出现的crash就是在#00行中。
如何定位解决呢?
使用ndk提供的命令addr2line
addr2line命令在ndk版本的toolchains目录下,我的是在
/Users/guxiuzhong/Library/Android/sdk/ndk/21.0.6113669/toolchains
B000000073160:toolchains guxiuzhong$ ls
aarch64-linux-android-4.9 renderscript
arm-linux-androideabi-4.9 x86-4.9
llvm x86_64-4.9
从上面可以看得出来NDK针对不同的CPU架构实现了多套相同的工具,所以你要根据目标机器的CPU架构来选择。如果不知道目标机器的CPU架构,把手机连上电脑,用adb shell cat /proc/cpuinfo
可以查看手机的CPU信息。我的目标机器是Processor : AArch64 Processor rev 1 (aarch64)
addr2line的使用方式是
addr2line -e 000000000000e0ec
-e 指定带符号表的so文件路径,也需要对应目标机器的CPU架构的so文件的绝对路径
000000000000e0ec 崩溃的汇编指令地址
addr2line一下
/Users/guxiuzhong/Library/Android/sdk/ndk/21.0.6113669/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/aarch64-linux-android-addr2line -e /Users/guxiuzhong/Desktop/JNIAPP/app/build/intermediates/cmake/debug/obj/arm64-v8a/libnative-lib.so 000000000000e0ec
结果如下
/Users/guxiuzhong/Desktop/JNIAPP/app/src/main/cpp/crash.cpp:9
回看下我们.cpp的源码,第9行由于int* p指针没有初始化导致了fault addr。
使用ndk提供的命令ndk-stack
这个命令可以简化上面的步骤。
使用adb获取logcat的日志,并通过管道输出给ndk-stack进行分析。
ndk-stack -sym使用方式是
ndk-stack -sym XXXX
-sym 指定带符号表的so文件路径,也需要对应目标机器的CPU架构的so文件的根目录即可
ndk-stack一下
adb logcat | /Users/guxiuzhong/Library/Android/sdk/ndk/21.0.6113669/ndk-stack -sym /Users/guxiuzhong/Desktop/JNIAPP/app/build/intermediates/cmake/debug/obj/arm64-v8a
这时候模拟一下crash,检测到crash,ndk-stack自动解析了,结果和上面的一样第9行:
********** Crash dump: **********
Build fingerprint: 'HONOR/STF-AL00/HWSTF:9/HUAWEISTF-AL00/9.1.0.219C00:user/release-keys'
#00 0x000000000000e0ec /data/app/com.bj.gxz.jniapp-wTViTmvyh8_za4_TB4rqtQ==/lib/arm64/libnative-lib.so (Java_com_bj_gxz_jniapp_crash_JNICrash_crash+48)
Java_com_bj_gxz_jniapp_crash_JNICrash_crash
/Users/guxiuzhong/Desktop/JNIAPP/app/src/main/cpp/crash.cpp:9:8
#01 0x0000000000588fe0 /system/lib64/libart.so (art_quick_generic_jni_trampoline+144)
#02 0x000000000057ff88 /system/lib64/libart.so (art_quick_invoke_stub+584)
这个命令也支持文件的解析(使用-dump
参数),这个好,因为有些crash不是必须的。
ndk-stack -dump 使用方式是
ndk-stack -sym XXXX -dump XXXX
-sym 指定带符号表的so文件路径,也需要对应目标机器的CPU架构的so文件的根目录即可
-dump crash的堆栈信息的文件
B000000073160:bin guxiuzhong$ /Users/guxiuzhong/Library/Android/sdk/ndk/21.0.6113669/ndk-stack -sym /Users/guxiuzhong/Desktop/JNIAPP/app/build/intermediates/cmake/debug/obj/arm64-v8a -dump ~/Desktop/crash.log
********** Crash dump: **********
Build fingerprint: 'HONOR/STF-AL00/HWSTF:9/HUAWEISTF-AL00/9.1.0.219C00:user/release-keys'
#00 0x000000000000e0ec /data/app/com.bj.gxz.jniapp-wTViTmvyh8_za4_TB4rqtQ==/lib/arm64/libnative-lib.so (Java_com_bj_gxz_jniapp_crash_JNICrash_crash+48)
Java_com_bj_gxz_jniapp_crash_JNICrash_crash
/Users/guxiuzhong/Desktop/JNIAPP/app/src/main/cpp/crash.cpp:9:8
#01 0x0000000000588fe0 /system/lib64/libart.so (art_quick_generic_jni_trampoline+144)
#02 0x000000000057ff88 /system/lib64/libart.so (art_quick_invoke_stub+584)
#03 0x00000000000d8608 /system/lib64/libart.so (art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)+200)
#04 0x000000000028cec8 /system/lib64/libart.so (art::interpreter::ArtInterpreterToCompiledCodeBridge(art::Thread*, art::ArtMethod*, art::ShadowFrame*, unsigned short, art::JValue*)+344)
#05 0x0000000000286ed0 /system/lib64/libart.so (bool art::interpreter::DoCall(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+968)
#06 0x000000000054747c /system/lib64/libart.so (MterpInvokeVirtual+588)
#07 0x0000000000572514 /system/lib64/libart.so (ExecuteMterpImpl+14228)
总结:两种方法来定位crash。 addr2line -e xxxx 指定汇编指令地址。 adb logcat | ndk-stack -sym 指定对应目标机器的CPU架构的so文件的绝对路径; ndk-stack -dump 对应目标机器的CPU架构的so文件的根目录 -dump crash的堆栈信息的文件。
源代码:https://github.com/ta893115871/JNIAPP