异常堆栈跟踪不显示抛出异常的位置

时间:2011-11-01 18:31:20

标签: c# exception

通常当我抛出一个异常,抓住它并打印出堆栈跟踪时,我会看到抛出异常的调用,导致该调用的调用,导致的调用,等等,回到整个程序的根源。

现在它只显示异常被捕获的调用,而不是显示抛出的位置。我无法弄清楚改变了什么导致了这一点。这是我的计划:

using System;

class foo {
    static void Main(string[] args) {
        try { f(); }
        catch (Exception e) { Console.WriteLine(e.StackTrace); }
    }
    static void f() { g(); }
    static void g() { throw new Exception(); }
}

以下是打印出来的内容:

at foo.Main(String[] args) in C:\Projects\test\root.cs:line 5

我的期望是这样的:

at foo.g...
at foo.f...
at foo.Main...

有什么想法吗?

4 个答案:

答案 0 :(得分:20)

我将借此机会再次声明 - 正如我经常做的那样 - 堆栈跟踪告诉你调用了什么方法来到达异常点被抛出。类似地,调试器中的堆栈窗口不会告诉您哪种方法称为当前方法

相反,在这两种情况下,堆栈跟踪都会告诉您控件将在何处进行,或者,在异常的情况下,显然控制消失接下来没有例外。堆栈跟踪是延续;它是一个描述程序的 future 执行的数据结构。它不一定描述过去。

“你将要去哪里”的事实几乎总是与“我来自哪里”的事实相同,是什么使得堆栈跟踪成为一种有用的调试工具。但是如果运行时可以安全地消除该信息而不会弄乱“我需要去哪里”的信息,那么堆栈跟踪不需要包含任何关于“我来自哪里”的信息。在某些情况下,运行时可以并确实消除了该信息。 你不能依靠堆栈跟踪告诉你他们来自哪里,只有你下一步的地方。

在下一版本的C#和VB中,“async / await”只会加剧这种情况;在通过连续传递样式实现异步方法的世界中,不需要将堆栈作为延续,因为continuation以委托的形式存储在堆上。在这个世界上,堆栈跟踪几乎不会告诉你“我从哪里来的?”

答案 1 :(得分:10)

它仍然显示抛出异常的位置,而不是它被捕获的位置。但是由于优化可能不是你期望它被抛出的地方。

最有可能抛出异常的函数被内联到调用函数中。

在这种情况下,我希望JIT优化器负责。默认情况下,它不会在调试器中运行时进行优化,但会在未在调试器中运行时优化并因此内联方法。我不确定Debug构建是否会对内联产生影响。

如果将[MethodImpl(MethodImplOptions.NoInlining)]添加到函数中,它将不会内联,并且堆栈跟踪应该符合预期。

答案 2 :(得分:3)

如果这是一个未附加调试器的发布版本,则JIT可能正在通过inlining方法进行优化;所以fg甚至不存在。

您可以通过将[MethodImpl(MethodImplOptions.NoInlining)]属性应用于方法gf来确认这是发生了什么,但这仅用于教育目的。您不应该停止JIT内联生产代码。

此外,x64 JIT may optimize将其作为尾调用,因此MethodImplAttribute可能对x64版本构建没有影响。

答案 3 :(得分:2)

来自MSDN的文档:

“由于优化期间发生的代码转换(例如内联),StackTrace属性可能无法报告尽可能多的方法调用。”