iOS_Crash收集之Mach

2019-04-08  本文已影响0人  佛祖拿屠刀

Mach是个什么鬼

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

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

这边主要关注Darwin:


Darwin架构.png

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

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

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

其中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异常捕获

理论依据:

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

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

一个异常基本跟一条消息的复杂度差不多,所以异常也是由出错线程/任务(通过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 将线程标识符和状态都传给异常程序

捕捉

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

接受选项


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

堆栈解析###

详见堆栈解析

上一篇下一篇

猜你喜欢

热点阅读