iOS崩潰學(xué)習(xí)筆記

語雀地址(全部內(nèi)容)
圖片如有侵權(quán)缆镣,請聯(lián)系儒恋,立刻刪

image
image
image
image
image
image
image

程序為何會發(fā)生崩潰

為什么會發(fā)生崩潰呢,這個是從安全方面來考慮的疹吃,當(dāng)我們的系統(tǒng)發(fā)現(xiàn)我們的程序出現(xiàn)異常了锰霜,為了防止繼續(xù)錯下去婴谱,就中止了我們的程序,這個中止的體現(xiàn)就是崩潰寂诱。

打個比方市殷,比如蘋果很注重個人隱私(起碼表面上看來是這樣子),如果你在沒有用戶授權(quán)的情況下刹衫,強行調(diào)用攝像頭的代碼醋寝,就讓你崩潰了,如果不崩潰那惡意程序是不是隨時給你拍照带迟。

但是系統(tǒng)也不是那么冷血無情音羞,說崩就崩,它還是秉承盡可能讓進(jìn)程執(zhí)行下去的原則的仓犬,比如在某些時候嗅绰,執(zhí)行不了某個指令,這時候就會返回一個error給進(jìn)程搀继,如果進(jìn)程可以處理窘面,那就不崩。那如果進(jìn)程不接受返回值呢叽躯?那么就通過信號告知進(jìn)程财边,如果這時候進(jìn)程還是可以處理,那也不會崩点骑。

崩潰的類型

說到這里可以知道酣难,發(fā)生崩潰有2個地方谍夭,一個是系統(tǒng)觸發(fā)的崩潰,一個是自己編寫的程序觸發(fā)崩潰

系統(tǒng)觸發(fā)的崩潰一般是指操作系統(tǒng)憨募,自己寫的程序不一定是自己調(diào)用NSException,也可能是OC調(diào)用的紧索,比如數(shù)組越界

但是之前定義的系統(tǒng)觸發(fā)的崩潰不叫系統(tǒng)崩潰,叫硬件異常菜谣,可能是因為和硬件有關(guān)吧珠漂,比如:執(zhí)行當(dāng)前計算機(jī)模式下不允許的指令、除以 0等情況

而硬件異常會轉(zhuǎn)化為Mach異常尾膊,Mach異常會轉(zhuǎn)化為UNIX信號媳危,關(guān)系如下

image

為何要監(jiān)聽多處異常

從圖里可以看到,所有的異常都會匯總到signal信號這里眯停,那是不是我們只要監(jiān)聽signal信號就可以了呢?其實不然卿泽。我們的程序異常是通過NSUncaughtExceptionHandler函數(shù)來捕獲的莺债,而Crash的報告需要通過這個handler 來獲取異常相關(guān)信息和堆棧。

那為什么還要監(jiān)聽Mach崩潰呢签夭?

有兩個原因:

不是所有的 "Mach異称氚睿” 類型都映射到了 “UNIX信號”。 如 EXC_GUARD 第租。

“UNIX信號” 在崩潰線程回調(diào)措拇,如果遇到 Stackoverflow 問題,已經(jīng)沒有條件(椛鞅觯空間)再執(zhí)行回調(diào)代碼了丐吓。簡單點來說,就是可能內(nèi)存不夠用了

按照上面的思路來看趟据,只要在Mach券犁、singal、用戶程序處汹碱,監(jiān)聽異常的發(fā)生粘衬,在發(fā)生異常的時候,我們就可以獲取到異常的相關(guān)信息了

核心架構(gòu) -- KScrash

騰訊開源的APM工具咳促,其中部分功能是基于KScrash來實現(xiàn)的稚新,所以我們也用KScrash來收集異常

異常的相關(guān)信息

在KSCrashDoctor的- (NSString) diagnoseCrash:(NSDictionary) report方法里,我們可以獲取到收集到的異常相關(guān)信息

下面是我在子線程修改UI而觸發(fā)崩潰的報告

image

其中system是記錄用戶操作系統(tǒng)跪腹、啟動時間等信息褂删,是自定義加進(jìn)去的

report也是用戶的一些信息

比較重要的是crash和binary_image

crash

crash主要有2部分內(nèi)容,分別是error和threads

image

error

image

其中包含了mach異常的類型 signal的類型還有nsexception的類型與崩潰的原因

上面的是子線程操作UI冲茸,下面的是調(diào)用了未實現(xiàn)的方法笤妙、數(shù)組越界冒掌、類型錯誤(NSLog(@"%@", 1);)、訪問野指針

image
image
image
image

可以看到蹲盘,不同類型的崩潰股毫,返回的信息都不一樣

但mach、signal召衔、type都會有值

threads

這個是崩潰時候铃诬,回溯堆棧的數(shù)組

 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

可以里面包含了是否是當(dāng)前線程、線程內(nèi)容信息苍凛、線程的名字等

我們可以看一下他的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的命名空間導(dǎo)致的

registers

