You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Linux x64下如何在信号处理程序中修改浮点上下文?

Linux x64下捕获SIGFPE后修改浮点上下文并继续执行的可行方案

你的方案完全可行,Linux下确实可以实现捕获一次SIGFPE后禁用浮点异常、让程序继续执行的需求。原代码的核心问题是错误地直接复制fpregs指针,而非修改上下文里的浮点控制寄存器内容,导致返回时内核无法正确恢复上下文,引发段错误。

问题分析

  1. uc_mcontext.fpregs是指向内核保存的浮点寄存器区域的指针,直接赋值指针会让内核后续访问非法内存,必然触发段错误。正确做法是修改该指针指向的结构体中的控制字,而非替换指针本身。
  2. 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;
}

关键修改说明

  1. 引入struct _fpstate结构体直接访问浮点控制寄存器,这是x86_64平台内核保存浮点状态的标准结构。
  2. 直接修改原上下文的cw(x87控制字)和mxcsr(SSE控制字):
    • 清除控制字中对应异常的启用位,后续浮点运算不会再触发SIGFPE
    • 清除MXCSR中的异常标志位,避免返回后内核再次检测到未处理的异常而重新触发信号
  3. 移除了原代码中错误的feholdexcept和getcontext调用,直接操作信号上下文更可靠。

测试效果

编译运行时带参数(如./a.out 1),程序会:

  • 第一次除零触发SIGFPE,进入信号处理程序
  • 修改浮点上下文后返回,输出5.0/zero_val = inf
  • 第二次除零不会触发信号,直接输出6.0/zero_val = inf
  • 最后输出num_fpe_signals = 1,完成执行

内容的提问来源于stack exchange,提问作者jHops

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 17:54:51