淺談Mach Exceptions

前言

大家都可能都在自己的應用中集成Crash收集服務哄陶,通常使用NSSetUncaughtExceptionHandler() + signal() / sigaction()的方式炎码。它可以幫助我們收集到大部分Crash谤饭,直到后來發(fā)現(xiàn)stack overflow并不能被以上方法撲捉到附鸽,而且其它一些SDK也未能收集。那這篇文章簡單介紹下Mach異常與signal的聯(lián)系郭厌。


OS X 诫硕、iOS系統(tǒng)架構

osx_architecture-kernels_drivers

這張圖片來之蘋果Mac Technology Overview,除了用戶體驗層鳄橘,OS X與iOS架構大體上是一直的声离。它們內(nèi)核核心都是XNU(包含Mach、BSD)瘫怜。Mach是微內(nèi)核术徊,負責操作系統(tǒng)中基本職責:進程和線程抽象、虛擬內(nèi)存管理鲸湃、任務調(diào)度赠涮、進程間通信和消息傳遞機制子寓。BSD層簡歷在Mach上,提供一套可靠且更現(xiàn)代的API笋除,提供了POSIX兼容性斜友。
本文用到的XNU的版本號為3248.60.10的源碼,下載地址垃它。
你也可以在http://opensource.apple.com 中下載歷史版本鲜屏。

Mach exceptions 與 POSIX signals

Exception Type項通常會包含兩個元素: Mach異常 和 Unix信號。
Mach exceptions: 允許在進程里或進程外處理国拇,處理程序通過Mach RPC調(diào)用洛史。
POSIX signals: 只在進程中處理,處理程序總是在發(fā)生錯誤的線程上調(diào)用酱吝。

Mach

異常首先是由處理器陷阱引發(fā)的也殖。 通用的Mach異常處理程序exception_triage(),負責將異常轉(zhuǎn)換成Mach 消息掉瞳。exception_triage()通過調(diào)用exception_deliver()嘗試把異常投遞到thread毕源、task最后是host。首先嘗試將異常拋給thread端口陕习,然后嘗試拋給task端口霎褐,最后再拋給host端口(默認端口),如果沒有一個端口返回KERN_SUCCESS该镣,那么任務就會被終止冻璃。

// 位于 osfmk/kern/exception.c
/*
 *  Routine:    exception_triage
 *  Purpose:
 *      The current thread caught an exception.
 *      We make an up-call to the thread's exception server.
 *  Conditions:
 *      Nothing locked and no resources held.
 *      Called from an exception context, so
 *      thread_exception_return and thread_kdb_return
 *      are possible.
 *  Returns:
 *      KERN_SUCCESS if exception is handled by any of the handlers.
 */
kern_return_t
exception_triage(
    exception_type_t exception,
    mach_exception_data_t code,
    mach_msg_type_number_t codeCnt)
{
    thread_t thread;
    task_t task;
    host_priv_t host_priv;
    lck_mtx_t *mutex;
    kern_return_t kr = KERN_FAILURE;

    assert(exception != EXC_RPC_ALERT);

    /*
     * If this behavior has been requested by the the kernel
     * (due to the boot environment), we should panic if we
     * enter this function.  This is intended as a debugging
     * aid; it should allow us to debug why we caught an
     * exception in environments where debugging is especially
     * difficult.
     */
    if (panic_on_exception_triage) {
        panic("called exception_triage when it was forbidden by the boot environment");
    }

    thread = current_thread();
        
        // 分別嘗試把異常投遞到thread、task最后是host损合。
    /*
     * Try to raise the exception at the activation level.
     */
    mutex = &thread->mutex;
    if (KERN_SUCCESS == check_exc_receiver_dependency(exception, thread->exc_actions, mutex))
    {
        kr = exception_deliver(thread, exception, code, codeCnt, thread->exc_actions, mutex);
        if (kr == KERN_SUCCESS || kr == MACH_RCV_PORT_DIED)
            goto out;
    }

    /*
     * Maybe the task level will handle it.
     */
    task = current_task();
    mutex = &task->lock;
    if (KERN_SUCCESS == check_exc_receiver_dependency(exception, task->exc_actions, mutex))
    {
        kr = exception_deliver(thread, exception, code, codeCnt, task->exc_actions, mutex);
        if (kr == KERN_SUCCESS || kr == MACH_RCV_PORT_DIED)
            goto out;
    }

    /*
     * How about at the host level?
     */
    host_priv = host_priv_self();
    mutex = &host_priv->lock;
    
    if (KERN_SUCCESS == check_exc_receiver_dependency(exception, host_priv->exc_actions, mutex))
    {
        kr = exception_deliver(thread, exception, code, codeCnt, host_priv->exc_actions, mutex);
        if (kr == KERN_SUCCESS || kr == MACH_RCV_PORT_DIED)
            goto out;
    }

out:
    if ((exception != EXC_CRASH) && (exception != EXC_RESOURCE) &&
        (exception != EXC_GUARD) && (exception != EXC_CORPSE_NOTIFY))
        thread_exception_return();
    return kr;
}