這個字典的basic記錄了寄存器信息

在上面的例子里,沒有用到悠栓,但如果是系統(tǒng)庫崩潰霉涨,這里的寄存器信息就有用到了,具體可以參考這邊文章

手把手教你 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

回調(diào)

回調(diào)的方法 onCrash

之前的監(jiān)聽器如果監(jiān)聽到有奔潰笙瑟,都會走回到KSCrashC.c的onCrash方法里面

image

可以看到KSCrash_MonitorContext里面存儲的就是奔潰的信息

KSCrash_MonitorContext -- 奔潰信息的model

里面記錄了奔潰的ID,以及奔潰的類型癞志,時間往枷,奔潰的上下文等信息

階段性小結(jié)

通過以上代碼可以知道,流程是先注冊監(jiān)聽器凄杯,然后有奔潰了就統(tǒng)一處理错洁,下面對創(chuàng)建的幾個類型的監(jiān)聽器代碼進(jìn)行分析

Mach 異常

初始化

實現(xiàn)監(jiān)聽的方法是 installExceptionHandler

 bool installExceptionHandler()
{
    KSLOG_DEBUG("Installing mach exception handler.");

    bool attributes_created = false;
    pthread_attr_t attr;

    kern_return_t kr;
    int error;

//    獲取當(dāng)前進(jìn)程的 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;

    //備份當(dāng)前異常端口數(shù)據(jù)
    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;
    }

    //創(chuàng)建一個接受異常的接口
    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;
    }

    //創(chuàng)建另外一個線程 (擔(dān)心處理奔潰的時候代碼有問題,自己也奔潰了)
    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(); //失敗了就不再監(jiān)聽了
    return false;
}

小結(jié):

  1. 獲取當(dāng)前進(jìn)程對應(yīng)的 task

  2. 獲取原本處理異常的 port戒突,并保存

  3. 創(chuàng)建新的異常處理端口

  4. 用這個新創(chuàng)建的端口申請權(quán)限

  5. 把異常接收的 port 設(shè)置為自己新創(chuàng)建的 port

  6. 創(chuàng)建好 port 之后墓臭,一直讀取自己創(chuàng)建的線程 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.
        /// 不斷調(diào)用 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);
        /// 上面一直循環(huán)讀取,直到讀取成功了膝舅,進(jìn)入后面的處理函數(shù)中
        if(kr == KERN_SUCCESS)
        {
            break;
        }

        // Loop and try again on failure.循環(huán)并在失敗時重試嗡载。
        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;
        /// 暫停所有非當(dāng)前線程以及白名單線程的線程
        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 后,恢復(fù)原來的 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
        /// 設(shè)置 crash 信息的 context
        KSLOG_DEBUG("Fetching machine state.");
        /// 創(chuàng)建一個 machineContext 用來保存異常信息
        KSMC_NEW_CONTEXT(machineContext);
        KSCrash_MonitorContext* crashContext = &g_monitorContext;
        crashContext->offendingMachineContext = machineContext;
        /// 創(chuàng)建一個遍歷調(diào)用棧的 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 異常轉(zhuǎn)為對應(yīng)的 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;
        /// 結(jié)束了捕獲恢復(fù)所有線程
        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;

    /// 發(fā)消息告知沒有處理這個異常
    mach_msg(&replyMessage.header,
             MACH_SEND_MSG,
             sizeof(replyMessage),
             0,
             MACH_PORT_NULL,
             MACH_MSG_TIMEOUT_NONE,
             MACH_PORT_NULL);

    return NULL;
}

小結(jié):

  1. 不停循環(huán)通過 mach_msg() 讀取 port 中傳來的消息

  2. 讀取成功后掛起所有線程

  3. 清除所有的 monitor仍稀,恢復(fù)原來的 port

  4. 抓取所有線程的信息保存到 KSMachineContext 結(jié)構(gòu)體中

  5. 將各種信息交給 crashContext

  6. 把 crashContext 拋出給外部處理方法

  7. 恢復(fù)所有的線程

  8. 通過 mach_msg() 再發(fā)出一個消息告知沒有處理這個異常

把mach轉(zhuǎn)為signal

image

取消監(jiān)聽

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

    /// 需要監(jiān)聽的 signal 數(shù)組
    const int* fatalSignals = kssignal_fatalSignals();
    /// 需要監(jiān)聽的 signal 數(shù)組大小
    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]);
        /// 設(shè)置該 signal 對應(yīng)的處理方法洼滚,并且保存原始的處理方法
        if(sigaction(fatalSignals[i], &action, &g_previousSignalHandlers[i]) != 0)
        {
            /// 設(shè)置失敗的時候走下面的方法
            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);
        /// 通知已經(jīng)捕獲到異常了
        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 傳給外部處理函數(shù)
        kscm_handleException(crashContext);
        /// 恢復(fù)原來的環(huán)境
        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);
}

小結(jié)

