关于android:Android-NativeCrash-捕获与解析

6次阅读

共计 8934 个字符,预计需要花费 23 分钟才能阅读完成。

Android 开发中,NE 始终是不可疏忽却又异样难解的一个问题,起因是这外面波及到了跨端开发和剖析,须要同时相熟 Java,C&C++,并且须要相熟 NDK 开发,并且解决起来不像 Java 异样那么明了,本文为了解决局部纳闷,将从 NE 的捕捉,解析与还原等三个方面进行摸索。

一、NE 简介

NE 全称 NativeCrash,就是 C 或者 C ++ 运行过程中产生的谬误,NE 不同于一般的 Java 谬误,一般的 logcat 无奈间接还原成可浏览的堆栈,个别没有源码也无奈调试。

所以日常应用层的工程师,即便咱们外部有云诊断的日志,个别也会疏忽 NE 的谬误,那么遇到这些问题,作为应用层、对 C ++ 不甚了解的工程师是否解决还原堆栈,是否疾速定位或者解决 NE 的问题呢?

上面将着重介绍:

1.1 so 组成

咱们先理解一下 so 的组成,一个残缺的 so 由 C 代码加一些 debug 信息组成,这些 debug 信息会记录 so 中所有办法的对照表,就是办法名和其便宜地址的对应表,也叫做符号表,这种 so  也叫做未 strip 的,通常体积会比拟大。

通常 release 的 so 都是须要通过一个 strip 操作的,这样 strip 之后的 so 中的 debug 信息会被剥离,整个 so 的体积也会放大。

如下图所示:

如下能够看到 strip 之前和之后的大小比照。

如果对 NE 或者 so 不理解的,能够简略将这个 debug 信息了解为 Java 代码混同中的 mapping 文件,只有领有这个 mapping 文件能力进行堆栈剖析。

如果堆栈信息丢了,基本上堆栈无奈还原,问题也无奈解决。

所以,这些 debug 信息尤为重要,是咱们剖析 NE 问题的要害信息,那么咱们在编译 so 时候务必保留一份未被 strip 的 so 或者剥离后的符号表信息,以供前面问题剖析,并且每次编译的 so 都须要保留,一旦产生代码批改从新编译,那么批改前后的符号表信息会无奈对应,也无奈进行剖析。

1.2 查看 so 状态

事实上,也能够通过命令行来查看 so 的状态,Mac 下应用 file 命令即可,在命令返回值外面能够查看到 so 的一些根本信息。

如下图所示,stripped 代表是没有 debug 信息的 so,with debug_info, not stripped 代表携带 debug 信息的 so。

file libbreakpad-core-s.so
libbreakpad-core-s.so: *******, BuildID[sha1]=54ad86d708f4dc0926ad220b098d2a9e71da235a, stripped
file libbreakpad-core.so
libbreakpad-core.so: ******, BuildID[sha1]=54ad86d708f4dc0926ad220b098d2a9e71da235a, with debug_info, not stripped

如果你是 Windows 零碎的话,那么我劝你装一个 Linux 子系统,而后在 Linux 执行同样的命令,同样也能够失去该信息。

接下来看下咱们如何获取两种状态下的 so。

1.3 获取 strip 和未被 strip 的 so

目前 Android Studio 无论是应用 mk 或者 Cmake 编译的形式都会同时输入 strip 和未 strip 的 so,如下图是 Cmake 编译 so 产生的两个对应的 so。

strip 之前的 so 门路:_build/intermediates/transforms/mergeJniLibs_

strip 之后的 so 门路:_build/intermediates/transforms/stripDebugSymbol_

另外也能够通过 Android SDK 提供的工具 aarch64-linux-android-strip 手动进行 strip,aarch64-linux-android-strip 这个工具位于 /Users/njvivo/Library/Android/sdk/ndk/21.3.6528147/toolchains 目录下。

这个工具有多种版本,次要针对不同的手机 CPU 架构,如果不晓得手机的 CPU 架构,能够连贯手机应用以下命令查看:

adb shell cat /proc/cpuinfo
Processor   : AArch64 Processor rev 12 (aarch64)

如上图能够看到我的手机 CPU 用的是 aarch64,所以应用 aarch64 对应的工具 aarch64-linux-android-strip,因为 NDK 提供了很多工具,后续都按照此准则应用即可:

aarch64 架构
/Users/njvivo/Library/Android/sdk/ndk/21.3.6528147/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/aarch64-linux-android-strip
arm 架构
/Users/njvivo/Library/Android/sdk/ndk/21.3.6528147/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/arm-linux-android-strip

