iOS異常淺析

異常簡介

處理器和系統內核中有設計標識不同事件的狀態碼,這些狀態被編碼爲不同的位和信號。每次處理器和內核檢測到狀態的變化時,便會觸發一個事件,該事件稱爲異常

系統中可能的每種類型的異常都分配了一個唯一的非負整數的異常號。這些異常號由處理器和操作系統的內核設計者分配。

當處理器檢測到事件時,會通過一張被稱爲異常表的跳轉表,進行間接過程調用到一個專門設計用來處理這類事件的操作系統的子程序,稱爲異常處理程序

在系統啓動時(計算機啓動),操作系統會分配和初始化一張異常表,該表的起始地址存放在一個稱爲異常表基寄存器exception table base register)的特殊CPU寄存器中。異常號是異常表中的索引。通過起始地址與異常號,找到異常程序的調用地址,最終執行異常程序。

異常的類別

異常一般分四類:中斷(interrupt),陷阱(trap),故障(fault),終止(abort)。

中斷:是來自處理器外部的I/O異常信號導致的,不是由任何一條專門的指令造成的,從這個層面上來講,它是異步的。硬件中斷的處理程序通常被稱爲中斷處理程序。比如:拔插U盤。

陷阱:是有意的異常,是執行一條指令的結果。和中斷一樣,陷阱處理程序將控制返回到下一條指令。陷阱最終的用途,是在用戶程序和內核之間提供一個像過程一樣的接口,稱爲系統調用。比如用戶程序經常需要向內核請求服務,比如讀一個文件(read)、創建一個進程(fork)、加載一個新的程序(execve)或者終止當前進程(exit),這些操作都需要通過觸發陷阱異常,來執行系統內核的程序來實現。

故障:是由錯誤情況引起的,它可能被故障處理程序修正。當故障發生時,處理器將控制轉移給故障處理程序。如果故障處理程序可以修正這個錯誤情況,便會將控制放回到故障指令,從而重新執行它,如果不能修正,故障處理程序便會將控制轉移到系統內核的abort()函數,abort()最終會終止引起故障的應用程序。

一般保護性故障,Unix不會嘗試恢復,而是將這種保護性故障報告爲段違規(segmentation violation)對應信號爲SIGSEGV,然後終止程序。比如:除零、程序嘗試寫入只讀的文本段等。

故障的經典示例便是缺頁異常。
終止:是由不可恢復的致命錯誤造成的結果。終止從不將控制返回給應用程序。如:奇偶校驗錯誤(機器硬件錯誤檢測)

Unix信號(signal)

更高層的軟件形式的異常,一個信號就是一條消息,它通知進程某種類型的消息已經在系統中發生了。信號提供了一種向用戶進程通知這些異常發生的機制,也允許進程中斷其他進程。

LinuxmacOS都是類Unix系統,下表列舉了Linux系統支持的30多種不同類型的信號,有許多Linux信號在macOS同樣適用:

號碼 名字 默認行爲 相應事件 號碼 名字 默認行爲 相應事件
1 SIGHUP 終止 終端線掛起 16 SIGSTKFLT 終止 協處理器上的棧故障
2 SIGINT 終止 來自鍵盤的中斷 17 SIGCHLD 忽略 一個子進程暫停或者終止
3 SIGQUIT 終止 來自鍵盤的退出 18 SIGCONT 忽略 若進程暫停則繼續進程
4 SIGILL 終止 非法指令 19 SIGSTOP 停止直到下一個SIGCONT 不來自終端的暫停信號
5 SIGTRAP 終止並轉儲存儲器 跟蹤陷阱 20 SIGTSTP 停止直到下一個SIGCONT 來自終端的暫停信號
6 SIGABRT 終止並轉儲存儲器 來自abort函數的終止信號 21 SIGTTIN 停止直到下一個SIGCONT 後臺進程從終端讀
7 SIGBUS 終止 總線錯誤 22 SIGTTOU 停止直到下一個SIGCONT 後臺進程向終端寫
8 SIGFPE 終止並轉儲存儲器 浮點異常 23 SIGURG 忽略 套接字上的緊急情況
9 SIGKILL 終止 殺死程序 24 SIGXCPU 終止 CPU時間超出限制
10 SIGUSER1 終止 用戶定義的信號1 25 SIGXFSZ 終止 文件大小超出限制
11 SIGSEGV 終止 無效的存儲器引用(段故障) 26 SIGVTALRM 終止 虛擬定時器期滿
12 SIGUSER2 終止 用戶定義的信號2 27 SIGPROF 終止 剖析定時器期滿
13 SIGPIPE 終止 向一個沒有用戶讀的管道做寫操作 28 SIGWINCH 忽略 窗口大小變化
14 SIGALRM 終止 來自alarm函數的定時器信號 29 SIGIO 終止 在某個描述符上執行I/O操作
15 SIGTERM 終止 軟件終止信號 30 SIGPWR 終止 電源故障