異常行為

/*
 *  Machine-independent exception behaviors
 */

# define EXCEPTION_DEFAULT  1  // Send a catch_exception_raise message including the identity.

# define EXCEPTION_STATE  2 // Send a catch_exception_raise_state message including the thread state.

# define EXCEPTION_STATE_IDENTITY   3 // Send a catch_exception_raise_state_identity message including the thread identity and state.
// 位于 osfmk/kern/exception.c
/*
 *  Routine:    exception_deliver
 *  Purpose:
 *      Make an upcall to the exception server provided.
 *  Conditions:
 *      Nothing locked and no resources held.
 *      Called from an exception context, so
 *      thread_exception_return and thread_kdb_return
 *      are possible.
 *  Returns:
 *      KERN_SUCCESS if the exception was handled
 */
kern_return_t 
exception_deliver(
    thread_t        thread,
    exception_type_t    exception,
    mach_exception_data_t   code,
    mach_msg_type_number_t  codeCnt,
    struct exception_action *excp,
    lck_mtx_t           *mutex)
{
    ...  // 省略部分代碼

    switch (behavior) {
        case EXCEPTION_STATE: {
            mach_msg_type_number_t state_cnt;
            thread_state_data_t state;
            
            c_thr_exc_raise_state++;
            state_cnt = _MachineStateCount[flavor];
            kr = thread_getstatus(thread, flavor,
                                  (thread_state_t)state,
                                  &state_cnt);
            if (kr == KERN_SUCCESS) {
                if (code64) {
                    kr = mach_exception_raise_state(exc_port,
                                                    exception,
                                                    code,
                                                    codeCnt,
                                                    &flavor,
                                                    state, state_cnt,
                                                    state, &state_cnt);
                } else {
                    kr = exception_raise_state(exc_port, exception,
                                               small_code,
                                               codeCnt,
                                               &flavor,
                                               state, state_cnt,
                                               state, &state_cnt);
                }
                if (kr == MACH_MSG_SUCCESS && exception != EXC_CORPSE_NOTIFY)
                    kr = thread_setstatus(thread, flavor,
                                          (thread_state_t)state,
                                          state_cnt);
            }
            return kr;
        }
            
        case EXCEPTION_DEFAULT:
            c_thr_exc_raise++;
            if (code64) {
                kr = mach_exception_raise(exc_port,
                                          retrieve_thread_self_fast(thread),
                                          retrieve_task_self_fast(thread->task),
                                          exception,
                                          code,
                                          codeCnt);
            } else {
                kr = exception_raise(exc_port,
                                     retrieve_thread_self_fast(thread),
                                     retrieve_task_self_fast(thread->task),
                                     exception,
                                     small_code, 
                                     codeCnt);
            }
            return kr;
            
        case EXCEPTION_STATE_IDENTITY: {
            mach_msg_type_number_t state_cnt;
            thread_state_data_t state;
            
            c_thr_exc_raise_state_id++;
            state_cnt = _MachineStateCount[flavor];
            kr = thread_getstatus(thread, flavor,
                                  (thread_state_t)state,
                                  &state_cnt);
            if (kr == KERN_SUCCESS) {
                if (code64) {
                    kr = mach_exception_raise_state_identity(exc_port,
                                                             retrieve_thread_self_fast(thread),
                                                             retrieve_task_self_fast(thread->task),
                                                             exception,
                                                             code, 
                                                             codeCnt,
                                                             &flavor,
                                                             state, state_cnt,
                                                             state, &state_cnt);
                } else {
                    kr = exception_raise_state_identity(exc_port,
                                                        retrieve_thread_self_fast(thread),
                                                        retrieve_task_self_fast(thread->task),
                                                        exception,
                                                        small_code, 
                                                        codeCnt,
                                                        &flavor,
                                                        state, state_cnt,
                                                        state, &state_cnt);
                }
                if (kr == MACH_MSG_SUCCESS && exception != EXC_CORPSE_NOTIFY)
                    kr = thread_setstatus(thread, flavor,
                                          (thread_state_t)state,
                                          state_cnt);
            }
            return kr;
        }
            
        default:
            panic ("bad exception behavior!");
            return KERN_FAILURE; 
    }/* switch */
}

