iOS_Crash收集之Mach

Mach是个什么鬼 苹果的官方OS X和iOS文档的分层:

  • 用户体验层
  • 应用框架层
  • 核心框架层
  • Darwin
Darwin 是完全开源的,是整个系统的基础,提供了底层API。上层是闭源的。
这边主要关注Darwin:

iOS_Crash收集之Mach
文章图片
Darwin架构.png 【iOS_Crash收集之Mach】图里面可以看出来,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_tmach_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_MSG0x00000001 //#define MACH_RCV_MSG0x00000002 //mach_msg_option_t 详见:message.h:632 extern mach_msg_return_tmach_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_ACCESS1/* 内存访问异常 */ /* code:描述错误的Kern_return_t*/ /* subcode:发生内存访问异常的地址 */#define EXC_BAD_INSTRUCTION 2/* 指令异常*/ /* 非法或未定义的指令或操作树*/#define EXC_ARITHMETIC3/* 算术异常*/ /* code:准确的异常来源*/#define EXC_EMULATION4/* 模拟指令异常*/ /* 遇到了模拟指令*/ /* code 和 subcode:详细信息*/#define EXC_SOFTWARE5/* 软件产生的异常 */ /* code:具体的异常 */ /* Codes 0 - 0xFFFF :硬件 */ /* Codes 0x10000 - 0x1FFFF :操作系统模拟(Unix) */#define EXC_BREAKPOINT6/* 和跟踪、断点相关的异常 */ /* code:详细信息 */#define EXC_SYSCALL7/* 系统调用 */#define EXC_MACH_SYSCALL8/* Mach 系统调用 */#define EXC_RPC_ALERT9/* RPC 报警 */#define EXC_CRASH10/* 异常的进程推出 */#define EXC_RESOURCE11/* 达到资源消耗限制 */ /* code:详细信息 */#define EXC_GUARD12/* 违反保护资源保护 */#define EXC_CORPSE_NOTIFY13/* 异常过程退出尸体状态*/#define EXC_CORPSE_VARIANT_BIT0x100/* 变位用。*/

处理异常行为(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_tmasks[EXC_TYPES_COUNT]; exception_handler_tports[EXC_TYPES_COUNT]; exception_behavior_tbehaviors[EXC_TYPES_COUNT]; thread_state_flavor_tflavors[EXC_TYPES_COUNT]; mach_msg_type_number_tcount; } 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; }

  • 添加端口权限:
iOS_Crash收集之Mach
文章图片
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; }

  • 实现异常处理方法:
接受选项

iOS_Crash收集之Mach
文章图片
msg_rcv_option.png 发送选项

iOS_Crash收集之Mach
文章图片
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类型解析
iOS_Crash收集之Mach
文章图片
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( intexception, mach_exception_code_tcode, 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; } }

堆栈解析###
详见堆栈解析

    推荐阅读