iOS_Crash收集之Mach

Mach是個(gè)什么鬼

蘋(píng)果的官方OS X和iOS文檔的分層:

  • 用戶體驗(yàn)層
  • 應(yīng)用框架層
  • 核心框架層
  • Darwin

Darwin 是完全開(kāi)源的,是整個(gè)系統(tǒng)的基礎(chǔ),提供了底層API。上層是閉源的。

這邊主要關(guān)注Darwin:


Darwin架構(gòu).png

圖里面可以看出來(lái),mach同層次的還有I/OKit、libKern等等,和BSD一起都包含于XNU內(nèi)核。

現(xiàn)在XNU已經(jīng)開(kāi)開(kāi)源了,鏈接如下:
xnu源碼

內(nèi)核XNU是Darwin的核心,也是真?zhèn)€OSX的核心,包括幾個(gè)組件:

  • Mach微內(nèi)核
  • BSD層
  • libKern
  • I/OKit

其中Mach的職責(zé)是:

  • 進(jìn)程和線程抽象
  • 虛擬內(nèi)存管理
  • 任務(wù)調(diào)度
  • 進(jìn)程間通訊和消息傳遞機(jī)制

打個(gè)比方:

extern mach_port_t mach_host_self(void);//獲取主線程
extern mach_port_t mach_thread_self(void);//獲取當(dāng)前線程

extern mach_msg_return_t    mach_msg(
                mach_msg_header_t *msg,
                mach_msg_option_t option,
                mach_msg_size_t send_size,
                mach_msg_size_t rcv_size,
                mach_port_name_t rcv_name,
                mach_msg_timeout_t timeout,
                mach_port_name_t notify);//向線程發(fā)送/接受消息

那么基本上mach基本上是個(gè)什么東西可以有個(gè)了解了。

mach異常捕獲

理論依據(jù):

  • 帶有一致語(yǔ)義的單一異常處理設(shè)施:Mach只提供了一個(gè)異常處理機(jī)制用于處理所有類(lèi)型的異常——包括用戶定義的異常、平臺(tái)無(wú)關(guān)的異常以及平臺(tái)特定的異常。根據(jù)異常的類(lèi)型對(duì)異常進(jìn)行分組,具體的平臺(tái)可以定義具體的字類(lèi)型。
  • 清晰和簡(jiǎn)潔:異常處理的接口依賴于Mach已有的良好定義的消息和端口架構(gòu),因此非常優(yōu)雅。這就允許調(diào)試器和外部處理程序的擴(kuò)展——甚至在理論上還支持?jǐn)U產(chǎn)給予網(wǎng)絡(luò)的異常處理。
  • Mach不提供異常處理邏輯:只提供傳遞異常通知的框架

帶有一致予語(yǔ)義的單一異常處理設(shè)施

異常是通過(guò)內(nèi)核中的基礎(chǔ)設(shè)施——消息傳遞機(jī)制異議處理的。

一個(gè)異常基本跟一條消息的復(fù)雜度差不多,所以異常也是由出錯(cuò)線程/任務(wù)(通過(guò)msg_send())拋出,然后由一個(gè)處理線程/端口/程序(通過(guò)msg_recv())捕獲。

//  usr/include/mach/message.h
//  #define MACH_SEND_MSG       0x00000001
//  #define MACH_RCV_MSG        0x00000002
//  mach_msg_option_t 詳見(jiàn):message.h:632
extern mach_msg_return_t    mach_msg(
                mach_msg_header_t *msg,
                mach_msg_option_t option,
                mach_msg_size_t send_size,
                mach_msg_size_t rcv_size,
                mach_port_name_t rcv_name,
                mach_msg_timeout_t timeout,
                mach_port_name_t notify);

處理程序可以處理異常,也可以清楚異常,或者終止線程。

Mach的異常處理模型和其他異常處理模型不同,其他異常處理模型可以運(yùn)行在出錯(cuò)線程的上下文中,但是Mach異常處理程序在不同的上下文中運(yùn)行。

// user/include/mach/task.h
/*
    通過(guò)設(shè)置端口監(jiān)聽(tīng)出錯(cuò)信息,
    但是并不是在出錯(cuò)線程監(jiān)聽(tīng),
    所以沒(méi)法獲取線程的上下文,
    要通過(guò)處理程序中提取出錯(cuò)線程的信息,
    然后才可以獲取出錯(cuò)線程的上下文。
*/
kern_return_t task_set_exception_ports
(
    task_t task,
    exception_mask_t exception_mask,
    mach_port_t new_port,
    exception_behavior_t behavior,
    thread_state_flavor_t new_flavor
);

通常情況下任務(wù)和線程的異常端口都是NULL,也就是異常不被處理。但是如果接了其他第三方崩潰收集的SDK的話。有可能不是NUUL。所以需要獲取第三方的端口。

