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.

 

Comments (16)

  1. Albert says:

    I guess the "don’t use the caller’s identity to determine how the method should behave" rule does not apply to CAS. That’s a prime example of methods changing their behavior depending on who’s calling…

  2. Barry Kelly says:

    What about when the calling function (the small function like SmallFunc() above) has an attribute on it? Can it still be inlined then?

    How do the security method attributes work in this case? I thought they used a stack trace to determine their callers.

  3. mihailik says:

    It is extremely exciting! Is there any statements in .NET SDK Documentation about that behavior o StackTrace?

    I mean it can omit some inlined stack frame(s).

  4. Mike Stall says:

    Barry – I just tried it out and functions with attributes can still get inlined.

    Mihailik – the StackTrace class is documented on MSDN (I have a hyperlink to the msdn docs in the entry above. It’s http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemdiagnosticsstacktraceclasstopic.asp )

    Albert – correct. Though even in the CAS case, a method shouldn’t explicitly check the caller’s identity. It makes its security declarations and then leaves the enforcement up to the CLR.

  5. mihailik says:

    Yeah, I found that little note in doc.

    I think, it must be stated with more tension in documentation — many people can skip this small note and fall into misunderstanding.

    Thank you for important information, Mike.

  6. Jason Coyne says:

    I am trying to use the StackTrace class to get my current stack trace for some logging. Everything is working fine, except when I am using threading (specifically WaitCallBack and ThreadPool.QueueUserWorkItem)

    When I try to walk the stack from a location that has been called via QueueUserWorkItem, the stack stops at the point where the thread was launched. Is there a way to walk back further into the parent thread’s stack (at the time that my own thread was created, not the current state)

    Thanks, Jason.

  7. Mike Stall says:

    Jason – your observations are right on. The stack trace is naive with respect to cross thread calls. It just traces the current thread and stops.

    FYI, VS can show cross-thread stack traces because it does a extra work under the covers:

    – getting stack trace from all threads of interest

    – tracking cross-thread calls in the stack trace

    – stitching the stacks back together based off the cross-thread calls into a single unified stack.

    One approach would be to have a wrapper around the Queue function which grabs a stack trace and stores it in your message.

  8. You can’t get a full-mixed (both managed+ native) stack of a thread within your own process. You can…

  9. You may have noticed that debugging optimized builds (eg, what you commonly get when you attach to a