Throwing C++ exceptions from a hardware exception handler. Why does -fnon-call-exceptions not behave as expected?
Asked Answered
H

1

9

I had this funny idea last night, to trap hardware exceptions and throw a C++ exception instead. Thought that might be useful for things like FPU exceptions, which normally either crash, or silently return NaN and then cause unexpected behaviour. A C++ exception would be far more desirable here.

So I've been hacking all morning and finally got it to work. Well, almost. The compiler still doesn't realize that arithmetic operations can now throw C++ exceptions, and will silently discard the try/catch block around it. It does work when the exception occurs in a function.

void throw_exception()
{ 
    throw std::runtime_error("Division by zero!");
}

__attribute__((noinline))
void try_div0()
{
    cout << 1 / 0 << endl;
}

int main()
{
    // this class traps a hardware exception (division by zero, in this case) and calls the supplied lambda function.
    // uh, no, you probably don't want to see the assembly code behind this...
    exception_wrapper div0_exc { 0, [] (exception_frame* frame, bool)
    { 
        if (frame->address.segment != get_cs()) return false;           // only handle exceptions that occured in our own code
        frame->stack.offset -= 4;                                       // sub <fault esp>, 4;
        auto* stack = reinterpret_cast<std::uintptr_t *>(frame->stack.offset); // get <fault esp>
        *stack = frame->address.offset;                                 // mov [<fault esp>], <fault address>;
        frame->address.offset = reinterpret_cast<std::uintptr_t>(throw_exception);  // set return address to throw_exception()
        return true;    // exception handled!
    } };

    try
    {
        // cout << 1 / 0 << endl;   // this throws, as expected, but calls std::terminate().
        try_div0();                 // this exception is caught.
    }
    catch (std::exception& e)
    {
        cout << "oops: " << e.what() << endl;
    }
}

I realize this is an unusual question... but is there any way I could make this work? Some way to tell gcc that exceptions can occur anywhere?

I'm compiling with djgpp which (I believe) uses DWARF exception handling.


edit: I just found gcc flags -fnon-call-exceptions and -fasynchronous-unwind-tables, which appear to be what I'm looking for. But it still doesn't work...


edit: Now using the previously mentioned gcc flags, it does catch when the exception occurs in between two function calls:

inline void nop() { asm(""); } 
    // or { cout << flush; } or something. empty function does not work.

int main()
{
    /* ... */
    try
    {
        nop();
        cout << 1 / 0 << endl;
        nop();
    }
    /* ... */
}

edit: Nested try/catch blocks have the same effect, no exception is caught unless the trapped instruction is preceded by a function call.

inline void nop() { asm(""); }

void try_div(int i)
{
    try
    {
        // this works, catches exception in try_div(0).
        nop();
        cout << 1 / i << endl;
        try_div(i - 1);

        // without the first nop(), calls std::terminate()
        //cout << 1 / i << endl;
        //try_div(i - 1);

        // reverse order, also terminates.
        //if (i != 0) try_div(i - 1);
        //cout << 1 / i << endl;
        //nop();
    }
    catch (std::exception& e)
    {
        cout << "caught in try_div(" << i << "): " << e.what() << endl;
    }
}

int main()
{
    /* ... */

    try
    {
        try_div(4);
    }
    catch (std::exception& e)
    {
        cout << "caught in main(): " << e.what() << endl;
    }
}

edit: I have submitted this as a possible bug in gcc, and reduced my code to a simple test case.

Heffron answered 22/3, 2016 at 12:37 Comment(8)
A Windows structured exception (SEH exception) is already pretty close to a hardware exception, and can represent a hardware exception. As I recall it was modeled directly on the i386 (or i286?) hardware exceptions. And Visual C++ provides translation from SEH exception to C++ exception.Greensward
Standard C++ exception are not asynchronous, so that's one one possible problem.Greensward
@Cheersandhth.-Alf: FPU exceptions aren't technically asynchronous either. With exceptions unmasked, every FPU instruction can raise one (or two, e.g. something about input params and something about the result of a computation. See Intel's x86 docs for an example.) Since all "normal" architectures support precise exception, the possible exceptions from every instruction are well defined (e.g. for x86, Intel's insn ref manual defines every possible exception for each instruction). Sometimes the compiler could prove that certain exceptions can't happen (e.g. using a known-good address).Irrelevance
@Peter the C++ standard does not assume any of this. It is not and should nit be constrained to "normal" architectures, whatever "normal" means this decade.Candace
@n.m.: This is/was an x86 question, and I generalized to all architectures with precise exceptions, but no further. The implementation details are necessarily platform-specific. I was trying to point out that on current platforms, there are limits on where a HW exception can be generated. This would allow the compiler to make better code in blocks that can't generate one of the enabled exceptions. I wasn't trying to say that a standardized fully-portable implementation of this idea could count on precise exceptions.Irrelevance
I would be interested to see the assembly behind this, despite the warning in the comment :)Synonymy
Hi @JosephGarvin - the code is up on Github: github.com/jwt27/libjwdpmi/blob/experimental/src/…Heffron
I really would love to know, how long it will take the LINUX folks to finally implement this capability, given that it exists on Windows since the beginning, doesn't it?Currency
H
3

It's been a while, but I finally figured it out... The throwing function needs to be marked as having a signal frame.

[[gnu::no_caller_saved_registers]]
void throw_exception()
{
    asm(".cfi_signal_frame"); 
    throw std::runtime_error("Division by zero!");
}
Heffron answered 13/8, 2018 at 23:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.