Mach是个什么鬼
苹果的官方OS X和iOS文档的分层:
- 用户体验层
- 应用框架层
- 核心框架层
- Darwin
Darwin 是完全开源的,是整个系统的基础,提供了底层API。上层是闭源的。
这边主要关注Darwin:
图里面可以看出来,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; }
添加端口权限:
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; }
实现异常处理方法:
接受选项
发送选项
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类型解析
通过结合exception和code[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;
}
}
堆栈解析###
详见堆栈解析