BSD

當?shù)谝粋€BSD進程調(diào)用bsdinit_task()函數(shù)啟動時省艳,這函數(shù)還調(diào)用了ux_handler_init()函數(shù)設置了一個Mach內(nèi)核線程跑ux_handler()的。

// 位于bsd/kern/bsd_init.c
/* Called with kernel funnel held */
void
bsdinit_task(void)
{
  proc_t p = current_proc();
  struct uthread *ut;
  thread_t thread;

  process_name("init", p);

  ux_handler_init(); // 初始化handler

  // 設置port
  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;
}
// 位于bsd/uxkern/ux_exception.c
void
ux_handler_init(void)
{
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();
}
static void
ux_handler(void)
{
    task_t      self = current_task();
    mach_port_name_t    exc_port_name;
    mach_port_name_t    exc_set_name;

    /* self->kernel_vm_space = TRUE; */
    ux_handler_self = self;

    ...  // 省略部分

    /* Message handling loop.   */
    //  消息處理循環(huán)
    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);
            // 消息處理
            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");
    }
}

每一個thread嫁审、task及host自身都有一個異常端口數(shù)組跋炕,通過調(diào)用xxx_set_exception_ports()(xxx為thread、task或host)可以設置這些異常端口律适。 xxx_set_exception_ports()第四個參數(shù)為exception_behavior_t behavior辐烂,這將會使用到與行為相匹配的實現(xiàn)(exc.defs 或 mach_exc.defs)。
各種行為都在host層被catch_[mach]_exception_xxx處理捂贿,64位的對應的是有mach函數(shù)(可在/bsd/uxkern/ux_exception.c查看)纠修。
這些函數(shù)通過調(diào)用ux_exception()將異常轉(zhuǎn)換為對應的UNIX信號,并通過threadsignal()將信號投遞到出錯線程厂僧。

// EXCEPTION_DEFAULT行為  64位處理函數(shù)
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
)
{
    task_t          self = current_task();
    thread_t        th_act;
    ipc_port_t      thread_port;
    struct proc     *p;
    kern_return_t       result = MACH_MSG_SUCCESS;
    int         ux_signal = 0;
    mach_exception_code_t   ucode = 0;
    struct uthread      *ut;
    mach_port_name_t thread_name = CAST_MACH_PORT_TO_NAME(thread);
    mach_port_name_t task_name = CAST_MACH_PORT_TO_NAME(task);

    /*
     *  Convert local thread name to global port.
     */
   if (MACH_PORT_VALID(thread_name) &&
       (ipc_object_copyin(get_task_ipcspace(self), thread_name,
               MACH_MSG_TYPE_PORT_SEND,
               (void *) &thread_port) == MACH_MSG_SUCCESS)) {
        if (IPC_PORT_VALID(thread_port)) {
       th_act = convert_port_to_thread(thread_port);
       ipc_port_release_send(thread_port);
    } else {
       th_act = THREAD_NULL;
    }

    /*
     *  Catch bogus ports
     */
    if (th_act != THREAD_NULL) {

        /*
         *  Convert exception to unix signal and code.
         *  調(diào)用 ux_exception將exception轉(zhuǎn)成UNIX信號
         */
        ux_exception(exception, code[0], code[1], &ux_signal, &ucode);

        ut = get_bsdthread_info(th_act);
        p = proc_findthread(th_act);

        /* Can't deliver a signal without a bsd process reference */
        // 如果未找到進程扣草,那么這個信號就不會投遞了
        if (p == NULL) {
            ux_signal = 0;
            result = KERN_FAILURE;
        }

        /*
         * Stack overflow should result in a SIGSEGV signal
         * on the alternate stack.
         * but we have one or more guard pages after the
         * stack top, so we would get a KERN_PROTECTION_FAILURE
         * exception instead of KERN_INVALID_ADDRESS, resulting in
         * a SIGBUS signal.
         * Detect that situation and select the correct signal.
         */
        if (code[0] == KERN_PROTECTION_FAILURE &&
        ux_signal == SIGBUS) {
            user_addr_t     sp, stack_min, stack_max;
            int         mask;
            struct sigacts  *ps;

            sp = code[1];

            stack_max = p->user_stack;
            stack_min = p->user_stack - MAXSSIZ;
            if (sp >= stack_min &&
            sp < stack_max) {
                /*
                 * This is indeed a stack overflow.  Deliver a
                 * SIGSEGV signal.
                 * 因為棧溢出需返回的是SIGSEGV,這里把SIGBUS替換成SIGSEGV
                 */
                ux_signal = SIGSEGV;

                /*
                 * If the thread/process is not ready to handle
                 * SIGSEGV on an alternate stack, force-deliver
                 * SIGSEGV with a SIG_DFL handler.
                 */
                mask = sigmask(ux_signal);
                ps = p->p_sigacts;
                if ((p->p_sigignore & mask) ||
                (ut->uu_sigwait & mask) ||
                (ut->uu_sigmask & mask) ||
                (ps->ps_sigact[SIGSEGV] == SIG_IGN) ||
                (! (ps->ps_sigonstack & mask))) {
                    p->p_sigignore &= ~mask;
                    p->p_sigcatch &= ~mask;
                    ps->ps_sigact[SIGSEGV] = SIG_DFL;
                    ut->uu_sigwait &= ~mask;
                    ut->uu_sigmask &= ~mask;
                }
            }
        }
        /*
         *  Send signal.  發(fā)送信號
         */
        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);
    }
    else
        result = KERN_INVALID_ARGUMENT;
    }
    else
        result = KERN_INVALID_ARGUMENT;

    /*
     *  Delete our send rights to the task port.
     */
    (void)mach_port_deallocate(get_task_ipcspace(ux_handler_self), task_name);

    return (result);
}