系統內核

Mac OS X&iOS&iPad OS系統內核都是DarwinDarwin包含了開放源代碼的XNU混合內核,它包含了Mach/BSDBSD是建立在Mach之上提供標準化(POSIX)的APIXNU的核心是Mach。下圖爲OS X 內核架構,查看來源

Mach:是一個微內核的操作系統。微內核僅處理最核心的任務,其他任務交給用戶態的程序,包括文件管理,設備驅動等服務,這些服務被分解到不同的地址空間,彼此消息傳遞需要IPC。主要負責:線程與進程管理、虛擬內存管理、進程通信與消息傳遞、任務調度。與之對應的單內核則是把所有的服務放在相同的地址空間下,服務之間可相互調用。

BSD:是Unix的衍生系統。主要負責:Unix進程模型、POSIX線程模型以及相關原語、文件系統訪問、設備訪問、網絡協議棧、Unix用戶與羣組。

異常來源

iOS中異常主要來源於硬件異常、軟件異常、Mach異常、Signal異常,它們之間的關係如下圖:

Mach異常

Mach異常是系統內核級異常,是由CPU觸發一個陷阱引發,調用到Mach的異常處理程序,將來自硬件的異常轉換爲Mach異常,然後將Mach異常傳遞到相應的threadtaskhost,若無結果返回,任務便會被終止。

Mach異常傳遞涉及到的內核函數如下圖:


依據上圖提供信息,查閱蘋果開源資料,找到對應的函數信息,簡單列舉如下(詳細查閱請前往此處):

struct ppc_saved_state *trap(int trapno,
                 struct ppc_saved_state *ssp,
                 unsigned int dsisr,
                 unsigned int dar) {
      //... 
      doexception(exception, code, subcode);
      //...
}
void doexception(
        int exc,
        int code,
        int sub) {
    exception_data_type_t   codes[EXCEPTION_CODE_MAX];

    codes[0] = code;    
    codes[1] = sub;
    exception(exc, codes, 2);
}
// Des:The current thread caught an exception.
// We make an up-call to the thread's exception server.
void exception(
    exception_type_t    exception,
    exception_data_t    code,
    mach_msg_type_number_t  codeCnt)
{
    thread_act_t        thr_act;
    task_t            task;
    host_priv_t        host_priv;
    struct exception_action *excp;
    mutex_t            *mutex;

    assert(exception != EXC_RPC_ALERT);
    if (exception == KERN_SUCCESS)
        panic("exception");
    /*
     * Try to raise the exception at the activation level.線程級別
     */
    thr_act = current_act();
    mutex = mutex_addr(thr_act->lock);
    excp = &thr_act->exc_actions[exception];
    exception_deliver(exception, code, codeCnt, excp, mutex);
    /*
     * Maybe the task level will handle it. 任務級別
     */
    task = current_task();
    mutex = mutex_addr(task->lock);
    excp = &task->exc_actions[exception];
    exception_deliver(exception, code, codeCnt, excp, mutex);
    /*
     * How about at the host level? 主機級別
     */
    host_priv = host_priv_self();
    mutex = mutex_addr(host_priv->lock);
    excp = &host_priv->exc_actions[exception];
    exception_deliver(exception, code, codeCnt, excp, mutex);
    /*
     * Nobody handled it, terminate the task. 沒有處理終止
     */
         // ...
    (void) task_terminate(task);
    thread_exception_return();
    /*NOTREACHED*/
}
// Make an upcall to the exception server provided.
void exception_deliver(
    exception_type_t    exception,
    exception_data_t    code,
    mach_msg_type_number_t  codeCnt,
    struct exception_action *excp,
    mutex_t            *mutex)
{
        ///...
    
    int behavior = excp->behavior;

    switch (behavior) {
    case EXCEPTION_STATE: {
        ///EXCEPTION_STATE:Send a `catch_exception_raise_state` message 
        ///including the thread state.
        //..
        kr = exception_raise_state(exc_port, exception,
                           code, codeCnt,
                           &flavor,
                           state, state_cnt,
                           state, &state_cnt);
                //..                                
        return;
    }

    case EXCEPTION_DEFAULT:
        ///EXCEPTION_DEFAULT表示:Send a `catch_exception_raise` message 
        ///including the thread identity.
        //..
        kr = exception_raise(exc_port,
                retrieve_act_self_fast(a_self),
                retrieve_task_self_fast(a_self->task),
                exception,
                code, codeCnt);
                //..                
        return;

    case EXCEPTION_STATE_IDENTITY: {
        /// EXCEPTION_STATE_IDENTITY:表示Send a `catch_exception_raise_state_identity` message 
        ///including the thread identity and state.
        //..
            kr = exception_raise_state_identity(exc_port,
                retrieve_act_self_fast(a_self),
                retrieve_task_self_fast(a_self->task),
                exception,
                code, codeCnt,
                &flavor,
                state, state_cnt,
                state, &state_cnt);
         //..
         return;
    }
    
    default:
        panic ("bad exception behavior!");
    }
}

