本文深入介绍Mach异常的底层实现细节,包括端口操作、消息机制以及如何实现自定义的Mach异常处理器。
关于Mach异常的基础概念(Task、Thread、Port、Message)和异常端口的查找顺序,请参考 崩溃-原理。
Mach端口详解
端口权限
端口权限类型:
┌─────────────────────────────────────────────────────────────┐
│ 端口权限 │
├─────────────────────────────────────────────────────────────┤
│ │
│ MACH_PORT_RIGHT_SEND │
│ ├── 发送权限 │
│ ├── 可以向端口发送消息 │
│ └── 可以复制给其他任务 │
│ │
│ MACH_PORT_RIGHT_RECEIVE │
│ ├── 接收权限 │
│ ├── 可以从端口接收消息 │
│ ├── 只能有一个持有者 │
│ └── 持有者销毁时端口销毁 │
│ │
│ MACH_PORT_RIGHT_SEND_ONCE │
│ ├── 一次性发送权限 │
│ └── 发送一次后自动失效 │
│ │
│ MACH_PORT_RIGHT_PORT_SET │
│ ├── 端口集合权限 │
│ └── 可以同时监听多个端口 │
│ │
│ MACH_PORT_RIGHT_DEAD_NAME │
│ ├── 死名权限 │
│ └── 端口已销毁但名字仍存在 │
│ │
└─────────────────────────────────────────────────────────────┘
端口操作
#include <mach/mach.h>
// 分配新端口
mach_port_t port;
kern_return_t kr = mach_port_allocate(
mach_task_self(), // 当前任务
MACH_PORT_RIGHT_RECEIVE, // 接收权限
&port // 输出端口名
);
// 添加发送权限
kr = mach_port_insert_right(
mach_task_self(),
port,
port,
MACH_MSG_TYPE_MAKE_SEND // 创建发送权限
);
// 销毁端口
kr = mach_port_destroy(mach_task_self(), port);
// 释放端口权限
kr = mach_port_deallocate(mach_task_self(), port);
Mach消息
消息结构
// 消息头部
typedef struct {
mach_msg_bits_t msgh_bits; // 消息类型和权限
mach_msg_size_t msgh_size; // 消息大小
mach_port_t msgh_remote_port; // 目标端口
mach_port_t msgh_local_port; // 本地端口(用于回复)
mach_port_name_t msgh_voucher_port;// 凭证端口
mach_msg_id_t msgh_id; // 消息ID
} mach_msg_header_t;
// 完整消息示例
typedef struct {
mach_msg_header_t header;
// 可选的消息体
mach_msg_body_t body;
// 可选的端口描述符
mach_msg_port_descriptor_t port;
// 可选的数据
char data[256];
} MyMessage;
消息发送与接收
// 发送消息
kern_return_t send_message(mach_port_t port, const char *data, size_t len) {
struct {
mach_msg_header_t header;
char data[256];
} message;
message.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
message.header.msgh_size = sizeof(message);
message.header.msgh_remote_port = port;
message.header.msgh_local_port = MACH_PORT_NULL;
message.header.msgh_id = 0;
size_t copy_len = len < sizeof(message.data) ? len : sizeof(message.data);
memcpy(message.data, data, copy_len);
return mach_msg(
&message.header,
MACH_SEND_MSG, // 发送
sizeof(message), // 发送大小
0, // 接收大小
MACH_PORT_NULL, // 接收端口
MACH_MSG_TIMEOUT_NONE, // 超时
MACH_PORT_NULL // 通知端口
);
}
// 接收消息
kern_return_t receive_message(mach_port_t port) {
struct {
mach_msg_header_t header;
char data[256];
} message;
kern_return_t kr = mach_msg(
&message.header,
MACH_RCV_MSG, // 接收
0, // 发送大小
sizeof(message), // 接收大小
port, // 接收端口
MACH_MSG_TIMEOUT_NONE, // 超时
MACH_PORT_NULL // 通知端口
);
if (kr == MACH_MSG_SUCCESS) {
// 处理消息
printf("Received: %s\n", message.data);
}
return kr;
}
异常端口操作
设置异常端口
#include <mach/mach.h>
// 设置任务级异常端口
kern_return_t setup_task_exception_port(mach_port_t exception_port) {
// 异常类型掩码
exception_mask_t mask =
EXC_MASK_BAD_ACCESS |
EXC_MASK_BAD_INSTRUCTION |
EXC_MASK_ARITHMETIC |
EXC_MASK_BREAKPOINT |
EXC_MASK_SOFTWARE;
return task_set_exception_ports(
mach_task_self(),
mask,
exception_port,
EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES,
THREAD_STATE_NONE
);
}
// 获取当前异常端口
kern_return_t get_exception_ports(void) {
exception_mask_t masks[EXC_TYPES_COUNT];
mach_msg_type_number_t count = EXC_TYPES_COUNT;
mach_port_t ports[EXC_TYPES_COUNT];
exception_behavior_t behaviors[EXC_TYPES_COUNT];
thread_state_flavor_t flavors[EXC_TYPES_COUNT];
return task_get_exception_ports(
mach_task_self(),
EXC_MASK_ALL,
masks,
&count,
ports,
behaviors,
flavors
);
}
异常处理流程
完整的异常处理流程
flowchart TD
A["1. 硬件检测到异常
CPU执行非法操作,触发硬件中断"] --> B["2. 陷入内核
CPU切换到内核态,执行异常处理程序"]
B --> C["3. 内核构造异常消息
创建包含异常信息的Mach消息"]
C --> D["4. 发送到异常端口
按照 Thread → Task → Host 顺序查找端口"]
D --> E["5. 等待回复
内核暂停线程,等待异常处理程序的回复"]
E --> F{"6. 处理回复"}
F -->|KERN_SUCCESS| G["恢复线程执行"]
F -->|其他| H["转换为Unix信号或终止进程"]
异常消息格式
// 异常消息结构
typedef struct {
mach_msg_header_t Head;
// 消息体
mach_msg_body_t msgh_body;
mach_msg_port_descriptor_t thread; // 发生异常的线程
mach_msg_port_descriptor_t task; // 发生异常的任务
// 异常信息
NDR_record_t NDR;
exception_type_t exception; // 异常类型
mach_msg_type_number_t codeCnt; // 代码数量
int64_t code[2]; // 异常代码
} __Request__mach_exception_raise_t;
// 异常回复消息
typedef struct {
mach_msg_header_t Head;
NDR_record_t NDR;
kern_return_t RetCode; // 返回码
} __Reply__mach_exception_raise_t;
实现Mach异常处理器
完整示例
#include <mach/mach.h>
#include <pthread.h>
// 异常处理线程
static pthread_t exception_thread;
static mach_port_t exception_port;
// 保存原始异常端口
static mach_port_t original_exception_port;
static exception_behavior_t original_behavior;
static thread_state_flavor_t original_flavor;
// 异常处理函数
static kern_return_t handle_exception(
mach_port_t exception_port,
mach_port_t thread,
mach_port_t task,
exception_type_t exception,
mach_exception_data_t code,
mach_msg_type_number_t code_count
) {
// 获取线程状态(ARM64架构)
// 注意:如需支持x86_64模拟器,需要使用x86_thread_state64_t和X86_THREAD_STATE64
#if defined(__arm64__)
arm_thread_state64_t state;
mach_msg_type_number_t state_count = ARM_THREAD_STATE64_COUNT;
thread_get_state(thread, ARM_THREAD_STATE64,
(thread_state_t)&state, &state_count);
// 记录崩溃信息
printf("Exception: %d\n", exception);
printf("Code: 0x%llx, 0x%llx\n", code[0], code[1]);
printf("PC: 0x%llx\n", (unsigned long long)state.__pc);
printf("LR: 0x%llx\n", (unsigned long long)state.__lr);
#endif
// 采集堆栈(需要自行实现)
// collect_backtrace(thread);
// 保存崩溃日志(需要自行实现)
// save_crash_log();
// 返回失败,让系统继续处理(转换为信号)
return KERN_FAILURE;
}
// 异常处理线程主函数
static void *exception_handler_thread(void *arg) {
kern_return_t kr;
while (1) {
// 接收异常消息
struct {
mach_msg_header_t head;
mach_msg_body_t body;
mach_msg_port_descriptor_t thread;
mach_msg_port_descriptor_t task;
NDR_record_t NDR;
exception_type_t exception;
mach_msg_type_number_t code_count;
int64_t code[2];
int flavor;
mach_msg_type_number_t state_count;
natural_t state[224];
} request;
struct {
mach_msg_header_t head;
NDR_record_t NDR;
kern_return_t ret_code;
} reply;
// 接收消息
kr = mach_msg(
&request.head,
MACH_RCV_MSG | MACH_RCV_LARGE,
0,
sizeof(request),
exception_port,
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL
);
if (kr != MACH_MSG_SUCCESS) {
continue;
}
// 处理异常
kr = handle_exception(
exception_port,
request.thread.name,
request.task.name,
request.exception,
request.code,
request.code_count
);
// 发送回复
reply.head.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MOVE_SEND_ONCE, 0);
reply.head.msgh_size = sizeof(reply);
reply.head.msgh_remote_port = request.head.msgh_remote_port;
reply.head.msgh_local_port = MACH_PORT_NULL;
// Mach IPC惯例:回复消息ID = 请求消息ID + 100
reply.head.msgh_id = request.head.msgh_id + 100;
reply.NDR = NDR_record;
reply.ret_code = kr;
mach_msg(
&reply.head,
MACH_SEND_MSG,
sizeof(reply),
0,
MACH_PORT_NULL,
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL
);
}
return NULL;
}
// 安装异常处理器
kern_return_t install_exception_handler(void) {
kern_return_t kr;
// 创建异常端口
kr = mach_port_allocate(
mach_task_self(),
MACH_PORT_RIGHT_RECEIVE,
&exception_port
);
if (kr != KERN_SUCCESS) return kr;
// 添加发送权限
kr = mach_port_insert_right(
mach_task_self(),
exception_port,
exception_port,
MACH_MSG_TYPE_MAKE_SEND
);
if (kr != KERN_SUCCESS) return kr;
// 保存原始异常端口
exception_mask_t mask = EXC_MASK_BAD_ACCESS |
EXC_MASK_BAD_INSTRUCTION |
EXC_MASK_ARITHMETIC |
EXC_MASK_BREAKPOINT;
// 用于接收查询结果的数组
exception_mask_t masks_out[EXC_TYPES_COUNT];
mach_port_t ports_out[EXC_TYPES_COUNT];
exception_behavior_t behaviors_out[EXC_TYPES_COUNT];
thread_state_flavor_t flavors_out[EXC_TYPES_COUNT];
mach_msg_type_number_t count = EXC_TYPES_COUNT;
kr = task_get_exception_ports(
mach_task_self(),
mask, // 输入:要查询的异常类型掩码
masks_out, // 输出:实际的掩码数组
&count, // 输入输出:数组大小
ports_out, // 输出:端口数组
behaviors_out, // 输出:行为数组
flavors_out // 输出:flavor数组
);
// 保存第一个有效的原始端口(简化处理)
if (count > 0) {
original_exception_port = ports_out[0];
original_behavior = behaviors_out[0];
original_flavor = flavors_out[0];
}
// 设置新的异常端口
kr = task_set_exception_ports(
mach_task_self(),
mask,
exception_port,
EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES,
THREAD_STATE_NONE
);
if (kr != KERN_SUCCESS) return kr;
// 创建异常处理线程
pthread_create(&exception_thread, NULL,
exception_handler_thread, NULL);
return KERN_SUCCESS;
}
调试器检测
调试器(如LLDB)通过在Task(任务/进程)级别注册异常端口来优先拦截异常。了解这一机制有助于理解为什么调试时某些崩溃行为会有所不同。
检测调试器
#include <sys/sysctl.h>
#include <unistd.h>
// 检测是否被调试
bool is_debugger_attached(void) {
int mib[4];
struct kinfo_proc info;
size_t size = sizeof(info);
mib[0] = CTL_KERN;
mib[1] = KERN_PROC;
mib[2] = KERN_PROC_PID;
mib[3] = getpid();
info.kp_proc.p_flag = 0;
if (sysctl(mib, 4, &info, &size, NULL, 0) == -1) {
return false;
}
return (info.kp_proc.p_flag & P_TRACED) != 0;
}
Mach异常与崩溃采集
采集时机
Mach异常采集的优势:
1. 最早的捕获时机
└── 在转换为Unix信号之前就能捕获
2. 更完整的信息
├── 可以获取原始异常类型
├── 可以获取完整的线程状态
└── 可以获取所有线程的信息
3. 更可靠的处理
├── 在独立线程中处理
├── 不受信号处理限制
└── 可以使用更多API
注意事项:
- 需要处理与其他异常处理器的冲突
- 调试器优先接收异常
- 某些异常可能不会发送到用户态
与Signal Handler的配合
// 推荐的崩溃采集架构
void setup_crash_handlers(void) {
// 1. 安装Mach异常处理器(优先)
install_mach_exception_handler();
// 2. 安装Signal Handler(备份)
install_signal_handlers();
// 3. 安装NSException Handler
NSSetUncaughtExceptionHandler(&exception_handler);
}
// Mach异常处理器
kern_return_t mach_exception_handler(...) {
// 采集崩溃信息
collect_crash_info();
// 返回失败,让异常继续传递
// 这样Signal Handler也能捕获到
return KERN_FAILURE;
}
// Signal Handler
void signal_handler(int sig, siginfo_t *info, void *context) {
// 检查是否已经被Mach异常处理器处理
if (already_handled) {
// 直接退出
return;
}
// 采集崩溃信息(备份)
collect_crash_info();
}
常见问题与解决方案
异常处理器冲突
问题:多个SDK都注册了Mach异常处理器
解决方案:
1. 保存原始异常端口
2. 处理完后转发给原始端口
3. 或者返回KERN_FAILURE让系统继续处理
死锁问题
问题:异常处理时发生死锁
原因:
- 异常线程持有锁
- 处理器尝试获取同一把锁
解决方案:
1. 异常处理器中只使用异步安全的操作
2. 使用独立的数据结构存储崩溃信息
3. 避免在异常处理器中调用可能加锁的API
递归异常
问题:异常处理器中又发生异常
解决方案:
1. 使用标志位防止重入
2. 异常处理器中的代码要尽量简单
3. 使用预分配的内存
4. 避免复杂的对象操作