由于ntdll而重新抛出异常时的Stackoverflow!RcConsolidateFrame(x64)

时间:2017-05-03 13:45:40

标签: visual-c++ 64-bit try-catch stack-overflow rethrow

我正在努力应对堆栈溢出异常,这会在重新抛出异常时发生。重新抛出的异常用于在递归函数调用自身超过一定次数后拆除调用堆栈。 (防止发生堆栈溢出)

我设法写了一个小程序,它重现了这个问题。只有当我使用x64(Release& Debug)编译程序时才会发生这种情况 我测试了MSVS2012&的片段。 MSVS2013(StackSize = 1MB - 默认值)。 使用G ++,大约5000次调用后会出现同样的问题。

代码:

#include <iostream>
using namespace std;

void recursiveFunction(int childCalls) {
  cout << "Recursive call, left calls: " << childCalls << endl;
  if (childCalls == 0) {
    cout << "Throwing std::exception" << endl;
    throw std::exception("Target depth reached");
  }

  try {
    recursiveFunction(childCalls - 1);
  } catch (std::exception&) {
    cout << "Caught exception at level: " << childCalls << endl;
    throw; //Simply rethrow exception
  }
}

int main() {
  //How many calls cause a stack overflow during unwinding with x64
  const int calls = 120; 

  //How many recursive calls I can make before the call stack overflows due to recursive calls
  //const int calls = 10600; 

  cout << "Initiating " << calls << " recursive calls" << endl;
  recursiveFunction(calls);
}

该程序的输出是:

Initiating 120 recursive calls
Recursive call, left calls: 120
Recursive call, left calls: 119
Recursive call, left calls: 118
...
Recursive call, left calls: 2
Recursive call, left calls: 1
Recursive call, left calls: 0
Throwing std::exception
Caught exception at level: 1
Caught exception at level: 2
Caught exception at level: 3
...
Caught exception at level: 104
Caught exception at level: 105  <-- Stack overflow here!!!

我没想到的是,堆栈溢出发生在callstack大部分被清除时,只剩下15个调用帧来清理。似乎重新抛出异常会分配堆栈空间而不是释放它。

当我使用WinDBG调试程序并在堆栈溢出时打开堆栈帧(knf)时,我得到以下图片:

0:000> knf
 #   Memory  Child-SP          RetAddr           Call Site
00           00000065`d5c37260 00007ffc`5ac10658 MSVCR110D!_chkstk+0x37
01        18 00000065`d5c37278 00007ffc`5ac105bf MSVCR110D!write_nolock+0x18
02         8 00000065`d5c37280 00007ffc`5ab23db1 MSVCR110D!write+0x21f
...
0a        f0 00000065`d5c37710 00007ffc`5ac09150 Crashtest!`recursiveFunction'::`1'::catch$0+0x26
0b        40 00000065`d5c37750 00007ffc`5abf93f2 MSVCR110D!CallSettingFrame+0x20
0c        30 00000065`d5c37780 00007ffc`7dd3a193 MSVCR110D!_CxxCallCatchBlock+0x162
0d        a0 00000065`d5c37820 00007ff7`f14714ca ntdll!RcConsolidateFrames+0x3
0e     f7970 00000065`d5d2f190 00007ff7`f14714ca Crashtest!recursiveFunction+0xba
0f        60 00000065`d5d2f1f0 00007ff7`f14714ca Crashtest!recursiveFunction+0xba
...

注意:框架的大小位于下一行的第二列
ntdll!RcConsolidateFrames的帧大0xf7970(1.014.128)字节,因此占用总可用堆栈大小为1MB的96%。

最让我感到烦恼的是,我可以(如snipped中所述)在调用堆栈用完之前递归调用函数达到近10.600次,而另一次调用导致堆栈溢出。但是如果我在超过120次调用后通过一个excption中止递归,我再次得到一个堆栈溢出,这个异常是为了防止。
因此,使用更大的堆栈编译程序只会将问题转移到略高的常量。 如前所述,此问题仅发生在x64编译中。如果使用Win32编译,一旦抛出std::exception,程序就不会遇到堆栈溢出。

程序有意义,在堆栈展开期间分配更多的堆栈空间,而不是它发布?
我怎么解决这个问题呢? 由于这只是一个非常简化的案例,我不能简单地用原始应用程序中的特殊返回值替换throw

Microsoft connect request (deleted)

编辑:Microsoft只是删除了请求而未提供任何帮助。我收到了以下答案,要求提供更多详细信息,但在我回复后一天,请求被删除了。

  

感谢您报告此问题。虽然大型堆栈的使用并不理想,但它是在Windows非x86平台上实现EH的重要结果。需要注意的一点是,RcConsolidateFrames下的堆栈使用有点误导。该函数的目的是使展开器隐藏一堆中间帧,以便堆栈使用反映出运行的EH机制的105个实例(每次执行重新抛出一个),以及所有递归子调用。

     

请您分享有关此高堆栈使用率阻止的真实场景的更多信息?修复此问题可能并非如此简单,但如果我们可以对重要方案做出一些简化假设,则可能会改进这一点。

     

谢谢,
  Neeraj Singh VC ++编译器后端开发人员

在我希望的意思中,问题在于重新抛出机制,它重新抛出相同的异常对象,它一次又一次地在堆栈上分配。

我认为其中一个
  • 复制例外并抛出异常副本
  • 抛出使用new
  • 创建的异常

可以解决问题,但结果没有差异。

0 个答案:

没有答案