Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c++

x86

gcc

djgpp

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.

like image 327
user5434231 Avatar asked Mar 22 '16 12:03

user5434231


People also ask

What happens if an exception is raised in the program and that exception is not handled by an exception section in either the current or enclosing PL SQL blocks?

If an exception is raised in your program and that exception is not handled by an exception section in either the current or enclosing PL/SQL blocks, that exception is "unhandled." PL/SQL returns the error which raised an unhandled exception all the way back to the application environment from which PL/SQL was run.

What happens if exceptions are not properly handled?

When an exception occurred, if you don't handle it, the program terminates abruptly and the code past the line that caused the exception will not get executed.

Can you throw exceptions in C?

C doesn't support exceptions. You can try compiling your C code as C++ with Visual Studio or G++ and see if it'll compile as-is. Most C applications will compile as C++ without major changes, and you can then use the try... catch syntax.

What is the reason that some languages do not include exception handling?

The sort answer to your question is that you need language support for exceptions in order to write them. Language support generally includes memory management; since an exception can be thrown anywhere and caught anywhere, there needs to be a way to dispose of objects that doesn't rely on the control flow.


1 Answers

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!");
}
like image 103
user5434231 Avatar answered Oct 13 '22 15:10

user5434231