主要捕获两种崩溃类型:NSException和Signal异常。
NSException是一个中断程序正常执行的描述对象,包含异常名称、通俗的原因描述、以及包含补充消息的字典。
@interface NSException : NSObject <NSCopying, NSCoding> { @private NSString *name;//名称 NSString *reason;//原因 NSDictionary *userInfo;//补充信息 id reserved; } 复制代码
其中name的值是NSExceptionName类型的字符串。常见值:
NSInvalidArgumentException:插入nil到容器类
NSRangeException:容器类越界或者字符串越界
更多请参考苹果开发者文档
developer.apple.com/documentati…
对于排查问题来说,名称其实不重要,重要的是通俗易懂的reason描述以及堆栈信息。
关键方法:
//执行NSException的方法,将自定义的异常处理的函数地址传进去,发生异常时,会调用该函数。 NSSetUncaughtExceptionHandler(NSUncaughtExceptionHandler * _Nullable); 复制代码
Doraemon完整代码
// DoraemonCrashUncaughtExceptionHandler.m #import "DoraemonCrashUncaughtExceptionHandler.h" #import "DoraemonCrashTool.h" // 记录之前的崩溃回调函数 static NSUncaughtExceptionHandler *previousUncaughtExceptionHandler = NULL; @implementation DoraemonCrashUncaughtExceptionHandler #pragma mark - Register + (void)registerHandler { // 备份现有的异常处理,在自己的处理函数中调用,防止多处设置异常函数时,后者替换前者,导致前者捕获不到异常的情况 previousUncaughtExceptionHandler = NSGetUncaughtExceptionHandler(); // 设置自定义的异常处理函数 NSSetUncaughtExceptionHandler(&DoraemonUncaughtExceptionHandler); } #pragma mark - Private // 崩溃时的回调函数 static void DoraemonUncaughtExceptionHandler(NSException * exception) { // 异常的堆栈信息 NSArray * stackArray = [exception callStackSymbols]; // 出现异常的原因 NSString * reason = [exception reason]; // 异常名称 NSString * name = [exception name]; NSString * exceptionInfo = [NSString stringWithFormat:@"========uncaughtException异常错误报告========\nname:%@\nreason:\n%@\ncallStackSymbols:\n%@", name, reason, [stackArray componentsJoinedByString:@"\n"]]; // 保存崩溃日志到沙盒cache目录 [DoraemonCrashTool saveCrashLog:exceptionInfo fileName:@"Crash(Uncaught)"]; // 调用之前崩溃的回调函数 if (previousUncaughtExceptionHandler) { previousUncaughtExceptionHandler(exception); } // 杀掉程序,这样可以防止同时抛出的SIGABRT被SignalException捕获 kill(getpid(), SIGKILL); } @end 复制代码
最常见的信号异常类型:SIGSEGV,段错误,访问一个无效的内存引用,如使用assign修饰delegate属性,当该delegate属性被释放后,再访问该属性会发出SIGSEGV信号。
核心方法:
/* int:要捕获的信号类型,如SIGSEGV对应的值为11 newAct:指定新的信号处理方式 oldAct:输出先前信号的处理方式 返回值:0 表示成功,-1 表示有错误发生 */ int sigaction(int, const struct sigaction * newAct, struct sigaction * oldAct); 复制代码
sigaction结构体:
struct sigaction { union __sigaction_u __sigaction_u; // 信号处理联合体,联合体结构展示下下面 sigset_t sa_mask; // 在此信号集中的信号在信号处理函数运行中会被屏蔽,函数处理完后才处理该信号 int sa_flags; // 处理信号时的一些配置,值支持"位或"组合,下表会列举值的含义。 }; //联合体的机构 包括sa_handler和sa_sigaction两种处理方式,当sa_flags的值为SA_SIGINFO时,将使用sa_sigaction 函数作为信号处理函数,否则使用 sa_handler 作为信号处理函数。将使用sa_sigaction可以获得更多信息。 union __sigaction_u { void (*__sa_handler)(int); void (*__sa_sigaction)(int, struct __siginfo *, void *); }; 复制代码
sa_flags的值 | 含义 |
---|---|
SA_NODEFER | 一般情况下, 当信号处理函数运行时,内核将阻塞该给定信号。但是如果设置了 SA_NODEFER标记, 那么在该信号处理函数运行时,内核将不会阻塞该信号 |
SA_SIGINFO | 使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数 |
SA_RESTART | 使被信号打断的系统调用自动重新发起 |
Doraemon中Signal注册方法:
static void DoraemonSignalRegister(int signal) { struct sigaction action; // 设置sa_sigaction异常处理函数 action.sa_sigaction = DoraemonSignalHandler; action.sa_flags = SA_NODEFER | SA_SIGINFO; sigemptyset(&action.sa_mask); // 设置新的信号处理函数 sigaction(signal, &action, 0); } 复制代码
同样的在添加新的sigaction要保存原始的sigaction,防止覆盖。
Doraemon完整代码:
// DoraemonCrashSignalExceptionHandler.m #import "DoraemonCrashSignalExceptionHandler.h" #import <execinfo.h> #import "DoraemonCrashTool.h" typedef void (*SignalHandler)(int signal, siginfo_t *info, void *context); // 常见信号异常类型的异常处理函数 static SignalHandler previousABRTSignalHandler = NULL; static SignalHandler previousBUSSignalHandler = NULL; static SignalHandler previousFPESignalHandler = NULL; static SignalHandler previousILLSignalHandler = NULL; static SignalHandler previousPIPESignalHandler = NULL; static SignalHandler previousSEGVSignalHandler = NULL; static SignalHandler previousSYSSignalHandler = NULL; static SignalHandler previousTRAPSignalHandler = NULL; @implementation DoraemonCrashSignalExceptionHandler #pragma mark - Register + (void)registerHandler { // 备份原始的异常处理函数 [self backupOriginalHandler]; // 注册自定义的函数 [self signalRegister]; } + (void)backupOriginalHandler { struct sigaction old_action_abrt; sigaction(SIGABRT, NULL, &old_action_abrt); if (old_action_abrt.sa_sigaction) { previousABRTSignalHandler = old_action_abrt.sa_sigaction; } struct sigaction old_action_bus; sigaction(SIGBUS, NULL, &old_action_bus); if (old_action_bus.sa_sigaction) { previousBUSSignalHandler = old_action_bus.sa_sigaction; } struct sigaction old_action_fpe; sigaction(SIGFPE, NULL, &old_action_fpe); if (old_action_fpe.sa_sigaction) { previousFPESignalHandler = old_action_fpe.sa_sigaction; } struct sigaction old_action_ill; sigaction(SIGILL, NULL, &old_action_ill); if (old_action_ill.sa_sigaction) { previousILLSignalHandler = old_action_ill.sa_sigaction; } struct sigaction old_action_pipe; sigaction(SIGPIPE, NULL, &old_action_pipe); if (old_action_pipe.sa_sigaction) { previousPIPESignalHandler = old_action_pipe.sa_sigaction; } struct sigaction old_action_segv; sigaction(SIGSEGV, NULL, &old_action_segv); if (old_action_segv.sa_sigaction) { previousSEGVSignalHandler = old_action_segv.sa_sigaction; } struct sigaction old_action_sys; sigaction(SIGSYS, NULL, &old_action_sys); if (old_action_sys.sa_sigaction) { previousSYSSignalHandler = old_action_sys.sa_sigaction; } struct sigaction old_action_trap; sigaction(SIGTRAP, NULL, &old_action_trap); if (old_action_trap.sa_sigaction) { previousTRAPSignalHandler = old_action_trap.sa_sigaction; } } + (void)signalRegister { DoraemonSignalRegister(SIGABRT); DoraemonSignalRegister(SIGBUS); DoraemonSignalRegister(SIGFPE); DoraemonSignalRegister(SIGILL); DoraemonSignalRegister(SIGPIPE); DoraemonSignalRegister(SIGSEGV); DoraemonSignalRegister(SIGSYS); DoraemonSignalRegister(SIGTRAP); } #pragma mark - Private #pragma mark Register Signal static void DoraemonSignalRegister(int signal) { struct sigaction action; action.sa_sigaction = DoraemonSignalHandler; action.sa_flags = SA_NODEFER | SA_SIGINFO; sigemptyset(&action.sa_mask); sigaction(signal, &action, 0); } #pragma mark SignalCrash Handler static void DoraemonSignalHandler(int signal, siginfo_t* info, void* context) { NSMutableString *mstr = [[NSMutableString alloc] init]; [mstr appendString:@"Signal Exception:\n"]; [mstr appendString:[NSString stringWithFormat:@"Signal %@ was raised.\n", signalName(signal)]]; [mstr appendString:@"Call Stack:\n"]; // 获取堆栈信息 for (NSUInteger index = 1; index < NSThread.callStackSymbols.count; index++) { NSString *str = [NSThread.callStackSymbols objectAtIndex:index]; [mstr appendString:[str stringByAppendingString:@"\n"]]; } [mstr appendString:@"threadInfo:\n"]; [mstr appendString:[[NSThread currentThread] description]]; // 保存崩溃日志到沙盒cache目录 [DoraemonCrashTool saveCrashLog:[NSString stringWithString:mstr] fileName:@"Crash(Signal)"]; DoraemonClearSignalRigister(); // 调用之前崩溃的回调函数 previousSignalHandler(signal, info, context); // 杀死应用 防止触发NSException异常回掉 kill(getpid(), SIGKILL); } #pragma mark Signal To Name static NSString *signalName(int signal) { NSString *signalName; switch (signal) { case SIGABRT: signalName = @"SIGABRT"; break; case SIGBUS: signalName = @"SIGBUS"; break; case SIGFPE: signalName = @"SIGFPE"; break; case SIGILL: signalName = @"SIGILL"; break; case SIGPIPE: signalName = @"SIGPIPE"; break; case SIGSEGV: signalName = @"SIGSEGV"; break; case SIGSYS: signalName = @"SIGSYS"; break; case SIGTRAP: signalName = @"SIGTRAP"; break; default: break; } return signalName; } #pragma mark Previous Signal static void previousSignalHandler(int signal, siginfo_t *info, void *context) { SignalHandler previousSignalHandler = NULL; switch (signal) { case SIGABRT: previousSignalHandler = previousABRTSignalHandler; break; case SIGBUS: previousSignalHandler = previousBUSSignalHandler; break; case SIGFPE: previousSignalHandler = previousFPESignalHandler; break; case SIGILL: previousSignalHandler = previousILLSignalHandler; break; case SIGPIPE: previousSignalHandler = previousPIPESignalHandler; break; case SIGSEGV: previousSignalHandler = previousSEGVSignalHandler; break; case SIGSYS: previousSignalHandler = previousSYSSignalHandler; break; case SIGTRAP: previousSignalHandler = previousTRAPSignalHandler; break; default: break; } if (previousSignalHandler) { previousSignalHandler(signal, info, context); } } #pragma mark Clear static void DoraemonClearSignalRigister() { signal(SIGSEGV,SIG_DFL); signal(SIGFPE,SIG_DFL); signal(SIGBUS,SIG_DFL); signal(SIGTRAP,SIG_DFL); signal(SIGABRT,SIG_DFL); signal(SIGILL,SIG_DFL); signal(SIGPIPE,SIG_DFL); signal(SIGSYS,SIG_DFL); } @end 复制代码