iOS_Crash收集之Mach

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

堆栈解析###

详见堆栈解析

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容