Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Exception's stacktrace doesn't show where the exception was thrown

Tags:

c#

exception

Typically when I throw an exception, catch it, and print out the stacktrace, I get to see the call where the exception was thrown, the call that led to that, the call that led to that, and so on back to the root of the entire program.

Now it's only showing me the call where the exception is being caught, not where it's being thrown. I can't figure out what's changed to lead to this. Here's my program:

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(); }
}

And here's what gets printed out:

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

What I expected would be something like this:

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

Any ideas?

like image 243
Joe Avatar asked Nov 01 '11 18:11

Joe


3 Answers

I'll take this opportunity to state again -- as I often do -- that the stack trace does not tell you what methods were called to get to the point where the exception was thrown. Similarly, the stack window in the debugger does not tell you what methods called the current method.

Rather, in both cases a stack trace tells you where control is going to go next, or, in the case of an exception, obviously where control would have gone next had there not been an exception. A stack trace is a continuation; it is a data structure that describes the future execution of the program. It does not necessarily describe the past.

The fact that "where you are going to go next" is almost always the same thing as "where I came from" is what makes the stack trace a useful debugging tool, for now. But the stack trace does not need to contain any information about "where I came from" if the runtime can safely eliminate that information without messing up the "where I need to go next" information. The runtime can, and does, eliminate that information in some cases. You cannot rely on the stack trace telling you where you came from, only where you are going next.

That situation is only going to be exacerbated by "async/await" in the next versions of C# and VB; in a world where asynchronous methods are implemented via continuation passing style, there is no need for a stack as a continuation because the continuation is stored on the heap in the form of a delegate. In this world the stack trace will almost never tell you "where did I come from?"

like image 162
Eric Lippert Avatar answered Oct 11 '22 17:10

Eric Lippert


It still shows where the exception was thrown, not where it was caught. But thanks to optimizations that might not be where you expect it to be thrown.

Most likely the function that throws the exception was inlined into the calling function.

In this case I expect the JIT optimizer to be responsible. By default it doesn't optimize when running in a debugger, but optimizes and thus inlines the method when not running in a debugger. I'm not sure if a Debug build has an effect on inlining.

If you add [MethodImpl(MethodImplOptions.NoInlining)] to a function it won't be inlined and your stack-trace should be as expected.

like image 32
CodesInChaos Avatar answered Oct 11 '22 17:10

CodesInChaos


If this is a release build with no debugger attached, it's likely that the JIT is making an optimization by inlining the methods; so f and g don't even exist.

You can confirm that that's what's happening by applying the [MethodImpl(MethodImplOptions.NoInlining)] attribute to methods g and f, but do this for educational purposes only. You shouldn't stop the JIT from inlining in production code.

Further more, the x64 JIT may optimize it as a tail call, so the MethodImplAttribute may have no affect on x64 release builds.

like image 22
vcsjones Avatar answered Oct 11 '22 17:10

vcsjones