開(kāi)發(fā)iOS應(yīng)用病曾,解決Crash問(wèn)題始終是一個(gè)難題锨侯。Crash分為兩種斤斧,
一種是由EXC_BAD_ACCESS引起的勺美,原因是訪(fǎng)問(wèn)了不屬于本進(jìn)程的內(nèi)存地址递胧,有可能是訪(fǎng)問(wèn)已被釋放的內(nèi)存;
另一種是未被捕獲的目標(biāo)C異常(NSException)記錄,導(dǎo)致程序向自身發(fā)送了SIGABRT信號(hào)而崩潰赡茸。
其實(shí)對(duì)于未捕獲的目標(biāo)C異常缎脾,我們是有辦法將它記錄下來(lái)的,如果日志記錄得當(dāng)占卧,能夠解決絕大部分崩潰的問(wèn)題遗菠。這里對(duì)于UI線(xiàn)程與后臺(tái)線(xiàn)程分別說(shuō)明
一、系統(tǒng)崩潰
對(duì)于系統(tǒng)崩潰而引起的程序異常退出华蜒,可以通過(guò)NSSetUncaughtExceptionHandler機(jī)制捕獲辙纬,代碼:實(shí)現(xiàn)一個(gè)用于處理異常的方法
void HandleException(NSException *exception) {
// 異常的堆棧信息
NSArray *stackArray = [exception callStackSymbols];
// 出現(xiàn)異常的原因
NSString *reason = [exception reason];
// 異常名稱(chēng)
NSString *name = [exception name];
NSString *exceptionInfo = [NSString stringWithFormat:@"Exception reason:%@\nException name:%@\nException stack:%@",name, reason, stackArray];
NSLog(@"%@", exceptionInfo);
NSString *logPath=[NSString stringWithFormat:@"%@/Documents/error.log",NSHomeDirectory()];
[exceptionInfo writeToFile:logPath atomically:YES encoding:NSUTF8StringEncoding error:nil];
}
然后在啟動(dòng)appdidFinishLaunchingWithOptions的時(shí)候設(shè)置
NSSetUncaughtExceptionHandler (&UncaughtExceptionHandlers);//系統(tǒng)異常捕獲
當(dāng)捕獲到異常時(shí),就會(huì)調(diào)用UncaughtExceptionHandlers來(lái)出來(lái)異常
二叭喜、處理signal
使用Objective-C的異常處理是不能得到signal的贺拣,如果要處理它,我們還要利用unix標(biāo)準(zhǔn)的signal機(jī)制,注冊(cè)SIGABRT, SIGBUS, SIGSEGV等信號(hào)發(fā)生時(shí)的處理函數(shù)譬涡。該函數(shù)中我們可以輸出棧信息闪幽,版本信息等其他一切我們所想要的。NSSetUncaughtExceptionHandler 用來(lái)做異常處理涡匀,但功能非常有限.而引起崩潰的大多數(shù)原因如:內(nèi)存訪(fǎng)問(wèn)錯(cuò)誤沟使,重復(fù)釋放等錯(cuò)誤就無(wú)能為力了,因?yàn)檫@種錯(cuò)誤它拋出的是Signal渊跋,所以必須要專(zhuān)門(mén)做Signal處理,代碼如下:
首先定義一個(gè)UncaughtExceptionHandler類(lèi)着倾,用來(lái)捕獲處理所有的崩潰信息
@interface UncaughtExceptionHandler : NSObject {
BOOL dismissed;
}
+ (void)InstallUncaughtExceptionHandler;
@end
#import <UIKit/UIKit.h>
#import "UncaughtExceptionHandler.h"
#include <libkern/OSAtomic.h>
#include <execinfo.h>
NSString * const UncaughtExceptionHandlerSignalExceptionName = @"UncaughtExceptionHandlerSignalExceptionName";
NSString * const UncaughtExceptionHandlerSignalKey = @"UncaughtExceptionHandlerSignalKey";
NSString * const UncaughtExceptionHandlerAddressesKey = @"UncaughtExceptionHandlerAddressesKey";
NSString * const UncaughtExceptionHandlerFileKey = @"UncaughtExceptionHandlerFileKey";
volatile int32_t UncaughtExceptionCount = 0;
const int32_t UncaughtExceptionMaximum = 10;
const NSInteger UncaughtExceptionHandlerSkipAddressCount = 4;
const NSInteger UncaughtExceptionHandlerReportAddressCount = 5;
void MySignalHandler(int signal);
@implementation UncaughtExceptionHandler
+ (void)InstallUncaughtExceptionHandler {
NSSetUncaughtExceptionHandler (&UncaughtExceptionHandlers);////系統(tǒng)異常捕獲(越界)
//信號(hào)量截?cái)? signal(SIGABRT, MySignalHandler);
signal(SIGILL, MySignalHandler);
signal(SIGSEGV, MySignalHandler);
signal(SIGFPE, MySignalHandler);
signal(SIGBUS, MySignalHandler);
signal(SIGPIPE, MySignalHandler);
}
//獲取函數(shù)堆棧信息
+ (NSArray *)backtrace {
void* callstack[128];
int frames = backtrace(callstack, 128);//用于獲取當(dāng)前線(xiàn)程的函數(shù)調(diào)用堆棧拾酝,返回實(shí)際獲取的指針個(gè)數(shù)
char **strs = backtrace_symbols(callstack, frames);//從backtrace函數(shù)獲取的信息轉(zhuǎn)化為一個(gè)字符串?dāng)?shù)組
int i;
NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];
for (i = UncaughtExceptionHandlerSkipAddressCount;
i < UncaughtExceptionHandlerSkipAddressCount+UncaughtExceptionHandlerReportAddressCount;
i++) {
[backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
}
free(strs);
return backtrace;
}
- (void)saveCreash:(NSException *)exception file:(NSString *)file {
NSArray *stackArray = [exception callStackSymbols];// 異常的堆棧信息
NSString *reason = [exception reason];// 出現(xiàn)異常的原因
NSString *name = [exception name];// 異常名稱(chēng)
//或者直接用代碼,輸入這個(gè)崩潰信息卡者,以便在console中進(jìn)一步分析錯(cuò)誤原因
NSLog(@"CRASH: %@", exception);
NSLog(@"Stack Trace: %@", [exception callStackSymbols]);
NSString * _libPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:file];
// NSString *_libPath=[NSHomeDirectory() stringByAppendingPathComponent:file];
if (![[NSFileManager defaultManager] fileExistsAtPath:_libPath]) {
[[NSFileManager defaultManager] createDirectoryAtPath:_libPath withIntermediateDirectories:YES attributes:nil error:nil];
}
NSDate* dat = [NSDate dateWithTimeIntervalSinceNow:0];
NSTimeInterval a=[dat timeIntervalSince1970];
NSString *timeString = [NSString stringWithFormat:@"%f", a];
NSString * savePath = [_libPath stringByAppendingFormat:@"/error%@.log",timeString];
NSString *exceptionInfo = [NSString stringWithFormat:@"Exception reason:%@\nException name:%@\nException stack:%@",name, reason, stackArray];
BOOL sucess = [exceptionInfo writeToFile:savePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
NSLog(@"保存崩潰日志 sucess:%d,%@",sucess,savePath);
}
//異常處理方法
- (void)handleException:(NSException *)exception {
NSDictionary *userInfo=[exception userInfo];
[self saveCreash:exception file:[userInfo objectForKey:UncaughtExceptionHandlerFileKey]];
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];
}
}
//獲取應(yīng)用信息
NSString* getAppInfo() {
NSString *appInfo = [NSString stringWithFormat:@"App : %@ %@(%@)\nDevice : %@\nOS Version : %@ %@\n",
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"],
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"],
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"],
[UIDevice currentDevice].model,
[UIDevice currentDevice].systemName,
[UIDevice currentDevice].systemVersion];
// [UIDevice currentDevice].uniqueIdentifier];
NSLog(@"Crash!!!! %@", appInfo);
return appInfo;
}
//NSSetUncaughtExceptionHandler捕獲異常的調(diào)用方法
//利用 NSSetUncaughtExceptionHandler蒿囤,當(dāng)程序異常退出的時(shí)候,可以先進(jìn)行處理崇决,然后做一些自定義的動(dòng)作
void UncaughtExceptionHandlers (NSException *exception) {
int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
if (exceptionCount > UncaughtExceptionMaximum)
{
return;
}
NSArray *callStack = [UncaughtExceptionHandler backtrace];
NSMutableDictionary *userInfo =
[NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
[userInfo setObject:callStack forKey:UncaughtExceptionHandlerAddressesKey];
[userInfo setObject:@"OCCrash" forKey:UncaughtExceptionHandlerFileKey];
[[[UncaughtExceptionHandler alloc] init] performSelectorOnMainThread:@selector(handleException:)
withObject:[NSException exceptionWithName:[exception name]
reason:[exception reason] userInfo:userInfo]
waitUntilDone:YES];
}
//Signal處理方法
void MySignalHandler(int signal) {
int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);//自動(dòng)增加一個(gè)32位的值
if (exceptionCount > UncaughtExceptionMaximum)
{
return;
}
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObject:[NSNumber numberWithInt:signal] forKey:UncaughtExceptionHandlerSignalKey];
NSArray *callStack = [UncaughtExceptionHandler backtrace];
[userInfo setObject:callStack forKey:UncaughtExceptionHandlerAddressesKey];
[userInfo setObject:@"SigCrash" forKey:UncaughtExceptionHandlerFileKey];
[[[UncaughtExceptionHandler alloc] init] performSelectorOnMainThread:@selector(handleException:)
withObject:[NSException exceptionWithName:UncaughtExceptionHandlerSignalExceptionName
reason:[NSString stringWithFormat:NSLocalizedString(@"Signal %d was raised.\n" @"%@", nil), signal, getAppInfo()] userInfo:userInfo]
waitUntilDone:YES];
}
@end
最后材诽,在didFinishLaunchingWithOptions中調(diào)用該函數(shù)
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[UncaughtExceptionHandler InstallUncaughtExceptionHandler];//捕獲內(nèi)存訪(fǎng)問(wèn)錯(cuò)誤,處理Signal
return YES;
}
三、測(cè)試
當(dāng)運(yùn)行時(shí)遇到系統(tǒng)錯(cuò)誤或內(nèi)存錯(cuò)誤Signal恒傻,就會(huì)被截獲處理并把錯(cuò)誤日志保存下來(lái)(一般可等下次啟動(dòng)時(shí)判斷一下脸侥,把日志上傳),可自己測(cè)試一下:
/1.信號(hào)量
int list[2]={1,2};
int *p = list;
free(p);//導(dǎo)致SIGABRT的錯(cuò)誤盈厘,因?yàn)閮?nèi)存中根本就沒(méi)有這個(gè)空間睁枕,哪來(lái)的free,就在棧中的對(duì)象而已
p[1] = 5;
//2.ios崩潰
NSArray *array= @[@"tom",@"xxx",@"ooo"];
[array objectAtIndex:5];
PS: SignalHandler不要在debug環(huán)境下測(cè)試沸手。因?yàn)橄到y(tǒng)的debug會(huì)優(yōu)先去攔截外遇。我們要運(yùn)行一次后,關(guān)閉debug狀態(tài)契吉。應(yīng)該直接在模擬器上點(diǎn)擊我們build上去的app去運(yùn)行跳仿。而UncaughtExceptionHandler可以在調(diào)試狀態(tài)下捕捉。
附錄科普:
一些信號(hào)說(shuō)明
SIGHUP 本信號(hào)在用戶(hù)終端連接(正尘杈В或非正常)結(jié)束時(shí)發(fā)出, 通常是在終端的控制進(jìn)程結(jié)束時(shí), 通知同一session內(nèi)的各個(gè)作業(yè), 這時(shí)它們與控制終端不再關(guān)聯(lián)菲语。 登錄Linux時(shí),系統(tǒng)會(huì)分配給登錄用戶(hù)一個(gè)終端(Session)租悄。在這個(gè)終端運(yùn)行的所有程序谨究,包括前臺(tái)進(jìn)程組和后臺(tái)進(jìn)程組,一般都屬于這個(gè) Session泣棋。當(dāng)用戶(hù)退出Linux登錄時(shí)胶哲,前臺(tái)進(jìn)程組和后臺(tái)有對(duì)終端輸出的進(jìn)程將會(huì)收到SIGHUP信號(hào)。這個(gè)信號(hào)的默認(rèn)操作為終止進(jìn)程潭辈,因此前臺(tái)進(jìn) 程組和后臺(tái)有終端輸出的進(jìn)程就會(huì)中止鸯屿。不過(guò)可以捕獲這個(gè)信號(hào)澈吨,比如wget能捕獲SIGHUP信號(hào),并忽略它寄摆,這樣就算退出了Linux登錄谅辣, wget也 能繼續(xù)下載。 此外婶恼,對(duì)于與終端脫離關(guān)系的守護(hù)進(jìn)程桑阶,這個(gè)信號(hào)用于通知它重新讀取配置文件。
SIGINT 程序終止(interrupt)信號(hào), 在用戶(hù)鍵入INTR字符(通常是Ctrl-C)時(shí)發(fā)出勾邦,用于通知前臺(tái)進(jìn)程組終止進(jìn)程蚣录。
SIGQUIT 和SIGINT類(lèi)似, 但由QUIT字符(通常是Ctrl-)來(lái)控制. 進(jìn)程在因收到SIGQUIT退出時(shí)會(huì)產(chǎn)生core文件, 在這個(gè)意義上類(lèi)似于一個(gè)程序錯(cuò)誤信號(hào)。
SIGILL 執(zhí)行了非法指令. 通常是因?yàn)榭蓤?zhí)行文件本身出現(xiàn)錯(cuò)誤, 或者試圖執(zhí)行數(shù)據(jù)段. 堆棧溢出時(shí)也有可能產(chǎn)生這個(gè)信號(hào)眷篇。
SIGTRAP 由斷點(diǎn)指令或其它trap指令產(chǎn)生. 由debugger使用萎河。
SIGABRT 調(diào)用abort函數(shù)生成的信號(hào)。
SIGBUS 非法地址, 包括內(nèi)存地址對(duì)齊(alignment)出錯(cuò)蕉饼。比如訪(fǎng)問(wèn)一個(gè)四個(gè)字長(zhǎng)的整數(shù), 但其地址不是4的倍數(shù)虐杯。它與SIGSEGV的區(qū)別在于后者是由于對(duì)合法存儲(chǔ)地址的非法訪(fǎng)問(wèn)觸發(fā)的(如訪(fǎng)問(wèn)不屬于自己存儲(chǔ)空間或只讀存儲(chǔ)空間)。
SIGFPE 在發(fā)生致命的算術(shù)運(yùn)算錯(cuò)誤時(shí)發(fā)出. 不僅包括浮點(diǎn)運(yùn)算錯(cuò)誤, 還包括溢出及除數(shù)為0等其它所有的算術(shù)的錯(cuò)誤昧港。
SIGKILL 用來(lái)立即結(jié)束程序的運(yùn)行. 本信號(hào)不能被阻塞擎椰、處理和忽略。如果管理員發(fā)現(xiàn)某個(gè)進(jìn)程終止不了慨飘,可嘗試發(fā)送這個(gè)信號(hào)确憨。
SIGUSR1 留給用戶(hù)使用
SIGSEGV 試圖訪(fǎng)問(wèn)未分配給自己的內(nèi)存, 或試圖往沒(méi)有寫(xiě)權(quán)限的內(nèi)存地址寫(xiě)數(shù)據(jù).
SIGUSR2 留給用戶(hù)使用
SIGPIPE 管道破裂。這個(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)終止稽坤。
SIGALRM 時(shí)鐘定時(shí)信號(hào), 計(jì)算的是實(shí)際的時(shí)間或時(shí)鐘時(shí)間. alarm函數(shù)使用該信號(hào).
SIGTERM 程序結(jié)束(terminate)信號(hào), 與SIGKILL不同的是該信號(hào)可以被阻塞和處理丈甸。通常用來(lái)要求程序自己正常退出,shell命令kill缺省產(chǎn)生這個(gè)信號(hào)尿褪。如果進(jìn)程終止不了睦擂,我們才會(huì)嘗試SIGKILL。
SIGCHLD 子進(jìn)程結(jié)束時(shí), 父進(jìn)程會(huì)收到這個(gè)信號(hào)杖玲。 如果父進(jìn)程沒(méi)有處理這個(gè)信號(hào)顿仇,也沒(méi)有等待(wait)子進(jìn)程,子進(jìn)程雖然終止,但是還會(huì)在內(nèi)核進(jìn)程表中占有表項(xiàng)臼闻,這時(shí)的子進(jìn)程稱(chēng)為僵尸進(jìn)程鸿吆。這種情 況我們應(yīng)該避免(父進(jìn)程或者忽略SIGCHILD信號(hào),或者捕捉它述呐,或者wait它派生的子進(jìn)程惩淳,或者父進(jìn)程先終止乓搬,這時(shí)子進(jìn)程的終止自動(dòng)由init進(jìn)程 來(lái)接管)进肯。
SIGCONT 讓一個(gè)停止(stopped)的進(jìn)程繼續(xù)執(zhí)行. 本信號(hào)不能被阻塞. 可以用一個(gè)handler來(lái)讓程序在由stopped狀態(tài)變?yōu)槔^續(xù)執(zhí)行時(shí)完成特定的工作. 例如, 重新顯示提示符
SIGSTOP 停止(stopped)進(jìn)程的執(zhí)行. 注意它和terminate以及interrupt的區(qū)別:該進(jìn)程還未結(jié)束, 只是暫停執(zhí)行. 本信號(hào)不能被阻塞, 處理或忽略.
SIGTSTP 停止進(jìn)程的運(yùn)行, 但該信號(hào)可以被處理和忽略. 用戶(hù)鍵入SUSP字符時(shí)(通常是Ctrl-Z)發(fā)出這個(gè)信號(hào)
SIGTTIN 當(dāng)后臺(tái)作業(yè)要從用戶(hù)終端讀數(shù)據(jù)時(shí), 該作業(yè)中的所有進(jìn)程會(huì)收到SIGTTIN信號(hào). 缺省時(shí)這些進(jìn)程會(huì)停止執(zhí)行.
SIGTTOU 類(lèi)似于SIGTTIN, 但在寫(xiě)終端(或修改終端模式)時(shí)收到.
SIGURG 有”緊急”數(shù)據(jù)或out-of-band數(shù)據(jù)到達(dá)socket時(shí)產(chǎn)生.
SIGXCPU 超過(guò)CPU時(shí)間資源限制. 這個(gè)限制可以由getrlimit/setrlimit來(lái)讀取/改變含蓉。
SIGXFSZ 當(dāng)進(jìn)程企圖擴(kuò)大文件以至于超過(guò)文件大小資源限制斟赚。
SIGVTALRM 虛擬時(shí)鐘信號(hào). 類(lèi)似于SIGALRM, 但是計(jì)算的是該進(jìn)程占用的CPU時(shí)間.
SIGPROF 類(lèi)似于SIGALRM/SIGVTALRM, 但包括該進(jìn)程用的CPU時(shí)間以及系統(tǒng)調(diào)用的時(shí)間.
SIGWINCH 窗口大小改變時(shí)發(fā)出.
SIGIO 文件描述符準(zhǔn)備就緒, 可以開(kāi)始進(jìn)行輸入/輸出操作.
SIGPWR Power failure
SIGSYS 非法的系統(tǒng)調(diào)用。
關(guān)鍵點(diǎn)注意
在以上列出的信號(hào)中,程序不可捕獲刃鳄、阻塞或忽略的信號(hào)有:SIGKILL,SIGSTOP 不能恢復(fù)至默認(rèn)動(dòng)作的信號(hào)有:SIGILL,SIGTRAP 默認(rèn)會(huì)導(dǎo)致進(jìn)程流產(chǎn)的信號(hào)有:SIGABRT,SIGBUS,SIGFPE,SIGILL,SIGIOT,SIGQUIT,SIGSEGV,SIGTRAP,SIGXCPU,SIGXFSZ 默認(rèn)會(huì)導(dǎo)致進(jìn)程退出的信號(hào)有: SIGALRM,SIGHUP,SIGINT,SIGKILL,SIGPIPE,SIGPOLL,SIGPROF,SIGSYS,SIGTERM,SIGUSR1,SIGUSR2,SIGVTALRM 默認(rèn)會(huì)導(dǎo)致進(jìn)程停止的信號(hào)有:SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU 默認(rèn)進(jìn)程忽略的信號(hào)有:SIGCHLD,SIGPWR,SIGURG,SIGWINCH 此外,SIGIO在SVR4是退出愉烙,在4.3BSD中是忽略;SIGCONT在進(jìn)程掛起時(shí)是繼續(xù)勺择,否則是忽略稿辙,不能被阻塞。
Crash Callstack分析 – 進(jìn)?一步分析
屬性 說(shuō)明 0x8badf00d 在啟動(dòng)、終?止應(yīng)?用或響應(yīng)系統(tǒng)事件花費(fèi)過(guò)?長(zhǎng)時(shí)間,意為“ate bad food”。 0xdeadfa11 ?用戶(hù)強(qiáng)制退出,意為“dead fall”勾扭。(系統(tǒng)?無(wú)響應(yīng)時(shí),?用戶(hù)按電源開(kāi)關(guān)和HOME) 0xbaaaaaad ?用戶(hù)按住Home鍵和?音量鍵,獲取當(dāng)前內(nèi)存狀態(tài),不代表崩潰 0xbad22222 VoIP應(yīng)?用因?yàn)榛謴?fù)得太頻繁導(dǎo)致crash 0xc00010ff 因?yàn)樘珷C了被干掉,意為“cool off” 0xdead10cc 因?yàn)樵诤笈_(tái)時(shí)仍然占據(jù)系統(tǒng)資源(?比如通訊錄)被干掉,意為“dead lock”
以上為摘抄內(nèi)容,僅供自己收藏使用。
原文地址:http://www.2cto.com/kf/201609/547867.html