Click here to Skip to main content
15,886,815 members
Articles / Programming Languages / C#

Walking up the StackTrace

Rate me:
Please Sign up or sign in to vote.
5.00/5 (7 votes)
11 Mar 2017CPOL3 min read 18K   5   2
How to walk up the StackTrace

This question is very simple: what does this application print and why?

The answer may not be as simple as you think.

C#
using System;
 
namespace StackTrace
{
    class Program
    {
        static void Main()
        {
            Method1();
            Console.ReadLine();
        }
 
        static void Method1()
        {
            var stackTrace = new System.Diagnostics.StackTrace(1);
            var stackFrame = stackTrace.GetFrame(0);
            Console.WriteLine(stackFrame.GetMethod().Name);
 
            Method2();
        }
 
        static void Method2()
        {
            var stackTrace = new System.Diagnostics.StackTrace(1);
            var stackFrame = stackTrace.GetFrame(0);
            Console.WriteLine(stackFrame.GetMethod().Name);
 
            Method3();
        }
 
        static void Method3()
        {
            var stackTrace = new System.Diagnostics.StackTrace(1);
            var stackFrame = stackTrace.GetFrame(0);
            Console.WriteLine(stackFrame.GetMethod().Name);
        }
    }
}

Feel free to run it!!

Answer

At first look and even when you run it, the answer would seem to be:

Main
Method1
Method2

which is right but not always! In fact, in production (i.e., in release mode), that answer is always wrong!! Let me explain:

Obviously StackTrace is populated at RunTime and not at compile time. When you are debugging this application or even when you are running it when it is built with 'debugging' configuration, the StackTrace is what you would hope it to be, which is what I wrote above. However, if you run the very same application in release mode, the situation is different.

At runtime, when it comes the time for JIT compiler to compile this module, it has a look at the methods and thinks to itself: "Hey, you know what. These calls are very simple and I could inline these methods and save myself, CPU and memory from a lot of trouble. This way, we will not have to deal with pushing addresses into stack and popping them up later. So I am just going to inline this. This not only performs better but is also really cool. So yeah, let's do it". OK, maybe JIT does not think like this; but the result is the same! :)

So what is going to happen at RunTime in release mode (and depending on your machine's CPU architecture) is that JIT is going to inline these methods and the application output all of a sudden becomes:

Main
Main
Main

The decision process is actually much more complex than what I naively explained above. So many criteria are involved in making a decision about inlining a method. Here are a few:

  • Methods that are greater than 32 bytes of IL will not be inlined.
  • Virtual functions are not inlined.
  • Methods that have complex flow control will not be in-lined. Complex flow control is any flow control other than if/then/else; in this case, switch or while.
  • Methods that contain exception-handling blocks are not inlined, though methods that throw exceptions are still candidates for inlining.
  • If any of the method's formal arguments are structs, the method will not be inlined.

And then there is tailcalling which makes it even more confusing. Here are a few considerations on tailcalling:

"For the 64-bit JIT, we tail call whenever we’re allowed to. Here’s what prevents us from tail calling (in no particular order):

  • We inline the call instead (we never inline recursive calls to the same method, but we will tail call them)
  • The call/callvirt/calli is followed by something other than nop or ret IL instructions.
  • The caller or callee return a value type.
  • The caller and callee return different types.
  • The caller is synchronized (MethodImplOptions.Synchronized).
  • The caller is a shared generic method.
  • The caller has imperative security (a call to Assert, Demand, Deny, etc.).
  • The caller has declarative security (custom attributes).
  • The caller is varargs.
  • The callee is varargs.

...."

If you want to learn more about these, Scott Hanselman has a good article that explains these in a good depth and provides much more info on them.

Hope this helps.

This article was originally posted at http://geekquiz.net/walking-up-the-stacktrace

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Chief Technology Officer Genie solutions
Australia Australia
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
PraiseGreat Pin
JMiller7012-Mar-17 5:36
JMiller7012-Mar-17 5:36 
GeneralMy vote of 5 Pin
Kabwla.Phone5-Dec-11 23:44
Kabwla.Phone5-Dec-11 23:44 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.