从硬件异常处理程序中抛出 C++ 异常。为什么 -fnon-call-exceptions 的行为不如预期?
Throwing C++ exceptions from a hardware exception handler. Why does -fnon-call-exceptions not behave as expected?
我昨晚有一个有趣的想法,捕获硬件异常并抛出 C++ exception
。认为这可能对 FPU exceptions
这样的事情有用,这些事情通常要么崩溃,要么默默地 return NaN
然后导致意外行为。 C++ exception
在这里更可取。
所以我整个上午都在黑客攻击,终于让它工作了。嗯,差不多。编译器仍然没有意识到算术运算现在可以抛出 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;
}
}
我知道这是一个不寻常的问题...但是有什么方法可以解决这个问题吗?告诉 gcc
任何地方都可能发生异常的方法?
我正在使用 djgpp
进行编译,(我相信)它使用 DWARF
异常处理。
编辑: 我刚找到 gcc
标志 -fnon-call-exceptions
和 -fasynchronous-unwind-tables
,这似乎是我要找的东西。但是还是不行...
edit: 现在使用前面提到的 gcc
标志,它 确实 在两个函数之间发生异常时捕获调用:
inline void nop() { asm(""); }
// or { cout << flush; } or something. empty function does not work.
int main()
{
/* ... */
try
{
nop();
cout << 1 / 0 << endl;
nop();
}
/* ... */
}
编辑: 嵌套的 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;
}
}
编辑: 我已将此作为可能提交 bug in gcc, and reduced my code to a simple test case。
好久好久终于搞明白了... throwing函数需要标记为有信号帧
[[gnu::no_caller_saved_registers]]
void throw_exception()
{
asm(".cfi_signal_frame");
throw std::runtime_error("Division by zero!");
}
我昨晚有一个有趣的想法,捕获硬件异常并抛出 C++ exception
。认为这可能对 FPU exceptions
这样的事情有用,这些事情通常要么崩溃,要么默默地 return NaN
然后导致意外行为。 C++ exception
在这里更可取。
所以我整个上午都在黑客攻击,终于让它工作了。嗯,差不多。编译器仍然没有意识到算术运算现在可以抛出 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;
}
}
我知道这是一个不寻常的问题...但是有什么方法可以解决这个问题吗?告诉 gcc
任何地方都可能发生异常的方法?
我正在使用 djgpp
进行编译,(我相信)它使用 DWARF
异常处理。
编辑: 我刚找到 gcc
标志 -fnon-call-exceptions
和 -fasynchronous-unwind-tables
,这似乎是我要找的东西。但是还是不行...
edit: 现在使用前面提到的 gcc
标志,它 确实 在两个函数之间发生异常时捕获调用:
inline void nop() { asm(""); }
// or { cout << flush; } or something. empty function does not work.
int main()
{
/* ... */
try
{
nop();
cout << 1 / 0 << endl;
nop();
}
/* ... */
}
编辑: 嵌套的 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;
}
}
编辑: 我已将此作为可能提交 bug in gcc, and reduced my code to a simple test case。
好久好久终于搞明白了... throwing函数需要标记为有信号帧
[[gnu::no_caller_saved_registers]]
void throw_exception()
{
asm(".cfi_signal_frame");
throw std::runtime_error("Division by zero!");
}