整個流程和 Mach 異常還是非常類似的技潘,先暫停線程遥巴,然后讀取線程信息千康,再把 signal 信息線程信息保存到 context 中,傳遞給外部的處理函數(shù)铲掐。最后恢復(fù)原來的環(huán)境拾弃。

取消監(jiān)聽

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.");
}

小結(jié):

取消捕捉的方式和啟動捕捉類似,都是通過 sigaction() 方法摆霉,不同的是豪椿,現(xiàn)在將原本的處理方法傳回。

CPP 異常

初始化

image

處理

static void CPPExceptionTerminate(void)
{
    thread_act_array_t threads = NULL;
    mach_msg_type_number_t numThreads = 0;
    /// 掛起非處理現(xiàn)場和白名單線程的其他所有線程
    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.");
    }
    /// 恢復(fù)線程
    ksmc_resumeEnvironment(threads, numThreads);

    KSLOG_DEBUG("Calling original terminate handler.");
    /// 觸發(fā)原本的  handler (其實也就是讓他崩潰了)
    g_originalTerminateHandler();
}

小結(jié):

在這里可以看到cpp里面的崩潰都是處理完就調(diào)用原來的handler來觸發(fā)崩潰了

NSException 異常

NSException就是oc語法提供的異常類搭盾,先通過 NSGetUncaughtExceptionHandler() 獲取原先的異常處理函數(shù),然后再通過 NSSetUncaughtExceptionHandler() 方法設(shè)置自己的處理函數(shù)

初始化

{
    if(isEnabled != g_isEnabled)
    {
        g_isEnabled = isEnabled;
        if(isEnabled)
        {
            KSLOG_DEBUG(@"Backing up original handler.");
            /// 拿到原來的 handler
            g_previousUncaughtExceptionHandler = NSGetUncaughtExceptionHandler();

            KSLOG_DEBUG(@"Setting new handler.");
            /// 設(shè)置新的 handler
            NSSetUncaughtExceptionHandler(&handleUncaughtException);
            KSCrash.sharedInstance.uncaughtExceptionHandler = &handleUncaughtException;
            KSCrash.sharedInstance.currentSnapshotUserReportedExceptionHandler = &handleCurrentSnapshotUserReportedException;
        }
        else
        {
            KSLOG_DEBUG(@"Restoring original handler.");
            /// 設(shè)置回原來的 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.");
        /// 調(diào)用堆棧的地址
        NSArray* addresses = [exception callStackReturnAddresses];
        NSUInteger numFrames = addresses.count;
        uintptr_t* callstack = malloc(numFrames * sizeof(*callstack));
        /// 轉(zhuǎn)為堆棧
        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);

        //是否回調(diào)的內(nèi)容
        free(callstack);
        //繼續(xù)處理
        if (currentSnapshotUserReported) {
            ksmc_resumeEnvironment(threads, numThreads);
        }

        //如果之前有處理崩潰的函數(shù)婉支,就調(diào)用
        if (g_previousUncaughtExceptionHandler != NULL)
        {
            KSLOG_DEBUG(@"Calling original exception handler.");
            g_previousUncaughtExceptionHandler(exception);
        }
    }
}

小結(jié):類似cpp鸯隅,調(diào)用堆棧的地址等內(nèi)容已經(jīng)封裝在NSException里面了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市向挖,隨后出現(xiàn)的幾起案子蝌以,更是在濱河造成了極大的恐慌,老刑警劉巖户誓,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件饼灿,死亡現(xiàn)場離奇詭異幕侠,居然都是意外死亡帝美,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進(jìn)店門晤硕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來悼潭,“玉大人,你說我怎么就攤上這事舞箍〗⑼剩” “怎么了?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵疏橄,是天一觀的道長占拍。 經(jīng)常有香客問我,道長捎迫,這世上最難降的妖魔是什么晃酒? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮窄绒,結(jié)果婚禮上贝次,老公的妹妹穿的比我還像新娘。我一直安慰自己彰导,他們只是感情好蛔翅,可當(dāng)我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布敲茄。 她就那樣靜靜地躺著,像睡著了一般山析。 火紅的嫁衣襯著肌膚如雪堰燎。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天盖腿,我揣著相機(jī)與錄音爽待,去河邊找鬼。 笑死翩腐,一個胖子當(dāng)著我的面吹牛鸟款,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播茂卦,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼何什,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了等龙?” 一聲冷哼從身側(cè)響起处渣,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蛛砰,沒想到半個月后罐栈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡泥畅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年荠诬,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片位仁。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡柑贞,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出聂抢,到底是詐尸還是另有隱情钧嘶,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布琳疏,位于F島的核電站有决,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏空盼。R本人自食惡果不足惜书幕,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望我注。 院中可真熱鬧按咒,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至掠抬,卻和暖如春吼野,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背两波。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工瞳步, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人腰奋。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓单起,卻偏偏與公主長得像,于是被迫代替她去往敵國和親劣坊。 傳聞我的和親對象是個殘疾皇子嘀倒,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,834評論 2 345

推薦閱讀更多精彩內(nèi)容