? ? ? ? ? ? 最近項目上需要對崩潰信息進行處理,要滿足崩潰后及時捕捉到崩潰信息,當應用下次打開后再將報文上傳至服務器。下面就來談談這些天個人的一些收獲。
? ? ? ?我們通常所接觸到的崩潰主要涉及到兩種:
? ? ? 一:由EXC_BAD_ACCESS引起的,原因是內存訪問錯誤,重復釋放等錯誤
? ? ? 二: 未被捕獲的Objective-C異常(NSException)
? ? ? 針對NSException這種錯誤,可以直接調用NSSetUncaughtExceptionHandler函數來捕獲;而針對EXC_BAD_ACCESS錯誤則需通過自定義注冊SIGNAL來捕獲。一般產生一個NSException異常的時候,同時也會拋出一個SIGNAL的信號(當然這只是一般情況,有時可能只是會單獨出現)。
/*!
*? 開啟異常的處理方法
*/
void InstallUncaughtExceptionHandler(void) {
//捕捉NSException錯誤
NSSetUncaughtExceptionHandler (&uncaughtExceptionHandler);
//以下是注冊SIGNAL信號
//4:執行了非法指令. 通常是因為可執行文件本身出現錯誤, 或者試圖執行數據段. 堆棧溢出時也有可能產生這個信號。
signal(SIGILL, mySignalHandler);
//6:調用abort函數生成的信號。
signal(SIGABRT, mySignalHandler);
//7:非法地址, 包括內存地址對齊(alignment)出錯。比如訪問一個四個字長的整數, 但其地址不是4的倍數。它與SIGSEGV的區別在于后者是由于對合法存儲地址的非法訪問觸發的(如訪問不屬于自己存儲空間或只讀存儲空間)。
signal(SIGBUS, mySignalHandler);
//8:在發生致命的算術運算錯誤時發出. 不僅包括浮點運算錯誤, 還包括溢出及除數為0等其它所有的算術的錯誤。
signal(SIGFPE, mySignalHandler);
//11:試圖訪問未分配給自己的內存, 或試圖往沒有寫權限的內存地址寫數據.
signal(SIGSEGV, mySignalHandler);
//13:管道破裂。這個信號通常在進程間通信產生,比如采用FIFO(管道)通信的兩個進程,讀管道沒打開或者意外終止就往管道寫,寫進程會收到SIGPIPE信號。此外用Socket通信的兩個進程,寫進程在寫Socket的時候,讀進程已經終止。
signal(SIGPIPE, mySignalHandler);
}
產生上述的signal的時候就會調用我們定義的mySignalHandler來處理異常,當產生NSException錯誤時就會調用系統提供的一個現成的函數NSSetUncaughtExceptionHandler(),這里面的方法名自己隨便取。
void uncaughtExceptionHandler(NSException *exception) {
int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
// 如果太多不用處理
if (exceptionCount > UncaughtExceptionMaximum) {
return;
}
//獲取調用堆棧
NSArray *callStack = [exception callStackSymbols];
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
[userInfo setObject:callStack forKey:UncaughtExceptionHandlerAddressesKey];
//在主線程中,執行指定的方法, withObject是執行方法傳入的參數
[[[UncaughtExceptionHandler alloc] init]
performSelectorOnMainThread:@selector(handleException:)
withObject:
[NSException exceptionWithName:[exception name]
reason:[exception reason]
userInfo:userInfo]
waitUntilDone:YES];
}
? ? ? ? 這部分的方法就是對應NSSetUncaughtExceptionHandler的處理,只要方法關聯到這個函數,那么發生相應錯誤時會自動調用該函數,調用時會傳入exception參數。獲取異常后會將捕獲的異常傳入最終調用處理的handleException函數,后面會提到。
//處理signal報錯
void mySignalHandler(int signal) {
int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
// 如果太多不用處理
if (exceptionCount > UncaughtExceptionMaximum) {
return;
}
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
//獲取調用堆棧
NSArray *callStack = [UncaughtExceptionHandler backtrace];
[userInfo setObject:callStack forKey:UncaughtExceptionHandlerAddressesKey];
[userInfo setObject:[NSNumber numberWithInt:signal] forKey:UncaughtExceptionHandlerSignalKey];
//在主線程中,執行指定的方法, withObject是執行方法傳入的參數
[[[UncaughtExceptionHandler alloc] init]
performSelectorOnMainThread:@selector(handleException:)
withObject:
[NSException exceptionWithName:UncaughtExceptionHandlerSignalExceptionName
reason: description
userInfo: userInfo]
waitUntilDone:YES];
}
上面就是用來處理NSSetUncaughtExceptionHandler無法捕獲的signal。和上面一樣,這里最后也是會將捕獲的異常傳入handleException函數。可以注意到這里獲取調用堆棧的方法和上面不同,上面的函數在系統調用時就會傳入exception,通過exception可以很方便的獲取到調用堆棧,但是這里不一樣,系統調用時傳入的僅僅是signal值。
//獲取調用當前線程的堆棧
+ (NSArray *)backtrace {
//指針列表
void* callstack[128];
//backtrace用來獲取當前線程的調用堆棧,獲取的信息存放在這里的callstack中
//128用來指定當前的buffer中可以保存多少個void*元素
//返回值是實際獲取的指針個數
int frames = backtrace(callstack, 128);
//backtrace_symbols將從backtrace函數獲取的信息轉化為一個字符串數組
//返回一個指向字符串數組的指針
//每個字符串包含了一個相對于callstack中對應元素的可打印信息,包括函數名、偏移地址、實際返回地址
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;
}
上面就是我們自己獲取到調用堆棧的方法。backtrace是Linux下用來追蹤函數調用堆棧以及定位段錯誤的函數。
- (void)handleException:(NSException *)exception {
[self validateAndSaveCriticalApplicationData:exception];
//在這里我們可以進行延時操作,保證在崩潰前我們能有充分的時間來完成對于崩潰信息的采集
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];
}
}
//使用開啟監聽
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after app launch
InstallUncaughtExceptionHandler();
return YES;
}