C编程:如何在段错误信号处理程序中仅用signum地址手动递增PC
手动从段错误恢复:别碰
signum的栈地址,用sigaction才是正道 兄弟,我懂你想手动把程序从段错误里拉回来的想法,但你现在走的路子(通过signum的地址找PC)其实根本走不通,而且风险极大,给你好好捋捋:
为什么通过signum地址找PC不可行?
- 完全没有统一标准:信号处理函数的栈布局是和平台、编译器、ABI绑定死的。x86和x86_64不一样,Linux和macOS不一样,甚至同平台换个GCC版本都可能变。你硬算出来的偏移量,换个环境直接失效。
- 信号上下文特殊:信号处理是内核帮你切换过来的,栈里的内容本来就不是常规函数调用的栈结构,直接瞎改很可能把程序搞成彻底崩溃,或者触发无限段错误这类未定义行为。
正确的姿势:用sigaction获取完整信号上下文
想要修改PC(程序计数器),你得拿到信号发生时的完整寄存器上下文,这时候就得用sigaction代替老旧的signal函数——signal根本没法给你传递这些关键信息。
给你改好的示例代码,直接能用:
#include <stdio.h> #include <signal.h> #include <ucontext.h> void segf_handler(int signum, siginfo_t *info, void *context) { // 把context转换成ucontext_t,里面存着所有寄存器状态 ucontext_t *uc = (ucontext_t *)context; // 注意:不同架构的PC寄存器名字不一样,这里做了兼容 #ifdef __x86_64__ // x86_64下,程序计数器存在REG_RIP寄存器里 // 我们把PC往后跳,跳过导致段错误的那条指令 // 这里加6是因为*(int*)0对应的汇编指令大概是6字节(具体长度可以看反汇编确认) uc->uc_mcontext.gregs[REG_RIP] += 6; #elif __i386__ // x86下是REG_EIP uc->uc_mcontext.gregs[REG_EIP] += 4; #elif __arm__ // ARM架构下是REG_PC uc->uc_mcontext.arm_pc += 4; #endif printf("Caught segfault, adjusted program counter\n"); } int main() { struct sigaction sa; sa.sa_sigaction = segf_handler; // 必须加SA_SIGINFO标志,才能让处理函数拿到上下文参数 sa.sa_flags = SA_SIGINFO; // 清空信号掩码,避免处理信号时被其他信号打断 sigemptyset(&sa.sa_mask); // 注册SIGSEGV信号处理函数 sigaction(SIGSEGV, &sa, NULL); int n = 0; n = *( (int *) 0 ); // 故意触发段错误 printf("recover from segfault\n"); // 现在这条语句能执行了! return 0; }
关键细节说明
SA_SIGINFO标志不能少:只有设置了这个标志,信号处理函数才能接收到siginfo_t和context这两个参数,否则拿不到寄存器上下文。- PC偏移量要对应指令长度:你得知道导致段错误的那条指令的字节长度,比如
*(int*)0在x86_64上的汇编指令长度是6字节(可以用gcc -S看反汇编确认),所以把RIP加6就能跳过这条错误指令,让程序从下一条继续执行。 - 仅限测试场景:这种恢复方式只适合练手玩,实际生产环境千万别用——段错误意味着程序已经访问了非法内存,此时程序状态已经不可靠,强行恢复可能导致数据损坏、无限循环等更糟的情况。
内容的提问来源于stack exchange,提问作者ghost tang
相关产品推荐
相关产品推荐

