对于Android Native开发的人员而言,可能经常会在开发过程及线上环境中遇到Native Crash的问题,对于这类native crash,我们一般都会直接addr2line,或使用ndk中附带的ndk-stack脚本分析。但是ndk-stack是不会对build id不匹配的库进行分析的。对于上述问题,即使build id不同,我们也是可以尝试性地进行分析的,大致流程如下:
基于此分析流程,我编写了一个AS插件:SmartNDKStack
,功能如下:
- 支持上传日志或选中日志分析,解析结果超链接显示
- 在buildId不匹配时根据函数偏移解析
- 展开inline调用
- Project视图中选中elf文件解析build id
- 指定NDK路径
- 指定symbol库目录
大家若有需要,可以在插件市场搜索安装。现在可能会存在一些不足,如果出现问题,请把截图和现象描述发我: wangshengyang96@gmail.com
下面是原理描述。
1. 调用栈帧内容说明
写个demo触发crash
__attribute__((always_inline)) void realThrowFatal(){
raise(SIGABRT);
}
__attribute__((always_inline)) void throwFatal(){
realThrowFatal();
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_ndkdemo_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
throwFatal();
return env->NewStringUTF(hello.c_str());
}
运行看调用栈
2021-11-14 16:04:45.573 14705-14705/? A/DEBUG: #00 pc 00000000000c5008 /apex/com.android.runtime/lib64/bionic/libc.so (tgkill+8) (BuildId: e81bf516b888e895d4e757da439c8117)
2021-11-14 16:04:45.574 14705-14705/? A/DEBUG: #01 pc 000000000000f0c8 /data/app/~~NiGDTVWkvUzCYf_UpH7RdQ==/com.example.ndkdemo-dfk3WrVVoUG5N0jUHQiyyw==/lib/arm64/libndkdemo.so (Java_com_example_ndkdemo_MainActivity_stringFromJNI+52) (BuildId: 8c26841b3c32a89935d095d8e916180628bded7b)
2021-11-14 16:04:45.574 14705-14705/? A/DEBUG: #02 pc 000000000013ced4 /apex/com.android.art/lib64/libart.so (art_quick_generic_jni_trampoline+148) (BuildId: 9f1408149c58982ae91ee6377c202d2b)
...
图片版:
对于如上调用栈而言,
#01
是开发者最关注的地方,挂在了自己的库里。
这一行的关键信息解读结果如下:
- crash所在库在本机的绝对路径是
/data/app/~~NiGDTVWkvUzCYf_UpH7RdQ==/com.example.ndkdemo-dfk3WrVVoUG5N0jUHQiyyw==/lib/arm64/libndkdemo.so
- crash地址相对于库的偏移是
0xf0c8
- crash地址在函数
Java_com_example_ndkdemo_MainActivity_stringFromJNI
中,相对于函数地址的偏移是52(十进制)
- crash所在库的BuildId是
8c26841b3c32a89935d095d8e916180628bded7b
2. 如何解析
-
常规操作
addr2line -Cfie libndkdemo.so 0xf0c8
网上的大部分教程都是直接进行如上的addr2line操作,对于自己开发过程中直接运行出现了crash的确可以快速定位,但如果代码已发生变更,库不匹配了,定位效率就会大幅下降,比如测试报了crash,但是本地代码已发生变更,库不匹配,如果想拿到build id相同的库,就要回退到当时的代码,重新编一个一样的库再做addr2line。
-
进阶操作
按照上述流程图所示,我们可以先确认函数在本地库中的地址,得到地址后,与函数偏移相加得到新的地址用作解析。
-
获取函数地址
-
解析
函数的地址是0xf094,加上十进制的52,即
0xf094 + 0x34
刚好是0xf0c8
,匹配。对于本地库文件发生变更的情况,我们依然可以使用这种方案解析(但是要确保变更和当前函数无关)。 -
本地库发生变更
修改其他函数,使crash所在的函数地址变更,变更后函数地址是
0xf0a0
,0xf0a0 + 0x34 = 0xf0d4
使用新的地址addr2line,可以发现,此时buildId虽然发生变更(修改前是:8c26841b3c32a89935d095d8e916180628bded7b),但是该方案依然能够解析。
-