標籤:
1. null 指標
2. 野指標
3. 數組越界
4. 整數除以零
5. 格式化輸出參數錯誤
6、緩衝區溢位
7、主動拋出異常
Android上的Crash可以分兩種:
1、Java Crash
java代碼導致jvm退出,彈出“程式已經崩潰”的對話方塊,終端使用者點擊關閉後進程退出。
Logcat 會在“AndroidRuntime”tag下輸出Java的調用棧。
2、Native Crash
通過NDK,使用C/C++開發,導致進程收到錯誤訊號,發生Crash,Android 5.0之前進程直接退出(閃退) , Android 5.0之後會彈“程式已崩潰”的對話方塊。
Logcat 會在“debug”tag下輸出dump資訊:
錯誤訊號:11是訊號量sigNum,SIGSEGV是訊號的名字,SEGV_MAPERR是SIGSEGV下的一種類型。
寄存器快照:進程收到錯誤訊號時儲存下來的寄存器快照,其中PC寄存器儲存的就是下個要啟動並執行指令(出錯的位置)。
調用棧:#00是棧頂,#02是棧底,#02調用#01調用#00方法,#00的方法時libspirit.so中的Spirit類下的testCrash方法,出錯的地方是testCrash方法內彙編位移17(不是行號哦!)
二、什麼是錯誤訊號
Android本質就是一個Linux,訊號跟Linux訊號是同一個東西,訊號本身是用於處理序間通訊的沒有正確錯誤之分,但官方給一些訊號賦予了特定的含義及特定處理動作,
通常我們說的錯誤訊號有5個(Bugly全部都能上報),系統預設處理就是dump出堆棧,並退出進程:
通常的來源有三個:
硬體發生異常,即硬體(通常是CPU)檢測到一個錯誤條件並通知Linux核心,核心處理該異常,給相應的進程發送訊號。硬體異常的例子包括執行一條異常的機器語言指令,諸如,被0除,或者引用了無法訪問的記憶體地區。大部分訊號如果沒有被進程處理,預設的操作就是殺死進程。在本文中,SIGSEGV(段錯誤),SIGBUS(記憶體訪問錯誤),SIGFPE(算數異常)屬於這種訊號。
進程調用的庫發現錯誤,給自己發送中止訊號,預設情況下,該訊號會終止進程。在本文中,SIGABRT(中止進程)屬於這種訊號。
使用者(手賤)或第三方App(惡意)通過kill-訊號 pid的方式給錯誤進程發送,這時signal中的si_code會小於0。
三、抖幾個常見錯誤
1. null 指標
程式碼範例
int* p = 0; //null 指標*p = 1; //寫null 指標指向的記憶體,產生SIGSEGV訊號,造成Crash
原因分析
在進程的地址空間中,從0開始的第一個頁面的許可權被設定為不可讀也不可寫,當進程的指令試圖訪問該頁面中的地址時(如讀取null 指標指向的記憶體),處理器就會產生一個異常,然後Linux核心會給該進程發送一個段錯誤訊號(SIGSEGV),預設的操作就是殺死進程,併產生core檔案。
解決方案
在使用指標前加以判斷,如果為空白,則是不可訪問的。
Bug評述
null 指標是很容易出現的一種bug,在代碼量大,趕開發進度時很容易出現,但是它也很容易被發現和修複。
2. 野指標
程式碼範例
int* p; //野指標,未初始化,其指向的地址通常是隨機的*p = 1; //寫野指標指向的記憶體,有可能不會馬上Crash,而是破壞了別處的記憶體
原因分析
野指標指向的是一個無效的地址,該地址如果是不可讀不可寫的,那麼會馬上Crash(核心給進程發送段錯誤訊號SIGSEGV),這時bug會很快被發現。
如果訪問的地址為可寫,而且通過野指標修改了該處的記憶體,那麼很有可能會等一段時間(其它的代碼使用了該處的記憶體後)才發生Crash。這時查看Crash時顯示的調用棧,和野指標所在的代碼部分,有可能基本上沒有任何關聯。
解決方案
在指標變數定義時,一定要初始化,特別是在結構體或類中的成員指標變數。
在釋放了指標指向的記憶體後,要把該指標置為NULL(但是如果在別的地方也有指標指向該處記憶體的話,這種方式就不好解決了)。
野指標造成的記憶體破壞的問題,有時候光看代碼很難尋找,通過程式碼分析工具也很難找出,只有通過專業的記憶體偵查工具,才能發現這類bug。
Bug評述
野指標的bug,特別是記憶體破壞的問題,有時候查起來毫無頭緒,沒有一點線索,讓開發人員感覺到很茫然和無助( Bugly上報的堆棧看不出任何問題)。可以說記憶體破壞bug是伺服器穩定性最大的殺手,也是C/C++在開發應用方面相比於其它語言(如Java, C#)的最大劣勢之一。
3. 數組越界
程式碼範例
int arr[10];arr[10] = 1; //數組越界,有可能不會馬上Crash,而是破壞了別處的記憶體
原因分析
數組越界和野指標類似,訪問了無效的地址,如果該地址不可讀寫,則會馬上Crash(核心給進程發送段錯誤訊號SIGSEGV),如果修改了該處的記憶體,造成記憶體破壞,那麼有可能會等一段時間才在別處發生Crash。
解決方案
所有數組遍曆的迴圈,都要加上越界判斷。
用下標訪問數組時,要判斷是否越界。
通過程式碼分析工具可以發現絕大部分的數組越界問題。
Bug評述
數組越界也是一種記憶體破壞的bug,有時候與野指標一樣也是很難尋找的。
4. 整數除以零
程式碼範例
int a = 1;int b = a / 0; //整數除以0,產生SIGFPE訊號,導致Crash
原因分析
整數除以零總是產生SIGFPE(浮點異常,產生SIGFPE訊號時並非一定要涉及浮點算術,整數運算異常也用浮點異常訊號是為了保持向下相容性)訊號,預設的處理方式是終止進程,並產生core檔案。
解決方案
在做整數除法時,要判斷被除數是否為0的情況。
Bug評述
整數被0除的bug很容易被開發人員忽視,因為通常被除數為0的情況在開發環境下很難出現,但是到了生產環境,龐大的使用者量和複雜的使用者輸入,就很容易導致被除數為0的情況出現了。
5. 格式化輸出參數錯誤
程式碼範例
//格式化參數錯誤,可能會導致非法的記憶體訪問,從而造成宕機char text[200];snprintf(text,200,"Valid %u, Invalid %u %s", 1);//format格式不匹配
原因分析
格式化參數錯誤也和野指標類似,但是只會讀取無效地址的記憶體,而不會造成記憶體破壞,因此其結果是要麼列印出錯亂的資料,要麼訪問了無讀寫權限的記憶體(收到段錯誤訊號SIGSEGV)而立即宕機。
解決方案
在書寫輸出格式和參數時,要做到參數個數和類型都要與輸出格式一致。
在GCC的編譯選項中加入-wformat,讓GCC在編譯時間檢測出此類錯誤。
6、緩衝區溢位
程式碼範例
char szBuffer[10];//由於函數棧是從高地址往低地址建立,而sprintf是從低地址往高地址列印字元,//如果超出了緩衝區的大小,函數的棧幀會被破壞,在函數返回時會跳轉到未知的地址上,//基本上都會造成訪問異常,從而產生SIGABRT或SIGSEGV,造成Crashsprintf(szBuffer, "Stack Buffer Overrun!111111111111111" "111111111111111111111");
原因分析
通過往程式的緩衝區寫超出其長度的內容,造成緩衝區的溢出,從而破壞函數調用的堆棧,修改函數調用的返回地址。如果不是駭客故意攻擊,那麼最終函數調用很可能會跳轉到無法讀寫的記憶體地區,產生段錯誤訊號SIGSEGV或SIGABRT,造成程式崩潰,並產生core檔案。
解決方案
檢查所有容易產生漏洞的庫調用,比如sprintf,strcpy等,它們都沒有檢查輸入參數的長度。
使用帶有長度檢查的庫調用,如用snprintf來代替sprintf,或者自己在sprintf上封裝一個帶長度檢查的函數。
在GCC編譯時間,在-O1以上的最佳化行為下,使用-D_FORTIFY_SOURCE=level進行編譯(其中level=1或2,level代表的是檢測層級的不同,數值越大越嚴格)。這樣GCC會在編譯時間報告緩衝區溢位的錯誤。
在GCC編譯時間加上-fstack-protector或-fstack-protector-all選項,使得堆棧保護(stack-smashingprotector, SSP)功能生效。該功能會在編譯後的彙編代碼中插入堆棧檢測的代碼,並在運行時能夠檢測到棧破壞並輸出報告。
Bug評述
緩衝區溢位是一種非常普遍、非常危險的漏洞,在各種作業系統、應用軟體中廣泛存在。駭客在進行攻擊時,輸入的字串一般不會讓程式崩潰,而是修改函數的返回地址,使程式跳轉到別的地方,轉而執行駭客安排好的指令,以達到攻擊的目的。
緩衝區溢位後,調試產生的core,可以看見調用棧是混亂的,因為函數的返回地址已經被修改到隨機的地址上去了。
伺服器宕機後,如果core檔案和可執行檔是匹配的,但是調用棧是錯亂的,那麼很大的可能性是發生了緩衝區溢位。
7、主動拋出異常
程式碼範例
if ((*env)->ExceptionOccurred(env) != 0) { //動態庫在內部運行出現錯誤時,大都會主動abort,終止運行 abort(); //給當前進程發送訊號SIGABRT }
解決方案
查看堆棧找出abort的原因
Bug評述
如果是程式主動abort的,通過堆棧加源碼還是很好定位的,但往往abort的位置是在系統庫中,就不好定位了,需要多查看系統API的使用方法,檢查是否使用不當。
四、小編有話說
Java異常已經搞得大家焦頭爛額了,Native異常更是恐怖,數量比Java異常多得多,只是看堆棧還不好定位(畫小圈圈詛咒萬惡的指標)。非常感謝王競原童鞋能在日常開發遇到的崩潰中總結出這一篇寶貴的文章!
Android--什麼是Android的C/C++ NativeCrash