iOS CrashLog分析詳細(xì)分析了iOS崩潰日志的非代碼層面的收集和解析朝群。這篇文章分析一下如何用純代碼來(lái)收集分析程序崩潰。
問(wèn)題一:平時(shí)程序遇到崩潰的本質(zhì)值纱?
引發(fā)崩潰的代碼本質(zhì)上就兩類:
1.一個(gè)是c++語(yǔ)言層面的錯(cuò)誤,比如野指針,除零其馏,內(nèi)存訪問(wèn)異常等等。無(wú)論是iOS還是android系統(tǒng)爆安,其底層都是unix或者是類unix系統(tǒng),都可以通過(guò)信號(hào)機(jī)制來(lái)獲取 signal或者是sigaction.設(shè)置一個(gè)回調(diào)函數(shù).
2.另一類是未捕獲異常(Uncaught Exception)叛复,iOS下面最常見(jiàn)的就是objective-c的NSException(通過(guò)@throw拋出,比如扔仓,NSArray訪問(wèn)元素越界)褐奥,android下面就是java拋出的異常了。這些異常如果沒(méi)有在最上層try住翘簇,那么程序就崩潰了.可以使用NSUncaughtExceptionHandler來(lái)進(jìn)行抓取撬码。這種取到的日志是已經(jīng)符號(hào)化過(guò)后的。
使用NSUncaughtExceptionHandler抓取NSException
NSSetUncaughtExceptionHandler(&ExceptionHandler);
void ExceptionHandler(NSException *exception)
{
//NSString *reason = [exception reason];
//NSString *name = [exception name];
NSLog(@"%@",exception.userInfo);
NSString* ret=[NSString stringWithFormat:@"異常名稱:\n%@\n\n異常原因:\n%@\n\n出錯(cuò)堆棧內(nèi)容:\n%@\n",name,reason,[exception callStackSymbols] ];
NSLog(@"%@",ret);
}
抓取Signal消息
signal信號(hào)是Unix系統(tǒng)中的,是一種異步通知機(jī)制.信號(hào)傳遞給進(jìn)程后,在沒(méi)有處理函數(shù)的情況下,程序可以指定三種行為,
- 忽略該信號(hào),但是對(duì)于信號(hào)SIGKILL和SIGSTOP不可忽略
- 使用默認(rèn)的處理函數(shù)SIG_DFL,大多數(shù)信號(hào)的默認(rèn)動(dòng)作是終止進(jìn)程.
捕獲信號(hào),執(zhí)行用戶定義的函數(shù). - 有兩個(gè)特殊的常量:
SIG_IGN,向內(nèi)核表示忽略此信號(hào).對(duì)于不能忽略的兩個(gè)信號(hào)SIGKILL和SIGSTOP,調(diào)用時(shí)會(huì)報(bào)錯(cuò).
SIG_DFL,執(zhí)行該信號(hào)的系統(tǒng)默認(rèn)動(dòng)作.
還有兩個(gè)常用的函數(shù)
int kill(pid_t pid, int signo); //發(fā)送信號(hào)到指定的進(jìn)程
int raise(int signo); //發(fā)送信號(hào)給自己.
UNIX系統(tǒng)中常用的信號(hào)有
SIGABRT--程序中止命令中止信號(hào)
SIGALRM--程序超時(shí)信號(hào)
SIGFPE--程序浮點(diǎn)異常信號(hào)
SIGILL--程序非法指令信號(hào)
SIGHUP--程序終端中止信號(hào)
SIGINT--程序鍵盤中斷信號(hào)
SIGKILL--程序結(jié)束接收中止信號(hào)
SIGTERM--程序kill中止信號(hào)
SIGSTOP--程序鍵盤中止信號(hào)
SIGSEGV--程序無(wú)效內(nèi)存中止信號(hào)
SIGBUS--程序內(nèi)存字節(jié)未對(duì)齊中止信號(hào)
SIGPIPE--程序Socket發(fā)送失敗中止信號(hào)
抓取Signal的代碼
signal(SIGHUP, SignalExceptionHandler);
signal(SIGINT, SignalExceptionHandler);
signal(SIGQUIT, SignalExceptionHandler);
signal(SIGABRT, SignalExceptionHandler);
signal(SIGILL, SignalExceptionHandler);
signal(SIGSEGV, SignalExceptionHandler);
signal(SIGFPE, SignalExceptionHandler);
signal(SIGBUS, SignalExceptionHandler);
signal(SIGPIPE, SignalExceptionHandler);
void SignalExceptionHandler(int sig)
{
NSMutableString *stackInfo = [[NSMutableString alloc] init];
[stackInfo appendString:@"Last Exception Backtrace:\n\n"];
void* callstack[128];
int i, frames = backtrace(callstack, 128);
char** strs = backtrace_symbols(callstack, frames);
for (i = 0; i <frames; ++i) {
[stackInfo appendFormat:@"%s\n", strs[i]];
}
}
但這里會(huì)將信號(hào)不斷的發(fā)向該處理函數(shù),導(dǎo)致應(yīng)用無(wú)法正常崩潰,因?yàn)橐话愕南⑻幚頃?huì)向進(jìn)程終結(jié),但是這里沒(méi)有,所以還會(huì)有同樣地信號(hào)不斷的發(fā)過(guò)來(lái)并被處理.所以處理函數(shù)后要終結(jié)該處理函數(shù)的處理,并將其由系統(tǒng)默認(rèn)處理,即:
signal(sig, SIG_DFL);
而對(duì)于有些時(shí)候,在iOS中,在應(yīng)用崩潰后,保持運(yùn)行狀態(tài)而不退出:
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
while (!dismissed)
{
for (NSString *mode in (__bridge NSArray *)allModes)
{
CFRunLoopRunInMode((__bridge CFStringRef)mode, 0.001, false);
}
}
CFRelease(allModes);
應(yīng)用以上代碼,可以做到崩潰時(shí)彈框提示應(yīng)用,以讓用戶還是可以正常操作,讓響應(yīng)更加友好.Demo
這里最關(guān)鍵的一步版保,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)下捕捉汞幢。
多個(gè)Crash日志收集服務(wù)共存的坑
自己的程序里集成多個(gè)Crash日志收集服務(wù)實(shí)在不是明智之舉驼鹅。通常情況下,第三方功能性SDK都會(huì)集成一個(gè)Crash收集服務(wù)森篷,以及時(shí)發(fā)現(xiàn)自己SDK的問(wèn)題输钩。如果同時(shí)有多方通過(guò)NSSetUncaughtExceptionHandler注冊(cè)異常處理程序,會(huì)導(dǎo)致程序發(fā)生exception的時(shí)候不知道調(diào)用了哪一個(gè)handler而導(dǎo)致錯(cuò)誤疾宏。
參考:
1张足、iOS異常捕獲
2、獲取 iOS crash log(分析得很詳細(xì))
3坎藐、漫談iOS Crash收集框架
4为牍、iOS崩潰信息收集