Android 開發中,NE一直是不可忽略卻又異常難解的一個問題,緣由是這裏面涉及到了跨端開發和分析,須要同時熟悉 Java,C&C++,而且須要熟悉 NDK開發,而且解決起來不像 Java異常那麼明瞭,本文爲了解決部分疑惑,將從NE的捕獲,解析與還原等三個方面進行探索。python
NE全稱NativeCrash,就是C或者C++運行過程當中產生的錯誤,NE不一樣於普通的 Java 錯誤,普通的logcat沒法直接還原成可閱讀的堆棧,通常沒有源碼也沒法調試。linux
因此平常應用層的工程師,即便咱們內部有云診斷的日誌,通常也會忽略NE的錯誤,那麼遇到這些問題,做爲應用層、對C++不甚瞭解的工程師可否解決還原堆棧,可否快速定位或者解決NE的問題呢?android
下面將着重介紹:shell
咱們先了解一下 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 都須要保存,一旦產生代碼修改從新編譯,那麼修改先後的符號表信息會沒法對應,也沒法進行分析。
事實上,也能夠經過命令行來查看 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。
目前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解析顧名思議就是堆棧解析,固然全部的前提就是須要保存一份帶符號表、也就是未被strip的so,若是你只有strip以後的so,那就無能爲力了,堆棧基本沒法還原了。
通常有如下三種方式能夠捕獲和還原堆棧。
顧名思義,就是經過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崩潰又該如何監測和定位呢,有兩種方式。
這個很簡單,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只有系統應用能訪問,非系統應用根本拿不到日誌,那麼,非系統應用該怎麼辦呢?
非系統應用能夠經過google提供的開源工具BreakPad進行監測分析,CrashSDK也是採用的此種方式,能夠實時監聽到NE的發生,而且記錄相關的文件, 從而能夠將崩潰和相應的應用崩潰時的啓動、場景等結合起來上報。
下面簡單介紹一下BreakPad的使用方式。
BreakPad主要提供兩個個功能,NE的監聽和回調,生成minidump文件,也就是dmp結尾的文件,另外提供兩個工具,符號表工具和堆棧還原工具。
這兩個工具會在編譯BreakPad源碼的時候產生。
編譯完以後會產生minidump_stackwalk工具,有些同窗不想編譯的話,Android Studio自己也提供了這個工具。
這個minidump_stackwalk程序在Android Studio的目錄下面也存在,能夠拿出來直接使用,若是不想編譯的話,直接到該目錄下面取便可,Mac路徑爲:
/Applications/Android Studio.app/Contents/bin/lldb/bin/minidump_stackwalk
由上述能夠得知,BreakPad在應用發生NE崩潰時,能夠將NE對應的minidump文件寫入到本地,同時會回調給應用層,應用層能夠針對本次崩潰作一些處理,達到捕獲統計的做用,後續將minidump文件上傳以後結合minidump_stackwalk以及addr2line工具能夠還原出實際堆棧,示意圖以下:
在應用發生NE時,BreakPad會在手機本地生成一個dump文件,如圖所示:
獲得了以上文件,咱們只能知道應用發生了NE,可是這些文件實際上是不可讀的,須要解析這些文件。
下面着重講一下如何分析上面產生的NE:
一、獲取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的位置
一、利用以前提到的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的詳細堆棧信息。
經過以上內容,咱們知道,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
以下圖所示就是輸出的符號表文件,結合上面的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)
google提供了一個Python的工具,將符號表和log結合起來能夠直接分析出堆棧,python工具訪問https://code.google.com/archive/p/android-ndk-stacktrace-analyzer/ 能夠進行下載。
執行命令,就能夠解析出相關堆棧,該工具能夠用於服務端批量進行解析,此處再也不詳細說明。
python parse_stack.py <asm-file> <logcat-file>
上面文章提到了一個偏移位置的概念,筆者對此瞭解也很少,不過大體有一個概念,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