写了个AS插件:SmartNDKStack,快速定位Android Native开发Crash

5,123 阅读3分钟

对于Android Native开发的人员而言,可能经常会在开发过程及线上环境中遇到Native Crash的问题,对于这类native crash,我们一般都会直接addr2line,或使用ndk中附带的ndk-stack脚本分析。但是ndk-stack是不会对build id不匹配的库进行分析的。对于上述问题,即使build id不同,我们也是可以尝试性地进行分析的,大致流程如下:

绘图2.jpg

基于此分析流程,我编写了一个AS插件:SmartNDKStack,功能如下:

  1. 支持上传日志或选中日志分析,解析结果超链接显示
  2. 在buildId不匹配时根据函数偏移解析
  3. 展开inline调用
  4. Project视图中选中elf文件解析build id
  5. 指定NDK路径
  6. 指定symbol库目录

大家若有需要,可以在插件市场搜索安装。现在可能会存在一些不足,如果出现问题,请把截图和现象描述发我: wangshengyang96@gmail.com

image.png

下面是原理描述。

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)
...

图片版:

image.png 对于如上调用栈而言, #01是开发者最关注的地方,挂在了自己的库里。

这一行的关键信息解读结果如下:

  1. crash所在库在本机的绝对路径是/data/app/~~NiGDTVWkvUzCYf_UpH7RdQ==/com.example.ndkdemo-dfk3WrVVoUG5N0jUHQiyyw==/lib/arm64/libndkdemo.so
  2. crash地址相对于库的偏移是0xf0c8
  3. crash地址在函数Java_com_example_ndkdemo_MainActivity_stringFromJNI中,相对于函数地址的偏移是52(十进制)
  4. crash所在库的BuildId是8c26841b3c32a89935d095d8e916180628bded7b

2. 如何解析

  • 常规操作

    addr2line -Cfie libndkdemo.so 0xf0c8

    网上的大部分教程都是直接进行如上的addr2line操作,对于自己开发过程中直接运行出现了crash的确可以快速定位,但如果代码已发生变更,库不匹配了,定位效率就会大幅下降,比如测试报了crash,但是本地代码已发生变更,库不匹配,如果想拿到build id相同的库,就要回退到当时的代码,重新编一个一样的库再做addr2line。

  • 进阶操作

    按照上述流程图所示,我们可以先确认函数在本地库中的地址,得到地址后,与函数偏移相加得到新的地址用作解析。

    • 获取函数地址

      image.png

    • 解析

      函数的地址是0xf094,加上十进制的52,即0xf094 + 0x34刚好是0xf0c8,匹配。对于本地库文件发生变更的情况,我们依然可以使用这种方案解析(但是要确保变更和当前函数无关)。

      image.png

    • 本地库发生变更

      修改其他函数,使crash所在的函数地址变更,变更后函数地址是0xf0a00xf0a0 + 0x34 = 0xf0d4 使用新的地址addr2line,可以发现,此时buildId虽然发生变更(修改前是:8c26841b3c32a89935d095d8e916180628bded7b),但是该方案依然能够解析。

      image.png

      image.png