關於如何捕獲Mach異常,蘋果文檔描述很少,也沒有提供可用的API,具體的Mach內核的API介紹,可從此處查閱。

Sigal信號

BSD派生自Unix操作系統,屬於類Unix系統,基於Mach內核進程任務,提供POSIX應用程序接口。詳見維基百科-XNU。基於此,Unix Signal機制同樣適用蘋果操作系統。蘋果系統對於Unix Signal的定義,可通過#import <sys/signal.h>跳轉查看。

Mach異常-> Signal信號

蘋果操作系統Mach異常與Signal信號共存。Mach將操作系統的核心部分當做獨立進程運行,與BSD服務進程之間通過IPC機制實現消息傳遞。同理Mach內核態的異常也是基於IPC將異常消息發送到BSDBSD將消息轉換爲用戶態的Signal信號。具體流程如下:

  1. 蘋果內核啓動時會執行bsdinit_task()並最終調用ux_handler_init()方法。
void bsdinit_task(void)
{
    proc_t p = current_proc();
    struct uthread *ut;
    thread_t thread;

    process_name("init", p);

    ux_handler_init();

    thread = current_thread();
    (void) host_set_exception_ports(host_priv_self(),
                    EXC_MASK_ALL & ~(EXC_MASK_RPC_ALERT),//pilotfish (shark) needs this port
                    (mach_port_t) ux_exception_port,
                    EXCEPTION_DEFAULT| MACH_EXCEPTION_CODES,
                    0);

    ut = (uthread_t)get_bsdthread_info(thread);

    bsd_init_task = get_threadtask(thread);
    init_task_failure_data[0] = 0;

#if CONFIG_MACF
    mac_cred_label_associate_user(p->p_ucred);
    mac_task_label_update_cred (p->p_ucred, (struct task *) p->task);
#endif
    load_init_program(p);
    lock_trace = 1;
}
  1. ux_handler_init()初始化一個ux_handler()方法,並創建線程執行它。
void ux_handler_init(void)
{
    thread_t thread = THREAD_NULL;

    ux_exception_port = MACH_PORT_NULL;
    (void) kernel_thread_start((thread_continue_t)ux_handler, NULL, &thread);
    thread_deallocate(thread);
    proc_list_lock();
    if (ux_exception_port == MACH_PORT_NULL)  {
        (void)msleep(&ux_exception_port, proc_list_mlock, 0, "ux_handler_wait", 0);
    }
    proc_list_unlock();
}
  1. ux_handler()申請用於接收Mach內核消息的端口(port)集合,接收來自Mach的異常消息
