Method Calls: Part 1 (Normal Call)

In the next few “under the covers” posts I am going to put up a bit about something that most people take for granted, how does that method call actually work (note in this article we are looking at a very basic IL “call”? While it seems mundane, this is actually very important information as it can greatly affect code performance and offer you a greater understanding of how various items work in the CLR. Let’s start by putting up a small bit of test code.









    class Foo {


        public void Test() {


            for (int i = 0; i < 10; i++) {


                Console.WriteLine(“Test”);


            }


        }


    }


    class Program {


        static void Main(string[] args) {


            Foo f = new Foo();


            f.Test();


            f.Test();


            f.Test();


        }


    }


Listing 1: A simple example for discussion


 


The JIT must deal with some huge problems just to run a simple bit of code like this. Let’s step into the process to get a better idea of what happens. Just before control is given to our code the Main method is JIT compiled, control is then passed to the main method to begin execution. We can see the disassembly for this code in Listing 2.









        static void Main(string[] args) {


            Foo f = new Foo();


00000000  push        esi 


00000001  mov         ecx,913080h


00000006  call        FFB21FAC


0000000b  mov         esi,eax


            f.Test();


0000000d  mov         ecx,esi


0000000f  cmp         dword ptr [ecx],ecx


00000011  call        dword ptr ds:[009130B8h]


            f.Test();


00000017  mov         ecx,esi


00000019  cmp         dword ptr [ecx],ecx


0000001b  call        dword ptr ds:[009130B8h]


            f.Test();


00000021  mov         ecx,esi


00000023  cmp         dword ptr [ecx],ecx


00000025  call        dword ptr ds:[009130B8h]


0000002b  pop         esi 


        }


0000002c  ret             


Listing 2: Disassembly of the main method


 


From looking at the disassembled code we can see that the generated code is dereferencing a pointer in order to make the call. There are a few reasons for this but before we get into them let’s look a bit at how this works. It’s time to break out our trusty debugger to see what’s going on. If you have not yet read it, now would be a good time to go back and read my first post here at CodeBetter Viewing Unmanaged Code in VS.NET as you need this and SOS (Son Of Strike) setup in order to follow along.


 I will put all debugger commands in bold fixed width and the output in normal fixed width.


Put a break point on the first line of code and start debugging. All commands for SOS should be typed in your immediate window.


.load SOS


extension C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\SOS.dll loaded


This first command loads the SOS debugging tool making it available for our use.


!Name2EE ConsoleApplication29.exe ConsoleApplication29.Foo.Test


PDB symbol for mscorwks.dll not loaded


Module: 00912c14 (ConsoleApplication29.exe)


Token: 0x06000001


MethodDesc: 00913070


Name: ConsoleApplication29.Foo.Test()


Not JITTED yet. Use !bpmd -md 00913070 to break on run.


This command gives us more information on our method. There is a lot of useful information here but the most important bit of informatio is the MethodDesc address. We can use this to find out more information about our method.


!DumpMD 00913070


Method Name: ConsoleApplication29.Foo.Test()


Class: 009113b8


MethodTable: 00913080


mdToken: 06000001


Module: 00912c14


IsJitted: no


m_CodeOrIL: ffffffffffffffff


Here we can get our method table, we can examine our full method table using this address.


!DumpMT -md 00913080


EEClass: 009113b8


Module: 00912c14


Name: ConsoleApplication29.Foo


mdToken: 02000002  (C:\Documents and Settings\Greg\My Documents\Visual Studio 2005\Projects\ConsoleApplication29\ConsoleApplication29\bin\Release\ConsoleApplication29.exe)


BaseSize: 0xc


ComponentSize: 0x0


Number of IFaces in IFaceMap: 0


Slots in VTable: 6


————————————–


MethodDesc Table


   Entry MethodDesc      JIT Name


79354bec   7913bd48   PreJIT System.Object.ToString()


793539c0   7913bd50   PreJIT System.Object.Equals(System.Object)


793539b0   7913bd68   PreJIT System.Object.GetHashCode()


7934a4c0   7913bd70   PreJIT System.Object.Finalize()


009130c8   00913070     NONE ConsoleApplication29.Foo.Test()


009130d4   00913078     NONE ConsoleApplication29.Foo..ctor()


As I am sure many people have noticed by now (it has been in every one of these listings J), our method has not yet been JIT’ed. This is one of the reasons why we dereference in order to make the call above, at the time that the main method was compiled it had no idea where it should call.  This leads us to an interesting question.


How does the JIT know when to compile a method?


 


Essentially the JIT is lazy loading our compilation for us… It uses a concept known as a “thunk” in order to catch the first time we try to call into our method. The thunk is a little piece of unmanaged code that is emitted by the CLR when it first loads the type. The thunk as we will see shortly contains code to call the JIT or code to call the method.










Figure 1 : Process of JIT’ing a method


The process as shown in Figure 1 is almost deceptively simple; in practice this would be far too slow to create functional systems with. The difference between this drawing and how it actually works has to deal with the decision. The picture would lead you to believe that the thunk has branching code; it does not. Instead the JIT uses a concept known as back patching.


The term back patching may be familiar to you as it is also used in garbage collection, it means to go back and update a pointer to reflect new information. When the method is called for the first time it reads from the method table an address that points to our thunk and calls it. Our thunk forwards us off to the JIT. The key is that when the JIT has completed we go back and change our method table to now directly point to the JIT’ed code (we update the table). Figure 2 & Figure 3 illustrate a before and after view of this process. Note that the call moves directly in the pictures while in reality they are actually reading a memory address that changes.


Now that we have a high level view of what happens, let’s verify it by watching it in the debugger. You can use either your memory window (debug->windows->memory and enter the memory address of your call (i.e. 009130B8h from the listing)) or you can use the registers window (debug->windows->registers (make sure effective address is enabled)) to view the data we need.


Showing stuff like this to your friends in the debugger is also good for asserting your role as alpha nerd in the office.


Using the debugger step to line 0011 (the first call). We can see in either location that the memory address 009130B8 (the address we are dereferencing for our call) contains 009130C8 just before we make our call. This memory address should look familiar as it was in our method table listing earlier as the entry for our Test method. This is pointing to our thunk, we can even look at our thunk as unmanaged code by disassembling that address with the !u command.


!u 009130C8


Unmanaged code


009130C8 B870309100       mov         eax,913070h


009130CD 89ED             mov         ebp,ebp


009130CF E938EEA2FF       jmp         00341F0C


009130D4 B878309100       mov         eax,913078h


009130D9 89ED             mov         ebp,ebp


009130DB E92CEEA2FF       jmp         00341F0C


009130E0 0000             add         byte ptr [eax],al


009130E2 0000             add         byte ptr [eax],al


009130E4 0000             add         byte ptr [eax],al


009130E6 0000             add         byte ptr [eax],al


 


This listing is a bit confusing because it is actually two thunks followed by nothing. So our call that we were looking at is set to jump to the first instruction here. That 913070 being loaded should also look familiar it is the address of our method desc, it is being pushed onto EAX to be passed as a parameter to the JIT compiler (so the JIT compiler knows what to compile!). Let’s set a break point in our other method hit f5 and take a look at our environment after to see what has changed.


!DumpMT -md 00913080


EEClass: 009113b8


Module: 00912c14


Name: ConsoleApplication29.Foo


mdToken: 02000002  (C:\Documents and Settings\Greg\My Documents\Visual Studio 2005\Projects\ConsoleApplication29\ConsoleApplication29\bin\Release\ConsoleApplication29.exe)


BaseSize: 0xc


ComponentSize: 0x0


Number of IFaces in IFaceMap: 0


Slots in VTable: 6


————————————–


MethodDesc Table


   Entry MethodDesc      JIT Name


79354bec   7913bd48   PreJIT System.Object.ToString()


793539c0   7913bd50   PreJIT System.Object.Equals(System.Object)


793539b0   7913bd68   PreJIT System.Object.GetHashCode()


7934a4c0   7913bd70   PreJIT System.Object.Finalize()


00de00b0   00913070      JIT ConsoleApplication29.Foo.Test()


009130d4   00913078     NONE ConsoleApplication29.Foo..ctor()


We can see here that our method has now been JIT’ed (as a normal JIT) and that our  method table entry has been updated to reflect a new location (00de00b0). This is the location that our unmanaged code has been created by the JIT. We can verify this by looking at our current stack as we are currently in the method.


!CLRStack


OS Thread Id: 0x8e8 (2280)


ESP       EIP    


0012f47c 00de00b0 ConsoleApplication29.Foo.Test()


0012f480 00de0087 ConsoleApplication29.Program.Main(System.String[])


0012f69c 79e88f63 [GCFrame: 0012f69c]


Let’s step out of this method and back out to our main method.  Our code is again setting up the same call, by stepping to line 1b and re-checking our pointer we can see that our code will now call the newly generated unmanaged code directly as opposed to passing through the thunk.


This will all stay just like this, unless our code is pitched which will be the next discussion!


This entry was posted in Under The Covers. Bookmark the permalink. Follow any comments here with the RSS feed for this post.

6 Responses to Method Calls: Part 1 (Normal Call)

  1. james says:

    Subject Line: Beat Long Poll Lines with Absentee Ballots from StateDemocracy.org
    Many state and local election officials are encouraging voters to use Absentee Ballots to avoid the long lines and delays expected at the polls on November 4th due to the record-breaking surge in newly registered voters.
    Voters in most states still have time to obtain an Absentee Ballot by simply downloading an official application form available through http://www.StateDemocracy.org, a completely FREE public service from the nonprofit State Democracy Foundation.
    Read More: http://us-2008-election.blogspot.com/2008/10/beat-long-poll-lines-with-absentee.html

  2. Greg says:

    No edit .. try it for yourself

    notice the construction code

    00000000 push esi
    00000001 mov ecx,913080h
    00000006 call FFB21FAC
    0000000b mov esi,eax

    The address in ECX look familiar? :)

    I believe what is happenning here is that the consructor is being inlined (and as such it is not being JIT’ed). Good catch, I hadn’t noticed that, that is worth a post on its own!

  3. Alois says:

    Hi Greg,

    you did very nicely explain how the JITer works. I was wondering about this table in your article
    00de00b0 00913070 JIT ConsoleApplication29.Foo.Test()
    009130d4 00913078 NONE ConsoleApplication29.Foo..ctor()

    How can it be that the test method has been jited before the ctor has been run? To get your program compiled you must instantiate the class before you can call a function of it. I assume that the ctor must have been JITed before the Test function or does the JITer know that the default ctor does nothing? Did I miss here something or did you “patch” the method table with notepad? ;-).

    Yours,
    Alois Kraus

    Yours,
    Alois Kraus

  4. Greg says:

    Ah good question, I will have to address that … Of course I can only look at disassembled code in the production JIT (and even doing that to make a comment is well umm against the EULA:))

    I can however point you to exactly how this works in the SSCLI the method being called is in prestub.cpp –http://dotnet.di.unipi.it/Content/sscli/docs/doxygen/clr/clr/prestub_8cpp-source.html, it is MethodDesc::DoPreStub .. it is quite a long and involved method but there are some comments there discussing how thread safety is handled (in fact in the SSCLI this is the method that the thunk calls). There is also a bit of discussion on this in SSCLI Essentials from O’Reilly

  5. johnwood says:

    Another interesting topic well explored.
    Any thought as to what happens in a multithreaded application when it comes to JIT a method. What if two threads hit that method… obviously one gets there first, but what does it do with the other call while it JITs? Does it somehow gain a lock on the method before it calls it, blocking the other call? How else would it stop the instructions changing from under its feet, and other race condition issues?

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>