SIGFPE not raised when dividing by zero

I have an app whose logic is in C++ and rest of the parts (UI) are in Swift and SwiftUI.

When an exception is raised by some C++ code, I'm using the Linux signal handler mechanism to trap it. From my previous post, I understand that fatal exceptions like SIGSEGV, SIGBUS, SIGFPE etc., there's nothing much that can be done by the process. My only intent for using a signal handler is to log something, so that it becomes easy to fix during development. Ofc, even that logging can fail, based on the severity of the exception, but that's okay... make an attempt to log - if it works, great, else the process can terminate.

I'm registering for SIGSEGV and SIGFPE with the following code

// ExceptionHandlingCpp.hpp file
struct tSignals {

    SignalHandlerFunc signalHandlerFunc;
    uint32_t          signal;
    [[maybe_unused]]
    uint8_t           reserved[4];
};

// ExceptionHandlingCpp.cpp file
tSignals ExceptionHandlingCpp::unixSignals[] = {

        {HandleSignals, SIGFPE, {0}},
        {HandleSignals, SIGSEGV, {0}},
        {HandleSignals, SIGKILL, {0}},
};

std::string ExceptionHandlingCpp::signalToString(int signal) {
    
    switch(signal) {
        case SIGFPE:
            return "SIGFPE";
        case SIGSEGV:
            return "SIGSEGV";
        case SIGKILL:
            return "SIGKILL";
        default:
            return "Unknown signal";
    }
}

void ExceptionHandlingCpp::RegisterSignals() {
    LOG("ExceptionHandlingCpp::RegisterSignals()");
    
    struct sigaction sa;

    sa.sa_flags = SA_SIGINFO;

    for(int i = 0; i < sizeof(unixSignals)/sizeof(tSignals); ++i) {

        sa.sa_sigaction = unixSignals[i].signalHandlerFunc;
        if(sigaction(unixSignals[i].signal, &sa, nullptr) == 1) {
            LOG("Failed to set " + signalToString(unixSignals[i].signal) + "'s signal handler!");
        } else {
            LOG(signalToString(unixSignals[i].signal) + "'s signal handler set sucessfully!");
        }
    }
}

In my signal handler (HandleSignals method), immediately after trapping a signal, I log something and set the default handler... This breaks out of the loop that occurs when returning from the signal handler.

// ExceptionHandlingCpp.cpp
void ExceptionHandlingCpp::HandleSignals(int pSignal, siginfo_t *pInfo, void *pContext) {
    LOG("ExceptionHandlingCpp::HandleSignals(int, signinfo_t*, void*)");

    LOG("signal = " + signalToString(pSignal));

     UnregisterSignals(pSignal);
 
    LOG("Returning from exception handler...");
}

void ExceptionHandlingCpp::UnregisterSignals(int pSignal) {

    LOG("UnregisterSignals(int)");

    struct sigaction defaultAction {};
    defaultAction.sa_handler = SIG_DFL;

    if(sigaction(pSignal, &defaultAction, nullptr) == -1) {
        LOG("Error in resetting action for " + signalToString(pSignal));
    } else {
        LOG("Successfully reset " + signalToString(pSignal) + "'s action to default!");
    }
}

When I test this code by raising SIGSEGV (as shown below),

void ExceptionHandlingCpp::DereferenceNullPtr ()
{
    LOG("DereferenceNullPtr()");

    int* ptr = nullptr;

    LOG("Raising exception...");
    int value = *ptr;
}

everything works as expected. Signal handler is invoked, default handler is set and the process immediately quits. But when I try to raise a SIGFPE,

void* ExceptionHandlingCpp::DivisionByZero ([[maybe_unused]] void* pParms)
{
    LOG("DivisionByZero()");

    int num1;
    int num2;
    int result;

    num1 = 5;
    num2 = 0;

    LOG("Raising exception...");

    result = num1 / num2;

    LOG("Returning from DivisionByZero() method");

    return nullptr;
}

my signal handler is not invoked (as shown in the logs below). The process doesn't terminate either. It seems that the flow simply 'walks over' this division by zero instruction as if nothing happened and returns from that method, which shouldn't have happened, as the process should've terminated after reaching my signal handler.

RegisterSignals()
SIGFPE's signal handler set sucessfully!
SIGSEGV's signal handler set sucessfully!
SIGKILL's signal handler set sucessfully!
....
DivisionByZero()
Raising exception...
Returning from DivisionByZero() method
....
AppDelegate.applicationWillBecomeActive(_:)
AppDelegate.applicationDidBecomeActive(_:)
...
// UI is displayed

Why is SIGFPE not raised? What am I missing here?

Answered by DTS Engineer in 815396022
Does it optimise-away the 5/0 ?

Almost certainly.

To generate a division by zero, do this:

static void test(void) {
    int n = getpid();
    int d1 = getpid();
    int d2 = getpid();
    int result = n / (d1 - d2);
    fprintf(stderr, "%d", result);
}

This works because:

  • You know that getpid always returns the same value, but the compiler can’t know that.

  • The print means that the compiler has to generate the result. Without it, the compiler can see that result is unused and not run the calculation.

Running through the assembly code in the debugger reveals that it does generate and run an sdiv instruction with a denominator of 0. However, that doesn’t generate SIGFPE because to get that you have to enable such trapping on the CPU. Instead it generates a result of 0.

This is, of course, allowed by the C standard because integer division by zero is undefined behaviour. Technically the above code is allowed to make demons fly out of your nose (-:

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

It’s not a “Linux signal handler”. “POSIX” or “Unix” might be a better description, though they are also defined in the C spec.

The first thing I’d do is look at the generated assembler. Does it optimise-away the 5/0 ?

Does it optimise-away the 5/0 ?

Almost certainly.

To generate a division by zero, do this:

static void test(void) {
    int n = getpid();
    int d1 = getpid();
    int d2 = getpid();
    int result = n / (d1 - d2);
    fprintf(stderr, "%d", result);
}

This works because:

  • You know that getpid always returns the same value, but the compiler can’t know that.

  • The print means that the compiler has to generate the result. Without it, the compiler can see that result is unused and not run the calculation.

Running through the assembly code in the debugger reveals that it does generate and run an sdiv instruction with a denominator of 0. However, that doesn’t generate SIGFPE because to get that you have to enable such trapping on the CPU. Instead it generates a result of 0.

This is, of course, allowed by the C standard because integer division by zero is undefined behaviour. Technically the above code is allowed to make demons fly out of your nose (-:

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

As you said, even after arriving at zero at runtime, the result is still 0 and the flow 'walks over' that instruction.

However, that doesn’t generate SIGFPE because to get that you have to enable such trapping on the CPU. Instead it generates a result of 0.

Is this something I'm supposed to be doing? If not, then, how to catch division by zero? What is the expectation?

SIGFPE not raised when dividing by zero
 
 
Q