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.solibbreakpad-core-s.so: *******, BuildID[sha1]=54ad86d708f4dc0926ad220b098d2a9e71da235a, strippedfile libbreakpad-core.solibbreakpad-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/cpuinfoProcessor : 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-striparm架构/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:8Java_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-addr2linearm架构/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 0x161a0Crash()/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 JNICALLJava_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-objdumparm架构/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