应用如下命令能够间接将 debug 的 so 进行 strip

aarch64-linux-android-strip --strip-all libbreakpad-core.so

应用 Cmake 进行编译的时候,能够减少如下命令,能够间接编译出 strip 的 so

#set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -s")
#set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -s")

应用 mk 文件进行编译的时候,能够减少如下命令,也能够间接编译出 strip 的 so

-fvisibility=hidden

二、NE 捕捉与解析

NE 解析顾名思议就是堆栈解析,当然所有的前提就是须要保留一份带符号表、也就是未被 strip 的 so,如果你只有 strip 之后的 so,那就无能为力了,堆栈根本无奈还原了。

个别有以下三种形式能够捕捉和还原堆栈。

2.1 logcat 捕捉

顾名思义,就是通过 logcat 进行捕捉,咱们通过 Android Studio 关上 logcat,制作一个 NE,只能看到很多相似 #00 pc 00000000000161a0 的符号,并没有一个能够间接浏览的日志,咱们想通过 logcat 间接输入一份能够间接浏览的 log。

能够应用 Android/SDK/NDK 上面提供的一个工具 ndk-stack,它能够间接将 NE 输入的 log 解析为可浏览的日志。

ndk-stack 个别是位于 ndk 的工具上面,Mac 下的地址为

/Users/XXXX/Library/Android/sdk/ndk/21.3.6528147/ndk-stack

而后在该目录下执行控制台命令,或者在 Android Studio 的 terminal 中执行也可

adb shell logcat | androidsdk 绝对路径 /ndk-stack -sym so 所在目录 

如此控制台在利用产生 NE 的时候便会输入如下日志,由日志能够看出,解体对应的 so 以及对应的办法名,如果有 c 的源码,那么就很容易定位问题。

promote:~ njvivo$ adb shell logcat | ndk-stack -sym libbreakpad-core.so
********** Crash dump: **********
Build fingerprint: 'vivo/PD1809/PD1809:8.1.0/OPM1.171019.026/compil04252203:user/release-keys'
#00 0x00000000000161a0 /data/app/com.android.necase-lEp0warh8FqicyY1YqGXXA==/lib/arm64/libbreakpad-core.so (Java_com_online_breakpad_BreakpadInit_nUpdateLaunchInfo+16)
#01 0x00000000000090cc /data/app/com.android.necase-lEp0warh8FqicyY1YqGXXA==/oat/arm64/base.odex (offset 0x9000)
Crash dump is completed

其实 ndk-stack 这个工具原理就是外部集成利用了 addr2line 来实时解析堆栈并且显示在控制台中。

看到这里有的小伙伴就感觉那这个不是很简略,然而理论的解体场景一是不容易复现,二是用户的场景有时候很难模仿,那么线上的 NE 解体又该如何监测和定位呢,有两种形式。

2.2 通过 DropBox 日志解析 – 实用于零碎利用

这个很简略,DropBox 会记录 JE,NE,ANR 的各种日志,只须要将 DropBox 上面的日志传上来即可进行剖析解决,上面贴上一份日志示例。

解析计划 1:

借助上述的 ndk-stack 工具,能够间接将 DropBox 上面的日志解析成堆栈,从中能够看出,解体在 breakpad.cpp 第 111 行的 Crash() 办法中。

ndk-stack -sym /Users/njvivo/Desktop/NE -dump data_app_native_crash@1605531663898.txt
********** Crash dump: **********
Build fingerprint: 'vivo/PD1809/PD1809:8.1.0/OPM1.171019.026/compil04252203:user/release-keys'
#00 0x00000000000161a0 /data/app/com.android.necase-lEp0warh8FqicyY1YqGXXA==/lib/arm64/libbreakpad-core.so (Java_com_online_breakpad_BreakpadInit_nUpdateLaunchInfo+16)
Crash()
/Users/njvivo/Documents/project/Breakpad/breakpad-build/src/main/cpp/breakpad.cpp:111:8
Java_com_online_breakpad_BreakpadInit_nUpdateLaunchInfo
/Users/njvivo/Documents/project/Breakpad/breakpad-build/src/main/cpp/breakpad.cpp:122:0
#01 0x00000000000090cc /data/app/com.android.necase-lEp0warh8FqicyY1YqGXXA==/oat/arm64/base.odex (offset 0x9000)
Crash dump is completed