所以,如果異常是棧溢出辰妙,那么signal是SIGSEGV而不是SIGBUS鹰祸;如果進程退出了或者線程/進程未準備好處理signal,所注冊的signal()是無法接收信號的上岗。

// 位于bsd/uxkern/ux_exception.c
/*
 *  ux_exception translates a mach exception, code and subcode to
 *  a signal and u.u_code.  Calls machine_exception (machine dependent)
 *  to attempt translation first.
 */

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;
    }
}

把Mach exception 和 UNIX signal 的轉(zhuǎn)換制表后福荸,如下


exception_c_signal

用下圖來展示而Mach exception轉(zhuǎn)換成signal的流程(圖截自 Mac OS X and iOS Internals)


Mach exception轉(zhuǎn)化成signal的流程

實踐

在Mach中蕴坪,異常是通過內(nèi)核中的主要設施-消息傳遞機制-進行處理的肴掷。一個異常與一條消息并無差別,由出錯的線程或任務(通過 msg_send())發(fā)送背传,并通過一個處理程(通過 msg_recv())接收呆瞻。
由于Mach的異常以消息機制處理而不是通過函數(shù)調(diào)用,exception messages可以被轉(zhuǎn)發(fā)到先前注冊的Mach exception處理程序径玖。這意味著你可以插入一個exception處理程序痴脾,而不干擾現(xiàn)有的無論是調(diào)試器或Apple's crash reporter∈嵝牵可以使用mach_msg() // flag MACH_SEND_MSG發(fā)送原始消息到以前注冊的處理程序的Mach端口赞赖,將消息轉(zhuǎn)發(fā)到一個現(xiàn)有的處理程序。

知道以上這些冤灾,那我們來嘗試撲捉一下Mach異常


void catchMACHExceptions() {
    
    kern_return_t rc = 0;
    exception_mask_t excMask = EXC_MASK_BAD_ACCESS |
    EXC_MASK_BAD_INSTRUCTION |
    EXC_MASK_ARITHMETIC |
    EXC_MASK_SOFTWARE |
    EXC_MASK_BREAKPOINT;
    
    rc = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &myExceptionPort);
    if (rc != KERN_SUCCESS) {
        fprintf(stderr, "------->Fail to allocate exception port\\\\\\\\n");
        return;
    }
    
    rc = mach_port_insert_right(mach_task_self(), myExceptionPort, myExceptionPort, MACH_MSG_TYPE_MAKE_SEND);
    if (rc != KERN_SUCCESS) {
        fprintf(stderr, "-------->Fail to insert right");
        return;
    }
    
    rc = thread_set_exception_ports(mach_thread_self(), excMask, myExceptionPort, EXCEPTION_DEFAULT, MACHINE_THREAD_STATE);
    if (rc != KERN_SUCCESS) {
        fprintf(stderr, "-------->Fail to  set exception\\\\\\\\n");
        return;
    }
    
    // at the end of catchMachExceptions, spawn the exception handling thread
    pthread_t thread;
    pthread_create(&thread, NULL, exc_handler, NULL);
} // end catchMACHExceptions

