【问题标题】:SEH StackOverflow exception - is it real not possible to catch?SEH StackOverflow 异常 - 真的无法捕捉吗?
【发布时间】:2014-02-11 13:03:55
【问题描述】:

我在 StackOverflow 和 CodeProject.net 上阅读了很多关于 SEH exceptions 的文章。

在我的 C++ 程序中实现SEH exceptions 处理后,我受到了堆栈溢出异常的影响,该异常没有被我的软件捕获。

经过下一部分研究,我了解到,无法以编程方式检测此类异常,因为我们没有可用的堆栈地址空间可供使用,因此程序内存已损坏。

想请教一下您在处理堆栈溢出异常方面的经验。这看起来像是一个挑战,如果在非托管代码编程语言中不可能实现,我真的很感兴趣?

下面我展示了我的示例程序(C++)的一部分,它复制了stack overflow exception。它适用于任何SEH exception,但不是堆栈溢出:

LONG WINAPI SehHandler(PEXCEPTION_POINTERS pExceptionPtrs)
{ 
    cerr << "Handled SEH exception!\n";
    cerr << "ContextRecord: " << pExceptionPtrs->ContextRecord << endl;
    cerr << "ExceptionRecord: " << pExceptionPtrs->ExceptionRecord << endl;

    // Write minidump file
    CreateMiniDump(pExceptionPtrs);

    // Terminate process
    TerminateProcess(GetCurrentProcess(), 1); 

    return EXCEPTION_EXECUTE_HANDLER;
}

int fib(unsigned int n) {
    if(n == 0) return 0;
    if(n == 1) return 1;
    return fib(n-1)+fib(n-2);
}

int main(){
    SetUnhandledExceptionFilter(SehHandler); 
    cout << fib(1000000);
    return 0;
}

【问题讨论】:

标签: c++ exception stack-overflow seh


【解决方案1】:

是的,您可以从 SO 崩溃中获得一个小型转储,但绝不是您现在这样做的方式。您的 SehHandler() 函数在触发异常的线程上运行。而且它处于危险状态,您还有大约 7080 字节的紧急堆栈空间来做您需要做的事情。如果您使用它,那么程序将失败并出现无法捕获的访问冲突异常。

你不能调用 MiniDumpWriteDump() 并希望在它中存活下来,该函数需要比你可用的更多的堆栈。所以这是一个没有 minidump 的硬 kaboom。

您需要 另一个 线程来进行该调用。例如,这可能是您在初始化时创建并使用 WaitForMultipleObjects() 调用阻塞的线程。你的 SehHandler() 可以调用 SetEvent() 来唤醒它。将 PEXCEPTION_POINTERS 值写入全局变量后。并无限期阻塞以允许线程创建 minidump 并中止进程。

Fwiw,到目前为止,该线程的最佳位置是在另一个进程中。这也允许您处理完全破坏进程状态的真正令人讨厌的问题。您在初始化时开始的“守卫”过程。用一个命名事件来发出信号,比如说,一个内存映射文件来传递 PEXCEPTION_POINTERS。不要在SehHandler()中启动它,进程堆不再可靠所以CreateProcess()不能再工作了,你必须早点做。

【讨论】:

  • 您能否澄清一下“剩余约 7080 字节的紧急堆栈空间”?它在哪里以及如何使用它?
  • 汉斯,我明白你为什么没有回应 n0p,因为很明显紧急空间在哪里(在堆栈上)以及如何使用它(异常过滤器中的自动变量和它调用的函数)。但是好奇的人想知道那个 7080 字节的数字是从哪里来的......
【解决方案2】:

Hans Passant 的回答表明有 7080 字节的紧急堆栈。我不知道该信息来自哪里,他也没有在上面回答@nop,我的发现表明该信息不正确。但是由于某种原因,这个网站不允许我在上面发表评论,所以我就把它留在这里......

有一个函数可用于查询和设置堆栈处理程序剩余多少紧急堆栈:SetThreadStackGuarantee()。请注意,从 Windows 10 开始(但我认为也是从 Windows 7 开始),在大多数情况下,这个值将是 0。因此,默认情况下,无法在处理程序中执行任何复杂的操作。正如 Hans 所建议的,您可能能够向另一个线程或外部进程发出信号,但仅此而已。

但是,如果您不想实现如此复杂的解决方案,并且可以在堆栈上腾出一些松弛空间,最简单的方法是使用SetThreadStackGuarantee()将其设置为足够高的值,以便您可以继续处理堆栈溢出异常,就像处理任何其他异常。请注意,您需要在需要此功能的每个线程上调用此函数,并在堆栈溢出发生之前调用它,因此最好在线程初始化时调用它。

【讨论】:

  • 公平地说,n0p 并没有问 Hans 信息是从哪里来的。
  • 他确实要求澄清,但他没有得到任何澄清。而且信息似乎不正确。我想在那里发表评论并询问它,因为我想知道汉斯从哪里得到它。然而,这个网站似乎对有趣的点和奖牌比实际的正确性和信息来源更感兴趣。所以我不能在那里发表评论,现在我可以给汉斯贴标签或指出他接受的答案需要澄清、更正和引用。
  • n0p 询问您如何使用堆栈空间。好吧,他问你如何使用“紧急堆栈空间”,好像它需要特殊访问一样。由于您像使用任何其他堆栈空间一样使用它,因此这不是一个真正有效的问题。
  • 如果您有任何想留给汉斯的评论,我很乐意这样做。您显然不是无能的 gimme-teh-codes 类型或垃圾邮件发送者,这就是离开 cmets 的最低 50 名声望的原因(在您自己的帖子上放弃 cmets)
  • FWIW,汉斯建议在另一个线程或进程中执行工作的解决方案是正确的。活动线程可能处于糟糕的状态(例如,语言运行时使用的线程本地存储值可能处于瞬态)。您必须记住,异常处理程序可能在并非设计为可重入的库函数的上下文中运行。同样,进程可能有问题(例如,如果全局堆锁被占用),这就是为什么进程外处理程序是最安全的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-05
  • 2012-12-14
  • 2012-01-10
  • 2020-04-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多