我的应用程序在空流上的 fprintf 上发布时崩溃了,即使我添加了捕手:
#include <windows.h>
#include <iostream>
#include <errhandlingapi.h>
void errorCatcher()
{
std::cout << "Caught!\n";
}
LONG windowsErrorCatcher(_EXCEPTION_POINTERS*)
{
std::cout << "Caught 2!\n";
return EXCEPTION_CONTINUE_SEARCH;
}
int main()
{
SetUnhandledExceptionFilter(windowsErrorCatcher);
set_unexpected(errorCatcher);
set_terminate(errorCatcher);
fprintf(nullptr, "hello"); //<<??
std::cout << "Hello World!\n";
}
也许还有另一种方法可以捕获它?
不清楚您在代码中观察到什么行为 问题。 当我运行该程序时(修复语法错误后
set_unexpected
在std::
),我看到它打印:
Caught 2!
在弹出“停止工作”对话框之前:
该消息表明正在捕获异常。 也许你做到了 没有注意到该消息,因为对话框覆盖了它,或者您可能 程序在终止之前没有刷新其标准输出流 (该行为取决于实现)。 在后一种情况下你 可以通过显式刷新输出来修复它,例如
std::endl
。
出现该对话框是因为显然返回
EXCEPTION_CONTINUE_SEARCH
导致该行为。 的文档
SetUnhandledExceptionFilter
(SUEF)对该返回值的描述如下:
继续正常执行UnhandledExceptionFilter。这意味着 遵守 SetErrorMode 标志,或调用应用程序错误 弹出消息框。
稍微推断一下,您可能希望做的是从 空指针取消引用而不是终止(有或没有 对话)。 为此:
注意:SUEF 的文档没有明确说明调用 来自处理程序的
longjmp
是受支持的行为,但它似乎有效
美好的。 另一种方法是手动复制系统特定的寄存器
值从 jmp_buf
进入异常上下文,然后返回
EXCEPTION_CONTINUE_EXECUTION
。
与捕获 C++ 语言异常相比,必须非常 小心从低级异常(例如访问)中恢复 违规/段错误。 可能存在损坏的数据结构不变量 C++ 库或其他地方。 我的建议是恢复 代码执行将调试信息保存到磁盘所需的最少操作,并且 然后在适当的情况下重新启动程序,而不是尝试继续 在原始过程中进行有用的计算。
这是一个完整的程序,演示恢复(无需任何重启 逻辑):
// catch-segfault.cc
// Demonstrate catching and recovering from a segfault under Windows.
#include <csetjmp> // setjmp, std::{longjmp, jmp_buf}
#include <iomanip> // std::{dec, hex}
#include <iostream> // std::{cout, endl}
#include <stdio.h> // ::fprintf
// Although the documentation explains that SEUF is declared in
// `errhandlingapi.h`, it also says to include windows.h to get it.
#include <windows.h> // SetUnhandledExceptionFilter
// Saved processor state we can return to.
std::jmp_buf g_savedContext{};
bool g_savedContextIsValid = false;
// Exception handler/filter that was active before ours.
LPTOP_LEVEL_EXCEPTION_FILTER g_prevFilter = nullptr;
// Called when a Windows exception occurs.
LONG myUEH(_EXCEPTION_POINTERS *eInfo)
{
// NOTE: It is not safe to call into the C++ library from an exception
// handler! This is here just for ease of demonstration, and should
// be removed before production usage.
std::cout << "in myUEH, code: 0x" << std::hex
<< eInfo->ExceptionRecord->ExceptionCode << std::dec
<< std::endl;
if (g_savedContextIsValid) {
g_savedContextIsValid = false;
// Continue execution after where `setjmp` was called.
//
// It is not clear whether this is truly safe. The SUEH docs do
// not mention this as a valid possibility, but it appears to work.
//
// An alternative would be to write system-specific code to copy the
// register values from `g_savedContext` into `eInfo->ContextRecord`
// and then return `EXCEPTION_CONTINUE_EXECUTION`.
//
std::longjmp(g_savedContext, 1);
}
if (g_prevFilter) {
return g_prevFilter(eInfo);
}
// If we return `EXCEPTION_CONTINUE_SEARCH`, then the dialog box
// that says "<program> has stopped working" pops up.
//return EXCEPTION_CONTINUE_SEARCH;
// If we return `EXCEPTION_EXECUTE_HANDLER`, then the program
// terminates with exit code 0xC0000005. The Cygwin shell interprets
// that as a "Segmentation fault", while cmd.exe ignores it.
return EXCEPTION_EXECUTE_HANDLER;
}
int main(int argc, char **argv)
{
std::cout << "in main\n";
if (setjmp(g_savedContext) == 0) {
// This branch is executed first.
g_savedContextIsValid = true;
g_prevFilter = SetUnhandledExceptionFilter(myUEH);
std::cout << "g_prevFilter: " << (void*)g_prevFilter << "\n";
// Use the number of command line arguments to decide what bad thing
// to do.
if (argc == 1) {
std::cout << "about to deref null" << std::endl;
*(((volatile int*)nullptr)) = 0;
}
else if (argc == 2) {
std::cout << "about to fprintf null" << std::endl;
fprintf(nullptr, "hello");
}
else if (argc == 3) {
std::cout << "about to div by zero" << std::endl;
volatile int z = 0;
int q = 2 / z;
std::cout << "q: " << q << "\n";
}
std::cout << "after potential badness\n";
}
else {
// This branch is executed after the call to `longjmp`.
std::cout << "in recovery code\n";
}
// We are past the point where it would be safe to `longjmp` to
// `g_savedContext`.
g_savedContextIsValid = false;
// Restore the old handler (this is not necessary if we are just going
// to terminate immediately).
SetUnhandledExceptionFilter(g_prevFilter);
std::cout << "end of main\n";
return 0;
}
// EOF
输出:
$ ./catch-segfault.exe
in main
g_prevFilter: 0x7ff70f39c200
about to deref null
in myUEH, code: 0xc0000005
in recovery code
end of main
$ ./catch-segfault.exe 2
in main
g_prevFilter: 0x7ff70f39c200
about to fprintf null
in myUEH, code: 0xc0000005
in recovery code
end of main
$ ./catch-segfault.exe 2 3
in main
g_prevFilter: 0x7ff70f39c200
about to div by zero
in myUEH, code: 0xc0000094
in recovery code
end of main