常見Android Native崩潰及錯誤原因


http://www.droidsec.cn/常見android-native崩潰及錯誤原因/

一、什么是Android的C/C++ NativeCrash

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(不是行號哦!)

UKYUQA}TYQ(3SPXORCQAU$4

二、什么是錯誤信號

Android本質就是一個Linux,信號跟Linux信號是同一個東西,信號本身是用於進程間通信的沒有正確錯誤之分,但官方給一些信號賦予了特定的含義及特定處理動作,

通常我們說的錯誤信號有5個(Bugly全部都能上報),系統默認處理就是dump出堆棧,並退出進程:

}RLH@[`~UMNVFJU`(~3NTVN

通常的來源有三個:
1、硬件發生異常,即硬件(通常是CPU)檢測到一個錯誤條件並通知Linux內核,內核處理該異常,給相應的進程發送信號。硬件異常的例子包括執行一條異常的機器語言指令,諸如,被0除,或者引用了無法訪問的內存區域。大部分信號如果沒有被進程處理,默認的操作就是殺死進程。在本文中,SIGSEGV(段錯誤),SIGBUS(內存訪問錯誤),SIGFPE(算數異常)屬於這種信號。

2、進程調用的庫發現錯誤,給自己發送中止信號,默認情況下,該信號會終止進程。在本文中,SIGABRT(中止進程)屬於這種信號。

3、用戶(手賤)或第三方App(惡意)通過kill-信號 pid的方式給錯誤進程發送,這時signal中的si_code會小於0。

三、抖幾個常見錯誤

1. 空指針

代碼示例

12 int* p = 0; //空指針*p = 1; //寫空指針指向的內存,產生SIGSEGV信號,造成Crash

原因分析

在進程的地址空間中,從0開始的第一個頁面的權限被設置為不可讀也不可寫,當進程的指令試圖訪問該頁面中的地址時(如讀取空指針指向的內存),處理器就會產生一個異常,然后Linux內核會給該進程發送一個段錯誤信號(SIGSEGV),默認的操作就是殺死進程,並產生core文件。

解決方法

在使用指針前加以判斷,如果為空,則是不可訪問的。

Bug評述

空指針是很容易出現的一種bug,在代碼量大,趕開發進度時很容易出現,但是它也很容易被發現和修復。

2. 野指針

代碼示例

12 int*p;//野指針,未初始化,其指向的地址通常是隨機的*p=1;//寫野指針指向的內存,有可能不會馬上Crash,而是破壞了別處的內存

原因分析

野指針指向的是一個無效的地址,該地址如果是不可讀不可寫的,那么會馬上Crash(內核給進程發送段錯誤信號SIGSEGV),這時bug會很快被發現。
如果訪問的地址為可寫,而且通過野指針修改了該處的內存,那么很有可能會等一段時間(其它的代碼使用了該處的內存后)才發生Crash。這時查看Crash時顯示的調用棧,和野指針所在的代碼部分,有可能基本上沒有任何關聯。

解決方法

  1. 在指針變量定義時,一定要初始化,特別是在結構體或類中的成員指針變量。
  2. 在釋放了指針指向的內存后,要把該指針置為NULL(但是如果在別的地方也有指針指向該處內存的話,這種方式就不好解決了)。
  3. 野指針造成的內存破壞的問題,有時候光看代碼很難查找,通過代碼分析工具也很難找出,只有通過專業的內存檢測工具,才能發現這類bug。

Bug評述

野指針的bug,特別是內存破壞的問題,有時候查起來毫無頭緒,沒有一點線索,讓開發者感覺到很茫然和無助( Bugly上報的堆棧看不出任何問題)。可以說內存破壞bug是服務器穩定性最大的殺手,也是C/C++在開發應用方面相比於其它語言(如Java, C#)的最大劣勢之一。

3. 數組越界

代碼示例

12 int arr[10];arr[10] = 1; //數組越界,有可能不會馬上Crash,而是破壞了別處的內存

原因分析

數組越界和野指針類似,訪問了無效的地址,如果該地址不可讀寫,則會馬上Crash(內核給進程發送段錯誤信號SIGSEGV),如果修改了該處的內存,造成內存破壞,那么有可能會等一段時間才在別處發生Crash。

解決方法

  1. 所有數組遍歷的循環,都要加上越界判斷。
  2. 用下標訪問數組時,要判斷是否越界。
  3. 通過代碼分析工具可以發現絕大部分的數組越界問題。

Bug評述

數組越界也是一種內存破壞的bug,有時候與野指針一樣也是很難查找的。

4. 整數除以零

代碼示例

12 inta=1;intb=a/0;//整數除以0,產生SIGFPE信號,導致Crash

原因分析

整數除以零總是產生SIGFPE(浮點異常,產生SIGFPE信號時並非一定要涉及浮點算術,整數運算異常也用浮點異常信號是為了保持向下兼容性)信號,默認的處理方式是終止進程,並生成core文件。

解決方法

在做整數除法時,要判斷被除數是否為0的情況。

Bug評述

整數被0除的bug很容易被開發者忽視,因為通常被除數為0的情況在開發環境下很難出現,但是到了生產環境,龐大的用戶量和復雜的用戶輸入,就很容易導致被除數為0的情況出現了。

5. 格式化輸出參數錯誤

代碼示例

123 //格式化參數錯誤,可能會導致非法的內存訪問,從而造成宕機char text[200];snprintf(text,200,"Valid %u, Invalid %u %s", 1);//format格式不匹配

原因分析

格式化參數錯誤也和野指針類似,但是只會讀取無效地址的內存,而不會造成內存破壞,因此其結果是要么打印出錯亂的數據,要么訪問了無讀寫權限的內存(收到段錯誤信號SIGSEGV)而立即宕機。

解決方法

  1. 在書寫輸出格式和參數時,要做到參數個數和類型都要與輸出格式一致。
  2. 在GCC的編譯選項中加入-wformat,讓GCC在編譯時檢測出此類錯誤。

6、緩沖區溢出

代碼示例

123456 charszBuffer[10];//由於函數棧是從高地址往低地址創建,而sprintf是從低地址往高地址打印字符,//如果超出了緩沖區的大小,函數的棧幀會被破壞,在函數返回時會跳轉到未知的地址上,//基本上都會造成訪問異常,從而產生SIGABRT或SIGSEGV,造成Crash sprintf(szBuffer,"Stack Buffer Overrun!111111111111111"  "111111111111111111111");

原因分析

通過往程序的緩沖區寫超出其長度的內容,造成緩沖區的溢出,從而破壞函數調用的堆棧,修改函數調用的返回地址。如果不是黑客故意攻擊,那么最終函數調用很可能會跳轉到無法讀寫的內存區域,產生段錯誤信號SIGSEGV或SIGABRT,造成程序崩潰,並生成core文件。

解決方法

  1. 檢查所有容易產生漏洞的庫調用,比如sprintf,strcpy等,它們都沒有檢查輸入參數的長度。
  2. 使用帶有長度檢查的庫調用,如用snprintf來代替sprintf,或者自己在sprintf上封裝一個帶長度檢查的函數。
  3. 在GCC編譯時,在-O1以上的優化行為下,使用-D_FORTIFY_SOURCE=level進行編譯(其中level=1或2,level代表的是檢測級別的不同,數值越大越嚴格)。這樣GCC會在編譯時報告緩沖區溢出的錯誤。
  4. 在GCC編譯時加上-fstack-protector或-fstack-protector-all選項,使得堆棧保護(stack-smashingprotector, SSP)功能生效。該功能會在編譯后的匯編代碼中插入堆棧檢測的代碼,並在運行時能夠檢測到棧破壞並輸出報告。

Bug評述

緩沖區溢出是一種非常普遍、非常危險的漏洞,在各種操作系統、應用軟件中廣泛存在。黑客在進行攻擊時,輸入的字符串一般不會讓程序崩潰,而是修改函數的返回地址,使程序跳轉到別的地方,轉而執行黑客安排好的指令,以達到攻擊的目的。
緩沖區溢出后,調試生成的core,可以看見調用棧是混亂的,因為函數的返回地址已經被修改到隨機的地址上去了。
服務器宕機后,如果core文件和可執行文件是匹配的,但是調用棧是錯亂的,那么很大的可能性是發生了緩沖區溢出。

7、主動拋出異常

代碼示例

1234   if((*env)->ExceptionOccurred(env)!=0){        //動態庫在內部運行出現錯誤時,大都會主動abort,終止運行        abort();//給當前進程發送信號SIGABRT  }

解決方法

查看堆棧找出abort的原因

Bug評述

如果是程序主動abort的,通過堆棧加源碼還是很好定位的,但往往abort的位置是在系統庫中,就不好定位了,需要多查看系統API的使用方法,檢查是否使用不當。

四、小編有話說

Java異常已經搞得大家焦頭爛額了,Native異常更是恐怖,數量比Java異常多得多,只是看堆棧還不好定位(畫小圈圈詛咒萬惡的指針)。非常感謝王競原童鞋能在日常開發遇到的崩潰中總結出這一篇寶貴的文章!

轉載自:http://bugly.qq.com/blog/?p=131  作者:王競原



注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com