美文网首页
iOS_Crash收集之Mach

iOS_Crash收集之Mach

作者: 佛祖拿屠刀 | 来源:发表于2019-04-08 15:56 被阅读0次

    Mach是个什么鬼

    苹果的官方OS X和iOS文档的分层:

    • 用户体验层
    • 应用框架层
    • 核心框架层
    • Darwin

    Darwin 是完全开源的,是整个系统的基础,提供了底层API。上层是闭源的。

    这边主要关注Darwin:


    Darwin架构.png

    图里面可以看出来,mach同层次的还有I/OKit、libKern等等,和BSD一起都包含于XNU内核。

    现在XNU已经开开源了,链接如下:
    xnu源码

    内核XNU是Darwin的核心,也是真个OSX的核心,包括几个组件:

    • Mach微内核
    • BSD层
    • libKern
    • I/OKit

    其中Mach的职责是:

    • 进程和线程抽象
    • 虚拟内存管理
    • 任务调度
    • 进程间通讯和消息传递机制

    打个比方:

    extern mach_port_t mach_host_self(void);//获取主线程
    extern mach_port_t mach_thread_self(void);//获取当前线程
    
    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基本上是个什么东西可以有个了解了。

    mach异常捕获

    理论依据:

    • 带有一致语义的单一异常处理设施:Mach只提供了一个异常处理机制用于处理所有类型的异常——包括用户定义的异常、平台无关的异常以及平台特定的异常。根据异常的类型对异常进行分组,具体的平台可以定义具体的字类型。
    • 清晰和简洁:异常处理的接口依赖于Mach已有的良好定义的消息和端口架构,因此非常优雅。这就允许调试器和外部处理程序的扩展——甚至在理论上还支持扩产给予网络的异常处理。
    • Mach不提供异常处理逻辑:只提供传递异常通知的框架

    带有一致予语义的单一异常处理设施

    异常是通过内核中的基础设施——消息传递机制异议处理的。

    一个异常基本跟一条消息的复杂度差不多,所以异常也是由出错线程/任务(通过msg_send())抛出,然后由一个处理线程/端口/程序(通过msg_recv())捕获。

    //  usr/include/mach/message.h
    //  #define MACH_SEND_MSG       0x00000001
    //  #define MACH_RCV_MSG        0x00000002
    //  mach_msg_option_t 详见: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的异常处理模型和其他异常处理模型不同,其他异常处理模型可以运行在出错线程的上下文中,但是Mach异常处理程序在不同的上下文中运行。

    // user/include/mach/task.h
    /*
        通过设置端口监听出错信息,
        但是并不是在出错线程监听,
        所以没法获取线程的上下文,
        要通过处理程序中提取出错线程的信息,
        然后才可以获取出错线程的上下文。
    */
    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
    );
    

    通常情况下任务和线程的异常端口都是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不提供异常处理逻辑###

    发生异常的时候,首先尝试将异常抛给线程的异常端口,然后尝试抛给任务的异常端口,左后再抛给主机的异常端口。如果没有一个端口返回 KERN_SUCCESS,整个任务被终止。

    Mach异常捕捉

    常见的Mach异常

    usr/include/mach.exception_types.h

    #define EXC_BAD_ACCESS      1   /* 内存访问异常 */
        /* code:描述错误的Kern_return_t*/
        /* subcode:发生内存访问异常的地址 */
    
    #define EXC_BAD_INSTRUCTION 2   /* 指令异常*/
            /* 非法或未定义的指令或操作树*/
    
    #define EXC_ARITHMETIC      3   /* 算术异常*/
            /* code:准确的异常来源*/
    
    #define EXC_EMULATION       4   /* 模拟指令异常*/
            /* 遇到了模拟指令*/
            /* code 和 subcode:详细信息  */
    
    #define EXC_SOFTWARE        5   /* 软件产生的异常 */
            /* code:具体的异常 */
            /* Codes 0 - 0xFFFF :硬件 */
            /* Codes 0x10000 - 0x1FFFF :操作系统模拟(Unix) */
    
    #define EXC_BREAKPOINT      6   /* 和跟踪、断点相关的异常 */
            /* code:详细信息 */
    
    #define EXC_SYSCALL     7   /* 系统调用 */
    
    #define EXC_MACH_SYSCALL    8   /* Mach 系统调用 */
    
    #define EXC_RPC_ALERT       9   /* RPC 报警 */
    
    #define EXC_CRASH       10  /* 异常的进程推出 */
    
    #define EXC_RESOURCE        11  /* 达到资源消耗限制 */
            /* code:详细信息 */
    
    #define EXC_GUARD       12  /* 违反保护资源保护 */
    
    #define EXC_CORPSE_NOTIFY   13  /* 异常过程退出尸体状态*/
    
    #define EXC_CORPSE_VARIANT_BIT  0x100  /* 变位用。*/
    

    处理异常行为(flavor)###

    行为 用途
    EXCEPTION_DEFAULT 将线程标识符传递给异常处理程序
    EXCEPTION_STATE 将线程的寄存器状态传递给异常处理程序。i386使用的是TREAD_STATE_X86和THREAD_STATE_64,ARM使用的是THREAD_STATE_ARM和THREAD_STATE_ARM_64
    EXCEPTION_STATE_IDENTITY 将线程标识符和状态都传给异常程序

    捕捉

    • 获取已存在的异常处理端口

        //用于储存已存在的异常端口
        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;;
            
            //获取当前的异常处理端口并储存
            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) {
                //获得当前端口失败
                return -1;
            }   
            return 0;
        }
      
    • 创建一个新端口:

        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) {
                    //创建端口失败
                    return -1 
                }
            return 0;
        }
      
    • 添加端口权限:

    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) {
                //设置权限失败
                return -1;
            }
            return 0;
        }
    
    • 设置异常监听端口:

        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) {
                //设置监听端口失败
                return -1;
            }
            return 0;
        }
      
    • 创建异常处理线程:

        int create_exception_thread() {
            pthread_t thread;
            if (pthread_create(&thread,NULL,exc_handler,NULL) != 0) {
                return -1;
            }
            return 0;
        }
      
    • 实现异常处理方法:

    接受选项


    msg_rcv_option.png

    发送选项


    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;}
        }
        
        /*
        ----------------
        可以在这里对request里面的数据进行处理
        如:
        1、堆栈获取
        2、崩溃类型
        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;
        
        //将消息发送出去,交给exception_port处理
        //但是发送出去之后可能会被signal再次捕捉,
        //所以需要过滤一些重复捕捉的东西。
        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异常解析

    奔溃类型解析

    //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来获取崩溃类型。

    SIGNAL类型解析

    mach_to_signal_trans.png

    通过结合exceptioncode[0]code[1]来解析,可以通过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;
        }
    }
    

    堆栈解析###

    详见堆栈解析

    相关文章

      网友评论

          本文标题:iOS_Crash收集之Mach

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