抛出StackOverflowException时,.NET为什么表现如此差?

我知道,.NET中的StackOverflowExceptions无法捕获,删除其进程并且没有堆栈跟踪。 这已在MSDN上正式记录。 但是,我想知道行为背后的技术(或其他)原因是什么。 所有MSDN都说:

在.NET Framework的早期版本中,您的应用程序可能会捕获   一个StackOverflowException对象(例如,从   无限递归)。 但是,目前不鼓励这种做法   因为需要大量额外的代码才能可靠地捕获   堆栈溢出异常并继续执行程序。

这是什么“重要的附加代码”? 是否有其他记录在案的行为原因? 即使我们无法捕获SOE,为什么我们至少也不能获得堆栈跟踪? 我和几个同事只是花了几个小时来调试生产StackOverflowException,而使用堆栈跟踪可能要花几分钟的时间,所以我想知道是否有充分的理由让我受苦。

ChaseMedallion asked 2019-10-09T16:59:09Z
3个解决方案
85 votes

线程的堆栈由Windows创建。 它使用所谓的保护页来检测堆栈溢出。 MSDN Library文章中介绍的用户模式代码通常可以使用的功能。 基本思想是保留堆栈的最后两页(2 x 4096 = 8192字节),任何处理器对它们的访问都会触发页面错误,该页面错误已变成SEH异常STATUS_GUARD_PAGE_VIOLATION。

对于那些属于线程堆栈的页面,内核会拦截它。 它更改了这两个页面中第一个页面的保护属性,从而为线程提供了一些紧急堆栈空间来处理故障,然后重新引发STATUS_STACK_OVERFLOW异常。

反过来,CLR会拦截此异常。 到那时,大约还有3 KB的堆栈空间。 一方面,这不足以运行即时编译器(JITter)来编译可以处理程序中异常的代码,JITter需要的空间要大得多。 因此,CLR只能无礼地中止线程,而不能做其他任何事情。 并通过.NET 2.0策略终止该过程。

请注意,这在Java中问题不大,它具有字节码解释器,因此可以保证可执行的用户代码可以运行。 或在以C,C ++或Delphi等语言编写的非托管程序中,代码是在构建时生成的。 但是,这仍然是一个非常困难的事故,堆栈中的紧急空间已被烧断,因此没有可以安全地继续在线程上运行代码的情况。 程序在完全随机的位置中止并处于损坏状态的线程继续运行的可能性很小。

如果在考虑在另一个线程上引发事件或消除winapi中的限制(保护页的数量不可配置)方面花了所有力气,那么这要么是一个保存良好的秘密,要么就是被认为没有用。 我怀疑是后者,事实并非如此。

Hans Passant answered 2019-10-09T16:59:52Z
16 votes

堆栈实际上是存储有关程序状态的所有内容的地方。 调用方法时每个返回站点的地址,局部变量,方法参数等。如果方法溢出了堆栈,则必须立即停止执行(因为没有更多的堆栈空间可以继续运行) 。 然后,为了正常恢复,有人需要清理该方法在堆栈消失之前所做的所有操作。 这意味着在调用该方法之前了解堆栈是什么样的。 这会产生一些开销。

而且,如果您无法清理堆栈,那么您也将无法获得堆栈跟踪,因为生成跟踪所需的信息来自“展开”堆栈以发现调用了哪些方法。

TypeIA answered 2019-10-09T17:00:25Z
7 votes

为了优雅地处理堆栈溢出或内存不足的情况,有必要在堆栈实际溢出或堆内存完全用尽之前触发一些异常,而此时可用的堆栈和堆资源将足以执行任何操作。捕获异常之前需要运行的清除代码。在堆栈溢出异常的情况下,干净地处理它们基本上需要检查每个方法的入口处的堆栈指针(实际上并不需要那么昂贵)。通常,通过在堆栈末尾设置访问冲突陷阱来处理它们,但是这样做的问题是,直到为时已晚,无法干净地处理陷阱,陷阱才会触发。可以将陷阱设置为在堆栈的最后一个内存块上触发,而不是过去,然后让系统将陷阱更改为在堆栈上的块触发后触发StackOverflowException,但是问题是一旦堆栈松开那么远,就没有很好的方法来确保重新启用“几乎用完”陷阱。

话虽这么说,另一种方法是允许线程为线程炸开其堆栈而应该设置的委托,然后说在StackOverflowException的情况下,将清除线程的堆栈并运行提供的委托。 陷阱可以在运行委托之前重新设置(此时堆栈将为空),并且代码可以维护线程状态对象,委托可以使用该线程状态对象来了解是否跳过了任何重要的finally块。

supercat answered 2019-10-09T17:01:00Z
translate from https://stackoverflow.com:/questions/22465541/why-does-net-behave-so-poorly-when-stackoverflowexception-is-thrown