解析计划 2:

还是利用 Android/SDK/NDK 提供的工具 linux-android-addr2line,这个工具位于 /Users/njvivo/Library/Android/sdk/ndk 目录下,有两个版本。

aarch64 架构
/Users/njvivo/Library/Android/sdk/ndk/21.3.6528147/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/aarch64-linux-android-addr2line
arm 架构
/Users/njvivo/Library/Android/sdk/ndk/21.3.6528147/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-addr2line

命令应用办法如下,联合未被 strip 的 so 以及日志外面呈现的堆栈符号 00000000000161a0,同样能够解析出解体地址和办法。

aarch64-linux-android-addr2line -f -C -e libbreakpad-core.so 00000000000161a0
 
Crash()
/Users/njvivo/Documents/project/Breakpad/breakpad-build/src/main/cpp/breakpad.cpp:111

基于以上,看似也很简略,然而有一个致命的问题就是 DropBox 只有零碎利用能拜访,非零碎利用基本拿不到日志,那么,非零碎利用该怎么办呢?

2.3 通过 BreakPad 捕捉解析 – 实用于所有利用

非零碎利用能够通过 google 提供的开源工具 BreakPad 进行监测剖析,CrashSDK 也是采纳的此种形式,能够实时监听到 NE 的产生,并且记录相干的文件,从而能够将解体和相应的利用解体时的启动、场景等联合起来上报。

上面简略介绍一下 BreakPad 的应用形式。

2.3.1 BreakPad 的实现性能

BreakPad 次要提供两个个性能,NE 的监听和回调,生成 minidump 文件,也就是 dmp 结尾的文件,另外提供两个工具,符号表工具和堆栈还原工具。

  • 符号表工具: 用于从 so 中提取出 debug 信息,获取到堆栈对应的符号表。
  • 堆栈还原工具: 用于将 BreakPad 生成的 dump 文件还原成符号,也就是堆栈偏移值。

这两个工具会在编译 BreakPad 源码的时候产生。

编译完之后会产生 minidump_stackwalk 工具,有些同学不想编译的话,Android Studio 自身也提供了这个工具。

这个 minidump_stackwalk 程序在 Android Studio 的目录上面也存在,能够拿进去间接应用,如果不想编译的话,间接到该目录上面取即可,Mac 门路为:

/Applications/Android Studio.app/Contents/bin/lldb/bin/minidump_stackwalk

2.3.2 BreakPad 的捕捉原理

由上述能够得悉,BreakPad 在利用产生 NE 解体时,能够将 NE 对应的 minidump 文件写入到本地,同时会回调给应用层,应用层能够针对本次解体做一些解决,达到捕捉统计的作用,后续将 minidump 文件上传之后联合 minidump_stackwalk 以及 addr2line 工具能够还原出理论堆栈,示意图如下:

在利用产生 NE 时,BreakPad 会在手机本地生成一个 dump 文件,如图所示:

失去了以上文件,咱们只能晓得利用产生了 NE,然而这些文件其实是不可读的,须要解析这些文件。

上面着重讲一下如何剖析下面产生的 NE:

2.3.3 解析 dump 文件

1、获取 NE 解体的 dump 文件,将方才失去的 minidump_stackwalk 和 dump 文件放在同一个目录,也能够不放,填写门路的时候填写绝对路径即可。

而后在该目录下的终端窗口执行以下命令,该命令示意用 minidump_stackwalk 解析 dump 文件,解析后的信息输入到当前目录下的 crashLog.txt 文件。

./minidump_stackwalk xxxxxxxx.dmp >crashLog.txt

2、执行完之后,minidump_stackwalk 会将 NE 的相干信息写到 crashLog.txt 外面,详细信息如图所示:

3、依据解析出的 NE 信息,关注图中红框,能够得悉,这个解体产生的 libbreakpad-core.so 外面,0x161a0 代表解体产生在绝对根地位偏移 161a0 的地位

2.3.4 获取解体堆栈

1、利用之前提到的 addr2line 工具,能够依据产生 Crash 的 so 文件以及偏移地址(0x161a0)能够得出产生 crash 的办法、行数和调用堆栈关系。

2、在其根目录对的终端窗口运行以下命令。

arm-linux-androideabi-addr2line -C -f -e ${SOPATH} ${Address}
-C -f           // 打印谬误行数所在的函数名称
-e                // 打印谬误地址的对应门路及行数
${SOPATH}         //so 库门路
${Address}        // 须要转换的堆栈错误信息地址,能够增加多个,然而两头要用空格隔开,例如:0x161a0

