? ? ? ? ? ? 最近項(xiàng)目上需要對(duì)崩潰信息進(jìn)行處理韭山,要滿足崩潰后及時(shí)捕捉到崩潰信息掀淘,當(dāng)應(yīng)用下次打開(kāi)后再將報(bào)文上傳至服務(wù)器沛贪。下面就來(lái)談?wù)勥@些天個(gè)人的一些收獲吨枉。
? ? ? ?我們通常所接觸到的崩潰主要涉及到兩種:
? ? ? 一:由EXC_BAD_ACCESS引起的伪冰,原因是內(nèi)存訪問(wèn)錯(cuò)誤誓酒,重復(fù)釋放等錯(cuò)誤
? ? ? 二: 未被捕獲的Objective-C異常(NSException)
? ? ? 針對(duì)NSException這種錯(cuò)誤,可以直接調(diào)用NSSetUncaughtExceptionHandler函數(shù)來(lái)捕獲;而針對(duì)EXC_BAD_ACCESS錯(cuò)誤則需通過(guò)自定義注冊(cè)SIGNAL來(lái)捕獲。一般產(chǎn)生一個(gè)NSException異常的時(shí)候靠柑,同時(shí)也會(huì)拋出一個(gè)SIGNAL的信號(hào)(當(dāng)然這只是一般情況寨辩,有時(shí)可能只是會(huì)單獨(dú)出現(xiàn))。
/*!
*? 開(kāi)啟異常的處理方法
*/
void InstallUncaughtExceptionHandler(void) {
//捕捉NSException錯(cuò)誤
NSSetUncaughtExceptionHandler (&uncaughtExceptionHandler);
//以下是注冊(cè)SIGNAL信號(hào)
//4:執(zhí)行了非法指令. 通常是因?yàn)榭蓤?zhí)行文件本身出現(xiàn)錯(cuò)誤, 或者試圖執(zhí)行數(shù)據(jù)段. 堆棧溢出時(shí)也有可能產(chǎn)生這個(gè)信號(hào)歼冰。
signal(SIGILL, mySignalHandler);
//6:調(diào)用abort函數(shù)生成的信號(hào)靡狞。
signal(SIGABRT, mySignalHandler);
//7:非法地址, 包括內(nèi)存地址對(duì)齊(alignment)出錯(cuò)。比如訪問(wèn)一個(gè)四個(gè)字長(zhǎng)的整數(shù), 但其地址不是4的倍數(shù)隔嫡。它與SIGSEGV的區(qū)別在于后者是由于對(duì)合法存儲(chǔ)地址的非法訪問(wèn)觸發(fā)的(如訪問(wèn)不屬于自己存儲(chǔ)空間或只讀存儲(chǔ)空間)甸怕。
signal(SIGBUS, mySignalHandler);
//8:在發(fā)生致命的算術(shù)運(yùn)算錯(cuò)誤時(shí)發(fā)出. 不僅包括浮點(diǎn)運(yùn)算錯(cuò)誤, 還包括溢出及除數(shù)為0等其它所有的算術(shù)的錯(cuò)誤。
signal(SIGFPE, mySignalHandler);
//11:試圖訪問(wèn)未分配給自己的內(nèi)存, 或試圖往沒(méi)有寫(xiě)權(quán)限的內(nèi)存地址寫(xiě)數(shù)據(jù).
signal(SIGSEGV, mySignalHandler);
//13:管道破裂腮恩。這個(gè)信號(hào)通常在進(jìn)程間通信產(chǎn)生梢杭,比如采用FIFO(管道)通信的兩個(gè)進(jìn)程,讀管道沒(méi)打開(kāi)或者意外終止就往管道寫(xiě)秸滴,寫(xiě)進(jìn)程會(huì)收到SIGPIPE信號(hào)武契。此外用Socket通信的兩個(gè)進(jìn)程,寫(xiě)進(jìn)程在寫(xiě)Socket的時(shí)候荡含,讀進(jìn)程已經(jīng)終止咒唆。
signal(SIGPIPE, mySignalHandler);
}
產(chǎn)生上述的signal的時(shí)候就會(huì)調(diào)用我們定義的mySignalHandler來(lái)處理異常,當(dāng)產(chǎn)生NSException錯(cuò)誤時(shí)就會(huì)調(diào)用系統(tǒng)提供的一個(gè)現(xiàn)成的函數(shù)NSSetUncaughtExceptionHandler()释液,這里面的方法名自己隨便取全释。
void uncaughtExceptionHandler(NSException *exception) {
int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
// 如果太多不用處理
if (exceptionCount > UncaughtExceptionMaximum) {
return;
}
//獲取調(diào)用堆棧
NSArray *callStack = [exception callStackSymbols];
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
[userInfo setObject:callStack forKey:UncaughtExceptionHandlerAddressesKey];
//在主線程中,執(zhí)行指定的方法, withObject是執(zhí)行方法傳入的參數(shù)
[[[UncaughtExceptionHandler alloc] init]
performSelectorOnMainThread:@selector(handleException:)
withObject:
[NSException exceptionWithName:[exception name]
reason:[exception reason]
userInfo:userInfo]
waitUntilDone:YES];
}
? ? ? ? 這部分的方法就是對(duì)應(yīng)NSSetUncaughtExceptionHandler的處理误债,只要方法關(guān)聯(lián)到這個(gè)函數(shù)浸船,那么發(fā)生相應(yīng)錯(cuò)誤時(shí)會(huì)自動(dòng)調(diào)用該函數(shù),調(diào)用時(shí)會(huì)傳入exception參數(shù)找前。獲取異常后會(huì)將捕獲的異常傳入最終調(diào)用處理的handleException函數(shù)糟袁,后面會(huì)提到。
//處理signal報(bào)錯(cuò)
void mySignalHandler(int signal) {
int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
// 如果太多不用處理
if (exceptionCount > UncaughtExceptionMaximum) {
return;
}
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
//獲取調(diào)用堆棧
NSArray *callStack = [UncaughtExceptionHandler backtrace];
[userInfo setObject:callStack forKey:UncaughtExceptionHandlerAddressesKey];
[userInfo setObject:[NSNumber numberWithInt:signal] forKey:UncaughtExceptionHandlerSignalKey];
//在主線程中躺盛,執(zhí)行指定的方法, withObject是執(zhí)行方法傳入的參數(shù)
[[[UncaughtExceptionHandler alloc] init]
performSelectorOnMainThread:@selector(handleException:)
withObject:
[NSException exceptionWithName:UncaughtExceptionHandlerSignalExceptionName
reason: description
userInfo: userInfo]
waitUntilDone:YES];
}
上面就是用來(lái)處理NSSetUncaughtExceptionHandler無(wú)法捕獲的signal项戴。和上面一樣,這里最后也是會(huì)將捕獲的異常傳入handleException函數(shù)槽惫≈芏#可以注意到這里獲取調(diào)用堆棧的方法和上面不同,上面的函數(shù)在系統(tǒng)調(diào)用時(shí)就會(huì)傳入exception界斜,通過(guò)exception可以很方便的獲取到調(diào)用堆棧仿耽,但是這里不一樣,系統(tǒng)調(diào)用時(shí)傳入的僅僅是signal值各薇。
//獲取調(diào)用當(dāng)前線程的堆棧
+ (NSArray *)backtrace {
//指針列表
void* callstack[128];
//backtrace用來(lái)獲取當(dāng)前線程的調(diào)用堆棧项贺,獲取的信息存放在這里的callstack中
//128用來(lái)指定當(dāng)前的buffer中可以保存多少個(gè)void*元素
//返回值是實(shí)際獲取的指針個(gè)數(shù)
int frames = backtrace(callstack, 128);
//backtrace_symbols將從backtrace函數(shù)獲取的信息轉(zhuǎn)化為一個(gè)字符串?dāng)?shù)組
//返回一個(gè)指向字符串?dāng)?shù)組的指針
//每個(gè)字符串包含了一個(gè)相對(duì)于callstack中對(duì)應(yīng)元素的可打印信息君躺,包括函數(shù)名、偏移地址开缎、實(shí)際返回地址
char **strs = backtrace_symbols(callstack, frames);
int i;
NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];
for (i = 0; i < frames; i ++) {
[backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
}
free(strs);
return backtrace;
}
上面就是我們自己獲取到調(diào)用堆棧的方法棕叫。backtrace是Linux下用來(lái)追蹤函數(shù)調(diào)用堆棧以及定位段錯(cuò)誤的函數(shù)。
- (void)handleException:(NSException *)exception {
[self validateAndSaveCriticalApplicationData:exception];
//在這里我們可以進(jìn)行延時(shí)操作奕删,保證在崩潰前我們能有充分的時(shí)間來(lái)完成對(duì)于崩潰信息的采集
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
while (!self.dismissed) {
for (NSString *mode in (NSArray *)allModes) {
//快速切換Mode
CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
}
}
CFRelease(allModes);
NSSetUncaughtExceptionHandler(NULL);
signal(SIGABRT, SIG_DFL);
signal(SIGILL, SIG_DFL);
signal(SIGSEGV, SIG_DFL);
signal(SIGFPE, SIG_DFL);
signal(SIGBUS, SIG_DFL);
signal(SIGPIPE, SIG_DFL);
if ([[exception name] isEqual:UncaughtExceptionHandlerSignalExceptionName]) {
kill(getpid(), [[[exception userInfo] objectForKey:UncaughtExceptionHandlerSignalKey] intValue]);
} else {
[exception raise];
}
}
//使用開(kāi)啟監(jiān)聽(tīng)
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after app launch
InstallUncaughtExceptionHandler();
return YES;
}