static void ux_handler(void)
{
    task_t        self = current_task();
    mach_port_name_t    exc_port_name;
    mach_port_name_t    exc_set_name;

    /*
     *    Allocate a port set that we will receive on.
     */
    if (mach_port_allocate(get_task_ipcspace(ux_handler_self), MACH_PORT_RIGHT_PORT_SET,  &exc_set_name) != MACH_MSG_SUCCESS)
        panic("ux_handler: port_set_allocate failed");

    /*
     *    Allocate an exception port and use object_copyin to
     *    translate it to the global name.  Put it into the set.
     */
    if (mach_port_allocate(get_task_ipcspace(ux_handler_self), MACH_PORT_RIGHT_RECEIVE, &exc_port_name) != MACH_MSG_SUCCESS)
    panic("ux_handler: port_allocate failed");
    if (mach_port_move_member(get_task_ipcspace(ux_handler_self),
                exc_port_name,  exc_set_name) != MACH_MSG_SUCCESS)
    panic("ux_handler: port_set_add failed");

    if (ipc_object_copyin(get_task_ipcspace(self), exc_port_name,
            MACH_MSG_TYPE_MAKE_SEND, 
            (void *) &ux_exception_port) != MACH_MSG_SUCCESS)
        panic("ux_handler: object_copyin(ux_exception_port) failed");

    proc_list_lock();
    thread_wakeup(&ux_exception_port);
    proc_list_unlock();

    /* Message handling loop. */

    for (;;) {
    struct rep_msg {
        mach_msg_header_t Head;
        NDR_record_t NDR;
        kern_return_t RetCode;
    } rep_msg;
    struct exc_msg {
        mach_msg_header_t Head;
        /* start of the kernel processed data */
        mach_msg_body_t msgh_body;
        mach_msg_port_descriptor_t thread;
        mach_msg_port_descriptor_t task;
        /* end of the kernel processed data */
        NDR_record_t NDR;
        exception_type_t exception;
        mach_msg_type_number_t codeCnt;
        mach_exception_data_t code;
        /* some times RCV_TO_LARGE probs */
        char pad[512];
    } exc_msg;
    mach_port_name_t    reply_port;
    kern_return_t     result;

    exc_msg.Head.msgh_local_port = CAST_MACH_NAME_TO_PORT(exc_set_name);
    exc_msg.Head.msgh_size = sizeof (exc_msg);
#if 0
    result = mach_msg_receive(&exc_msg.Head);
#else
    result = mach_msg_receive(&exc_msg.Head, MACH_RCV_MSG,
                 sizeof (exc_msg), exc_set_name,
                 MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL,
                 0);
#endif
    if (result == MACH_MSG_SUCCESS) {
        reply_port = CAST_MACH_PORT_TO_NAME(exc_msg.Head.msgh_remote_port);
            ///收到消息後調用 mach_exc_server() 
        if (mach_exc_server(&exc_msg.Head, &rep_msg.Head)) {
                ///收到消息,回覆消息
        result = mach_msg_send(&rep_msg.Head, MACH_SEND_MSG,
            sizeof (rep_msg),MACH_MSG_TIMEOUT_NONE,MACH_PORT_NULL);
        if (reply_port != 0 && result != MACH_MSG_SUCCESS)
            mach_port_deallocate(get_task_ipcspace(ux_handler_self), reply_port);
        }

    }
    else if (result == MACH_RCV_TOO_LARGE)
        /* ignore oversized messages */;
    else
        panic("exception_handler");
    }
}
  1. ux_handler(void)收到Mach內核消息後,便會調用mach_exc_server函數,這個函數會根據異常的行爲調用對應的catch_mach_exception_raise(), catch_mach_exception_raise_state(), 和 catch_mach_exception_raise_state_identity()catch_mach_exception_raise()會觸發Mach異常消息到Unix信號的轉換。而關於mach_exc_server()的實現,並未像其他函數直接給出,具體請見此處

  2. 調用catch_mach_exception_raise()Mach異常轉換爲Unix信號,最終發送到對應線程。