3、如下图是实在运行的示例

aarch64-linux-android-addr2line -f -C -e libbreakpad-core.so 0x161a0
Crash()
/Users/njvivo/Documents/project/Breakpad/breakpad-build/src/main/cpp/breakpad.cpp:111

由上图能够晓得,该解体产生在 breakpad.cpp 文件的第 111 行,函数名是 Crash(),与实在的文件统一,解体代码如下:

void Crash() {volatile int *a = (int *) (NULL);
    *a = 1; // 此处在代码里是 111 行
}
 
extern "C"
JNIEXPORT void JNICALL
Java_com_online_breakpad_BreakpadInit_nUpdateLaunchInfo(JNIEnv *env, jobject instance,
                                                        jstring mLaunchInfoStr_) {
 
 
    DO_TRY
    {Crash();
        const char *mLaunchInfoStr = env->GetStringUTFChars(mLaunchInfoStr_, 0);
        launch_info = (char *) mLaunchInfoStr;
//        env->ReleaseStringUTFChars(mLaunchInfoStr_, mLaunchInfoStr);
    }
    DO_CATCH("updateLaunchInfo");
 
}

基于以上,便能够通过利用收集的 dump 文件解析的 NE 的具体堆栈信息。

 三、so 符号表的提取

3.1 提取 so 的符号表

通过以上内容,咱们晓得,so 中蕴含了一些 debug 信息,又叫做符号表,那么咱们如何将这些 debug 信息独自剥离进去呢,ndk 也给咱们提供了相干的工具。

aarch64 架构
/Users/njvivo/Library/Android/sdk/ndk/21.3.6528147/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/aarch64-linux-android-objdump
arm 架构
/Users/njvivo/Library/Android/sdk/ndk/21.3.6528147/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/arm-linux-android-objdump

如下是命令运行的形式,通过此命令,能够将 so 中的 debug 信息提取到文件中。

promote:~ njvivo$ aarch64-linux-android-objdump -S libbreakpad-core.so > breakpad.asm

3.2 符号表剖析

3.2.1 间接剖析

如下图所示就是输入的符号表文件,联合下面的 log 以及上面的符号表文件,咱们同样能够剖析出堆栈。

如 log 中所示,曾经表明了解体地址是 161a0,而 161a0 对应的代码是 *a=1, 由下面的剖析咱们曾经晓得该解体是在 breakpad.cpp 的 111 行,也就是 *a= 1 的地位,完全符合预期。

backtrace:
#00 pc 00000000000161a0 /data/app/com.android.necase-lEp0warh8FqicyY1YqGXXA==/lib/arm64/libbreakpad-core.so (Java_com_online_breakpad_BreakpadInit_nUpdateLaunchInfo+16)
#01 pc 00000000000090cc /data/app/com.android.necase-lEp0warh8FqicyY1YqGXXA==/oat/arm64/base.odex (offset 0x9000)

3.2.2 工具解析

google 提供了一个 Python 的工具,将符号表和 log 联合起来能够间接剖析出堆栈,python 工具拜访 https://code.google.com/archive/p/android-ndk-stacktrace-analyzer/ 能够进行下载。

执行命令,就能够解析出相干堆栈,该工具能够用于服务端批量进行解析,此处不再具体阐明。

python parse_stack.py <asm-file> <logcat-file>

3.2.3 偏移地位简析

下面文章提到了一个偏移地位的概念,笔者对此理解也不多,不过大抵有一个概念,C 代码有一个根地位的代码的,每行代码绝对根代码都有一个偏移地位。

如上图示例 log 中有一行语句 (Java\_com\_online\_breakpad\_BreakpadInit_nUpdateLaunchInfo+16),+16 就是代表绝对 nUpdateLaunchInfo 办法的地位往后偏移 16。

由上图能够看到,nUpdateLaunchInfo 办法的地位是 16190,偏移 16,也就是 16190+10(10 进制的 16 转化 16 进制后为 10)=161a0,同日志输入的一样。

 四、总结

以上就是本篇文章的所有内容,次要简述了 so 的一些基础知识,以及 Android 中 NE 的解体,捕捉解析计划,心愿通过该文档对波及到 NE 相干的小伙伴带来帮忙,同时后续 CrashSDK 也会反对相干 NE 的解析性能。

作者:vivo-MaLian

正文完
 0