一、崩潰的類型
APP的崩潰可以分為兩類:信號可捕捉崩潰 和 信號不可捕捉崩潰。
信號可捕捉的崩潰
- 數組越界:取數據時候索引越界,APP發生崩潰。給數組添加nil會崩潰。
- 多線程問題:多個線程進行數據的存取,可能會崩潰。例如有一個線程在置空數據的同時另一個線程在讀取數據。
- 野指針問題:指針指向一個已刪除的對象訪問內存區域時,會出現野指針崩潰。野指針問題是導致 App 崩潰的最常見,也是最難定位的一種情況。
- NSNotification線程問題:NSNotification 有很多種線程實現方式,同步、異步、聚合,所以不恰當的線程發送和接收會出現崩潰問題。
- KVO問題:‘If your app targets iOS 9.0 and later or OS X v10.11 and later, you don't need to unregister an observer in its deallocation method。’ 在9.0之前需要手動remove 觀察者,如果沒有移除會出現觀察者崩潰情況。
信號不可捕捉的崩潰
- 后台任務超時
- App超過系統限制的內存大小被殺死
- 主線程卡頓被殺死
二、崩潰日志
1、什么情況下會產生崩潰日志?
application:didFinishLaunchingWithOptions:
applicationWillResignActive:
applicationDidEnterBackground:
applicationWillEnterForeground:
applicationDidBecomeActive:
applicationWillTerminate:
find /Applications/Xcode.app -name symbolicatecrash -type f
可以把它拷貝出來, 然后通過以下命令來解析崩潰日志。
./symbolicatecrash ./xxx.crash ./xxx.app.dSYM > symbol.crash
3、解析符號化后崩潰報告
1、頭部關鍵信息
Incident Identifier: 一個唯一的標識. 不會存在共用一個標識的崩潰報告. CrashReporter Key:是與設備標識相對應的唯一鍵值。雖然它不是真正的設備標識符,但也是一個非常有用的情報:如果你看到100個崩潰日志的CrashReporter Key值都是相同的,或者只有少數幾個不同的CrashReport值,說明這不是一個普遍的問題,只發生在一個或少數幾個設備上。 Process: 是應用名稱。中括號里面的數字是閃退時應用的進程ID。 Version: 崩潰進程的版本號. 這個值包含在 CFBundleVersion and CFBundleVersionString中. Code Type: 崩潰日志所在設備的架構. 會是ARM-64, ARM, x86-64, or x86中的一個. OS Version: 崩潰發生時的系統版本
2、異常信息中的關鍵字段
Exception Codes: 處理器的具體信息有關的異常編碼成一個或多個64位進制數。通常情況下,這個區域不會被呈現,因為將異常代碼解析成人們可以看懂的描述是在其它區域進行的。
Exception Subtype: 供人們可讀的異常代碼的名字
Exception Message: 從異常代碼中提取的額外的可供人們閱讀的信息.
Exception Note: 不是特定於一個異常類型的額外信息.如果這個區域包含SIMULATED (這不是一個崩潰)然后進程沒有崩潰,但是被watchdog殺掉了
Termination Reason: 當一個進程被終止的時的原因。
Triggered by Thread: 異常所在的線程.
3、其他常見的異常
Bad Memory Access [EXC_BAD_ACCESS // SIGSEGV // SIGBUS]進程試圖訪問無效的內存,或試圖以內存的保護級別所不允許的方式去訪問內存(例如,寫入到只讀存儲器)。異常類型字段(Exception Subtype)包含一個kern_return_t描述錯誤,和錯誤的訪問的內存地址。這里是調試一個Bad Memory Access的一些小技巧:
(2).如果gpus_ReturnNotPermittedKillClient在回溯的頂部附近,這個進程是由於在后台嘗試用OpenGL ES 或者 Metal來渲染,而被殺掉的。See QA1766: How to fix OpenGL ES application crashes when moving to the background.
(3).用 Address Sanitizer (xcode7引入的新特性)來跑程序。
The address sanitizer adds additional instrumentation around memory access in your compiled code. As your application runs, Xcode will alert you if memory is accessed in a way that could lead to a crash.
Abnormal Exit (異常退出)[EXC_CRASH // SIGABRT]
進程異常退出。該異常類型崩潰最常見的原因是未捕獲的Objective-C和C++異常和調用abort()。
如果他們需要太多的時間來初始化,程序將被終止,因為觸發watchdog。如果是因為啟動的時候被掛起,所產生的崩潰報告異常類型(Exception Subtype)將是launch_hang。
Trace Trap (追蹤捕獲)[EXC_BREAKPOINT // SIGTRAP]
類似於異常退出,這個異常是為了給附加的調試器中斷的過程的機會在其執行一個特定的點。您可以通過主動調用__builtin_trap()函數引發此異常使用,如果沒有調試器連接,進程將被終止並生成崩潰報告。
Illegal Instruction(非法指令) [EXC_BAD_INSTRUCTION // SIGILL]
進程試圖執行非法或未定義指令。這個進程可能試圖通過一個配置錯誤的函數指針,跳到一個無效的地址。
Resource Limit [EXC_RESOURCE]
這個進程超出了資源消耗的限制。這是一個從操作系統通知,進程是使用太多的資源。這雖然不是崩潰但也會生成崩潰日志。
其它的異常信息
0x8badf00d: 讀做 “ate bad food”! (把數字換成字母,是不是很像 :p)該編碼表示應用是因為發生watchdog超時而被iOS終止的。通常是應用花費太多時間而無法啟動、終止或響應用系統事件。
0xdead10cc: 讀做 “dead lock”!該代碼表明應用因為在后台運行時占用系統資源,如通訊錄數據庫不釋放而被終止 。
0xdeadfa11: 讀做 “dead fall”! 該代碼表示應用是被用戶強制退出的。根據蘋果文檔, 強制退出發生在用戶長按開關按鈕直到出現 “滑動來關機”, 然后長按 Home按鈕。強制退出將產生 包含0xdeadfa11 異常編碼的崩潰日志, 因為大多數是強制退出是因為應用阻塞了界面。
4、線程回溯 如以下 線程幀 記錄了 這些信息

幀編號—— 此處是2。(數子從大到小為發生的順序)
二進制庫的名稱 ——此處是 XYZLib.
調用方法的地址 ——此處是 0x34648e88.
第四列分為兩個子列,一個基本地址和一個偏移量。此處是0×83000 + 8740, 第一個數字指向文件,第二個數字指向文件中的代碼行。
4、崩潰日志中常見的信號
Defined in header <signal.h>
#define SIGTERM /*implementation defined*/
#define SIGSEGV /*implementation defined*/
#define SIGINT /*implementation defined*/
#define SIGILL /*implementation defined*/
#define SIGABRT /*implementation defined*/
#define SIGFPE /*implementation defined*/
iOS中一般不會處理到這個信號
SIGSEGV
無效的內存地址引用信號(常見的野指針訪問)
非ARC模式下,iOS中經常會出現在 Delegate對象野指針訪問
ARC模式下,iOS經常會出現在Block代碼塊內 強持有可能釋放的對象
通常由用戶輸入的整型中斷信號
在iOS中一般不會處理到該信號
不管在任何情況下得殺死進程的信號
由於iOS應用程序平台的限制,在iOS APP內禁止kill掉進程,所以一般不會處理
通常由於異常引起的中斷信號,異常發生時系統會調用abort()函數發出該信號
iOS平台,一種是由於方法調用錯誤(調用了不能調用的方法)
iOS平台,一種是由於數組訪問越界的問題
浮點數異常的信號通知
一般是由於 除數為0引起的
5、自定義代碼來獲取崩潰日志信息
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSSetUncaughtExceptionHandler(&caughtExceptionHandler); /*
Changes the top-level error handler. Sets the top-level error-handling function where you can perform last-minute logging before the program terminates */
return YES; } void caughtExceptionHandler(NSException *exception){ /** * 獲取異常崩潰信息 */ NSArray *callStack = [exception callStackSymbols]; NSString *reason = [exception reason]; NSString *name = [exception name]; NSString *content = [NSString stringWithFormat:@"========異常錯誤報告========\\nname:%@\\nreason:\\n%@\\ncallStackSymbols:\\n%@",name,reason,[callStack componentsJoinedByString:@"\\n"]]; //把異常崩潰信息發送至開發者郵件
NSMutableString *mailUrl = [NSMutableString string]; [mailUrl appendString:@"mailto:xxx@qq.com"]; [mailUrl appendString:@"?subject=程序異常崩潰信息,請配合發送異常報告,謝謝合作!"]; [mailUrl appendFormat:@"&body=%@", content]; // 打開地址
NSString *mailPath = [mailUrl stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; [[UIApplication sharedApplication] openURL:[NSURL URLWithString:mailPath]]; }
三、野指針導致崩潰
1、對象釋放后內存沒被改動過,原來的內存保存完好,可能不Crash或者出現邏輯錯誤(隨機Crash)。
2、對象釋放后內存沒被改動過,但是它自己析構的時候已經刪掉某些必要的東西,可能不Crash、Crash在訪問依賴的對象比如類成員上、出現邏輯錯誤(隨機Crash)。
3、對象釋放后內存被改動過,寫上了不可訪問的數據,直接就出錯了很可能Crash在objc_msgSend上面(必現Crash,常見)。
4、對象釋放后內存被改動過,寫上了可以訪問的數據,可能不Crash、出現邏輯錯誤、間接訪問到不可訪問的數據(隨機Crash)。
5、對象釋放后內存被改動過,寫上了可以訪問的數據,但是再次訪問的時候執行的代碼把別的數據寫壞了,遇到這種Crash只能哭了(隨機Crash,難度大,概率低)!!
6、對象釋放后再次release(幾乎是必現Crash,但也有例外,很常見)。