SEH StackOverflow异常 - 是否真的无法捕获?

时间:2014-02-11 13:03:56

标签: c++ exception stack-overflow seh

我在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;
}

2 个答案:

答案 0 :(得分:9)

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

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

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

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

答案 1 :(得分:1)

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

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

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