To trap floating point exceptions on MacOS, I use an extension that provides feenableexcept
functionality. The original extension (written in 2009) is here
http://www-personal.umich.edu/~williams/archive/computation/fe-handling-example.c
NOTE: If you came across this post to see how you can trap floating point exceptions on MacOS (either with Intel or Apple silicon), you might want to skip over the assembly discussion to the DETAILS below.
I'd now like to update this extension for Apple silicon and possibly remove some outdated code. Digging through fenv.h
, it is clear how to update the routines feenableexcept
, fegetexcept
and fedisableexcept
for Apple silicon. However, it is less clear what to do with the assembly code provided in the 2009 extension, or why this code is even included.
The extension provided in the link above is quite long, so I'll just extract the fragments involving the assembly :
#if DEFINED_INTEL
// x87 fpu
#define getx87cr(x) __asm ("fnstcw %0" : "=m" (x));
#define setx87cr(x) __asm ("fldcw %0" : "=m" (x));
#define getx87sr(x) __asm ("fnstsw %0" : "=m" (x));
// SIMD, gcc with Intel Core 2 Duo uses SSE2(4)
#define getmxcsr(x) __asm ("stmxcsr %0" : "=m" (x));
#define setmxcsr(x) __asm ("ldmxcsr %0" : "=m" (x));
#endif // DEFINED_INTEL
This code is used in a handler for a sigaction
mechanism that is provided to report on the type of floating point exception trapped.
fhdl ( int sig, siginfo_t *sip, ucontext_t *scp )
{
int fe_code = sip->si_code;
unsigned int excepts = fetestexcept (FE_ALL_EXCEPT);
/* ... see complete code in link above ... */
if ( sig == SIGFPE )
{
#if DEFINED_INTEL
unsigned short x87cr,x87sr;
unsigned int mxcsr;
getx87cr (x87cr);
getx87sr (x87sr);
getmxcsr (mxcsr);
printf ("X87CR: 0x%04X\n", x87cr);
printf ("X87SR: 0x%04X\n", x87sr);
printf ("MXCSR: 0x%08X\n", mxcsr);
#endif
// ....
}
printf ("signal: SIGFPE with code %s\n", fe_code_name[fe_code]);
printf ("invalid flag: 0x%04X\n", excepts & FE_INVALID);
printf ("divByZero flag: 0x%04X\n", excepts & FE_DIVBYZERO);
}
else printf ("Signal is not SIGFPE, it's %i.\n", sig);
abort();
}
An example is provided that traps exceptions and handles them through sigaction
. The call to feenableexcept
will either be a native implementation for systems that have feenableexcept
defined (e.g. non Apple hardware) or the implementation provided in the extension linked above.
int main (int argc, char **argv)
{
double s;
struct sigaction act;
act.sa_sigaction = (void(*))fhdl;
sigemptyset (&act.sa_mask);
act.sa_flags = SA_SIGINFO;
// printf ("Old divByZero exception: 0x%08X\n", feenableexcept (FE_DIVBYZERO));
printf ("Old invalid exception: 0x%08X\n", feenableexcept (FE_INVALID));
printf ("New fp exception: 0x%08X\n", fegetexcept ());
// set handler
if (sigaction(SIGFPE, &act, (struct sigaction *)0) != 0)
{
perror("Yikes");
exit(-1);
}
// s = 1.0 / 0.0; // FE_DIVBYZERO
s = 0.0 / 0.0; // FE_INVALID
return 0;
}
When I run this on an Intel-based Mac, I get;
Old invalid exception: 0x0000003F
New fp exception: 0x0000003E
X87CR: 0x037F
X87SR: 0x0000
MXCSR: 0x00001F80
signal: SIGFPE with code FPE_FLTINV
invalid flag: 0x0000
divByZero flag: 0x0000
Abort trap: 6
My questions are:
Why is the assembly code and a call to
fetestexcept
both included in the handler? Are both necessary to report on the type of exception that was trapped?An
FE_INVALID
exception was trapped by the handler. Why, then isexcepts & FE_INVALID
zero?The
sigaction
handler is completely ignored on Apple silicon. Should it work? Or am I not understanding something more fundamental about the signal handing works usingsigaction
, vs. what happens when a FP exception is raised?
I am compiling with gcc and clang.
DETAILS : Here is a minimal example extracted from the original code that distills my questions above. In this example, I provide the missing feeableexcept
functionality for MacOS on Intel or Apple silicon. Then I test with and without sigaction
.
#include <fenv.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#if defined(__APPLE__)
#if defined(__arm) || defined(__arm64) || defined(__aarch64__)
#define DEFINED_ARM 1
#define FE_EXCEPT_SHIFT 8
#endif
void feenableexcept(unsigned int excepts)
{
fenv_t env;
fegetenv(&env);
#if (DEFINED_ARM==1)
env.__fpcr = env.__fpcr | (excepts << FE_EXCEPT_SHIFT);
#else
/* assume Intel */
env.__control = env.__control & ~excepts;
env.__mxcsr = env.__mxcsr & ~(excepts << 7);
#endif
fesetenv(&env);
}
#else
/* Linux may or may not have feenableexcept. */
#endif
static void
fhdl ( int sig, siginfo_t *sip, ucontext_t *scp )
{
int fe_code = sip->si_code;
unsigned int excepts = fetestexcept (FE_ALL_EXCEPT);
if (fe_code == FPE_FLTDIV)
printf("In signal handler : Division by zero. Flag is : 0x%04X\n", excepts & FE_DIVBYZERO);
abort();
}
void main()
{
#ifdef HANDLE_SIGNAL
struct sigaction act;
act.sa_sigaction = (void(*))fhdl;
sigemptyset (&act.sa_mask);
act.sa_flags = SA_SIGINFO;
sigaction(SIGFPE, &act, NULL);
#endif
feenableexcept(FE_DIVBYZERO);
double x = 0;
double y = 1/x;
}
Results without sigaction
On Intel:
% gcc -o stack_except stack_except.c
% stack_except
Floating point exception: 8
And on Apple silicon :
% gcc -o stack_except stack_except.c
% stack_except
Illegal instruction: 4
The above works as expected and code terminates when the division by zero is encountered.
Results with sigaction
Results on Intel:
% gcc -o stack_signal stack_signal.c -DHANDLE_SIGNAL
% stack_signal
In signal handler : Division by zero. Flag is : 0x0000
Abort trap: 6
The code works as expected on Intel. However,
- The return from
fetestexcept
(called from the signal handler) is zero. Why is this? Was the exception cleared before being processed by the handler?
Results on Apple silicon :
% gcc -o stack_signal stack_signal.c -DHANDLE_SIGNAL
% stack_signal
Illegal instruction: 4
The signal handler is ignored completely. Why is this? Am I missing something fundamental about how signals are processed?
Use of assembly in original code (see link at top of post)
My final question was concerning the use of assembly in the original example posted at the top of the post. Why was assembly used to query the flags in the signal handler? Is it not enough to use fetestexcept
? Or to check siginfo.si_code
? Possible answer: fetestexcept
, when used inside the handler doesn't detect the exception (?). (Is this why only 0x0000
is printed from inside the handler?.)
Here is related post with a similar questions. How to trap floating-point exceptions on M1 Macs?