Android NativeCrash 捕获与解析

Android 开发中,NE一直是不可忽略却又异常难解的一个问题,缘由是这里面涉及到了跨端开发和分析,须要同时熟悉 Java,C&C++,而且须要熟悉 NDK开发,而且解决起来不像 Java异常那么明了,本文为了解决部分疑惑,将从NE的捕获,解析与还原等三个方面进行探索。python

1、NE 简介

NE全称NativeCrash,就是C或者C++运行过程当中产生的错误,NE不一样于普通的 Java 错误,普通的logcat没法直接还原成可阅读的堆栈,通常没有源码也没法调试。linux

因此平常应用层的工程师,即便咱们内部有云诊断的日志,通常也会忽略NE的错误,那么遇到这些问题,做为应用层、对C++不甚了解的工程师可否解决还原堆栈,可否快速定位或者解决NE的问题呢?android

下面将着重介绍:shell

1.1 so 组成

咱们先了解一下 so 的组成,一个完整的 so 由C代码加一些 debug 信息组成,这些debug信息会记录 so 中全部方法的对照表,就是方法名和其便宜地址的对应表,也叫作符号表,这种 so  也叫作未 strip 的,一般体积会比较大。架构

一般release的 so 都是须要通过一个strip操做的,这样strip以后的 so 中的debug信息会被剥离,整个 so 的体积也会缩小。app

以下图所示:ide

以下能够看到strip以前和以后的大小对比。函数

若是对 NE 或者 so 不了解的,能够简单将这个debug信息理解为Java代码混淆中的mapping文件,只有拥有这个mapping文件才能进行堆栈分析。工具

若是堆栈信息丢了,基本上堆栈没法还原,问题也没法解决。ui

因此,这些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

2、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文件

一、获取NE崩溃的dump文件,将刚才获得的minidump_stackwalk和dump文件放在同一个目录,也能够不放,填写路径的时候填写绝对路径便可。

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

./minidump_stackwalk xxxxxxxx.dmp >crashLog.txt

二、执行完以后,minidump_stackwalk会将NE的相关信息写到crashLog.txt里面,详细信息如图所示:

三、根据解析出的NE信息,关注图中红框,能够得知,这个崩溃发生的 libbreakpad-core.so 里面,0x161a0表明崩溃发生在相对根位置偏移161a0的位置

2.3.4 获取崩溃堆栈

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

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

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

三、以下图是真实运行的示例

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的详细堆栈信息。

 3、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,同日志输出的同样。

 4、总结

以上就是本篇文章的全部内容,主要简述了so的一些基础知识,以及Android中NE的崩溃,捕获解析方案,但愿经过该文档对涉及到NE相关的小伙伴带来帮助,同时后续CrashSDK也会支持相关NE的解析功能。

做者:vivo-MaLian
相关文章
相关标签/搜索