C++:无法捕获 fprintf 异常

问题描述 投票:0回答:1

我的应用程序在空流上的 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";
    }

也许还有另一种方法可以捕获它?

c++ windows exception
1个回答
0
投票

不清楚您在代码中观察到什么行为 问题。 当我运行该程序时(修复语法错误后

set_unexpected
std::
),我看到它打印:

Caught 2!

在弹出“停止工作”对话框之前:

App stopped working dialog

该消息表明正在捕获异常。 也许你做到了 没有注意到该消息,因为对话框覆盖了它,或者您可能 程序在终止之前没有刷新其标准输出流 (该行为取决于实现)。 在后一种情况下你 可以通过显式刷新输出来修复它,例如

std::endl

出现该对话框是因为显然返回

EXCEPTION_CONTINUE_SEARCH
导致该行为。 的文档
SetUnhandledExceptionFilter
(SUEF)对该返回值的描述如下:

继续正常执行UnhandledExceptionFilter。这意味着 遵守 SetErrorMode 标志,或调用应用程序错误 弹出消息框。

从错误中恢复

稍微推断一下,您可能希望做的是从 空指针取消引用而不是终止(有或没有 对话)。 为此:

  • 在程序开始附近的某个地方,调用

    setjmp
    将 CPU(线程)状态保存到可以返回的状态。

  • 在异常处理程序中,调用

    longjmp
    返回到那个状态。

  • 在调用站点

    setjmp
    将恢复代码放入适当的位置 调用它的
    if
    语句的分支。

注意: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
© www.soinside.com 2019 - 2024. All rights reserved.