在NDK开发中经常会出现应用Crash的情况,而JNI层的报错信息,不像Java层报错信息那样可以直接在日志中看到错误的行数,JNI层中出现的错误直接看根本定位不到错误的位置。通常来说,JNI报的基本都是堆栈信息,需要NDK的一些工具进行地址转换,转换后即可看到错误的位置。这些地址转换的工具有addr2line、ndk-stack等,我比较喜欢addr2line,平时也用这个工具进行调试。
错误信息如下:
A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
A/DEBUG: Build fingerprint: 'xiaomi/mido/mido:7.0/NRD90M/V10.1.1.0.NCFCNFI:user/release-keys'
A/DEBUG: Revision: '0'
A/DEBUG: ABI: 'arm'
A/DEBUG: pid: 29290, tid: 29290, name: e.hasee.ndkdemo >>> com.example.hasee.ndkdemo <<<
A/DEBUG: signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
A/DEBUG: r0 00000000 r1 0000726a r2 00000006 r3 00000008
A/DEBUG: r4 f6cbc590 r5 00000006 r6 f6cbc538 r7 0000010c
A/DEBUG: r8 12e45dc0 r9 f3f88000 sl ff90e8ec fp f3f88000
A/DEBUG: ip 00000058 sp ff90e5f8 lr f5c3a2c7 pc f5c3cb48 cpsr 200f0010
A/DEBUG: backtrace: //堆栈信息,只需要关注这部分就好
A/DEBUG: #00 pc 00049b48 /system/lib/libc.so (tgkill+12)
A/DEBUG: #01 pc 000472c3 /system/lib/libc.so (pthread_kill+34)
A/DEBUG: #02 pc 0001d565 /system/lib/libc.so (raise+10)
A/DEBUG: #03 pc 000190b1 /system/lib/libc.so (__libc_android_abort+34)
A/DEBUG: #04 pc 00017114 /system/lib/libc.so (abort+4)
A/DEBUG: #05 pc 0009063f /data/app/com.example.hasee.ndkdemo-2/lib/arm/libthird.so (_ZN7GSMutex4LockEv+90)
A/DEBUG: #06 pc 00090837 /data/app/com.example.hasee.ndkdemo-2/lib/arm/libthird.so (_ZN11GSAutoMutexC1ER7GSMutex+10)
A/DEBUG: #07 pc 0004b4d7 /data/app/com.example.hasee.ndkdemo-2/lib/arm/libthird.so (_ZN12GSGB28181SDK9CSipStack18GetHistoryRegisterEv+34)
A/DEBUG: #08 pc 0004b60b /data/app/com.example.hasee.ndkdemo-2/lib/arm/libthird.so (_ZN12GSGB28181SDK9CSipStack13SendRegistMsgERK16_Stru_Regist_MsgjPc+58)
A/DEBUG: #09 pc 000033a3 /data/app/com.example.hasee.ndkdemo-2/lib/arm/libnative-lib.so (Java_com_example_hasee_ndkdemo_NDKUtil_agentRegister+686)
上面的这些报错信息里面,backtrace部分显示的是错误的堆栈信息,所以我们只需要关注 backtrace部分就可以了。
提取后如下所示:
A/DEBUG: backtrace:
A/DEBUG: #00 pc 00049b48 /system/lib/libc.so (tgkill+12)
A/DEBUG: #01 pc 000472c3 /system/lib/libc.so (pthread_kill+34)
A/DEBUG: #02 pc 0001d565 /system/lib/libc.so (raise+10)
A/DEBUG: #03 pc 000190b1 /system/lib/libc.so (__libc_android_abort+34)
A/DEBUG: #04 pc 00017114 /system/lib/libc.so (abort+4)
A/DEBUG: #05 pc 0009063f /data/app/com.example.hasee.ndkdemo-2/lib/arm/libthird.so (_ZN7GSMutex4LockEv+90)
A/DEBUG: #06 pc 00090837 /data/app/com.example.hasee.ndkdemo-2/lib/arm/libthird.so (_ZN11GSAutoMutexC1ER7GSMutex+10)
A/DEBUG: #07 pc 0004b4d7 /data/app/com.example.hasee.ndkdemo-2/lib/arm/libthird.so (_ZN12GSGB28181SDK9CSipStack18GetHistoryRegisterEv+34)
A/DEBUG: #08 pc 0004b60b /data/app/com.example.hasee.ndkdemo-2/lib/arm/libthird.so (_ZN12GSGB28181SDK9CSipStack13SendRegistMsgERK16_Stru_Regist_MsgjPc+58)
A/DEBUG: #09 pc 000033a3 /data/app/com.example.hasee.ndkdemo-2/lib/arm/libnative-lib.so (Java_com_example_hasee_ndkdemo_NDKUtil_agentRegister+686)
可以看到每一行最后面都跟着“xxx.so”信息
“#00” 至 “#04” 部分后面的都是 “/system/lib/libc.so” ,这些都是系统的库,这个我们不需要管;
“#05” 至 “#09” 部分后面的so库都是我自己的,这些才是我们需要定位的部分。
“/data/app/com.example.hasee.ndkdemo-2/lib/arm/libthird.so” // 引入的第三方so库
“/data/app/com.example.hasee.ndkdemo-2/lib/arm/libnative-lib.so ” // 编译生成的so库
将“#05” 至 “#09” 部分提取出来,如下所示:
A/DEBUG: #05 pc 0009063f /data/app/com.example.hasee.ndkdemo-2/lib/arm/libthird.so (_ZN7GSMutex4LockEv+90)
A/DEBUG: #06 pc 00090837 /data/app/com.example.hasee.ndkdemo-2/lib/arm/libthird.so (_ZN11GSAutoMutexC1ER7GSMutex+10)
A/DEBUG: #07 pc 0004b4d7 /data/app/com.example.hasee.ndkdemo-2/lib/arm/libthird.so (_ZN12GSGB28181SDK9CSipStack18GetHistoryRegisterEv+34)
A/DEBUG: #08 pc 0004b60b /data/app/com.example.hasee.ndkdemo-2/lib/arm/libthird.so (_ZN12GSGB28181SDK9CSipStack13SendRegistMsgERK16_Stru_Regist_MsgjPc+58)
A/DEBUG: #09 pc 000033a3 /data/app/com.example.hasee.ndkdemo-2/lib/arm/libnative-lib.so (Java_com_example_hasee_ndkdemo_NDKUtil_agentRegister+686)
剩下到的这几行中,重点看 “pc” 后面的十六进制数,这些是对应so库中的具体错误信息地址,这些地址才是我们最终需要转换的对象。为了更直观的展示,我把前面的“A/DEBUG” 和 “pc” 两个标签去掉,剩下行号还有地址以及so库部分提取出来。
提取如下所示:
#05 0009063f /data/app/com.example.hasee.ndkdemo-2/lib/arm/libthird.so
#06 00090837 /data/app/com.example.hasee.ndkdemo-2/lib/arm/libthird.so
#07 0004b4d7 /data/app/com.example.hasee.ndkdemo-2/lib/arm/libthird.so
#08 0004b60b /data/app/com.example.hasee.ndkdemo-2/lib/arm/libthird.so
#09 000033a3 /data/app/com.example.hasee.ndkdemo-2/lib/arm/libnative-lib.so
上面就是最终我们需要关注的部分,有了十六进制地址值以及后面的so库,下面我们就可以使用addr2line进行地址转换了。
${NDK} // 你的NDK解压包路径
${ABI} // 你的调试设备的CPU架构,通常来说实体机一般都是对应 arm-linux-androideabi
arm-linux-androideabi-addr2line -C -f -e ${SOPATH} ${Address}
-C -f //打印错误行数所在的函数名称
-e //打印错误地址的对应路径及行数
${SOPATH} //so库路径
${Address} //需要转换的堆栈错误信息地址,可以添加多个,但是中间要用空格隔开
对上面最终提取的错误信息(“#05 – #09”)中的堆栈地址进行转换
对于“#05” 至 “#08” 部分的地址,我对应的so库路径为
F:/Android/workspace/NDKDemo/app/src/main/jniLibs/armeabi-v7a/libthird.so
对于“#09”行中的地址,我对应的so库为:
F:/Android/workspace/NDKDemo/app/build/intermediates/cmake/debug/obj/armeabi-v7a/libnative-lib.so
对于“#05” 至 “#08” 部分的地址,我输入的命令为:
arm-linux-androideabi-addr2line -C -f -e F:/Android/workspace/NDKDemo/app/src/main/jniLibs/armeabi-v7a/libthird.so 0009063f 00090837 0004b4d7 0004b60b
对于“#09”行中的地址,我输入的命令为:
arm-linux-androideabi-addr2line -C -f -e F:/Android/workspace/NDKDemo/app/build/intermediates/cmake/debug/obj/armeabi-v7a/libnative-lib.so 000033a3
注意:
1)我这里已经将addr2line配置到环境变量中,所以可以直接使用arm-linux-androideabi-addr2line命令。如果没有配置的话,记得要加上自己的完整路径,否则会出现Terminal报错:
“'arm-linux-androideabi-addrline2' 不是内部或外部命令,也不是可运行的程序或批处理文件。”
2)要看清楚后面的so库,我这里第三方的so库和我自己的编译打包的so库都有报错地址信息,所以转换地址的时候,要注意地址后面对应的是哪个so库,不然的话会转换失败,转换的地址显示为 " ? ? 0 “(一个so库的错误地址信息跑到另外一个so库去查,当然查不到,除非两个错误位置在两个so库中的堆栈地址相同,查不到的时候,就会输出” ? ? 0 ")这个问题也困扰了我一个上午,要特别注意。
3)libnative-lib.so是我自己打包编译的库,自己打包编译的so库可以在Project视图的
" ./app/build/intermediates/cmake/debug/obj/&{ABI}/ " 路径下找到
第三方libthird.so库的显示信息
自己编译的libnatve-lib.so库的显示信息
点击一下转换后的地址就可以跳转到错误的位置了。