static void *exc_handler(void *ignored) {
    // Exception handler – runs a message loop. Refactored into a standalone function
    // so as to allow easy insertion into a thread (can be in same program or different)
    mach_msg_return_t rc;
    fprintf(stderr, "Exc handler listening\\\\\\\\n");
    // The exception message, straight from mach/exc.defs (following MIG processing) // copied here for ease of reference.
    typedef struct {
        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;
        integer_t code[2];
        int flavor;
        mach_msg_type_number_t old_stateCnt;
        natural_t old_state[144];
    } Request;
    
    Request exc;

    
    struct rep_msg {
        mach_msg_header_t Head;
        NDR_record_t NDR;
        kern_return_t RetCode;
    } rep_msg;
    
    
    for(;;) {
        // Message Loop: Block indefinitely until we get a message, which has to be
        // 這里會阻塞前域,直到接收到exception message,或者線程被中斷韵吨。
        // an exception message (nothing else arrives on an exception port)
        rc = mach_msg( &exc.Head,
                      MACH_RCV_MSG|MACH_RCV_LARGE,
                      0,
                      sizeof(Request),
                      myExceptionPort, // Remember this was global – that's why.
                      MACH_MSG_TIMEOUT_NONE,
                      MACH_PORT_NULL);
        
        if(rc != MACH_MSG_SUCCESS) {
            /*... */
            break ;
        };
        
        
        // Normally we would call exc_server or other. In this example, however, we wish
        // to demonstrate the message contents:
        
        printf("Got message %d. Exception : %d Flavor: %d. Code %lld/%lld. State count is %d\\\\\\\\n" ,
               exc.Head.msgh_id, exc.exception, exc.flavor,
               exc.code[0], exc.code[1], // can also print as 64-bit quantity
               exc.old_stateCnt);
        
        rep_msg.Head = exc.Head;
        rep_msg.NDR = exc.NDR;
        rep_msg.RetCode = KERN_FAILURE;
        
        kern_return_t result;
        if (rc == MACH_MSG_SUCCESS) {
            result = mach_msg(&rep_msg.Head,
                              MACH_SEND_MSG,
                              sizeof (rep_msg),
                              0,
                              MACH_PORT_NULL,
                              MACH_MSG_TIMEOUT_NONE,
                              MACH_PORT_NULL);
        }
    }
    
    return  NULL;
    
} // end exc_handler

接下來匿垄,我們測試一下。

- (void)test
{
    [self test];
}

調(diào)用這個方法归粉,結(jié)果如下

Exc handler listening
Got message 2401. Exception : 1 Flavor: 0. Code 2/1486065656. State count is 8
(lldb)

我們可以查看mach/exception_types.h 對exception type的定義
Exception: 1 椿疗,即為EXC_BAD_ACCESS
code: 2,即KERN_PROTECTION_FAILURE
ux_exception() 函數(shù)告訴我們Code與signal是怎么轉(zhuǎn)換的。
也就是 Exception = EXC_BAD_ACCESS糠悼, code = 2 對應的是SIGBUS信號届榄,又因為為是stack overflow,信號應該是SIGSEGV倔喂。

那么結(jié)這個exception就是:

Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_PROTECTION_FAILURE

再試試其它的:

int *pi = (int*)0x00001111;
 *pi = 17;

Exc handler listening
Got message 2401. Exception : 1 Flavor: 0. Code 1/4369. State count is 8
(lldb)

Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS

最后

