1.前言
- crash認識
- 一套系統(tǒng)鲁沥、一款app经窖、一個功能、甚至一行代碼都可能會出現(xiàn)crash,crash伴隨著我們的日常生活狭园,如果我們正在玩游戲,LOL打團時,游戲閃退、電腦死機专控,那就想要跳起來砸鍵盤了。質量太差的產(chǎn)品遏餐,會導致產(chǎn)品負面評價越來越多伦腐,用戶流失越來越嚴重,如果在關鍵環(huán)節(jié)出現(xiàn)了crash失都,比如下單蔗牡、支付等,那就會造成直接損失嗅剖。作為技術開發(fā),我們對于這種問題要抱有零容忍的態(tài)度嘁扼,雖然我們不可能做到
0
crash,但是我們要盡量避免項目中存在的crash
- 為什么沒有0 crash
- crash的原因有很多信粮,在工作中我們也有很多方法去避免造成crash,比如自定義一個安全處理方法趁啸,做好異常crash因素判斷强缘。但是為什么沒有一個0crash的系統(tǒng)呢,就不能提供給我們絕對穩(wěn)定可靠的API嗎不傅?
- 個人認為旅掂,每個API的設計都有其作用價值。比如iOS系統(tǒng)提供的
[NSAarray objectAtIndex:]
方法访娶,如果出現(xiàn)越界就會crash商虐,在項目中也有[NSArray safetyObjctAtIndex:]
這樣的自定義安全處理方法,越界時返回nil崖疤,但是返回nil秘车,就一定是安全的嗎?一定是符合所有場景需求的嗎劫哼?比如某一個代碼異常時叮趴,后面的代碼是否應該執(zhí)行,執(zhí)行是否會帶來其他的負面影響
2.異常指標
當開發(fā)階段出現(xiàn)crash時权烧,我們可以直接通過xcode定位crash堆棧眯亦,在測試階段crash時,我們勉強還可以直接拿到測試機連接xcode查看日志般码,但是線上用戶或者拿不到crash測試機的情況下妻率,我們需要自己收集crash日志
造成crash的異常主要類型
-
Objective-C uncaughtException異常
- 這類異常通常可以手動捕獲侈询,處理
@try...@catch...@finallly
舌涨,在項目中有很多地方使用
- 這類異常通常可以手動捕獲侈询,處理
NSArray *arr = @[@1,@2];
@try {
[arr objectAtIndex:3];
} @catch (NSException *exp){
} @finally {
}
-
NSInvalidArgumentException,非法參數(shù)
- nil參數(shù)
- unrecognized selector sendt to instance
- ...
- NSRangeException,越界異常
- nil參數(shù)
- 數(shù)組囊嘉,字符串獲取下標
- NSGenericException温技,通用異常
-
for ...in
循環(huán)中對可遍數(shù)組進行增加或者刪除,在for( ; ; )中不會造成crash扭粱,但是容易造成邏輯上的錯誤舵鳞,需要注意 - NSMallocException,內存分配異常
NSMutableData *mData = [[NSMutableData alloc] initWithCapacity:1];
NSUInteger len = 1844674407370955161;
[mData increaseLengthBy:len];
*** Terminating app due to uncaught exception 'NSMallocException',
reason: 'Failed to grow buffer'
-
Mach異常
-
先看下OSX和iOS的系統(tǒng)結構琢蛤,了解幾個基本概念
-
- Mach的職責主要是進程和線程抽象蜓堕、虛擬內存管理、任務調度博其、進程間通信和消息傳遞機制等
- BSD層則在Mach之上套才,提供一套可靠且更現(xiàn)代的API,提供了POSIX(可移植操作系統(tǒng)接口)兼容性慕淡。
- Mach異常是指最底層的內核級異常,
-
Mach異常處理流程
-
BSD Signals
-
Mach已經(jīng)通過異常機制提供了底層的陷阱處理背伴,而BSD則在異常機制之上構建了信號處理機制。硬件產(chǎn)生的信號被Mach捕捉峰髓,然后轉換為對應的UNIX信號傻寂。為了維護一個統(tǒng)一的機制,操作系統(tǒng)和用戶嘗試的信號首先被轉換為Mach異常携兵,然后再轉換為信號(Signals)疾掰,如下圖所示:
-
- 信號可以看做是對硬件異常跟軟件異常的封裝,需要處理的signals
static const int g_fatalSignals[] =
{
SIGABRT,//調用abort生成的信號,有可能是NSException也有可能是Mach
SIGBUS,//非法地址
SIGFPE,//算術運算錯誤
SIGILL,//執(zhí)行非法指令
SIGPIPE,//進程間通信產(chǎn)生徐紧,通信管道破裂
SIGSEGV,//非法訪問地址静檬,比如野指針,通常是對象的異常釋放并级,指針未清空
SIGSYS,//非法的系統(tǒng)調用
SIGTRAP,//斷點指令巴柿,一般出現(xiàn)在debug調試時
};
-
Mach exception和Signal轉換關系
3.crash收集
-
Objective-C uncaughtException
可以通過NSSetUncaughtExceptionHandler
系統(tǒng)回調進行捕獲 - Mach異常,BSD將mach異常最終都轉換為信號異常死遭,所以我們只需要捕捉那些fatal signals广恢,如果開發(fā)者沒有捕獲Mach異常,則會被host層的方法ux_exception()將異常轉換為對應的UNIX信號呀潭,并通過方法threadsignal()將信號投遞到出錯線程钉迷。可以通過方法signal(x, SignalHandler)來捕獲single钠署。
- C++ exceptions使用系統(tǒng)封裝好的函數(shù)
std::set_terminate(CPPExceptionTerminate)
來設置回調 - 未被
try catch
的NSException
會發(fā)出kill
或pthread_kill
信號-> Mach異常-> Unix信號(SIGABRT),但是SIGABRT
在處理收集信息時糠聪,獲取當前堆棧時獲取不到,所以采用NSSetUncaughtExceptionHandler
,可以參考KSCrash 源碼
4.Crash快速分析
- 當我們拿到crash日志時谐鼎,應首先從
crash Type
舰蟆,crash thread
快速定位到造成crash的代碼段。之所以首先要看這兩個,是因為type能大致知道crash的類型身害,如果是OC類型的異常味悄,那基本上處理起來比較簡單,如果是mach signals類型的塌鸯,通過查看造成crash的線程堆棧侍瑟,也能快速定位到方法,舉個實際項目中的例子:
線上有個偶現(xiàn)的crash丙猬,crash Type為SIGSEGV涨颜,且thread不定,子線程茧球,主線程都會存在庭瑰,但是代碼段相同,由于SIGSEGV是野指針異常類型抢埋,且由于在多線程中都會觸發(fā)见擦,說明問題基本上是多線程的對象讀寫安全問題