kern_return_t catch_mach_exception_raise(
        __unused mach_port_t exception_port,
        mach_port_t thread,
        mach_port_t task,
        exception_type_t exception,
        mach_exception_data_t code,
        __unused mach_msg_type_number_t codeCnt
)
{
    ///...

    /*
     * Convert exception to unix signal and code.
     */
    ux_exception(exception, code[0], code[1], &ux_signal, &ucode);
        ///struct uthread *ut
        ///struct proc    *p;
        ut = get_bsdthread_info(th_act);
    p = proc_findthread(th_act);
        ///...
         /*
      * Send signal.
      */
      if (ux_signal != 0) {
        ut->uu_exception = exception;
        //ut->uu_code = code[0]; // filled in by threadsignal
        ut->uu_subcode = code[1];            
        threadsignal(th_act, ux_signal, code[0]);
       }
     if (p != NULL) 
       proc_rele(p);
       thread_deallocate(th_act);
    ///...
}
static void ux_exception(
        int            exception,
        mach_exception_code_t     code,
        mach_exception_subcode_t subcode,
        int            *ux_signal,
        mach_exception_code_t     *ux_code)
{
    /*
     *    Try machine-dependent translation first.
     */
    if (machine_exception(exception, code, subcode, ux_signal, ux_code))
    return;
    
    switch(exception) {

    case EXC_BAD_ACCESS:
        if (code == KERN_INVALID_ADDRESS)
            *ux_signal = SIGSEGV;
        else
            *ux_signal = SIGBUS;
        break;

    case EXC_BAD_INSTRUCTION:
        *ux_signal = SIGILL;
        break;

    case EXC_ARITHMETIC:
        *ux_signal = SIGFPE;
        break;

    case EXC_EMULATION:
        *ux_signal = SIGEMT;
        break;

    case EXC_SOFTWARE:
        switch (code) {

        case EXC_UNIX_BAD_SYSCALL:
        *ux_signal = SIGSYS;
        break;
        case EXC_UNIX_BAD_PIPE:
        *ux_signal = SIGPIPE;
        break;
        case EXC_UNIX_ABORT:
        *ux_signal = SIGABRT;
        break;
        case EXC_SOFT_SIGNAL:
        *ux_signal = SIGKILL;
        break;
        }
        break;

    case EXC_BREAKPOINT:
        *ux_signal = SIGTRAP;
        break;
    }
}

ux_exception()函數,展示了Mach異常與Signal信號轉換關係。關於iOSMach異常信號的定義,可通過#include <mach/exception_types.h>跳轉查看。

硬件異常

硬件異常依據前文所述,主要爲:中斷、缺陷、故障、終止。硬件異常的觸發流程如下圖:


軟件異常

應用級別的異常,在iOS中就是NSException。如果NSException異常沒有捕獲處理(try-catch),系統最終會調用abort()函數,嚮應用程序發送SIGABRT的信號。

void abort() {
         ///...
    /* <rdar://problem/7397932> abort() should call pthread_kill to deliver a signal to the aborting thread 
     * This helps gdb focus on the thread calling abort()
     */
    if (__is_threaded) {
        //...
        (void)pthread_kill(pthread_self(), SIGABRT);
    } else {
        //...
        (void)kill(getpid(), SIGABRT);
    }
    //...
}

異常捕獲

上文分析可知道硬件異常與軟件異常最終都會轉換爲Unix Signal,因此對於Signal信號的處理,可以覆蓋大部分的崩潰信息。除此之外系統給我們提供的NSException,可以用來獲取更詳細的奔潰信息。基於此,下文我們將只對SignalNSException的捕獲進行簡單示例。

Signal捕獲

在進行Signal捕獲時,需要注意覆蓋問題。因爲每個Signal對應一個Handler的處理函數,當我們通過綁定我們自己的Hanlder來收集奔潰信息時,可能會覆蓋其他三方庫已經綁定的Handler導致他們無法收集奔潰信息。

核心代碼如下:

