如何定位Android NDK開(kāi)發(fā)中遇到的錯(cuò)誤Android NDK是什么,?Android NDK 是在SDK前面又加上了“原生”二字,即Native Development Kit,,因此又被Google稱為“NDK”,。眾所周知,Android程序運(yùn)行在Dalvik虛擬機(jī)中,,NDK允許用戶使用類似C / C++之類的原生代碼語(yǔ)言執(zhí)行部分程序,。NDK包括:
為何要用到NDK,?概括來(lái)說(shuō)主要分為以下幾種情況:
Android JNI與NDK的關(guān)系Java Native Interface(JNI)標(biāo)準(zhǔn)是Java平臺(tái)的一部分,它允許Java代碼和其他語(yǔ)言寫(xiě)的代碼進(jìn)行交互,。JNI是本地編程接口,,它使得在Java虛擬機(jī)(VM)內(nèi)部運(yùn)行的Java代碼能夠與用其它編程語(yǔ)言(如C、C++和匯編語(yǔ)言)編寫(xiě)的應(yīng)用程序和庫(kù)進(jìn)行交互操作,。 簡(jiǎn)單來(lái)說(shuō),,可以認(rèn)為NDK就是能夠方便快捷開(kāi)發(fā).so文件的工具。JNI的過(guò)程比較復(fù)雜,,生成.so需要大量操作,,而NDK的作用則是簡(jiǎn)化了這個(gè)過(guò)程,。 哪些常見(jiàn)的NDK類型異常會(huì)導(dǎo)致程序Crash,?NDK編譯生成的.so文件作為程序的一部分,在運(yùn)行發(fā)生異常時(shí)同樣會(huì)造成程序崩潰,。不同于Java代碼異常造成的程序崩潰,,在NDK的異常發(fā)生時(shí),程序在Android設(shè)備上都會(huì)立即退出,即通常所說(shuō)的閃退,,而不會(huì)彈出“程序xxx無(wú)響應(yīng),,是否立即關(guān)閉”之類的提示框。 NDK是使用C/C++來(lái)進(jìn)行開(kāi)發(fā)的,,熟悉C/C++的程序員都知道,,指針和內(nèi)存管理是最重要也是最容易出問(wèn)題的地方,稍有不慎就會(huì)遇到諸如內(nèi)存無(wú)效訪問(wèn),、無(wú)效對(duì)象,、內(nèi)存泄露、堆棧溢出等常見(jiàn)的問(wèn)題,,最后都是同一個(gè)結(jié)果:程序崩潰,。例如我們常說(shuō)的空指針錯(cuò)誤,就是當(dāng)一個(gè)內(nèi)存指針被置為空(NULL)之后再次對(duì)其進(jìn)行訪問(wèn),;另外一個(gè)經(jīng)常出現(xiàn)的錯(cuò)誤是,,在程序的某個(gè)位置釋放了某個(gè)內(nèi)存空間,而后在程序的其他位置試圖訪問(wèn)該內(nèi)存地址,,這就會(huì)產(chǎn)生無(wú)效地址錯(cuò)誤,。常見(jiàn)的錯(cuò)誤類型如下:
如何發(fā)現(xiàn)并解決NDK錯(cuò)誤,?利用Android NDK開(kāi)發(fā)本地應(yīng)用時(shí),,幾乎所有的程序員都遇到過(guò)程序崩潰的問(wèn)題,但它的崩潰會(huì)在logcat中打印一堆看起來(lái)類似天書(shū)的堆棧信息,,讓人舉足無(wú)措,。單靠添加一行行的打印信息來(lái)定位錯(cuò)誤代碼做在的行數(shù),無(wú)疑是一件令人崩潰的事情,。在網(wǎng)上搜索“Android NDK崩潰”,,可以搜索到很多文章來(lái)介紹如何通過(guò)Android提供的工具來(lái)查找和定位NDK的錯(cuò)誤,但大都晦澀難懂,。下面以一個(gè)實(shí)際的例子來(lái)說(shuō)明,,如何通過(guò)兩種不同的方法,來(lái)定位錯(cuò)誤的函數(shù)名和代碼行,。 首先,,來(lái)看看我們?cè)趆ello-jni程序的代碼中做了什么(有關(guān)如何創(chuàng)建或?qū)牍こ?,此處略),下面代碼中:在JNI_OnLoad()的函數(shù)中,,即so加載時(shí),,調(diào)用willCrash()函數(shù),而在willCrash()函數(shù)中,, std::string的這種賦值方法會(huì)產(chǎn)生一個(gè)空指針錯(cuò)誤,。這樣,在hello-jni程序加載時(shí)就會(huì)閃退,。我們記一下這兩個(gè)行數(shù):在61行調(diào)用了willCrash()函數(shù),;在69行發(fā)生了崩潰。 下面我們來(lái)看看發(fā)生崩潰(閃退)時(shí)系統(tǒng)打印的logcat日志: 如果你看過(guò)logcat打印的NDK錯(cuò)誤的日志就會(huì)知道,,我省略了后面很多的內(nèi)容,,很多人看到這么多密密麻麻的日志就已經(jīng)頭暈?zāi)X脹了,即使是很多資深的Android開(kāi)發(fā)者,,在面對(duì)NDK日志時(shí)也大都默默地選擇了無(wú)視,。 其實(shí),只要你細(xì)心的查看,,再配合Google 提供的工具,,完全可以快速地準(zhǔn)確定位出錯(cuò)的代碼位置,這個(gè)工作我們稱之為“符號(hào)化”,。需要注意的是,,如果要對(duì)NDK錯(cuò)誤進(jìn)行符號(hào)化的工作,需要保留編譯過(guò)程中產(chǎn)生的包含符號(hào)表的so文件,,這些文件一般保存在$PROJECT_PATH/obj/local/目錄下,。 第一種方法:ndk-stack這個(gè)命令行工具包含在NDK工具的安裝目錄,和ndk-build及其他常用的一些NDK命令放在一起,,比如在我的電腦上,,其位置是/android-ndk-r9d/ndk-stack。根據(jù)Google官方文檔,,NDK從r6版本開(kāi)始提供ndk-stack命令,,如果你用的之前的版本,建議還是盡快升級(jí)至最新的版本,。使用ndk –stack命令也有兩種方式
使用方法: 前提: 你的代碼必須是使用ndk build打包出來(lái)的,。這樣地址信息就會(huì)是 $PROJECT_PATH/obj/local/<abi> 其中 <abi> 是根據(jù)你的手機(jī)的ABI來(lái)的。(比如:’armeabi‘就是默認(rèn)值) 打開(kāi)你的控制臺(tái),,輸入($NDK是你的環(huán)境變量,,指向NDK目錄): adb logcat | $NDK/ndk-stack -sym $PROJECT_PATH/obj/local/armeabi 大工完成。 實(shí)時(shí)分析日志在運(yùn)行程序的同時(shí),,使用adb獲取logcat日志,,并通過(guò)管道符輸出給ndk-stack,,同時(shí)需要指定包含符號(hào)表的so文件位置,;如果你的程序包含了多種CPU架構(gòu),,在這里需求根據(jù)錯(cuò)誤發(fā)生時(shí)的手機(jī)CPU類型,選擇不同的CPU架構(gòu)目錄,,如: 當(dāng)崩潰發(fā)生時(shí),,會(huì)得到如下的信息: 我們重點(diǎn)看一下#03和#04,這兩行都是在我們自己生成的libhello-jni.so中的報(bào)錯(cuò)信息,,因此會(huì)發(fā)現(xiàn)如下關(guān)鍵信息: 回想一下我們的代碼,,在JNI_OnLoad()函數(shù)中(第61行),我們調(diào)用了willCrash()函數(shù),;在willCrash()函數(shù)中(第69行),,我們制造了一個(gè)錯(cuò)誤。這些信息都被準(zhǔn)確無(wú)誤的提取了出來(lái),!是不是非常簡(jiǎn)單,? 先獲取日志再分析這種方法其實(shí)和上面的方法沒(méi)有什么大的區(qū)別,僅僅是logcat日志獲取的方式不同,??梢栽诔绦蜻\(yùn)行的過(guò)程中將logcat日志保存到一個(gè)文件,甚至可以在崩潰發(fā)生時(shí),,快速的將logcat日志保存起來(lái),,然后再進(jìn)行分析,比上面的方法稍微靈活一點(diǎn),,而且日志可以留待以后繼續(xù)分析,。 第二種方法:使用addr2line和objdump命令這個(gè)方法適用于那些不滿足于上述ndk-stack的簡(jiǎn)單用法,而喜歡刨根問(wèn)底的程序員們,,這兩個(gè)方法可以揭示ndk-stack命令的工作原理是什么,,盡管用起來(lái)稍微麻煩一點(diǎn),但可以稍稍滿足一下程序員的好奇心,。 先簡(jiǎn)單說(shuō)一下這兩個(gè)命令,,在絕大部分的Linux發(fā)行版本中都能找到他們,如果你的操作系統(tǒng)是Linux,,而你測(cè)試手機(jī)使用的是Intel x86系列,,那么你使用系統(tǒng)中自帶的命令就可以了。然而,,如果僅僅是這樣,,那么絕大多數(shù)人要絕望了,因?yàn)榍∏〈蟛糠珠_(kāi)發(fā)者使用的是Windows,,而手機(jī)很有可能是armeabi系列,。 在NDK中自帶了適用于各個(gè)操作系統(tǒng)和CPU架構(gòu)的工具鏈,,其中就包含了這兩個(gè)命令,只不過(guò)名字稍有變化,,你可以在NDK目錄的toolchains目錄下找到他們,。以我的Mac電腦為例,如果我要找的是適用于armeabi架構(gòu)的工具,,那么他們分別為arm-linux-androideabi-addr2line和arm-linux-androideabi-objdump,;位置在下面目錄中,后續(xù)介紹中將省略此位置: 假設(shè)你的電腦是Windows系統(tǒng),,CPU架構(gòu)為mips,,那么你要的工具可能包含在一下目錄中: 接下來(lái)就讓我們來(lái)看看如何使用這兩個(gè)工具,下面具體介紹,。 找到日志中的關(guān)鍵函數(shù)指針其實(shí)很簡(jiǎn)單,,就是找到backtrace信息中,屬于我們自己的so文件報(bào)錯(cuò)的行,。 首先要找到backtrace信息,,有的手機(jī)會(huì)明確打印一行backtrace(比如我們這次使用的手機(jī)),那么這一行下面的一系列以“#兩位數(shù)字 pc”開(kāi)頭的行就是backtrace信息了,。有時(shí)可能有的手機(jī)并不會(huì)打印一行backtrace,,那么只要找到一段以“#兩位數(shù)字 pc ”開(kāi)頭的行,就可以了,。 其次要找到屬于自己的so文件報(bào)錯(cuò)的行,,這就比較簡(jiǎn)單了。找到這些行之后,,記下這些行中的函數(shù)地址,。 使用addr2line查找代碼位置執(zhí)行如下的命令,多個(gè)指針地址可以在一個(gè)命令中帶入,,以空格隔開(kāi)即可 結(jié)果如下: 從addr2line的結(jié)果就能看到,,我們拿到了我們自己的錯(cuò)誤代碼的調(diào)用關(guān)系和行數(shù),在hello-jni.cpp的69行和61行(另外兩行因?yàn)槭褂玫氖菢?biāo)準(zhǔn)函數(shù),,可以忽略掉),,結(jié)果和ndk-stack是一致的,說(shuō)明ndk-stack也是通過(guò)addr2line來(lái)獲取代碼位置的,。 使用objdump獲取函數(shù)信息通過(guò)addr2line命令,,其實(shí)我們已經(jīng)找到了我們代碼中出錯(cuò)的位置,已經(jīng)可以幫助程序員定位問(wèn)題所在了,。但是,,這個(gè)方法只能獲取代碼行數(shù),并沒(méi)有顯示函數(shù)信息,顯得不那么“完美”,,對(duì)于追求極致的程序員來(lái)說(shuō),,這當(dāng)然是不夠的。下面我們就演示一下怎么來(lái)定位函數(shù)信息,。 首先使用如下命令導(dǎo)出函數(shù)表: 在生成的asm文件中查找剛剛我們定位的兩個(gè)關(guān)鍵指針00004fb4和00004f58: 從這兩張圖可以清楚的看到(要注意的是,,在不同的NDK版本和不同的操作系統(tǒng)中,asm文件的格式不是完全相同,,但都大同小異,,請(qǐng)大家仔細(xì)比對(duì)),,這兩個(gè)指針?lè)謩e屬于willCrash()和JNI_OnLoad()函數(shù),,再結(jié)合剛才addr2line的結(jié)果,那么這兩個(gè)地址分別對(duì)應(yīng)的信息就是: 相當(dāng)完美,,和ndk-stack得到的信息完全一致,! Testin崩潰分析如何幫開(kāi)發(fā)者發(fā)現(xiàn)NDK錯(cuò)誤以上提到的方法,只適合在開(kāi)發(fā)測(cè)試期間,,如果你的應(yīng)用或游戲已經(jīng)上線,,而用戶經(jīng)常反饋說(shuō)崩潰、閃退,,指望用戶幫你收集信息定位問(wèn)題幾乎是不可能的,。這個(gè)時(shí)候,我們就需要用其他的手段來(lái)捕獲崩潰信息,。 目前業(yè)界已經(jīng)有一些公司推出了崩潰信息收集的服務(wù),,通過(guò)嵌入SDK,在程序發(fā)生崩潰時(shí)收集堆棧信息,,發(fā)送到云服務(wù)平臺(tái),,從而幫助開(kāi)發(fā)者定位錯(cuò)誤信息。在這方面,,國(guó)內(nèi)的Testin和國(guó)外的crittercism都可以提供類似服務(wù),。 Testin從1.4版本開(kāi)始支持NDK的崩潰分析,其最新版本已升級(jí)到1.7,。當(dāng)程序發(fā)生NDK錯(cuò)誤時(shí),,其內(nèi)嵌的SDK會(huì)收集程序在用戶手機(jī)上發(fā)生崩潰時(shí)的堆棧信息(主要就是上面我們通過(guò)logcat日志獲取到的函數(shù)指針)、設(shè)備信息,、線程信息等,,SDK將這些信息上報(bào)至Testin云服務(wù)平臺(tái),在平臺(tái)進(jìn)行唯一性的處理,、并可以自定義時(shí)段進(jìn)行詳盡的統(tǒng)計(jì)分析,,從多維度展示程序崩潰的信息和嚴(yán)重程度;最新版本還支持用戶自定義場(chǎng)景,,方便開(kāi)發(fā)者定位問(wèn)題所在,。 從用戶手機(jī)上報(bào)的堆棧信息,,Testin為NDK崩潰提供了符號(hào)化的功能,只要將我們編譯過(guò)程中產(chǎn)生的包含符號(hào)表的so文件上傳,,就可以自動(dòng)將函數(shù)指針地址定位到函數(shù)名稱和代碼行數(shù),。符號(hào)化之后,看起來(lái)就和我們前面在本地測(cè)試的結(jié)果是一樣的了,,一目了然,。而且使用這個(gè)功能還有一個(gè)好處:這些包含符號(hào)表的so文件,在每次開(kāi)發(fā)者編譯之后都會(huì)改變,,很有可能我們發(fā)布之后就已經(jīng)變了,,因?yàn)殚_(kāi)發(fā)者會(huì)修改程序。在這樣的情況下,,即使我們拿到了崩潰時(shí)的堆棧信息,,那也無(wú)法再進(jìn)行符號(hào)化了。我們可以將這些文件上傳到Testin進(jìn)行符號(hào)化的工作,,Testin會(huì)為我們保存和管理不同版本的so文件,,確保信息不會(huì)丟失。 |
|