Loading...
Searching...
No Matches
fpe_handler.cpp
Go to the documentation of this file.
1/*
2** This file is part of eOn.
3**
4** SPDX-License-Identifier: BSD-3-Clause
5**
6** Copyright (c) 2010--present, eOn Development Team
7** All rights reserved.
8**
9** Repo:
10** https://github.com/TheochemUI/eOn
11*/
12#include "eon/fpe_handler.h"
13
14#include <cfenv>
15#include <csignal>
16#include <cstdio>
17
18#ifndef _WIN32
19#include <unistd.h>
20#endif
21
22#ifdef _WIN32
23#define WIN32_LEAN_AND_MEAN
24#include <float.h>
25#include <windows.h>
26#endif
27
28#if defined(__linux__)
29#include <ucontext.h>
30#endif
31
32#if defined(__APPLE__) && defined(__x86_64__)
33#include <xmmintrin.h>
34#endif
35
36namespace eonc {
37
38#ifdef _WIN32
39// Report each exception class once. Clearing the status alone is not enough
40// for a true continue: the faulting op re-executes and re-traps forever.
41// After the first report, unmask-trapping is demoted for that class so the
42// instruction completes with the IEEE default (Inf/NaN) and the process
43// proceeds.
44static LONG WINAPI windowsFPEHandler(EXCEPTION_POINTERS *info) {
45 DWORD code = info->ExceptionRecord->ExceptionCode;
46 static bool reported_div = false;
47 static bool reported_inv = false;
48 static bool reported_ovf = false;
49 static bool reported_other = false;
50 switch (code) {
51 case EXCEPTION_FLT_DIVIDE_BY_ZERO:
52 if (!reported_div) {
53 reported_div = true;
54 fprintf(stderr, "FPE (continuing, masking further): division by zero\n");
55 }
56 break;
57 case EXCEPTION_FLT_INVALID_OPERATION:
58 if (!reported_inv) {
59 reported_inv = true;
60 fprintf(stderr, "FPE (continuing, masking further): invalid operation\n");
61 }
62 break;
63 case EXCEPTION_FLT_OVERFLOW:
64 if (!reported_ovf) {
65 reported_ovf = true;
66 fprintf(stderr, "FPE (continuing, masking further): overflow\n");
67 }
68 break;
69 case EXCEPTION_FLT_UNDERFLOW:
70 case EXCEPTION_FLT_INEXACT_RESULT:
71 case EXCEPTION_FLT_DENORMAL_OPERAND:
72 case EXCEPTION_FLT_STACK_CHECK:
73 if (!reported_other) {
74 reported_other = true;
75 fprintf(stderr, "FPE (continuing, masking further): other float fault\n");
76 }
77 break;
78 default:
79 return EXCEPTION_CONTINUE_SEARCH;
80 }
81 // Re-mask every class we care about so CONTINUE_EXECUTION does not re-trap.
82 _clearfp();
83 unsigned int control = 0;
84 _controlfp_s(&control, _MCW_EM, _MCW_EM);
85 return EXCEPTION_CONTINUE_EXECUTION;
86}
87#else
88// MXCSR exception-mask bits (Intel SDM): bit7=IM, bit8=DM, bit9=ZM,
89// bit10=OM, bit11=UM, bit12=PM. Sticky status flags are bits 0-5.
90static constexpr unsigned MXCSR_MASK_IM = 1u << 7;
91static constexpr unsigned MXCSR_MASK_ZM = 1u << 9;
92static constexpr unsigned MXCSR_MASK_OM = 1u << 10;
93
94static void fpe_signal_handler(int sig, siginfo_t *sip, void *scp) {
95 // Async-signal-safe only: write(2) and sig_atomic_t. No iostream, malloc,
96 // backtrace, or fenv helpers (fedisableexcept / feclearexcept are not
97 // async-signal-safe). All continue-state is written into the saved ucontext
98 // so it is restored on sigreturn.
99 //
100 // x86 cannot "continue" past a trapped FP op by clearing sticky flags:
101 // flags are bits 0-5 of MXCSR/swd, but the exception MASK bits live at
102 // MXCSR 7-12. Clearing 0x3F leaves trapping armed, so the faulting
103 // instruction re-executes on the same operands and re-raises forever
104 // (report, sigreturn, refault) -- multi-GB identical stderr lines and a
105 // client stuck at ~100% CPU. Mask the class in the restored MXCSR so
106 // re-execution produces the IEEE default (Inf/NaN) and proceeds.
107 static volatile sig_atomic_t reported_div = 0;
108 static volatile sig_atomic_t reported_inv = 0;
109 static volatile sig_atomic_t reported_ovf = 0;
110 static volatile sig_atomic_t reported_unk = 0;
111
112 static constexpr char prefix[] = "FPE (continuing, masking further): ";
113 static constexpr char msg_div[] = "division by zero\n";
114 static constexpr char msg_inv[] = "invalid operation\n";
115 static constexpr char msg_ovf[] = "overflow\n";
116 static constexpr char msg_unk[] = "unknown\n";
117
118 // Default: mask all three classes we enable at startup, so an unknown
119 // si_code cannot leave trapping armed and re-storm.
120 unsigned mxcsr_mask_bits = MXCSR_MASK_IM | MXCSR_MASK_ZM | MXCSR_MASK_OM;
121 volatile sig_atomic_t *reported = &reported_unk;
122 const char *msg = msg_unk;
123 size_t msg_len = sizeof(msg_unk) - 1;
124
125 switch (sip->si_code) {
126 case FPE_FLTDIV:
127 reported = &reported_div;
128 msg = msg_div;
129 msg_len = sizeof(msg_div) - 1;
130 mxcsr_mask_bits = MXCSR_MASK_ZM;
131 break;
132 case FPE_FLTINV:
133 reported = &reported_inv;
134 msg = msg_inv;
135 msg_len = sizeof(msg_inv) - 1;
136 mxcsr_mask_bits = MXCSR_MASK_IM;
137 break;
138 case FPE_FLTOVF:
139 reported = &reported_ovf;
140 msg = msg_ovf;
141 msg_len = sizeof(msg_ovf) - 1;
142 mxcsr_mask_bits = MXCSR_MASK_OM;
143 break;
144 default:
145 break;
146 }
147
148 if (*reported == 0) {
149 *reported = 1;
150 write(STDERR_FILENO, prefix, sizeof(prefix) - 1);
151 write(STDERR_FILENO, msg, msg_len);
152#if defined(__linux__) && defined(__x86_64__)
153 // First-fault RIP for post-mortem addr2line / offline diagnosis.
154 ucontext_t *ctx_log = static_cast<ucontext_t *>(scp);
155 unsigned long rip =
156 static_cast<unsigned long>(ctx_log->uc_mcontext.gregs[REG_RIP]);
157 char hex[] = "FPE rip=0x0000000000000000\n";
158 for (int i = 0; i < 16; ++i) {
159 unsigned nibble = static_cast<unsigned>((rip >> (4 * (15 - i))) & 0xFu);
160 hex[10 + i] =
161 static_cast<char>(nibble < 10 ? '0' + nibble : 'a' + (nibble - 10));
162 }
163 write(STDERR_FILENO, hex, sizeof(hex) - 1);
164#endif
165 }
166
167#if defined(__linux__) && (defined(__x86_64__) || defined(__i386__))
168 ucontext_t *ctx = static_cast<ucontext_t *>(scp);
169 if (ctx->uc_mcontext.fpregs) {
170 // Clear sticky exception FLAGS (bits 0-5) and arm the MASK bit(s) for
171 // the fault class (bits 7-12). Mask sticks after sigreturn because the
172 // restored MXCSR becomes the live CPU state.
173 ctx->uc_mcontext.fpregs->swd &= ~0x3Fu;
174 ctx->uc_mcontext.fpregs->mxcsr &= ~0x3Fu;
175 ctx->uc_mcontext.fpregs->mxcsr |= mxcsr_mask_bits;
176 // x87 control word: mask bits are 0-5 of cwd (IM, DM, ZM, OM, UM, PM).
177 // Set the matching masks so a legacy x87 fault cannot re-storm either.
178 if (mxcsr_mask_bits & MXCSR_MASK_ZM) {
179 ctx->uc_mcontext.fpregs->cwd |= (1u << 2); // x87 ZM
180 }
181 if (mxcsr_mask_bits & MXCSR_MASK_IM) {
182 ctx->uc_mcontext.fpregs->cwd |= (1u << 0); // x87 IM
183 }
184 if (mxcsr_mask_bits & MXCSR_MASK_OM) {
185 ctx->uc_mcontext.fpregs->cwd |= (1u << 3); // x87 OM
186 }
187 }
188#endif
189 (void)sig;
190}
191#endif
192
193void enableFPE() {
194#ifdef _WIN32
195 // Register Windows SEH handler for FPE reporting
196 SetUnhandledExceptionFilter(windowsFPEHandler);
197 // Enable floating-point exceptions on Windows
198 _controlfp_s(nullptr, 0, _MCW_EM);
199 _controlfp_s(nullptr, ~(_EM_ZERODIVIDE | _EM_INVALID | _EM_OVERFLOW),
200 _MCW_EM);
201#elif defined(__unix__)
202 // Enable floating-point exceptions on Unix
203 feenableexcept(FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW);
204#elif defined(__APPLE__) && defined(__aarch64__)
205 // Enable floating-point exceptions on ARM macOS
206 fenv_t env;
207 fegetenv(&env);
208 env.__fpsr &= ~(FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW);
209 fesetenv(&env);
210#elif defined(__APPLE__) && defined(__x86_64__)
211 // Enable floating-point exceptions on Intel macOS
212 _MM_SET_EXCEPTION_MASK(
213 _MM_MASK_MASK &
214 ~(_MM_MASK_INVALID | _MM_MASK_DIV_ZERO | _MM_MASK_OVERFLOW));
215#else
216 fprintf(stderr, "FPE trapping not supported on this platform.\n");
217#endif
218
219#ifndef _WIN32
220 // Register POSIX signal handler
221 struct sigaction act;
222 act.sa_sigaction = fpe_signal_handler;
223 sigemptyset(&act.sa_mask);
224 act.sa_flags = SA_SIGINFO;
225 sigaction(SIGFPE, &act, nullptr);
226#endif
227}
228
230#ifdef _WIN32
231 // Mask all floating-point exceptions (restore default behavior)
232 unsigned int control;
233 _controlfp_s(&control, _MCW_EM, _MCW_EM);
234#elif defined(__unix__)
235 fedisableexcept(FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW);
236#elif defined(__APPLE__)
237 fenv_t env;
238 fegetenv(&env);
239#if defined(__aarch64__)
240 env.__fpsr |= (FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW);
241#endif
242 fesetenv(&env);
243#endif
244}
245
247 std::lock_guard<std::mutex> lock(mutex_);
248 feholdexcept(&orig_feenv);
249}
250
252 std::lock_guard<std::mutex> lock(mutex_);
253 fesetenv(&orig_feenv);
254}
255
256} // namespace eonc
std::mutex mutex_
Definition fpe_handler.h:34
RAII resource manager for the ARTn C library with global synchronization.
static void fpe_signal_handler(int sig, siginfo_t *sip, void *scp)
static constexpr unsigned MXCSR_MASK_ZM
void enableFPE()
static constexpr unsigned MXCSR_MASK_OM
void disableFPE()
static constexpr unsigned MXCSR_MASK_IM