除了上面硬件產(chǎn)生的信號铝条,另外還有軟件產(chǎn)生的信號。軟件產(chǎn)生的信號來自kill()滴劲、pthread_kill()兩個函數(shù)的調(diào)用攻晒,大概過程是這樣的:kill()/pthread_kill() --> ... --> psignal_internal() --> act_set_astbsd()。最終也會調(diào)用act_set_astbsd()發(fā)送信號到目標線程班挖。這意味著Mach exception流程是不會走的鲁捏。
另外,在abort()源碼注釋著:<rdar://problem/7397932> abort() should call pthread_kill to deliver a signal to the aborting thread , 它是這樣調(diào)用的(void)pthread_kill(pthread_self(), SIGABRT);。Apple’s Crash Reporter 把SIGABRT的Mach exception type記為EXC_CRASH给梅,不同與上面轉(zhuǎn)變表假丧。

Exception Type: EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000

所以盡管Mach exception handle 比 UNIX signal handle 更有優(yōu)勢,但我們還是須要注冊signal handle用于處理EXC_SOFTWARE/EXC_CRASH动羽。

由于篇幅有限包帚,有些細節(jié)可能并沒有展現(xiàn)出來,如果你想有深入的了解运吓,可以閱讀下面參考文獻渴邦,或者一些開源框架。最后拘哨,希望這篇文章可以幫助你了解Mach exception谋梭。


參考文獻:
Apple Kernel Programming Guide
漫談iOS Crash收集框架
Mac OS X and iOS Internals:To the Apple’s Core(中文譯名:深入解析 MAC OS X & IOS 操作系統(tǒng))
Mach Exception Handlers
xnu-3248.60.10.tar.gz
Understanding Crash Reports on iPhone OS(wwdc)
Understanding and Analyzing iOS Application Crash Reports

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市倦青,隨后出現(xiàn)的幾起案子瓮床,更是在濱河造成了極大的恐慌,老刑警劉巖产镐,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件隘庄,死亡現(xiàn)場離奇詭異,居然都是意外死亡癣亚,警方通過查閱死者的電腦和手機丑掺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來逃糟,“玉大人吼鱼,你說我怎么就攤上這事〈卵剩” “怎么了菇肃?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長取募。 經(jīng)常有香客問我琐谤,道長,這世上最難降的妖魔是什么玩敏? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任斗忌,我火速辦了婚禮,結(jié)果婚禮上旺聚,老公的妹妹穿的比我還像新娘织阳。我一直安慰自己,他們只是感情好砰粹,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布唧躲。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪弄痹。 梳的紋絲不亂的頭發(fā)上饭入,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天,我揣著相機與錄音肛真,去河邊找鬼谐丢。 笑死,一個胖子當著我的面吹牛蚓让,可吹牛的內(nèi)容都是我干的乾忱。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼凭疮,長吁一口氣:“原來是場噩夢啊……” “哼饭耳!你這毒婦竟也來了串述?” 一聲冷哼從身側(cè)響起执解,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎纲酗,沒想到半個月后衰腌,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡觅赊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年右蕊,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖箱叁,靈堂內(nèi)的尸體忽然破棺而出腰鬼,到底是詐尸還是另有隱情,我是刑警寧澤甘凭,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響规惰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜泉蝌,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一歇万、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧勋陪,春花似錦贪磺、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春壕曼,著一層夾襖步出監(jiān)牢的瞬間苏研,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工腮郊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留摹蘑,地道東北人。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓轧飞,卻偏偏與公主長得像衅鹿,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子过咬,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353

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

  • 來源:程序媛念茜的博客 Crash日志收集 為了能夠第一時間發(fā)現(xiàn)程序問題大渤,應用程序需要實現(xiàn)自己的崩潰日志收集服務,...
    幸福的魚閱讀 1,164評論 0 2
  • 以下為文章正文掸绞,如果覺得有用泵三,歡迎給她打賞。 為了能夠第一時間發(fā)現(xiàn)程序問題衔掸,應用程序需要實現(xiàn)自己的崩潰日志收集服務...
    赤色追風閱讀 2,548評論 1 11
  • 轉(zhuǎn)載(漫談 iOS Crash 收集框架) 前言 很早以前就和念茜認識烫幕,念茜不但技術功底扎實,而且長得很漂亮敞映,說她...
    狂風無跡閱讀 3,298評論 1 11
  • 比較好的轉(zhuǎn)載:http://www.cocoachina.com/ios/20151218/14748.html轉(zhuǎn)...
    liudhkk閱讀 930評論 0 2
  • 今天较曼,我們進入第四福—— 饑渴慕義之福 經(jīng)文|太5:6 饑渴慕義的人有福了振愿,因為他們必得飽足捷犹。 在中東巴勒斯坦地區(qū)...
    服從閣下的仆人閱讀 2,403評論 0 1