哆啦A梦(Doraemon)源码研读--崩溃日志获取
主要捕获两种崩溃类型:NSException和Signal异常。
NSException是什么?
NSException是一个中断程序正常执行的描述对象,包含异常名称、通俗的原因描述、以及包含补充消息的字典。
@interface NSException : NSObject <NSCopying, NSCoding> {
@private
NSString *name;//名称
NSString *reason;//原因
NSDictionary *userInfo;//补充信息
id reserved;
}
其中name的值是NSExceptionName类型的字符串。常见值:
-
NSInvalidArgumentException:插入nil到容器类
-
NSRangeException:容器类越界或者字符串越界
-
更多请参考苹果开发者文档
-
https://developer.apple.com/documentation/foundation/nsexceptionname?language=objc:
对于排查问题来说,名称其实不重要,重要的是通俗易懂的reason描述以及堆栈信息。
如何捕获NSException异常
关键方法:
//执行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
Signal异常
最常见的信号异常类型:SIGSEGV,段错误,访问一个无效的内存引用,如使用assign修饰delegate属性,当该delegate属性被释放后,再访问该属性会发出SIGSEGV信号。
使用sigaction捕获Signal异常
核心方法:
/*
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
总结:
- NSException通过NSSetUncaughtExceptionHandler方法注册异常处理函数;通过NSGetUncaughtExceptionHandler获取原始异常处理函数。
- signal异常通过int sigaction(int, sigaction * newAction, sigaction * oldAction)获取原始异常处理函数和设置自定义异常处理函数。
- NSException通过[exception callStackSymbols]获取堆栈信息,signal通过NSThread.callStackSymbols获取堆栈信息。
- 崩溃日志存储在本地,下次启动App时检查上传。
网友评论