从硬件异常处理程序抛出 C++ 异常。为什么 -fnon-call-exceptions 的行为不符合预期?

use*_*231 7 c++ x86 gcc djgpp

昨晚我有一个有趣的想法,捕获硬件异常并抛出一个C++ exception。认为这可能对诸如 之类的事情有用FPU exceptions,这些事情通常要么崩溃,要么默默返回NaN,然后导致意外行为。AC++ exception在这里更可取。

所以我整个上午都在进行黑客攻击,终于让它工作了。嗯,差不多。编译器仍然没有意识到算术运算现在可以 throw C++ exceptions,并且会默默地丢弃try/catch它周围的块。当异常发生在函数中时它确实起作用。

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

我意识到这是一个不寻常的问题……但是有什么办法可以使这项工作发挥作用吗?某种方式来告诉gcc异常可能发生在任何地方?

我正在编译djgpp它(我相信)使用DWARF异常处理。


编辑:我刚刚找到gcc标志 -fnon-call-exceptions-fasynchronous-unwind-tables,这似乎是我正在寻找的。但它仍然不起作用......


编辑:现在,使用前面提到的gcc标志,它不会在异常的两个函数调用之间发生陷阱:

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

int main()
{
    /* ... */
    try
    {
        nop();
        cout << 1 / 0 << endl;
        nop();
    }
    /* ... */
}
Run Code Online (Sandbox Code Playgroud)

编辑:嵌套try/catch块具有相同的效果,除非被捕获的指令前面有函数调用,否则不会捕获异常。

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑:我已将此作为gcc 中的可能错误提交,并将我的代码简化为一个简单的测试用例

use*_*231 3

已经有一段时间了,但我终于弄清楚了......投掷函数需要被标记为具有信号帧。

[[gnu::no_caller_saved_registers]]
void throw_exception()
{
    asm(".cfi_signal_frame"); 
    throw std::runtime_error("Division by zero!");
}
Run Code Online (Sandbox Code Playgroud)