哆啦A梦(Doraemon)源码研读--崩溃日志获取

1,991 阅读5分钟

主要捕获两种崩溃类型:NSExceptionSignal异常。

NSException是什么?

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异常

关键方法:

//执行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

总结:
  1. NSException通过NSSetUncaughtExceptionHandler方法注册异常处理函数;通过NSGetUncaughtExceptionHandler获取原始异常处理函数。
  2. signal异常通过int sigaction(int, sigaction * newAction, sigaction * oldAction)获取原始异常处理函数和设置自定义异常处理函数。
  3. NSException通过[exception callStackSymbols] 获取堆栈信息,signal通过NSThread.callStackSymbols获取堆栈信息。
  4. 崩溃日志存储在本地,下次启动App时检查上传。