//頭文件
#import <sys/signal.h>
#import "execinfo.h"
///1.用以保存舊的handler
static struct sigaction *previous_signalHandlers = NULL;
///2.定義我們要處理的信號
static int signals[] = {SIGABRT,SIGBUS,SIGFPE,SIGILL,SIGPIPE,SIGSEGV,SIGSYS,SIGTRAP};
///3.註冊`Handler`
+ (BOOL)registerSignalHandler; {
    ///初始化我們的Sigaction
    struct sigaction action = { 0 };
    ///初始化存放舊的Sigaction數組
    int count = sizeof(signals) / sizeof(int);
    if (previous_signalHandlers == NULL) {
        previous_signalHandlers = malloc(sizeof(struct sigaction) * count);
    }
    action.sa_flags = SA_SIGINFO;
    sigemptyset(&action.sa_mask);
    /// 綁定我們的處理函數
    action.sa_sigaction = &_handleSignal;
    for (int i = 0; i < count; i ++) {
        ///遍歷信號
        int signal = signals[i];///or  *(signals + i)
        ///綁定新的`Sigaction`,存儲舊的`Sigaction`
        int result = sigaction(signal, &action, &previous_signalHandlers[i]);
        /// 綁定失敗
        if (result != 0) {
            NSLog(@"signal:%d,error:%s",signal,strerror(errno));
            for (int j =i--; j >= 0;j--) {
                /// 恢復舊的Sigaction,此次函數返回NO
                sigaction(signals[j], &previous_signalHandlers[j], NULL);
            }
            return NO;
        }
    }
    return YES;
}
/// 4. 信號處理函數
void _handleSignal(int sigNum,siginfo_t *info,void *ucontext_t) {
    
    /// todo our operation
    NSLog(@"❌攔截到崩潰信號:%d,打印堆棧信息:%@",[CrashSignals callStackSymbols]);
    /// 獲取`sigNum`在信號數組中對應的`index`
    int index = -1,count = sizeof(signals) / sizeof(int);
    for (int i = 0; i < count; i++) {
        if (*(signals + i) == sigNum) {
            index = i;
            break;
        }
    }
    if (index == -1) return;
    /// 取出舊的`Sigaction`
    struct sigaction previous_action = previous_signalHandlers[index];
    if (previous_action.sa_handler == SIG_IGN) {
        //`SIG_IGN`忽略信號,`SIG_DFL`默認方式處理信號
        return;
    }
    /// 恢復舊的`Sigaction`與Signal的綁定關係
    sigaction(sigNum, &previous_action, NULL);
    /// 重新拋出這個`Signal`,此時便會被`previous_action`的處理程序攔截到。
    raise(sigNum);
}
//5. 函數的調用棧
+ (NSArray*)callStackSymbols {
    /// ` int backtrace(void ** buffer , int size )`
    /// void ** buffer:在`buffer`指向的數組返回程序棧楨的回溯,
    /// void ** buffer: Each item in the array pointed to by buffer is of type void *
    void* backtrace_buffer[128];
    /// 返回值可能比 128大,大便截斷,小則全部顯示
    int numberOfReturnAdderss = backtrace(backtrace_buffer, 128);
    ///char **backtrace_symbols(void *const *buffer, int size);
    /// `backtrace_symbols()` translates the addresses into an array of strings that describe the addresses symbolically
    /// The size argument specifies the number of addresses in buffer
    char **symbols = backtrace_symbols(backtrace_buffer, numberOfReturnAdderss);
    /// 提取每個返回地址對應的符號信息,棧楨是嵌套的
    NSMutableArray *tempArray = [[NSMutableArray alloc]initWithCapacity:numberOfReturnAdderss];
    for (int i = 0 ; i < numberOfReturnAdderss; i++) {
        char *cstr_item = symbols[i];
        NSString *objc_str = [NSString stringWithUTF8String:cstr_item];
        [tempArray addObject:objc_str];
    }
    return [tempArray copy];
}

NSException捕獲

系統提供了對應的處理iOS系統中未被捕獲的NSExceptionAPI,我們只需要按照API進行操作即可,但與Signal一樣,需要注意多處註冊的覆蓋問題,避免影響項目中其他收集程序。

核心代碼如下:

///1.聲明用以保存舊的`Hanlder`的靜態變量
static NSUncaughtExceptionHandler *previous_uncaughtExceptionHandler;
///註冊處理應用級異常的`handler`
+ (void)registerExceptionHandler; {
    previous_uncaughtExceptionHandler = NSGetUncaughtExceptionHandler();
    NSSetUncaughtExceptionHandler(&_handleException);
}
///我們的異常處理程序
void _handleException(NSException *exception) {
    /// Todo our operation
    NSLog(@"✅攔截到異常的堆棧信息:%@",exception.callStackSymbols);
    
    /// 傳遞異常
    if (previous_uncaughtExceptionHandler != NULL) {
        previous_uncaughtExceptionHandler(exception);
    }
    // 殺掉程序,這樣可以防止同時拋出的SIGABRT被Signal異常捕獲
    /// kill (cannot be caught or ignored)
    kill(getpid(), SIGKILL);
}

調試驗證

XcodeDebug環境下,Signal異常與NSException異常都會被Xcode調試器攔截,不會走到我們的處理程序。因此代碼的調試驗證,筆者採用模擬器運行程序後,停止運行,脫離Xcode的調試環境,重新在模擬器打開程序,開啓Mac的控制檯程序,點擊按鈕觸發奔潰,查看控制檯對應模擬器的log記錄,來驗證是否正確捕獲。另:Signal奔潰採用kill(getpid(), SIGBUS);來觸發。

參考資料

https://flylib.com/books/en/3.126.1.109/1/

http://shevakuilin.com/ios-crashprotection/

https://minosjy.com/2021/04/10/00/377/

https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/sigaction.2.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章