// user/include/mach/task.h
kern_return_t task_get_exception_ports
(
    task_inspect_t task,
    exception_mask_t exception_mask,
    exception_mask_array_t masks,
    mach_msg_type_number_t *masksCnt,
    exception_handler_array_t old_handlers,
    exception_behavior_array_t old_behaviors,
    exception_flavor_array_t old_flavors
);

Mach不提供異常處理邏輯###

發(fā)生異常的時(shí)候,首先嘗試將異常拋給線程的異常端口,然后嘗試拋給任務(wù)的異常端口,左后再拋給主機(jī)的異常端口。如果沒(méi)有一個(gè)端口返回 KERN_SUCCESS,整個(gè)任務(wù)被終止。

Mach異常捕捉

常見(jiàn)的Mach異常

usr/include/mach.exception_types.h

#define EXC_BAD_ACCESS      1   /* 內(nèi)存訪問(wèn)異常 */
    /* code:描述錯(cuò)誤的Kern_return_t*/
    /* subcode:發(fā)生內(nèi)存訪問(wèn)異常的地址 */

#define EXC_BAD_INSTRUCTION 2   /* 指令異常*/
        /* 非法或未定義的指令或操作樹(shù)*/

#define EXC_ARITHMETIC      3   /* 算術(shù)異常*/
        /* code:準(zhǔn)確的異常來(lái)源*/

#define EXC_EMULATION       4   /* 模擬指令異常*/
        /* 遇到了模擬指令*/
        /* code 和 subcode:詳細(xì)信息  */

#define EXC_SOFTWARE        5   /* 軟件產(chǎn)生的異常 */
        /* code:具體的異常 */
        /* Codes 0 - 0xFFFF :硬件 */
        /* Codes 0x10000 - 0x1FFFF :操作系統(tǒng)模擬(Unix) */

#define EXC_BREAKPOINT      6   /* 和跟蹤、斷點(diǎn)相關(guān)的異常 */
        /* code:詳細(xì)信息 */

#define EXC_SYSCALL     7   /* 系統(tǒng)調(diào)用 */

#define EXC_MACH_SYSCALL    8   /* Mach 系統(tǒng)調(diào)用 */

#define EXC_RPC_ALERT       9   /* RPC 報(bào)警 */

#define EXC_CRASH       10  /* 異常的進(jìn)程推出 */

#define EXC_RESOURCE        11  /* 達(dá)到資源消耗限制 */
        /* code:詳細(xì)信息 */

#define EXC_GUARD       12  /* 違反保護(hù)資源保護(hù) */

#define EXC_CORPSE_NOTIFY   13  /* 異常過(guò)程退出尸體狀態(tài)*/

#define EXC_CORPSE_VARIANT_BIT  0x100  /* 變位用。*/

處理異常行為(flavor)###

行為 用途
EXCEPTION_DEFAULT 將線程標(biāo)識(shí)符傳遞給異常處理程序
EXCEPTION_STATE 將線程的寄存器狀態(tài)傳遞給異常處理程序。i386使用的是TREAD_STATE_X86和THREAD_STATE_64,ARM使用的是THREAD_STATE_ARM和THREAD_STATE_ARM_64
EXCEPTION_STATE_IDENTITY 將線程標(biāo)識(shí)符和狀態(tài)都傳給異常程序

