人間觀察
你有多久沒(méi)有十點(diǎn)之前睡過(guò)覺(jué)了歌焦。
假期ing~~~
在Android中進(jìn)行JNI的開(kāi)發(fā)的當(dāng)然也會(huì)發(fā)生crash虎忌,而發(fā)生crash后比較難定位佑惠。
因?yàn)閖ni是使用C/C++來(lái)進(jìn)行開(kāi)發(fā)的嘹履,熟悉C/C++語(yǔ)言的同學(xué)都知道宙址,指針和內(nèi)存申請(qǐng)的使用時(shí)需要自己申請(qǐng)和釋放的轴脐,它不像java那樣有jvm有垃圾回收管理機(jī)制gc,稍微管理不當(dāng)就會(huì)導(dǎo)致問(wèn)題抡砂。比如:內(nèi)存地址訪問(wèn)錯(cuò)誤大咱、堆棧溢出、指針使用錯(cuò)誤等等注益,最后都會(huì)導(dǎo)致程序崩潰碴巾。
幸好Android NDK提供了一些工具來(lái)幫助精確定位到出問(wèn)題的代碼。
我們模擬一下crash丑搔,模擬一個(gè)指針未初始化訪問(wèn)的case厦瓢。
#include "common.h"
#include <malloc.h>
extern "C"
JNIEXPORT void JNICALL
Java_com_bj_gxz_jniapp_crash_JNICrash_crash(JNIEnv *env, jobject thiz) {
// int *p = static_cast<int *>(malloc(sizeof(int)));
int *p;
*p = 1;
LOG_D("*P=%d", *p);
free(p);
}
*p沒(méi)有初始化
崩潰的日志如下:
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<false, false>(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發(fā)生時(shí)函數(shù)的調(diào)用關(guān)系及其它地址信息等啤月。
在backtrace中煮仇,從#00到#04 共五行信息代表是crash時(shí)函數(shù)調(diào)用關(guān)系,從下往上倒著看顽冶,#04行的方法調(diào)用了#03行的方法欺抗;#03行的方法調(diào)用了#02行的方法;向上類推强重,最后就是#01行的方法調(diào)用了#00行的方法绞呈。而最終出現(xiàn)的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
從上面可以看得出來(lái)NDK針對(duì)不同的CPU架構(gòu)實(shí)現(xiàn)了多套相同的工具,所以你要根據(jù)目標(biāo)機(jī)器的CPU架構(gòu)來(lái)選擇倘要。如果不知道目標(biāo)機(jī)器的CPU架構(gòu)圾亏,把手機(jī)連上電腦,用adb shell cat /proc/cpuinfo
可以查看手機(jī)的CPU信息封拧。我的目標(biāo)機(jī)器是Processor : AArch64 Processor rev 1 (aarch64)
addr2line的使用方式是
addr2line -e 000000000000e0ec
-e 指定帶符號(hào)表的so文件路徑志鹃,也需要對(duì)應(yīng)目標(biāo)機(jī)器的CPU架構(gòu)的so文件的絕對(duì)路徑
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
結(jié)果如下
/Users/guxiuzhong/Desktop/JNIAPP/app/src/main/cpp/crash.cpp:9
回看下我們.cpp的源碼,第9行由于int* p指針沒(méi)有初始化導(dǎo)致了fault addr泽西。
使用ndk提供的命令ndk-stack
這個(gè)命令可以簡(jiǎn)化上面的步驟曹铃。
使用adb獲取logcat的日志,并通過(guò)管道輸出給ndk-stack進(jìn)行分析捧杉。
ndk-stack -sym使用方式是
ndk-stack -sym XXXX
-sym 指定帶符號(hào)表的so文件路徑陕见,也需要對(duì)應(yīng)目標(biāo)機(jī)器的CPU架構(gòu)的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
這時(shí)候模擬一下crash秘血,檢測(cè)到crash,ndk-stack自動(dòng)解析了评甜,結(jié)果和上面的一樣第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)
這個(gè)命令也支持文件的解析(使用-dump
參數(shù))灰粮,這個(gè)好,因?yàn)橛行ヽrash不是必須的忍坷。
ndk-stack -dump 使用方式是
ndk-stack -sym XXXX -dump XXXX
-sym 指定帶符號(hào)表的so文件路徑粘舟,也需要對(duì)應(yīng)目標(biāo)機(jī)器的CPU架構(gòu)的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<false, false>(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)
總結(jié):兩種方法來(lái)定位crash。 addr2line -e xxxx 指定匯編指令地址承匣。 adb logcat | ndk-stack -sym 指定對(duì)應(yīng)目標(biāo)機(jī)器的CPU架構(gòu)的so文件的絕對(duì)路徑蓖乘; ndk-stack -dump 對(duì)應(yīng)目標(biāo)機(jī)器的CPU架構(gòu)的so文件的根目錄 -dump crash的堆棧信息的文件。