捕獲與解析Android NativeCrash
NE全稱 NativeException,就是C或者C++運(yùn)行過(guò)程中產(chǎn)生的錯(cuò)誤,NE不同于普通的 Java 錯(cuò)誤,普通的logcat無(wú)法直接還原成可閱讀的堆棧,一般沒(méi)有源碼也無(wú)法調(diào)試。
所以日常應(yīng)用層的工程師,即使我們內(nèi)部有云診斷的日志,一般也會(huì)忽略NE的錯(cuò)誤,那么遇到這些問(wèn)題,作為應(yīng)用層、對(duì)C++不甚了解的工程師能否解決還原堆棧,能否快速定位或者解決NE的問(wèn)題呢?
下面將著重介紹:
1.1、so 組成我們先了解一下 so 的組成,一個(gè)完整的 so 由C代碼加一些 debug 信息組成,這些debug信息會(huì)記錄 so 中所有方法的對(duì)照表,就是方法名和其偏移地址的對(duì)應(yīng)表,也叫做符號(hào)表,這種 so 也叫做未 strip 的,通常體積會(huì)比較大。
通常release的 so 都是需要經(jīng)過(guò)一個(gè)strip操作的,這樣strip之后的 so 中的debug信息會(huì)被剝離,整個(gè) so 的體積也會(huì)縮小。
如下圖所示:
如下可以看到strip之前和之后的大小對(duì)比。
如果對(duì) NE 或者 so 不了解的,可以簡(jiǎn)單將這個(gè)debug信息理解為Java代碼混淆中的mapping文件,只有擁有這個(gè)mapping文件才能進(jìn)行堆棧分析。
如果堆棧信息丟了,基本上堆棧無(wú)法還原,問(wèn)題也無(wú)法解決。
所以,這些debug信息尤為重要,是我們分析NE問(wèn)題的關(guān)鍵信息,那么我們?cè)诰幾g so 時(shí)候務(wù)必保留一份未被strip的so 或者剝離后的符號(hào)表信息,以供后面問(wèn)題分析,并且每次編譯的so 都需要保存,一旦產(chǎn)生代碼修改重新編譯,那么修改前后的符號(hào)表信息會(huì)無(wú)法對(duì)應(yīng),也無(wú)法進(jìn)行分析。
1.2、查看 so 狀態(tài)事實(shí)上,也可以通過(guò)命令行來(lái)查看 so 的狀態(tài),Mac下使用 file 命令即可,在命令返回值里面可以查看到so的一些基本信息。
如下圖所示,stripped代表是沒(méi)有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系統(tǒng)的話,那么我勸你裝一個(gè) Linux子系統(tǒng),然后在 Linux執(zhí)行同樣的命令,同樣也可以得到該信息。
接下來(lái)看下我們?nèi)绾潍@取兩種狀態(tài)下的so。
1.3、獲取 strip 和未被 strip 的 so目前Android Studio無(wú)論是使用mk或者Cmake編譯的方式都會(huì)同時(shí)輸出strip和未strip的so,如下圖是Cmake編譯so產(chǎn)生的兩個(gè)對(duì)應(yīng)的so。
strip之前的so路徑:build/intermediates/transforms/mergeJniLibs
strip之后的so路徑:build/intermediates/transforms/stripDebugSymbol
另外也可以通過(guò)Android SDK提供的工具aarch64-linux-android-strip手動(dòng)進(jìn)行strip,aarch64-linux-android-strip這個(gè)工具位于/Users/njvivo/Library/Android/sdk/ndk/21.3.6528147/toolchains目錄下。
這個(gè)工具有多種版本,主要針對(duì)不同的手機(jī)CPU架構(gòu),如果不知道手機(jī)的CPU架構(gòu),可以連接手機(jī)使用以下命令查看:
adb shell cat /proc/cpuinfo
Processor : AArch64 Processor rev 12 (aarch64)
如上圖可以看到我的手機(jī)CPU用的是aarch64,所以使用aarch64對(duì)應(yīng)的工具aarch64-linux-android-strip,由于NDK提供了很多工具,后續(xù)都依照此原則使用即可:
aarch64架構(gòu)
/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架構(gòu)
/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進(jìn)行strip
aarch64-linux-android-strip --strip-all libbreakpad-core.so
使用Cmake進(jìn)行編譯的時(shí)候,可以增加如下命令,可以直接編譯出strip的so
#set(CMAKE_C_FLAGS_RELEASE '${CMAKE_C_FLAGS_RELEASE} -s')
#set(CMAKE_CXX_FLAGS_RELEASE '${CMAKE_CXX_FLAGS_RELEASE} -s')
使用mk文件進(jìn)行編譯的時(shí)候,可以增加如下命令,也可以直接編譯出strip的so
-fvisibility=hidden
二、NE 捕獲與解析NE解析顧名思議就是堆棧解析,當(dāng)然所有的前提就是需要保存一份帶符號(hào)表、也就是未被strip的so,如果你只有strip之后的so,那就無(wú)能為力了,堆棧基本無(wú)法還原了。
一般有以下三種方式可以捕獲和還原堆棧。
2.1、logcat捕獲顧名思義,就是通過(guò)logcat進(jìn)行捕獲,我們通過(guò)Android Studio打開(kāi)logcat,制造一個(gè)NE,只能看到很多類似#00 pc 00000000000161a0的符號(hào),并沒(méi)有一個(gè)可以直接閱讀的日志,我們想通過(guò)logcat直接輸出一份可以直接閱讀的log。
可以使用Android/SDK/NDK下面提供的一個(gè)工具ndk-stack,它可以直接將NE輸出的log解析為可閱讀的日志。
ndk-stack一般是位于ndk的工具下面,Mac下的地址為
/Users/XXXX/Library/Android/sdk/ndk/21.3.6528147/ndk-stack
然后在該目錄下執(zhí)行控制臺(tái)命令,或者在 Android Studio的terminal中執(zhí)行也可
adb shell logcat | androidsdk絕對(duì)路徑/ndk-stack -sym so所在目錄
如此控制臺(tái)在應(yīng)用發(fā)生NE的時(shí)候便會(huì)輸出如下日志,由日志可以看出,崩潰對(duì)應(yīng)的so以及對(duì)應(yīng)的方法名,如果有c的源碼,那么就很容易定位問(wèn)題。
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
其實(shí)ndk-stack這個(gè)工具原理就是內(nèi)部集成利用了addr2line來(lái)實(shí)時(shí)解析堆棧并且顯示在控制臺(tái)中。
看到這里有的小伙伴就覺(jué)得那這個(gè)不是很簡(jiǎn)單,但是實(shí)際的崩潰場(chǎng)景一是不容易復(fù)現(xiàn),二是用戶的場(chǎng)景有時(shí)候很難模擬,那么線上的NE崩潰又該如何監(jiān)測(cè)和定位呢,有兩種方式。
2.2、通過(guò)DropBox日志解析--適用于系統(tǒng)應(yīng)用這個(gè)很簡(jiǎn)單,DropBox會(huì)記錄JE,NE,ANR的各種日志,只需要將DropBox下面的日志傳上來(lái)即可進(jìn)行分析解決,下面貼上一份日志示例。
解析方案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,這個(gè)工具位于/Users/njvivo/Library/Android/sdk/ndk目錄下,有兩個(gè)版本。
aarch64架構(gòu)
/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架構(gòu)
/Users/njvivo/Library/Android/sdk/ndk/21.3.6528147/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-addr2line
命令使用方法如下,結(jié)合未被strip的so以及日志里面出現(xiàn)的堆棧符號(hào)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
基于以上,看似也很簡(jiǎn)單,但是有一個(gè)致命的問(wèn)題就是DropBox只有系統(tǒng)應(yīng)用能訪問(wèn),非系統(tǒng)應(yīng)用根本拿不到日志,那么,非系統(tǒng)應(yīng)用該怎么辦呢?
2.3、通過(guò)BreakPad捕獲解析--適用于所有應(yīng)用非系統(tǒng)應(yīng)用可以通過(guò)google提供的開(kāi)源工具BreakPad進(jìn)行監(jiān)測(cè)分析,CrashSDK也是采用的此種方式,可以實(shí)時(shí)監(jiān)聽(tīng)到NE的發(fā)生,并且記錄相關(guān)的文件, 從而可以將崩潰和相應(yīng)的應(yīng)用崩潰時(shí)的啟動(dòng)、場(chǎng)景等結(jié)合起來(lái)上報(bào)。
下面簡(jiǎn)單介紹一下BreakPad的使用方式。
2.3.1、BreakPad的實(shí)現(xiàn)功能BreakPad主要提供兩個(gè)個(gè)功能,NE的監(jiān)聽(tīng)和回調(diào),生成minidump文件,也就是dmp結(jié)尾的文件,另外提供兩個(gè)工具,符號(hào)表工具和堆棧還原工具。
這兩個(gè)工具會(huì)在編譯BreakPad源碼的時(shí)候產(chǎn)生。
編譯完之后會(huì)產(chǎn)生minidump_stackwalk工具,有些同學(xué)不想編譯的話,Android Studio本身也提供了這個(gè)工具。
這個(gè)minidump_stackwalk程序在Android Studio的目錄下面也存在,可以拿出來(lái)直接使用,如果不想編譯的話,直接到該目錄下面取即可,Mac路徑為:
/Applications/Android Studio.app/Contents/bin/lldb/bin/minidump_stackwalk
2.3.2、BreakPad的捕獲原理由上述可以得知,BreakPad在應(yīng)用發(fā)生NE崩潰時(shí),可以將NE對(duì)應(yīng)的minidump文件寫(xiě)入到本地,同時(shí)會(huì)回調(diào)給應(yīng)用層,應(yīng)用層可以針對(duì)本次崩潰做一些處理,達(dá)到捕獲統(tǒng)計(jì)的作用,后續(xù)將minidump文件上傳之后結(jié)合minidump_stackwalk以及addr2line工具可以還原出實(shí)際堆棧,示意圖如下:
在應(yīng)用發(fā)生NE時(shí),BreakPad會(huì)在手機(jī)本地生成一個(gè)dump文件,如圖所示:
得到了以上文件,我們只能知道應(yīng)用發(fā)生了NE,但是這些文件其實(shí)是不可讀的,需要解析這些文件。
下面著重講一下如何分析上面產(chǎn)生的NE:
2.3.3、解析dump文件1、獲取NE崩潰的dump文件,將剛才得到的minidump_stackwalk和dump文件放在同一個(gè)目錄,也可以不放,填寫(xiě)路徑的時(shí)候填寫(xiě)絕對(duì)路徑即可。
然后在該目錄下的終端窗口執(zhí)行以下命令,該命令表示用minidump_stackwalk解析dump文件,解析后的信息輸出到當(dāng)前目錄下的crashLog.txt文件。
./minidump_stackwalk xxxxxxxx.dmp >crashLog.txt
2、執(zhí)行完之后,minidump_stackwalk會(huì)將NE的相關(guān)信息寫(xiě)到crashLog.txt里面,詳細(xì)信息如圖所示:
3、根據(jù)解析出的NE信息,關(guān)注圖中紅框,可以得知,這個(gè)崩潰發(fā)生的libbreakpad-core.so里面,0x161a0代表崩潰發(fā)生在相對(duì)根位置偏移161a0的位置
2.3.4、獲取崩潰堆棧1、利用之前提到的addr2line工具,可以根據(jù)發(fā)生Crash的so文件以及偏移地址(0x161a0)可以得出產(chǎn)生crash的方法、行數(shù)和調(diào)用堆棧關(guān)系。
2、在其根目錄對(duì)的終端窗口運(yùn)行以下命令。
arm-linux-androideabi-addr2line -C -f -e ${SOPATH} ${Address}
-C -f //打印錯(cuò)誤行數(shù)所在的函數(shù)名稱
-e //打印錯(cuò)誤地址的對(duì)應(yīng)路徑及行數(shù)
${SOPATH} //so庫(kù)路徑
${Address} //需要轉(zhuǎn)換的堆棧錯(cuò)誤信息地址,可以添加多個(gè),但是中間要用空格隔開(kāi),例如:0x161a0
3、如下圖是真實(shí)運(yùn)行的示例
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
由上圖可以知道,該崩潰發(fā)生在breakpad.cpp文件的第111行,函數(shù)名是Crash(),與真實(shí)的文件一致,崩潰代碼如下:
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'); }
基于以上,便可以通過(guò)應(yīng)用收集的dump文件解析的NE的詳細(xì)堆棧信息。
三、so符號(hào)表的提取3.1、提取 so 的符號(hào)表通過(guò)以上內(nèi)容,我們知道,so中包含了一些debug信息,又叫做符號(hào)表,那么我們?nèi)绾螌⑦@些debug信息單獨(dú)剝離出來(lái)呢,ndk也給我們提供了相關(guān)的工具。
aarch64架構(gòu)
/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架構(gòu)
/Users/njvivo/Library/Android/sdk/ndk/21.3.6528147/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/arm-linux-android-objdump
如下是命令運(yùn)行的方式,通過(guò)此命令,可以將so中的debug信息提取到文件中。
promote:~ njvivo$ aarch64-linux-android-objdump -S libbreakpad-core.so > breakpad.asm
3.2、符號(hào)表分析3.2.1、直接分析如下圖所示就是輸出的符號(hào)表文件,結(jié)合上面的log以及下面的符號(hào)表文件,我們同樣可以分析出堆棧。
如log中所示,已經(jīng)表明了崩潰地址是161a0,而161a0對(duì)應(yīng)的代碼是*a=1,由上面的分析我們已經(jīng)知道該崩潰是在breakpad.cpp的111行,也就是*a=1的位置,完全符合預(yù)期。
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)
google提供了一個(gè)Python的工具,將符號(hào)表和log結(jié)合起來(lái)可以直接分析出堆棧
執(zhí)行命令,就可以解析出相關(guān)堆棧,該工具可以用于服務(wù)端批量進(jìn)行解析,此處不再詳細(xì)說(shuō)明。
python parse_stack.py <asm-file> <logcat-file>
3.2.3、偏移位置簡(jiǎn)析上面文章提到了一個(gè)偏移位置的概念,筆者對(duì)此了解也不多,不過(guò)大致有一個(gè)概念,C代碼有一個(gè)根位置的代碼的,每行代碼相對(duì)根代碼都有一個(gè)偏移位置。
如上圖示例log中有一行語(yǔ)句(Java_com_online_breakpad_BreakpadInit_nUpdateLaunchInfo+16),+16就是代表相對(duì)nUpdateLaunchInfo方法的位置往后偏移16。
由上圖可以看到,nUpdateLaunchInfo方法的位置是16190,偏移16,也就是16190+10(10進(jìn)制的16轉(zhuǎn)化16進(jìn)制后為10)=161a0,同日志輸出的一樣。
四、總結(jié)以上就是本篇文章的所有內(nèi)容,主要簡(jiǎn)述了so的一些基礎(chǔ)知識(shí),以及Android中NE的崩潰,捕獲解析方案,希望通過(guò)該文檔對(duì)涉及到NE相關(guān)的小伙伴帶來(lái)幫助,同時(shí)后續(xù)CrashSDK也會(huì)支持相關(guān)NE的解析功能。
以上就是捕獲與解析Android NativeCrash的詳細(xì)內(nèi)容,更多關(guān)于Android NativeCrash的資料請(qǐng)關(guān)注好吧啦網(wǎng)其它相關(guān)文章!
相關(guān)文章:
1. XML解析錯(cuò)誤:未組織好 的解決辦法2. 概述IE和SQL2k開(kāi)發(fā)一個(gè)XML聊天程序3. phpstudy apache開(kāi)啟ssi使用詳解4. 簡(jiǎn)單了解XML 樹(shù)結(jié)構(gòu)5. XML入門(mén)精解之結(jié)構(gòu)與語(yǔ)法6. PHP循環(huán)與分支知識(shí)點(diǎn)梳理7. ASP中實(shí)現(xiàn)字符部位類似.NET里String對(duì)象的PadLeft和PadRight函數(shù)8. ASP的Global.asa文件技巧用法9. CSS3使用過(guò)度動(dòng)畫(huà)和緩動(dòng)效果案例講解10. XHTML 1.0:標(biāo)記新的開(kāi)端
