Caveats about System.Diagnostics.StackTrace

The  .Net frameworks provides the System.Diagnostics.StackTrace class which allows you to get a stack trace at runtime.

This can be handy for diagnostic purposes such as implementing an assert dialog. However, beware of the following about the StackTrace class:

1) it represents the actual stack running and so is subject to optimizations such as inlining. I’d suggest only using the StackTrace class from debug helper code (like assert dialogs).

2) it only shows managed frames and does not show any native frames. There is no good way for a application to get a mixed (managed + unmanaged) stack trace of itself. Obviously the debugging APIs allow this, but since an application can’t debug itself, that solution is insufficient.

3) The stack trace class can get method names and even IL offsets without symbols (pdbs). However it requires symbols to get source file information.

So do not use this to do something like determine a method’s current caller. If a method needs to have different behavior for different callers, then pass in an additional parameter or find some other refactoring.

 

Consider this code snippet that uses the StackTrace class (note that GetFrames() is new in v2.0):

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

using System;

using System.Diagnostics;

class Foo

{

static void Main()

{

    SmallFunc();

}

static void SmallFunc()

{

    PrintStack();

}

static void PrintStack()

{

    StackTrace st = new StackTrace(true); // true means get line numbers.

    foreach(StackFrame f in st.GetFrames()) {

        Console.Write(f);

    }

}

} // end class

 

If you compile it non-optimized (/optimize- in C#) and generate symbols (/debug+), it prints the stack as you’d expect (Main, SmallFunc, PrintStack) with offsets and source line numbers:

 

C:\temp>csc stack.cs /optimize- /debug+ & stack.exe

Microsoft (R) Visual C# .NET Compiler version 8.00.40607.16

for Microsoft (R) Windows (R) .NET Framework version 2.0.40607

Copyright (C) Microsoft Corporation 2001-2003. All rights reserved.

PrintStack at offset 74 in file:line:column c:\temp\stack.cs:17:5

SmallFunc at offset 27 in file:line:column c:\temp\stack.cs:13:5

Main at offset 27 in file:line:column c:\temp\stack.cs:8:5

 

But when you run the same program in a retail setting (optimized), the function “SmallFunc” gets inlined and so doesn’t show up in the stack trace. The /debug+ will cause the PDB to still be generated and so you still get line numbers. However, the optimizations may cause the line numbers to be off.

 

C:\temp>csc stack.cs /optimize+ /debug+ & stack.exe

Microsoft (R) Visual C# .NET Compiler version 8.00.40607.16

for Microsoft (R) Windows (R) .NET Framework version 2.0.40607

Copyright (C) Microsoft Corporation 2001-2003. All rights reserved.

PrintStack at offset 44 in file:line:column c:\temp\stack.cs:18:29

Main at offset 6 in file:line:column c:\temp\stack.cs:9:1

 

If you compile without generating symbols (PDBs),  you’ll still get thee function name and offsets but source info will be missing.

C:\temp>csc stack.cs /optimize+ /debug- & stack.exe

Microsoft (R) Visual C# .NET Compiler version 8.00.40607.16

for Microsoft (R) Windows (R) .NET Framework version 2.0.40607

Copyright (C) Microsoft Corporation 2001-2003. All rights reserved.

PrintStack at offset 44 in file:line:column <filename unknown>:0:0

Main at offset 6 in file:line:column <filename unknown>:0:0

In all cases, you’ll notice there are no native frames (such as CLR internal frames from mscorwks.dll) on the stack.

[Update] David Notario, JIT dev, blogs more about inlining here.