在Android應用crash的類型中,native類型crash應該是比較難的一種了,由於你們接觸的少,而後相對也要多轉幾道工序,全部大部分對這個都比較生疏。雖然相關文章也有不少了,可是我在剛開始學的過程當中仍是遇到一些問題,下面一一記錄,以便未來翻閱。java
分析native crash 日誌須要幾個東西:linux
logandroid
native crash的日誌都是從一行星號(*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***)開始,這行星號也是ndk-stack工具用來查找native crash的標誌。一個native crash日誌例子:程序員
1 04-16 11:18:00.323 26512 26512 F DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 2 04-16 11:18:00.324 26512 26512 F DEBUG : Build fingerprint: 'nubia/NX531J/NX531J:7.1.1/NMF26F/nubia04130311:user/release-keys' 3 04-16 11:18:00.324 26512 26512 F DEBUG : Revision: '0' 4 04-16 11:18:00.324 26512 26512 F DEBUG : ABI: 'arm' 5 04-16 11:18:00.324 26512 26512 F DEBUG : pid: 26452, tid: 26491, name: Thread-4 >>> com.willhua.opencvstudy <<< 6 04-16 11:18:00.324 26512 26512 F DEBUG : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xc8080000 7 04-16 11:18:00.324 26512 26512 F DEBUG : r0 c807a400 r1 c7e80000 r2 00000069 r3 00000069 8 04-16 11:18:00.324 26512 26512 F DEBUG : r4 caa4ca9c r5 007e9000 r6 00000964 r7 c69838f8 9 04-16 11:18:00.324 26512 26512 F DEBUG : r8 00005c00 r9 00017002 sl 001fa400 fp cac6ec00 10 04-16 11:18:00.324 26512 26512 F DEBUG : ip e71aac64 sp c69838e8 lr caa8e949 pc caa8e97c cpsr 800f0030 11 04-16 11:18:00.326 26512 26512 F DEBUG : 12 04-16 11:18:00.326 26512 26512 F DEBUG : backtrace: 13 04-16 11:18:00.326 26512 26512 F DEBUG : #00 pc 0004097c /data/app/com.willhua.opencvstudy-1/lib/arm/libOpenCV.so (_Z14darkGrayThreadPv+179) 14 04-16 11:18:00.326 26512 26512 F DEBUG : #01 pc 000475d3 /system/lib/libc.so (_ZL15__pthread_startPv+22) 15 04-16 11:18:00.326 26512 26512 F DEBUG : #02 pc 00019d3d /system/lib/libc.so (__start_thread+6)
帶symbols的so文件shell
對於好比手機公司的開發人員來講,通常來講出問題的so對應的帶symbols的so都在out/target/product/<model_name>/symbols/system/lib/下面,而對於常見的使用AndroidStudio開發的單個應用來講,其對應的帶symbols的在<PROJECT_ROOT>\app\src\main\obj\local\<ABI>\下面的so,而不能是\app\src\main\libs\<ABI>的,這裏面的是不包含symbols信息的,拿這個去分析,輸出的結果就是「??:?」。其實這兩個so的體積對比也是很明顯的的,在個人應用中,前一個帶symbols的so的體積爲7M多,然後一個只有2M。編程
分析工具windows
對於使用linux系統做爲開發環境的,linux就自帶addr2line命令。而對於筆者這種使用Windows的,在sdk中安裝了NDK以後,在ndk中就帶有這些工具。數組
好比addr2line工具在:sdk\ndk-bundle\toolchains\arm-linux-androideabi-4.9\prebuilt\windows-x86_64\bin下面,同時這個bin下面包含不少其餘工具,好比objdump,readelf等;架構
ndk-stack工具則在sdk\ndk-bundle下面;app
關於這些工具的具體使用,在https://www.oschina.net/question/2241352_213433這篇文章中講的很詳細,我也就再也不重複。
可是提醒一點:crash log與對應的so必定要對應起來。即錯誤的狀況是:你拿了一份舊的log,而後你修改了so相關的源碼,而後編譯出來了新的so,你拿着這個新的so以及舊log中的地址去讓addr2line等分析,那確定是是得不到正確的結果的。
剛剛提到的那篇文章講的很詳細,爲了不之後找不到因此我就把它複製到這裏。
/****************************************************************************************************************************************/
轉自:https://www.oschina.net/question/2241352_213433
Android NDK 是在SDK前面又加上了「原生」二字,即Native Development Kit,所以又被Google稱爲「NDK」。衆所周知,Android程序運行在Dalvik虛擬機中,NDK容許用戶使用相似C / C++之類的原生代碼語言執行部分程序。NDK包括了:
爲什麼要用到NDK?歸納來講主要分爲如下幾種狀況:
Java Native Interface(JNI)標準是java平臺的一部分,它容許Java代碼和其餘語言寫的代碼進行交互。JNI是本地編程接口,它使得在 Java 虛擬機(VM) 內部運行的 Java 代碼可以與用其它編程語言(如 C、C++和彙編語言)編寫的應用程序和庫進行交互操做。
簡單來講,能夠認爲NDK就是可以方便快捷開發.so文件的工具。JNI的過程比較複雜,生成.so須要大量操做,而NDK就是簡化了這個過程。
NDK編譯生成的.so文件做爲程序的一部分,在運行發生異常時一樣會形成程序崩潰。不一樣於Java代碼異常形成的程序崩潰,在NDK的異常發生時,程序在Android設備上都會當即退出,即一般所說的閃退,而不會彈出「程序xxx無響應,是否當即關閉」之類的提示框。
NDK是使用C/C++來進行開發的,熟悉C/C++的程序員都知道,指針和內存管理是最重要也是最容易出問題的地方,稍有不慎就會遇到諸如內存無效訪問、無效對象、內存泄露、堆棧溢出等常見的問題,最後都是同一個結果:程序崩潰。例如咱們常說的空指針錯誤,就是當一個內存指針被置爲空(NULL)以後再次對其進行訪問;另一個常常出現的錯誤是,在程序的某個位置釋放了某個內存空間,然後在程序的其餘位置試圖訪問該內存地址,這就會產生一個無效地址錯誤。常見的錯誤類型以下:
利用Android NDK開發本地應用的時候,幾乎全部的程序員都遇到過程序崩潰的問題,但它的崩潰會在logcat中打印一堆看起來相似天書的堆棧信息,讓人舉足無措。單靠添加一行行的打印信息來定位錯誤代碼作在的行數,無疑是一件使人崩潰的事情。在網上搜索「Android NDK崩潰」,能夠搜索到不少文章來介紹如何經過Android提供的工具來查找和定位NDK的錯誤,但大都晦澀難懂。下面以一個實際的例子來講明,首先生成一個錯誤,而後演示如何經過兩種不一樣的方法,來定位錯誤的函數名和代碼行。
首先,看咱們在hello-jni程序的代碼中作了什麼(有關如何建立或導入工程,此處略),看下圖:在JNI_OnLoad()的函數中,即so加載時,調用willCrash()函數,而在willCrash()函數中, std::string的這種賦值方法會產生一個空指針錯誤。這樣,在hello-jni程序加載時就會閃退。咱們記一下這兩個行數:在61行調用了willCrash()函數;在69行發生了崩潰。
下面來看看發生崩潰(閃退)時系統打印的logcat日誌:
若是你看過logcat打印的NDK錯誤時的日誌就會知道,我省略了後面不少的內容,不少人看到這麼多密密麻麻的日誌就已經頭暈腦脹了,即便是不少資深的Android開發者,在面對NDK日誌時也大都默默的選擇了無視。
其實,只要你細心的查看,再配合Google 提供的工具,徹底能夠快速的準肯定位出錯的代碼位置,這個工做咱們稱之爲「符號化」。須要注意的是,若是要對NDK錯誤進行符號化的工做,須要保留編譯過程當中產生的包含符號表的so文件,這些文件通常保存在$PROJECT_PATH/obj/local/目錄下。
這個命令行工具包含在NDK工具的安裝目錄,和ndk-build和其餘一些經常使用的NDK命令放在一塊兒,好比在個人電腦上,其位置是/android-ndk-r9d/ndk-stack。根據Google官方文檔,NDK從r6版本開始提供ndk-stack命令,若是你用的以前的版本,建議仍是儘快升級至最新的版本。使用ndk –stack命令也有兩種方式
在運行程序的同時,使用adb獲取logcat日誌,並經過管道符輸出給ndk-stack,同時須要指定包含符號表的so文件位置;若是你的程序包含了多種CPU架構,在這裏需求根據錯誤發生時的手機CPU類型,選擇不一樣的CPU架構目錄,如:
當崩潰發生時,會獲得以下的信息:
咱們重點看一下#03和#04,這兩行都是在咱們本身生成的libhello-jni.so中的報錯信息,那麼會發現以下關鍵信息:
回想一下咱們的代碼,在JNI_OnLoad()函數中(第61行),咱們調用了willCrash()函數;在willCrash()函數中(第69行),咱們製造了一個錯誤。這些信息都被準確無誤的提取了出來!是否是很是簡單?
這種方法其實和上面的方法沒有什麼大的區別,僅僅是logcat日誌獲取的方式不一樣。能夠在程序運行的過程當中將logcat日誌保存到一個文件,甚至能夠在崩潰發生時,快速的將logcat日誌保存起來,而後再進行分析,比上面的方法稍微靈活一點,並且日誌能夠留待之後繼續分析。
這個方法適用於那些,不知足於上述ndk-stack的簡單用法,而喜歡刨根問底的程序員們,這兩個方法能夠揭示ndk-stack命令的工做原理是什麼,儘管用起來稍微麻煩一點,可是能夠知足一下程序員的好奇心。
先簡單說一下這兩個命令,在絕大部分的linux發行版本中都能找到他們,若是你的操做系統是linux,而你測試手機使用的是Intel x86系列,那麼你使用系統中自帶的命令就能夠了。然而,若是僅僅是這樣,那麼絕大多數人要絕望了,由於偏偏大部分開發者使用的是Windows,而手機頗有多是armeabi系列。
別急,在NDK中自帶了適用於各個操做系統和CPU架構的工具鏈,其中就包含了這兩個命令,只不過名字稍有變化,你能夠在NDK目錄的toolchains目錄下找到他們。以個人Mac電腦爲例,若是我要找的是適用於armeabi架構的工具,那麼他們分別爲arm-linux-androideabi-addr2line和arm-linux-androideabi-objdump;位置在下面目錄中,後續介紹中將省略此位置:
假設你的電腦是windows, CPU架構爲mips,那麼你要的工具可能包含在這個目錄中:
好了言歸正傳,如何使用這兩個工具,下面具體介紹:
其實很簡單,就是找到backtrace信息中,屬於咱們本身的so文件報錯的行。
首先要找到backtrace信息,有的手機會明確打印一行backtrace(好比咱們此次使用的手機),那麼這一行下面的一系列以「#兩位數字 pc」開頭的行就是backtrace信息了。有時可能有的手機並不會打印一行backtrace,那麼只要找到一段以「#兩位數字 pc 」開頭的行,就能夠了。
其次要找到屬於本身的so文件報錯的行,這就比較簡單了。找到這些行以後,記下這些行中的函數地址
執行以下的命令,多個指針地址能夠在一個命令中帶入,以空格隔開便可
從addr2line的結果就能看到,咱們拿到了咱們本身的錯誤代碼的調用關係和行數,在hello-jni.cpp的69行和61行(另外兩行由於使用的是標準函數,能夠忽略掉),結果和ndk-stack是一致的,說明ndk-stack也是經過addr2line來獲取代碼位置的。
經過addr2line命令,其實咱們已經找到了咱們代碼中出錯的位置,已經能夠幫助程序員定位問題所在了。可是,這個方法只能獲取代碼行數,並無顯示函數信息,顯得不那麼「完美」,對於追求極致的程序員來講,這固然是不夠的。下面咱們就演示怎麼來定位函數信息。
使用以下命令導出函數表:
在生成的asm文件中查找剛剛咱們定位的兩個關鍵指針00004fb4和00004f58
從這兩張圖能夠清楚的看到(要注意的是,在不一樣的NDK版本和不一樣的操做系統中,asm文件的格式不是徹底相同,但都大同小異,請你們仔細比對),這兩個指針分別屬於willCrash()和JNI_OnLoad()函數,再結合剛纔addr2line的結果,那麼這兩個地址分別對應的信息就是:
至關完美,和ndk-stack獲得的信息徹底一致!
以上提到的方法,只適合在開發測試期間,若是你的應用或者遊戲已經發布上線,而用戶常常反饋說崩潰、閃退,期望用戶幫你收集信息定位問題,幾乎是不可能的。這個時候,咱們就須要用其餘的手段來捕獲崩潰信息。
目前業界已經有一些公司推出了崩潰信息收集的服務,經過嵌入SDK,在程序發生崩潰時收集堆棧信息,發送到雲服務平臺,從而幫助開發者定位錯誤信息。在這方面,處於領先地位的是國內的Testin和國外的crittercism,其中crittercism須要付費,並且沒有專門的中國開發者支持,咱們更推薦Testin,其崩潰分析服務是徹底免費的。
Testin從1.4版本開始支持NDK的崩潰分析,其最新版本已經升級到1.7。當程序發生NDK錯誤時,其內嵌的SDK會收集程序在用戶手機上發生崩潰時的堆棧信息(主要就是上面咱們經過logcat日誌獲取到的函數指針)、設備信息、線程信息等等,SDK將這些信息上報至Testin雲服務平臺,只要登錄到Testin平臺,就能夠看到全部用戶上報的崩潰信息,包括NDK;而且這些崩潰作過歸一化的處理,在不一樣系統和ROM的版本上打印的信息會略有不一樣,可是在Testin的網站上這些都作了很好的處理,避免了咱們一些重複勞動。
上圖的紅框部分,就是從用戶手機上報的,咱們本身的so中報錯的函數指針地址堆棧信息,就和咱們開發時從logcat讀到的日誌同樣,是一些晦澀難懂的指針地址,Testin爲NDK崩潰提供了符號化的功能,只要將咱們編譯過程當中產生的包含符號表的so文件上傳(上文咱們提到過的obj/local/目錄下的適用於各個CPU架構的so),就能夠自動將函數指針地址定位到函數名稱和代碼行數。符號化以後,看起來就和咱們前面在本地測試的結果是同樣的了,一目瞭然。
並且使用這個功能還有一個好處:這些包含符號表的so文件,在每次咱們本身編譯以後都會改變,頗有可能咱們剛剛發佈一個新版本,這些目錄下的so就已經變了,由於開發者會程序的修改程序;在這樣的狀況下,即便咱們拿到了崩潰時的堆棧信息,那也沒法再進行符號化了。因此咱們在編譯打包完成後記得備份咱們的so文件。這時咱們能夠將這些文件上傳到Testin進行符號化的工做,Testin會爲咱們保存和管理不一樣版本的so文件,確保信息不會丟失。來看一下符號化以後的顯示: