美文网首页
iOS崩溃学习笔记

iOS崩溃学习笔记

作者: 小沛2016 | 来源:发表于2022-02-17 10:21 被阅读0次

    原文地址[https://www.yuque.com/docs/share/fc22726e-772b-4c9d-a95d-22bbf7bf1788?# 《了解崩溃产生的原因和阅读KSCrash部分代码》]
    图片如有侵权,请联系,立刻删

    image image image image image image image

    程序为何会发生崩溃

    为什么会发生崩溃呢,这个是从安全方面来考虑的,当我们的系统发现我们的程序出现异常了,为了防止继续错下去,就中止了我们的程序,这个中止的体现就是崩溃。

    打个比方,比如苹果很注重个人隐私(起码表面上看来是这样子),如果你在没有用户授权的情况下,强行调用摄像头的代码,就让你崩溃了,如果不崩溃那恶意程序是不是随时给你拍照。

    但是系统也不是那么冷血无情,说崩就崩,它还是秉承尽可能让进程执行下去的原则的,比如在某些时候,执行不了某个指令,这时候就会返回一个error给进程,如果进程可以处理,那就不崩。那如果进程不接受返回值呢?那么就通过信号告知进程,如果这时候进程还是可以处理,那也不会崩。

    崩溃的类型

    说到这里可以知道,发生崩溃有2个地方,一个是系统触发的崩溃,一个是自己编写的程序触发崩溃

    系统触发的崩溃一般是指操作系统,自己写的程序不一定是自己调用NSException,也可能是OC调用的,比如数组越界

    但是之前定义的系统触发的崩溃不叫系统崩溃,叫硬件异常,可能是因为和硬件有关吧,比如:执行当前计算机模式下不允许的指令、除以 0等情况

    而硬件异常会转化为Mach异常,Mach异常会转化为UNIX信号,关系如下

    image

    为何要监听多处异常

    从图里可以看到,所有的异常都会汇总到signal信号这里,那是不是我们只要监听signal信号就可以了呢?其实不然。我们的程序异常是通过NSUncaughtExceptionHandler函数来捕获的,而Crash的报告需要通过这个handler 来获取异常相关信息和堆栈。

    那为什么还要监听Mach崩溃呢?

    有两个原因:

    不是所有的 "Mach异常” 类型都映射到了 “UNIX信号”。 如 EXC_GUARD 。

    “UNIX信号” 在崩溃线程回调,如果遇到 Stackoverflow 问题,已经没有条件(栈空间)再执行回调代码了。简单点来说,就是可能内存不够用了

    按照上面的思路来看,只要在Mach、singal、用户程序处,监听异常的发生,在发生异常的时候,我们就可以获取到异常的相关信息了

    核心架构 -- KScrash

    腾讯开源的APM工具,其中部分功能是基于KScrash来实现的,所以我们也用KScrash来收集异常

    异常的相关信息

    在KSCrashDoctor的- (NSString) diagnoseCrash:(NSDictionary) report方法里,我们可以获取到收集到的异常相关信息

    下面是我在子线程修改UI而触发崩溃的报告

    image

    其中system是记录用户操作系统、启动时间等信息,是自定义加进去的

    report也是用户的一些信息

    比较重要的是crash和binary_image

    crash

    crash主要有2部分内容,分别是error和threads

    image

    error

    image

    其中包含了mach异常的类型 signal的类型还有nsexception的类型与崩溃的原因

    上面的是子线程操作UI,下面的是调用了未实现的方法、数组越界、类型错误(NSLog(@"%@", 1);)、访问野指针

    image image image image

    可以看到,不同类型的崩溃,返回的信息都不一样

    但mach、signal、type都会有值

    threads

    这个是崩溃时候,回溯堆栈的数组

     po report[@"crash"][@"threads"][0]
    {
        backtrace =     {
            contents =         (
                            {
                    "instruction_addr" = 7230210588;
                    "object_addr" = 7230132224;
                    "object_name" = "libsystem_malloc.dylib";
                    "symbol_addr" = 7230210540;
                    "symbol_name" = "malloc_size";
                },
                            {
                    "instruction_addr" = 6962686124;
                    "object_addr" = 6962675712;
                    "object_name" = CoreFoundation;
                    "symbol_addr" = 6962685844;
                    "symbol_name" = "<redacted>";
                },
                            {
                    "instruction_addr" = 4368837244;
                    "object_addr" = 4368367616;
                    "object_name" = JPCrash;
                    "symbol_addr" = 4368836988;
                    "symbol_name" = blockRecordAsyncTrace;
                },
                            {
                    "instruction_addr" = 4368835884;
                    "object_addr" = 4368367616;
                    "object_name" = JPCrash;
                    "symbol_addr" = 4368835772;
                    "symbol_name" = "warp_dispatch_async";
                },
                            {
                    "instruction_addr" = 4368404096;
                    "object_addr" = 4368367616;
                    "object_name" = JPCrash;
                    "symbol_addr" = 4368403828;
                    "symbol_name" = "-[JPCrashEntity wildPointer]";
                },
                            {
                    "instruction_addr" = 4368400316;
                    "object_addr" = 4368367616;
                    "object_name" = JPCrash;
                    "symbol_addr" = 4368400064;
                    "symbol_name" = "-[ViewController tableView:didSelectRowAtIndexPath:]";
                },
                            {
                    "instruction_addr" = 7009785904;
                    "object_addr" = 6995255296;
                    "object_name" = UIKitCore;
                    "symbol_addr" = 7009784832;
                    "symbol_name" = "<redacted>";
                },
                            {
                    "instruction_addr" = 7009784792;
                    "object_addr" = 6995255296;
                    "object_name" = UIKitCore;
                    "symbol_addr" = 7009784680;
                    "symbol_name" = "<redacted>";
                },
                            {
                    "instruction_addr" = 7009786776;
                    "object_addr" = 6995255296;
                    "object_name" = UIKitCore;
                    "symbol_addr" = 7009786504;
                    "symbol_name" = "<redacted>";
                },
                            {
                    "instruction_addr" = 7012758852;
                    "object_addr" = 6995255296;
                    "object_name" = UIKitCore;
                    "symbol_addr" = 7012758780;
                    "symbol_name" = "<redacted>";
                },
                            {
                    "instruction_addr" = 7007678476;
                    "object_addr" = 6995255296;
                    "object_name" = UIKitCore;
                    "symbol_addr" = 7007678176;
                    "symbol_name" = "<redacted>";
                },
                            {
                    "instruction_addr" = 7007607200;
                    "object_addr" = 6995255296;
                    "object_name" = UIKitCore;
                    "symbol_addr" = 7007606996;
                    "symbol_name" = "<redacted>";
                },
                            {
                    "instruction_addr" = 7007820724;
                    "object_addr" = 6995255296;
                    "object_name" = UIKitCore;
                    "symbol_addr" = 7007820644;
                    "symbol_name" = "<redacted>";
                },
                            {
                    "instruction_addr" = 6963331928;
                    "object_addr" = 6962675712;
                    "object_name" = CoreFoundation;
                    "symbol_addr" = 6963331892;
                    "symbol_name" = "<redacted>";
                },
                            {
                    "instruction_addr" = 6963307972;
                    "object_addr" = 6962675712;
                    "object_name" = CoreFoundation;
                    "symbol_addr" = 6963307396;
                    "symbol_name" = "<redacted>";
                },
                            {
                    "instruction_addr" = 6963309428;
                    "object_addr" = 6962675712;
                    "object_name" = CoreFoundation;
                    "symbol_addr" = 6963308372;
                    "symbol_name" = "<redacted>";
                },
                            {
                    "instruction_addr" = 6963307036;
                    "object_addr" = 6962675712;
                    "object_name" = CoreFoundation;
                    "symbol_addr" = 6963306436;
                    "symbol_name" = CFRunLoopRunSpecific;
                },
                            {
                    "instruction_addr" = 7361546116;
                    "object_addr" = 7361531904;
                    "object_name" = GraphicsServices;
                    "symbol_addr" = 7361545952;
                    "symbol_name" = GSEventRunModal;
                },
                            {
                    "instruction_addr" = 7007612648;
                    "object_addr" = 6995255296;
                    "object_name" = UIKitCore;
                    "symbol_addr" = 7007611576;
                    "symbol_name" = "<redacted>";
                },
                            {
                    "instruction_addr" = 7007635292;
                    "object_addr" = 6995255296;
                    "object_name" = UIKitCore;
                    "symbol_addr" = 7007635124;
                    "symbol_name" = UIApplicationMain;
                },
                            {
                    "instruction_addr" = 4368404912;
                    "object_addr" = 4368367616;
                    "object_name" = JPCrash;
                    "symbol_addr" = 4368404780;
                    "symbol_name" = main;
                },
                            {
                    "instruction_addr" = 6959900336;
                    "object_addr" = 6959894528;
                    "object_name" = "libdyld.dylib";
                    "symbol_addr" = 6959900332;
                    "symbol_name" = "<redacted>";
                }
            );
            skipped = 0;
        };
        crashed = 0;
        "current_thread" = 0;
        index = 0;
        registers =     {
            basic =         {
                cpsr = 1610612736;
                fp = 6098510464;
                lr = 2122639090221459628;
                pc = 7230210588;
                sp = 6098510416;
                x0 = 0;
                x1 = 104668372234960;
                x10 = 10745377488;
                x11 = 1323302912;
                x12 = 271;
                x13 = 1;
                x14 = 103;
                x15 = 92;
                x16 = 7230210540;
                x17 = 8554235480;
                x18 = 0;
                x19 = 10745377488;
                x2 = 2;
                x20 = 4;
                x21 = 8623001600;
                x22 = 10745377488;
                x23 = "-8417212446817811928";
                x24 = 10745376640;
                x25 = 2912;
                x26 = 4;
                x27 = 10;
                x28 = 1;
                x29 = 6098510464;
                x3 = 2301;
                x4 = 25308;
                x5 = 5086723200;
                x6 = "-8417212641514943448";
                x7 = 10749520752;
                x8 = 5;
                x9 = 13008;
            };
        };
    }
    
    image image

    可以里面包含了是否是当前线程、线程内容信息、线程的名字等

    我们可以看一下他的contents

                    "current_thread":true,
                    "crashed":true,
                    "name":"",
                    "contents":[
                        {
                            "symbol_addr":6963877776,
                            "instruction_addr":6963877996,
                            "symbol_name":"<redacted>",
                            "object_name":"CoreFoundation",
                            "object_addr":6962675712
                        },
                        {
                            "symbol_addr":7316302868,
                            "instruction_addr":7316302928,
                            "symbol_name":"objc_exception_throw",
                            "object_name":"libobjc.A.dylib",
                            "object_addr":7316275200
                        },
                        {
                            "symbol_addr":7319101544,
                            "instruction_addr":7319101960,
                            "symbol_name":"<redacted>",
                            "object_name":"CoreAutoLayout",
                            "object_addr":7319035904
                        },
                        {
                            "symbol_addr":7319102660,
                            "instruction_addr":7319102696,
                            "symbol_name":"<redacted>",
                            "object_name":"CoreAutoLayout",
                            "object_addr":7319035904
                        },
                        {
                            "symbol_addr":7012182528,
                            "instruction_addr":7012182744,
                            "symbol_name":"<redacted>",
                            "object_name":"UIKitCore",
                            "object_addr":6995255296
                        },
                        {
                            "symbol_addr":7013081752,
                            "instruction_addr":7013081852,
                            "symbol_name":"<redacted>",
                            "object_name":"UIKitCore",
                            "object_addr":6995255296
                        },
                        {
                            "symbol_addr":7013080808,
                            "instruction_addr":7013081616,
                            "symbol_name":"<redacted>",
                            "object_name":"UIKitCore",
                            "object_addr":6995255296
                        },
                        {
                            "symbol_addr":7013072508,
                            "instruction_addr":7013072676,
                            "symbol_name":"<redacted>",
                            "object_name":"UIKitCore",
                            "object_addr":6995255296
                        },
                        {
                            "symbol_addr":7013070780,
                            "instruction_addr":7013071168,
                            "symbol_name":"<redacted>",
                            "object_name":"UIKitCore",
                            "object_addr":6995255296
                        },
                        {
                            "symbol_addr":4341135784,
                            "instruction_addr":4341135852,
                            "symbol_name":"__41-[ViewController changUIWithNoMainThread]_block_invoke",
                            "object_name":"JPCrash",
                            "object_addr":4341104640
                        },
                        {
                            "symbol_addr":4341574608,
                            "instruction_addr":4341574752,
                            "symbol_name":"__blockRecordAsyncTrace_block_invoke",
                            "object_name":"JPCrash",
                            "object_addr":4341104640
                        },
                        {
                            "symbol_addr":6959628844,
                            "instruction_addr":6959628876,
                            "symbol_name":"<redacted>",
                            "object_name":"libdispatch.dylib",
                            "object_addr":6959620096
                        },
                        {
                            "symbol_addr":6959635868,
                            "instruction_addr":6959635888,
                            "symbol_name":"<redacted>",
                            "object_name":"libdispatch.dylib",
                            "object_addr":6959620096
                        },
                        {
                            "symbol_addr":6959648652,
                            "instruction_addr":6959649068,
                            "symbol_name":"<redacted>",
                            "object_name":"libdispatch.dylib",
                            "object_addr":6959620096
                        },
                        {
                            "symbol_addr":6959646212,
                            "instruction_addr":6959646804,
                            "symbol_name":"<redacted>",
                            "object_name":"libdispatch.dylib",
                            "object_addr":6959620096
                        },
                        {
                            "symbol_addr":6959704024,
                            "instruction_addr":6959704380,
                            "symbol_name":"<redacted>",
                            "object_name":"libdispatch.dylib",
                            "object_addr":6959620096
                        },
                        {
                            "symbol_addr":6959706284,
                            "instruction_addr":6959706400,
                            "symbol_name":"<redacted>",
                            "object_name":"libdispatch.dylib",
                            "object_addr":6959620096
                        },
                        {
                            "symbol_addr":8233359104,
                            "instruction_addr":8233359320,
                            "symbol_name":"_pthread_wqthread",
                            "object_name":"libsystem_pthread.dylib",
                            "object_addr":8233345024
                        },
                        {
                            "symbol_addr":8233387876,
                            "instruction_addr":8233387884,
                            "symbol_name":"start_wqthread",
                            "object_name":"libsystem_pthread.dylib",
                            "object_addr":8233345024
                        }
                    ],
                    "index":1
                },
    

    可以看到,第一个object_name为项目名字的字典里的symbol_name就记录引起崩溃的方法, "symbol_name":"__41-[ViewController changUIWithNoMainThread]_block_invoke",

    object_addr记录了地址的偏移量

    "object_addr":4341104640

    symbol_name

    在oc里方法名和文件名没有没有问题,在swift里文件名和方法名中间会有2个字符,这是因为swift的命名空间导致的

    registers

    这个字典的basic记录了寄存器信息

    在上面的例子里,没有用到,但如果是系统库崩溃,这里的寄存器信息就有用到了,具体可以参考这边文章

    手把手教你 Debug — iOS 14 ImageIO Crash 分析 https://juejin.cn/post/6964562873427165220

    binary_image

    包含了 crash 时,app 所加载的所有库

    image image

    KSCrash源码分析

    初始化

    image

    从GitHub里可以获取到初始化的代码

    KSCrashInstallation install

    我们可以先看看KSCrashInstallation的install的方法

    image

    然后去 KSCrash里查看

    image

    再查看kscrash_install

    image

    回调

    回调的方法 onCrash

    之前的监听器如果监听到有奔溃,都会走回到KSCrashC.c的onCrash方法里面

    image

    可以看到KSCrash_MonitorContext里面存储的就是奔溃的信息

    KSCrash_MonitorContext -- 奔溃信息的model

    里面记录了奔溃的ID,以及奔溃的类型,时间,奔溃的上下文等信息

    阶段性小结

    通过以上代码可以知道,流程是先注册监听器,然后有奔溃了就统一处理,下面对创建的几个类型的监听器代码进行分析

    Mach 异常

    初始化

    实现监听的方法是 installExceptionHandler

     bool installExceptionHandler()
    {
        KSLOG_DEBUG("Installing mach exception handler.");
    
        bool attributes_created = false;
        pthread_attr_t attr;
    
        kern_return_t kr;
        int error;
    
    //    获取当前进程的 task
        const task_t thisTask = mach_task_self();
        exception_mask_t mask = EXC_MASK_BAD_ACCESS |
        EXC_MASK_BAD_INSTRUCTION |
        EXC_MASK_ARITHMETIC |
        EXC_MASK_SOFTWARE |
        EXC_MASK_BREAKPOINT;
    
        //备份当前异常端口数据
        KSLOG_DEBUG("Backing up original exception ports.");
        kr = task_get_exception_ports(thisTask,
                                      mask,
                                      g_previousExceptionPorts.masks,
                                      &g_previousExceptionPorts.count,
                                      g_previousExceptionPorts.ports,
                                      g_previousExceptionPorts.behaviors,
                                      g_previousExceptionPorts.flavors);
        if(kr != KERN_SUCCESS)
        {
            KSLOG_ERROR("task_get_exception_ports: %s", mach_error_string(kr));
            goto failed;
        }
    
        //创建一个接受异常的接口
        if(g_exceptionPort == MACH_PORT_NULL)
        {
            KSLOG_DEBUG("Allocating new port with receive rights.");
            kr = mach_port_allocate(thisTask,
                                    MACH_PORT_RIGHT_RECEIVE,
                                    &g_exceptionPort);
            if(kr != KERN_SUCCESS)
            {
                KSLOG_ERROR("mach_port_allocate: %s", mach_error_string(kr));
                goto failed;
            }
    
            KSLOG_DEBUG("Adding send rights to port.");
            kr = mach_port_insert_right(thisTask,
                                        g_exceptionPort,
                                        g_exceptionPort,
                                        MACH_MSG_TYPE_MAKE_SEND);
            if(kr != KERN_SUCCESS)
            {
                KSLOG_ERROR("mach_port_insert_right: %s", mach_error_string(kr));
                goto failed;
            }
        }
    
        //把接收异常的端口改为g_exceptionPort
        KSLOG_DEBUG("Installing port as exception handler.");
        kr = task_set_exception_ports(thisTask,
                                      mask,
                                      g_exceptionPort,
                                      (int)(EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES),
                                      THREAD_STATE_NONE);
        if(kr != KERN_SUCCESS)
        {
            KSLOG_ERROR("task_set_exception_ports: %s", mach_error_string(kr));
            goto failed;
        }
    
        //创建另外一个线程 (担心处理奔溃的时候代码有问题,自己也奔溃了)
        KSLOG_DEBUG("Creating secondary exception thread (suspended).");
        pthread_attr_init(&attr);
        attributes_created = true;
        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
        error = pthread_create(&g_secondaryPThread,
                               &attr,
                               &handleExceptions,
                               (void*)kThreadSecondary);
        if(error != 0)
        {
            KSLOG_ERROR("pthread_create_suspended_np: %s", strerror(error));
            goto failed;
        }
        g_secondaryMachThread = pthread_mach_thread_np(g_secondaryPThread);
        ksmc_addReservedThread(g_secondaryMachThread);
    
        KSLOG_DEBUG("Creating primary exception thread.");
        error = pthread_create(&g_primaryPThread,
                               &attr,
                               &handleExceptions,
                               (void*)kThreadPrimary);
        if(error != 0)
        {
            KSLOG_ERROR("pthread_create: %s", strerror(error));
            goto failed;
        }
        pthread_attr_destroy(&attr);
        g_primaryMachThread = pthread_mach_thread_np(g_primaryPThread);
        ksmc_addReservedThread(g_primaryMachThread);
    
        KSLOG_DEBUG("Mach exception handler installed.");
        return true;
    
    failed:
        KSLOG_DEBUG("Failed to install mach exception handler.");
        if(attributes_created)
        {
            pthread_attr_destroy(&attr);
        }
        uninstallExceptionHandler(); //失败了就不再监听了
        return false;
    }
    

    小结:

    1. 获取当前进程对应的 task

    2. 获取原本处理异常的 port,并保存

    3. 创建新的异常处理端口

    4. 用这个新创建的端口申请权限

    5. 把异常接收的 port 设置为自己新创建的 port

    6. 创建好 port 之后,一直读取自己创建的线程 port 上的消息

    处理

    static void* handleExceptions(void* const userData)
    {
        MachExceptionMessage exceptionMessage = {{0}};
        MachReplyMessage replyMessage = {{0}};
        char* eventID = g_primaryEventID;
    
        const char* threadName = (const char*) userData;
        pthread_setname_np(threadName);
        if(threadName == kThreadSecondary)
        {
            KSLOG_DEBUG("This is the secondary thread. Suspending.这是第二个线程。暂停。");
            thread_suspend((thread_t)ksthread_self());
            eventID = g_secondaryEventID;
        }
    
        for(;;)
        {
            KSLOG_DEBUG("Waiting for mach exception");
    
            // Wait for a message.
            /// 不断调用 mach_msg 接收消息,从异常端口中读取信息到 exceptionMessage 中
            kern_return_t kr = mach_msg(&exceptionMessage.header,
                                        MACH_RCV_MSG,
                                        0,
                                        sizeof(exceptionMessage),
                                        g_exceptionPort,
                                        MACH_MSG_TIMEOUT_NONE,
                                        MACH_PORT_NULL);
            /// 上面一直循环读取,直到读取成功了,进入后面的处理函数中
            if(kr == KERN_SUCCESS)
            {
                break;
            }
    
            // Loop and try again on failure.循环并在失败时重试。
            KSLOG_ERROR("mach_msg: %s", mach_error_string(kr));
        }
    
        KSLOG_DEBUG("Trapped mach exception code 0x%llx, subcode 0x%llx",
                    exceptionMessage.code[0], exceptionMessage.code[1]);
        if(g_isEnabled)
        {
            thread_act_array_t threads = NULL;
            mach_msg_type_number_t numThreads = 0;
            /// 暂停所有非当前线程以及白名单线程的线程
            ksmc_suspendEnvironment(&threads, &numThreads);
            g_isHandlingCrash = true;
            /// 捕捉到异常之后清除所有的 monitor
            kscm_notifyFatalExceptionCaptured(true);
    
            KSLOG_DEBUG("Exception handler is installed. Continuing exception handling.");
    
            // Switch to the secondary thread if necessary, or uninstall the handler
            // to avoid a death loop.
            /// 捕捉到 exception 后,恢复原来的 port
            if(ksthread_self() == g_primaryMachThread)
            {
                KSLOG_DEBUG("This is the primary exception thread. Activating secondary thread.");
    // TODO: This was put here to avoid a freeze. Does secondary thread ever fire?
                restoreExceptionPorts();
                if(thread_resume(g_secondaryMachThread) != KERN_SUCCESS)
                {
                    KSLOG_DEBUG("Could not activate secondary thread. Restoring original exception ports.");
                }
            }
            else
            {
                KSLOG_DEBUG("This is the secondary exception thread.");// Restoring original exception ports.");
    //            restoreExceptionPorts();
            }
    
            // Fill out crash information
            /// 设置 crash 信息的 context
            KSLOG_DEBUG("Fetching machine state.");
            /// 创建一个 machineContext 用来保存异常信息
            KSMC_NEW_CONTEXT(machineContext);
            KSCrash_MonitorContext* crashContext = &g_monitorContext;
            crashContext->offendingMachineContext = machineContext;
            /// 创建一个遍历调用栈的 cursor
            kssc_initCursor(&g_stackCursor, NULL, NULL);
            /// 把线程信息附加到 machineContext 上
            if(ksmc_getContextForThread(exceptionMessage.thread.name, machineContext, true))
            {
                kssc_initWithMachineContext(&g_stackCursor, 100, machineContext);
                KSLOG_TRACE("Fault address %p, instruction address %p",
                            kscpu_faultAddress(machineContext), kscpu_instructionAddress(machineContext));
                if(exceptionMessage.exception == EXC_BAD_ACCESS)
                {
                    crashContext->faultAddress = kscpu_faultAddress(machineContext);
                }
                else
                {
                    crashContext->faultAddress = kscpu_instructionAddress(machineContext);
                }
            }
    
            KSLOG_DEBUG("Filling out context.");
            crashContext->crashType = KSCrashMonitorTypeMachException;
            crashContext->eventID = eventID;
            crashContext->registersAreValid = true;
            crashContext->mach.type = exceptionMessage.exception;
            crashContext->mach.code = exceptionMessage.code[0] & (int64_t)MACH_ERROR_CODE_MASK;
            crashContext->mach.subcode = exceptionMessage.code[1] & (int64_t)MACH_ERROR_CODE_MASK;
            if(crashContext->mach.code == KERN_PROTECTION_FAILURE && crashContext->isStackOverflow)
            {
                // A stack overflow should return KERN_INVALID_ADDRESS, but
                // when a stack blasts through the guard pages at the top of the stack,
                // it generates KERN_PROTECTION_FAILURE. Correct for this.
                crashContext->mach.code = KERN_INVALID_ADDRESS;
            }
            /// 将 mach 异常转为对应的 signal
            crashContext->signal.signum = signalForMachException(crashContext->mach.type, crashContext->mach.code);
            crashContext->stackCursor = &g_stackCursor;
    
            /// context 交给 kscrashmonitor 处理
            kscm_handleException(crashContext);
    
            KSLOG_DEBUG("Crash handling complete. Restoring original handlers.");
            g_isHandlingCrash = false;
            /// 结束了捕获恢复所有线程
            ksmc_resumeEnvironment(threads, numThreads);
        }
    
        KSLOG_DEBUG("Replying to mach exception message.");
        // Send a reply saying "I didn't handle this exception".
        replyMessage.header = exceptionMessage.header;
        replyMessage.NDR = exceptionMessage.NDR;
        replyMessage.returnCode = KERN_FAILURE;
    
        /// 发消息告知没有处理这个异常
        mach_msg(&replyMessage.header,
                 MACH_SEND_MSG,
                 sizeof(replyMessage),
                 0,
                 MACH_PORT_NULL,
                 MACH_MSG_TIMEOUT_NONE,
                 MACH_PORT_NULL);
    
        return NULL;
    }
    

    小结:

    1. 不停循环通过 mach_msg() 读取 port 中传来的消息

    2. 读取成功后挂起所有线程

    3. 清除所有的 monitor,恢复原来的 port

    4. 抓取所有线程的信息保存到 KSMachineContext 结构体中

    5. 将各种信息交给 crashContext

    6. 把 crashContext 抛出给外部处理方法

    7. 恢复所有的线程

    8. 通过 mach_msg() 再发出一个消息告知没有处理这个异常

    把mach转为signal

    image

    取消监听

    image

    Signal 异常

    初始化

    static bool installSignalHandler()
    {
        KSLOG_DEBUG("Installing signal handler.");
    
    #if KSCRASH_HAS_SIGNAL_STACK
    
        if(g_signalStack.ss_size == 0)
        {
            KSLOG_DEBUG("Allocating signal stack area.");
            g_signalStack.ss_size = SIGSTKSZ;
            g_signalStack.ss_sp = malloc(g_signalStack.ss_size);
        }
    
        KSLOG_DEBUG("Setting signal stack area.");
        if(sigaltstack(&g_signalStack, NULL) != 0)
        {
            KSLOG_ERROR("signalstack: %s", strerror(errno));
            goto failed;
        }
    #endif
    
        /// 需要监听的 signal 数组
        const int* fatalSignals = kssignal_fatalSignals();
        /// 需要监听的 signal 数组大小
        int fatalSignalsCount = kssignal_numFatalSignals();
    
        if(g_previousSignalHandlers == NULL)
        {
            KSLOG_DEBUG("Allocating memory to store previous signal handlers.");
            g_previousSignalHandlers = malloc(sizeof(*g_previousSignalHandlers)
                                              * (unsigned)fatalSignalsCount);
        }
    
        struct sigaction action = {{0}};
        action.sa_flags = SA_SIGINFO | SA_ONSTACK;
    #if KSCRASH_HOST_APPLE && defined(__LP64__)
        action.sa_flags |= SA_64REGSET;
    #endif
        sigemptyset(&action.sa_mask);
        action.sa_sigaction = &handleSignal;
    
        for(int i = 0; i < fatalSignalsCount; i++)
        {
            KSLOG_DEBUG("Assigning handler for signal %d", fatalSignals[i]);
            /// 设置该 signal 对应的处理方法,并且保存原始的处理方法
            if(sigaction(fatalSignals[i], &action, &g_previousSignalHandlers[i]) != 0)
            {
                /// 设置失败的时候走下面的方法
                char sigNameBuff[30];
                const char* sigName = kssignal_signalName(fatalSignals[i]);
                if(sigName == NULL)
                {
                    snprintf(sigNameBuff, sizeof(sigNameBuff), "%d", fatalSignals[i]);
                    sigName = sigNameBuff;
                }
                KSLOG_ERROR("sigaction (%s): %s", sigName, strerror(errno));
                // Try to reverse the damage
                for(i--;i >= 0; i--)
                {
                    sigaction(fatalSignals[i], &g_previousSignalHandlers[i], NULL);
                }
                goto failed;
            }
        }
        KSLOG_DEBUG("Signal handlers installed.");
        return true;
    
        //失败走的方法
    failed:
        KSLOG_DEBUG("Failed to install signal handlers.");
        return false;
    }
    

    fatal_signal 包括如下:

    image

    处理

    static void handleSignal(int sigNum, siginfo_t* signalInfo, void* userContext)
    {
        KSLOG_DEBUG("Trapped signal %d", sigNum);
        if(g_isEnabled)
        {
            thread_act_array_t threads = NULL;
            mach_msg_type_number_t numThreads = 0;
            /// 暂停线程
            ksmc_suspendEnvironment(&threads, &numThreads);
            /// 通知已经捕获到异常了
            kscm_notifyFatalExceptionCaptured(false);
    
            KSLOG_DEBUG("Filling out context.");
            KSMC_NEW_CONTEXT(machineContext);
            /// 保存 context 到 machineContext 中,并且获取 thread 信息
            ksmc_getContextForSignal(userContext, machineContext);
            /// 把 machineContext 放到  g_stackCursor 中
            kssc_initWithMachineContext(&g_stackCursor, 100, machineContext);
    
            /// 生成真正的 context
            KSCrash_MonitorContext* crashContext = &g_monitorContext;
            memset(crashContext, 0, sizeof(*crashContext));
            crashContext->crashType = KSCrashMonitorTypeSignal;
            crashContext->eventID = g_eventID;
            crashContext->offendingMachineContext = machineContext;
            crashContext->registersAreValid = true;
            crashContext->faultAddress = (uintptr_t)signalInfo->si_addr;
            crashContext->signal.userContext = userContext;
            crashContext->signal.signum = signalInfo->si_signo;
            crashContext->signal.sigcode = signalInfo->si_code;
            crashContext->stackCursor = &g_stackCursor;
    
            /// 把 context 传给外部处理函数
            kscm_handleException(crashContext);
            /// 恢复原来的环境
            ksmc_resumeEnvironment(threads, numThreads);
        }
    
        KSLOG_DEBUG("Re-raising signal for regular handlers to catch.");
        // This is technically not allowed, but it works in OSX and iOS.
        /// 重新抛出 signal
        raise(sigNum);
    }
    

    小结

    整个流程和 Mach 异常还是非常类似的,先暂停线程,然后读取线程信息,再把 signal 信息线程信息保存到 context 中,传递给外部的处理函数。最后恢复原来的环境。

    取消监听

    static void uninstallSignalHandler(void)
    {
        KSLOG_DEBUG("Uninstalling signal handlers.");
    
        const int* fatalSignals = kssignal_fatalSignals();
        int fatalSignalsCount = kssignal_numFatalSignals();
    
        for(int i = 0; i < fatalSignalsCount; i++)
        {
            KSLOG_DEBUG("Restoring original handler for signal %d", fatalSignals[i]);
            sigaction(fatalSignals[i], &g_previousSignalHandlers[i], NULL);
        }
    
    #if KSCRASH_HAS_SIGNAL_STACK
        g_signalStack = (stack_t){0};
    #endif
        KSLOG_DEBUG("Signal handlers uninstalled.");
    }
    

    小结:

    取消捕捉的方式和启动捕捉类似,都是通过 sigaction() 方法,不同的是,现在将原本的处理方法传回。

    CPP 异常

    初始化

    image

    处理

    static void CPPExceptionTerminate(void)
    {
        thread_act_array_t threads = NULL;
        mach_msg_type_number_t numThreads = 0;
        /// 挂起非处理现场和白名单线程的其他所有线程
        ksmc_suspendEnvironment(&threads, &numThreads);
        KSLOG_DEBUG("Trapped c++ exception");
        const char* name = NULL;
        std::type_info* tinfo = __cxxabiv1::__cxa_current_exception_type();
        if(tinfo != NULL)
        {
            name = tinfo->name();
        }
    
        if(name == NULL || strcmp(name, "NSException") != 0)
        {
            /// 捕捉到 crash 后,清空 KSCrash 的所有 monitor
            kscm_notifyFatalExceptionCaptured(false);
            KSCrash_MonitorContext* crashContext = &g_monitorContext;
            memset(crashContext, 0, sizeof(*crashContext));
    
            char descriptionBuff[DESCRIPTION_BUFFER_LENGTH];
            const char* description = descriptionBuff;
            descriptionBuff[0] = 0;
    
            KSLOG_DEBUG("Discovering what kind of exception was thrown.");
            g_captureNextStackTrace = false;
            try
            {
                throw;
            }
            catch(std::exception& exc)
            {
                strncpy(descriptionBuff, exc.what(), sizeof(descriptionBuff));
            }
    #define CATCH_VALUE(TYPE, PRINTFTYPE) \
    catch(TYPE value)\
    { \
        snprintf(descriptionBuff, sizeof(descriptionBuff), "%" #PRINTFTYPE, value); \
    }
            CATCH_VALUE(char,                 d)
            CATCH_VALUE(short,                d)
            CATCH_VALUE(int,                  d)
            CATCH_VALUE(long,                ld)
            CATCH_VALUE(long long,          lld)
            CATCH_VALUE(unsigned char,        u)
            CATCH_VALUE(unsigned short,       u)
            CATCH_VALUE(unsigned int,         u)
            CATCH_VALUE(unsigned long,       lu)
            CATCH_VALUE(unsigned long long, llu)
            CATCH_VALUE(float,                f)
            CATCH_VALUE(double,               f)
            CATCH_VALUE(long double,         Lf)
            CATCH_VALUE(char*,                s)
            catch(...)
            {
                description = NULL;
            }
            g_captureNextStackTrace = g_isEnabled;
    
            // TODO: Should this be done here? Maybe better in the exception handler?
            KSMC_NEW_CONTEXT(machineContext);
            ksmc_getContextForThread(ksthread_self(), machineContext, true);
    
            KSLOG_DEBUG("Filling out context.");
            crashContext->crashType = KSCrashMonitorTypeCPPException;
            crashContext->eventID = g_eventID;
            crashContext->registersAreValid = false;
            crashContext->stackCursor = &g_stackCursor;
            crashContext->CPPException.name = name;
            crashContext->exceptionName = name;
            crashContext->crashReason = description;
            crashContext->offendingMachineContext = machineContext;
    
            /// 处理异常
            kscm_handleException(crashContext);
        }
        else
        {
            KSLOG_DEBUG("Detected NSException. Letting the current NSException handler deal with it.");
        }
        /// 恢复线程
        ksmc_resumeEnvironment(threads, numThreads);
    
        KSLOG_DEBUG("Calling original terminate handler.");
        /// 触发原本的  handler (其实也就是让他崩溃了)
        g_originalTerminateHandler();
    }
    

    小结:

    在这里可以看到cpp里面的崩溃都是处理完就调用原来的handler来触发崩溃了

    NSException 异常

    NSException就是oc语法提供的异常类,先通过 NSGetUncaughtExceptionHandler() 获取原先的异常处理函数,然后再通过 NSSetUncaughtExceptionHandler() 方法设置自己的处理函数

    初始化

    {
        if(isEnabled != g_isEnabled)
        {
            g_isEnabled = isEnabled;
            if(isEnabled)
            {
                KSLOG_DEBUG(@"Backing up original handler.");
                /// 拿到原来的 handler
                g_previousUncaughtExceptionHandler = NSGetUncaughtExceptionHandler();
    
                KSLOG_DEBUG(@"Setting new handler.");
                /// 设置新的 handler
                NSSetUncaughtExceptionHandler(&handleUncaughtException);
                KSCrash.sharedInstance.uncaughtExceptionHandler = &handleUncaughtException;
                KSCrash.sharedInstance.currentSnapshotUserReportedExceptionHandler = &handleCurrentSnapshotUserReportedException;
            }
            else
            {
                KSLOG_DEBUG(@"Restoring original handler.");
                /// 设置回原来的 handler
                NSSetUncaughtExceptionHandler(g_previousUncaughtExceptionHandler);
            }
        }
    }
    

    处理

    static void handleException(NSException* exception, BOOL currentSnapshotUserReported) {
        KSLOG_DEBUG(@"Trapped exception %@", exception);
        if(g_isEnabled)
        {
            thread_act_array_t threads = NULL;
            mach_msg_type_number_t numThreads = 0;
            ksmc_suspendEnvironment(&threads, &numThreads);
            kscm_notifyFatalExceptionCaptured(false);
    
            KSLOG_DEBUG(@"Filling out context.");
            /// 调用堆栈的地址
            NSArray* addresses = [exception callStackReturnAddresses];
            NSUInteger numFrames = addresses.count;
            uintptr_t* callstack = malloc(numFrames * sizeof(*callstack));
            /// 转为堆栈
            for(NSUInteger i = 0; i < numFrames; i++)
            {
                callstack[i] = (uintptr_t)[addresses[i] unsignedLongLongValue];
            }
    
            char eventID[37];
            ksid_generate(eventID);
            KSMC_NEW_CONTEXT(machineContext);
            ksmc_getContextForThread(ksthread_self(), machineContext, true);
            KSStackCursor cursor;
            kssc_initWithBacktrace(&cursor, callstack, (int)numFrames, 0);
    
            KSCrash_MonitorContext* crashContext = &g_monitorContext;
            memset(crashContext, 0, sizeof(*crashContext));
            crashContext->crashType = KSCrashMonitorTypeNSException;
            crashContext->eventID = eventID;
            crashContext->offendingMachineContext = machineContext;
            crashContext->registersAreValid = false;
            crashContext->NSException.name = [[exception name] UTF8String];
            crashContext->NSException.userInfo = [[NSString stringWithFormat:@"%@", exception.userInfo] UTF8String];
            crashContext->exceptionName = crashContext->NSException.name;
            crashContext->crashReason = [[exception reason] UTF8String];
            crashContext->stackCursor = &cursor;
            crashContext->currentSnapshotUserReported = currentSnapshotUserReported;
    
            KSLOG_DEBUG(@"Calling main crash handler.");
            kscm_handleException(crashContext);
    
            //是否回调的内容
            free(callstack);
            //继续处理
            if (currentSnapshotUserReported) {
                ksmc_resumeEnvironment(threads, numThreads);
            }
    
            //如果之前有处理崩溃的函数,就调用
            if (g_previousUncaughtExceptionHandler != NULL)
            {
                KSLOG_DEBUG(@"Calling original exception handler.");
                g_previousUncaughtExceptionHandler(exception);
            }
        }
    }
    

    小结:类似cpp,调用堆栈的地址等内容已经封装在NSException里面了

    相关文章

      网友评论

          本文标题:iOS崩溃学习笔记

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