Linux x64下如何在信号处理程序中修改浮点上下文?
Linux x64下捕获SIGFPE后修改浮点上下文并继续执行的可行方案
你的方案完全可行,Linux下确实可以实现捕获一次SIGFPE后禁用浮点异常、让程序继续执行的需求。原代码的核心问题是错误地直接复制fpregs指针,而非修改上下文里的浮点控制寄存器内容,导致返回时内核无法正确恢复上下文,引发段错误。
问题分析
uc_mcontext.fpregs是指向内核保存的浮点寄存器区域的指针,直接赋值指针会让内核后续访问非法内存,必然触发段错误。正确做法是修改该指针指向的结构体中的控制字,而非替换指针本身。feholdexcept修改的是信号处理程序自身的浮点环境,和触发信号时的上下文无关,无法通过getcontext获取到需要的状态来覆盖原上下文。
修正方案
在x86_64平台上,我们可以直接操作ucontext_t中的浮点控制寄存器:
- 修改x87控制字(
cw),禁用FE_INVALID、FE_DIVBYZERO、FE_OVERFLOW这几个异常 - 修改MXCSR寄存器(SSE浮点控制字),同样禁用对应异常并清除已触发的异常标志
以下是修正后的完整代码:
#include <stdio.h> #include <unistd.h> #include <fenv.h> #include <ucontext.h> #include <signal.h> #include <string.h> #include <errno.h> /* zero value as global to prevent compiler optimization */ double zero_val = 0.0; /* global controlling behavior of sig handler*/ int flag_enable_fpe_context_reset = 0; /* globals to be updated by signal handler */ int num_fpe_signals = 0; int last_fpe_signum = 0; siginfo_t last_fpe_siginfo; ucontext_t last_fpe_context; // x86_64平台的浮点状态结构体定义 struct _fpstate { unsigned short cw; unsigned short sw; unsigned short tag; unsigned short ipoff; unsigned short cssel; unsigned short dataoff; unsigned short datasel; uint32_t mxcsr; uint32_t mxcsr_mask; uint32_t st_space[32]; uint32_t xmm_space[64]; uint32_t padding[24]; }; void my_fpe_signal_handler(int signum, siginfo_t *siginfo, void *context){ write(STDERR_FILENO , "CAUGHT SIGFPE!\n" , 15); ucontext_t *pCallerContext = (ucontext_t*) context; struct _fpstate *fpstate = (struct _fpstate*)pCallerContext->uc_mcontext.fpregs; // 记录异常信息 num_fpe_signals++; last_fpe_signum = signum; last_fpe_siginfo = *siginfo; last_fpe_context = *pCallerContext; if(flag_enable_fpe_context_reset){ // 修改x87控制字:禁用FE_INVALID、FE_DIVBYZERO、FE_OVERFLOW fpstate->cw &= ~(FE_INVALID | FE_DIVBYZERO | FE_OVERFLOW); // 修改MXCSR:禁用对应异常,同时清除已触发的异常标志 fpstate->mxcsr &= ~((FE_INVALID | FE_DIVBYZERO | FE_OVERFLOW) << 7); fpstate->mxcsr &= ~(FE_INVALID | FE_DIVBYZERO | FE_OVERFLOW); } // 无限循环检测 if(num_fpe_signals > 10){ write(STDERR_FILENO, "Infinite loop detection triggered. Exiting Program From Signal Handler!\n",72); _Exit(1); } } int main(int argc, char* argv[] ){ double retval; if(argc > 1){ flag_enable_fpe_context_reset = 1; } // 设置信号处理程序 struct sigaction sig_action = {}; sig_action.sa_sigaction = my_fpe_signal_handler; sigemptyset(&sig_action.sa_mask); sig_action.sa_flags = SA_SIGINFO; if (sigaction(SIGFPE, &sig_action, NULL) != 0) { perror("sigaction"); return 1; } // 启用浮点异常 if (feenableexcept(FE_INVALID | FE_DIVBYZERO | FE_OVERFLOW) == -1) { perror("feenableexcept"); return 1; } // 第一次除零,触发SIGFPE retval = 5.0/zero_val; printf("5.0/zero_val = %f\n",retval); // 第二次除零,此时异常已禁用,不会触发SIGFPE retval = 6.0/zero_val; printf("6.0/zero_val = %f\n",retval); printf("num_fpe_signals = %d\n" , num_fpe_signals); return 0; }
关键修改说明
- 引入
struct _fpstate结构体直接访问浮点控制寄存器,这是x86_64平台内核保存浮点状态的标准结构。 - 直接修改原上下文的
cw(x87控制字)和mxcsr(SSE控制字):- 清除控制字中对应异常的启用位,后续浮点运算不会再触发SIGFPE
- 清除MXCSR中的异常标志位,避免返回后内核再次检测到未处理的异常而重新触发信号
- 移除了原代码中错误的
feholdexcept和getcontext调用,直接操作信号上下文更可靠。
测试效果
编译运行时带参数(如./a.out 1),程序会:
- 第一次除零触发SIGFPE,进入信号处理程序
- 修改浮点上下文后返回,输出
5.0/zero_val = inf - 第二次除零不会触发信号,直接输出
6.0/zero_val = inf - 最后输出
num_fpe_signals = 1,完成执行
内容的提问来源于stack exchange,提问作者jHops
相关产品推荐
相关产品推荐

