捕获SIGSEGV异常及MacOS下线程上下文寄存器读取问题
问题解决:MacOS M1下捕获SIGSEGV并读取线程上下文
一、旧代码未触发handler的核心原因
旧代码的致命错误:设置SA_SIGINFO标志后,错误地将处理函数赋值给sa_handler字段,而非sa_sigaction字段。SA_SIGINFO要求处理函数采用三参数签名(void handler(int, siginfo_t*, void*)),必须通过sa_sigaction注册,否则系统无法正确匹配并调用处理函数。
旧代码错误片段:
act.sa_flags = SA_SIGINFO; act.sa_handler = handler; // 错误:SA_SIGINFO需绑定sa_sigaction
二、修正后的可运行代码(M1 ARM64适配)
以下代码解决了handler注册、ucontext引入及ARM64寄存器读取的问题:
#include <stdio.h> #include <signal.h> #include <unistd.h> // 必须在所有头文件前定义_XOPEN_SOURCE,确保ucontext.h正确暴露 #define _XOPEN_SOURCE 700 #include <ucontext.h> // SA_SIGINFO要求的三参数信号处理函数 void handler(int sig, siginfo_t *info, void *uc) { (void)sig; (void)info; write(STDOUT_FILENO, "Caught SIGSEGV\n", 15); // 转换为ucontext结构体(ARM64版本) struct ucontext *ctx = (struct ucontext *)uc; mcontext_t *mc = &ctx->uc_mcontext; // 示例:读取并打印关键寄存器 printf("PC(程序计数器): 0x%lx\n", mc->__ss.__pc); printf("SP(栈指针): 0x%lx\n", mc->__ss.__sp); // 打印通用寄存器X0-X30 for (int i = 0; i < 31; i++) { printf("X%d: 0x%lx\n", i, mc->__ss.__x[i]); } } int main(int argc, const char *argv[]) { struct sigaction act; sigemptyset(&act.sa_mask); act.sa_flags = SA_SIGINFO; // 启用带上下文的信号处理 act.sa_sigaction = handler; // 正确绑定三参数处理函数 if (sigaction(SIGSEGV, &act, NULL) == -1) { perror("Failed to register handler"); return 1; } printf("Handler registered successfully\n"); // 触发非法内存访问(模拟SIGSEGV) *(int *)0 = 0; return 0; }
三、MacOS ARM64下ucontext的关键注意事项
- _XOPEN_SOURCE定义时机:必须在包含任何系统头文件之前定义
_XOPEN_SOURCE 700(600版本也兼容,但700适配性更强),否则ucontext.h会抛出未定义的编译错误。 - ARM64寄存器结构:MacOS ARM64的
mcontext_t中,寄存器集合存储在__ss子结构体中:__pc:触发信号时的程序计数器(错误指令地址)__sp:当前栈指针__x[0..30]:ARM64通用寄存器X0到X30
- 编译器智能提示问题:部分IDE对MacOS私有结构体字段缺少提示,可直接查看系统头文件
/usr/include/ucontext.h确认结构体定义。
四、编译与运行
使用clang直接编译运行即可,无需额外参数:
clang -o segv_handler segv_handler.c ./segv_handler
内容的提问来源于stack exchange,提问作者Kayla
相关产品推荐
相关产品推荐