捕捉

  • 獲取已存在的異常處理端口

      //用于儲(chǔ)存已存在的異常端口
      static struct
      {
          exception_mask_t        masks[EXC_TYPES_COUNT];
          exception_handler_t     ports[EXC_TYPES_COUNT];
          exception_behavior_t    behaviors[EXC_TYPES_COUNT];
          thread_state_flavor_t   flavors[EXC_TYPES_COUNT];
          mach_msg_type_number_t  count;
      } dt_previousExceptionPorts;
      
      int get_previous_ports() {
          const task_t thisTask = mach_task_self();
          kern_return_t kr;
          exception_mask_t exception_mask = 
          EXC_MASK_BAD_ACCESS |
          EXC_MASK_BAD_INSTRUCTION|
          EXC_MASK_ARITHMETIC|
          EXC_MASK_CRASH|
          EXC_MASK_BREAKPOINT;;
          
          //獲取當(dāng)前的異常處理端口并儲(chǔ)存
          kr = task_get_exception_ports(thisTask,
                                        mask,
                                            dt_previousExceptionPorts.masks,
                                            &dt_previousExceptionPorts.count,
                                            dt_previousExceptionPorts.ports,
                                            dt_previousExceptionPorts.behaviors,
                                            dt_previousExceptionPorts.flavors);   
                                            
          if (KERN_SUCCESS  != kr) {
              //獲得當(dāng)前端口失敗
              return -1;
          }   
          return 0;
      }
    
  • 創(chuàng)建一個(gè)新端口:

      static mach_port_t exception_port = MACH_PORT_NULL;
    
      int create_new_port() {
          if (MACH_PORT_NULL == exception_port) {
    
              const task_t thisTask = mach_task_self();
              kern_return_t kr;
              kr = mach_port_allocate(thisTask,
          MACH_PORT_RIGHT_RECEIVE, &exception_port);
              if (KERN_SUCCESS  != kr) {
                  //創(chuàng)建端口失敗
                  return -1 
              }
          return 0;
      }
    
  • 添加端口權(quán)限:

mach_right.png
    int insert_right() {
        const task_t thisTask = mach_task_self();
        kern_return_t kr;
        
        kr = mach_port_insert_right(thisTask,
                                exception_port,
                                exception_port,
                                MACH_MSG_TYPE_MAKE_SEND);
        if(KERN_SUCCESS != kr) {
            //設(shè)置權(quán)限失敗
            return -1;
        }
        return 0;
    }
  • 設(shè)置異常監(jiān)聽(tīng)端口:

      int set_exception_port() {
          const task_t thisTask = mach_task_self();
          kern_return_t kr;
          kr = task_set_exception_ports(thisTask,
                                mask,
                                exception_port,
                                EXCEPTION_STATE_IDENTITY,
                                MACHINE_THREAD_STATE);
          if(KERN_SUCCESS != kr) {
              //設(shè)置監(jiān)聽(tīng)端口失敗
              return -1;
          }
          return 0;
      }
    
  • 創(chuàng)建異常處理線程:

      int create_exception_thread() {
          pthread_t thread;
          if (pthread_create(&thread,NULL,exc_handler,NULL) != 0) {
              return -1;
          }
          return 0;
      }
    
  • 實(shí)現(xiàn)異常處理方法:

接受選項(xiàng)


msg_rcv_option.png

發(fā)送選項(xiàng)


msg_send_option.png
static void* exc_handler(void *arg) {
    mach_msg_return_t mr;
    __Request__exception_raise_state_identity_t request = {{0}};
    while(true) {
    //接受exception 消息
        kr = mach_msg(&request.Head,
                                MACH_RCV_MSG,
                                0,
                                sizeof(__Request__exception_raise_state_identity_t),
                                exception_port,
                                MACH_MSG_TIMEOUT_NONE,
                                MACH_PORT_NULL);
                                
        if (KERN_SUCCESS != kr) {return NULL;} else {break;}
    }
    
    /*
    ----------------
    可以在這里對(duì)request里面的數(shù)據(jù)進(jìn)行處理
    如:
    1、堆棧獲取
    2、崩潰類(lèi)型
    3、code
    4、subcode
    ----------------
    */
    
    
    //重置端口
    const task_t thisTask = mach_task_self();

    kern_return_t kr;
    
    for (mach_msg_type_number_t i = 0; i < dt_previousExceptionPorts.count; i ++) {
        CuckooInfo(@"Resotring port index %d",i);
        kr = task_set_exception_ports(thisTask,
                                      dt_previousExceptionPorts.masks[i],
                                      dt_previousExceptionPorts.ports[i],
                                      dt_previousExceptionPorts.behaviors[i],
                                      dt_previousExceptionPorts.flavors[i]);
        if (KERN_SUCCESS != kr) {
            return NULL;
        }
    }
    __Reply__exception_raise_t reply = {{0}};
    reply.Head     = request.Head;
    reply.NDR      = request.NDR;
    reply.RetCode  = KERN_FAILURE;
    
    //將消息發(fā)送出去,交給exception_port處理
    //但是發(fā)送出去之后可能會(huì)被signal再次捕捉,
    //所以需要過(guò)濾一些重復(fù)捕捉的東西。
    kern_return_t kr =  mach_msg(& reply.Head,
     MACH_SEND_MSG,
     sizeof(__Reply__exception_raise_t),
     0,
     MACH_PORT_NULL,
     MACH_MSG_TIMEOUT_NONE,
     MACH_PORT_NULL);
    if (KERN_SUCCESS != kr) {
        return NULL;
    }
    
    return NULL;
}

Mach異常解析

奔潰類(lèi)型解析

//usr/include/mach/exc.h
//__Request__exception_raise_state_t
//和__Request__exception_raise_state_identity_t 就不列舉了
    
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];
} __Request__exception_raise_t __attribute__((unused));

可以獲取request中的exception來(lái)獲取崩潰類(lèi)型。

SIGNAL類(lèi)型解析

mach_to_signal_trans.png

通過(guò)結(jié)合exceptioncode[0]、code[1]來(lái)解析,可以通過(guò)xnu源碼看的到

bsd/dev/arm/unix_signal.c:713:

boolean_t
machine_exception(
          int exception,
          mach_exception_subcode_t code,
          __unused mach_exception_subcode_t subcode,
          int *unix_signal,
          mach_exception_subcode_t * unix_code
)
{
    switch (exception) {
    case EXC_BAD_INSTRUCTION:
        *unix_signal = SIGILL;
        *unix_code = code;
        break;

    case EXC_ARITHMETIC:
        *unix_signal = SIGFPE;
        *unix_code = code;
        break;

    default:
        return (FALSE);
    }
    return (TRUE);
}

bsd/uxkern/ux_exception.c:427:

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

堆棧解析###

詳見(jiàn)堆棧解析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容