美文网首页
源码研读--崩溃日志获取

源码研读--崩溃日志获取

作者: 一意孤行的程序猿 | 来源:发表于2020-05-28 15:20 被阅读0次

    主要捕获两种崩溃类型: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时检查上传。

    推荐👇:

    • 020 持续更新,精品小圈子每日都有新内容,干货浓度极高。

    • 结实人脉、讨论技术 你想要的这里都有!

    • 抢先入群,跑赢同龄人!(入群无需任何费用)

    • (直接搜索群号:789143298,快速入群)
    • 点击此处,与iOS开发大牛一起交流学习

    申请即送:

    • BAT大厂面试题、独家面试工具包,

    • 资料免费领取,包括 数据结构、底层进阶、图形视觉、音视频、架构设计、逆向安防、RxSwift、flutter,

    作者:Im_Free
    链接:https://juejin.im/post/5eafc387f265da7b9f07ad44

    相关文章

      网友评论

          本文标题:源码研读--崩溃日志获取

          本文链接:https://www.haomeiwen.com/subject/ttlaahtx.html