在SIGSEGV信号处理函数中分配内存后仍无限段错误如何修正
问题根因
你观察到的现象是正确的:*p = 1对应的两条汇编指令里,movq p(%rip), %rax已经把p的NULL值加载到了rax寄存器,才触发后续movb $1, (%rax)的段错误。信号处理函数修改的是内存中p的取值,不会修改内核保存的程序执行上下文中的rax值,信号处理结束返回时会恢复旧的rax值,再次执行出错指令就会无限触发段错误。
解决方案
方案1:修改信号上下文的寄存器值
使用sigaction注册信号处理函数并开启SA_SIGINFO标志,即可在处理函数中拿到内核保存的执行上下文,直接修改上下文中的rax寄存器为新分配的地址,信号返回后就会用新地址执行指令。
示例代码如下(仅适用于x86_64架构):
#include <signal.h> #include <unistd.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <ucontext.h> volatile char *p = NULL; void segv_handler(int signum, siginfo_t *info, void *ctx) { char text[100]; int len = sprintf(text, "Segv handler! p: %p\n", p); write(STDOUT_FILENO, text, len); if (p == NULL) { p = malloc(sizeof(char)); // 直接修改上下文中的RAX寄存器值为新的p地址 ((ucontext_t *)ctx)->uc_mcontext.gregs[REG_RAX] = (greg_t)p; } } int main() { struct sigaction sa = { .sa_flags = SA_SIGINFO, .sa_sigaction = segv_handler }; sigaction(SIGSEGV, &sa, NULL); *p = 1; // 验证赋值成功 printf("Assign success, *p = %d\n", *p); free((void*)p); return 0; }
方案2:用siglongjmp跳转重新执行赋值语句
不在信号处理结束后返回出错指令,而是直接跳转到*p = 1之前的位置,让编译器重新读取内存中p的最新值到寄存器,即可正常执行。
示例代码如下:
#include <signal.h> #include <unistd.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <setjmp.h> volatile char *p = NULL; sigjmp_buf jmp_buf; void segv_handler(int signum) { char text[100]; int len = sprintf(text, "Segv handler! p: %p\n", p); write(STDOUT_FILENO, text, len); if (p == NULL) { p = malloc(sizeof(char)); } siglongjmp(jmp_buf, 1); } int main() { signal(SIGSEGV, segv_handler); sigsetjmp(jmp_buf, 1); *p = 1; // 验证赋值成功 printf("Assign success, *p = %d\n", *p); free((void*)p); return 0; }
注意事项
- 上述两种方案仅作逻辑演示,
malloc、sprintf都不属于异步信号安全函数,实际生产环境在信号处理函数中调用这类函数可能导致不可预期的错误。 - 这类段错误修复逻辑强依赖CPU架构、操作系统实现,可移植性极低,非特殊场景不建议使用。
内容的提问来源于stack exchange,提问作者Franciszek Malinka
相关产品推荐
相关产品推荐

