Take the 2-minute tour ×

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?

share|improve this question
+1 for the nice compact sample but no Repro. It works for me... –  Henk Holterman Nov 1 '11 at 18:38
@HenkHolterman: Run with Ctrl+F5 in Release mode –  Austin Salonen Nov 1 '11 at 18:38
@Austin I did, you indeed need both to loose the intermediate frames. –  Henk Holterman Nov 1 '11 at 18:40
add comment

4 Answers

up vote 10 down vote accepted

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.

share|improve this answer
A debug build by default has /optimize- passed to csc, so no, a debug build will not exhibit this behavior. –  vcsjones Nov 1 '11 at 19:01
@vcsjones in first order this only affects the C# compiler optimizations, and not the JIT optimizations. But the unoptimized C# code can be JITted differently, for example it contains additional sequence points that affect instruction reordering. –  CodesInChaos Nov 1 '11 at 19:07
Eh, after digging a little further (what you said makes complete sense) it appears it is based on the PDB that gets generated, so it's based on the /debug flag. Making a debug build in VS then killing the PDB exhibits the same behavior as a release build. –  vcsjones Nov 1 '11 at 19:42
add comment

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?"

share|improve this answer
add comment

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.

share|improve this answer
On my machine... with debugger attached 3 lines; without, 1 line. +1 –  Austin Salonen Nov 1 '11 at 18:38
add comment

From the documentation at MSDN:


share|improve this answer
add comment

Your Answer


