How to debug compiler generated code


Edit 2/8/2010: Update for recent F# language changes


 


James Margetson recently showed me a great tip for debugging compiler generated code. Typically the problem is that using the Visual Studio debugger all you see is the x86 disassembly of the JIT-ed methods, but what you really need is the IL opcodes that F# compiled down to with the x86 under that. Fortunately all the tools you need to do this come with the .Net SDK. Consider the following, bugged F# program:


 


// Simple F# program



open System


 


type Season =


    | Spring


    | Summer


    | Fall


    | Winter


    override this.ToString() =


        match this with


        | Spring -> “Spring”


        | Summer -> “Summer”


        | Fall   -> “Fall”


        | Winter -> “Winter”


 


type SeasonUtils = class


    // Gets the next season


    static member GetNextSeason season =


        match season with


        | Spring -> Summer


        | Summer -> Fall


        | Fall   -> Winter


        | Winter -> Spring


       


    // Gets the current name of the season by calling .ToString()


    static member GetSeasonName (season : Season) = season.ToString()


 


    // Returns the current season


    static member CurrentSeason () =


       


        // Convert a list of months and tupifies them with the target season


        let genSeasonTuples monthRange season =


            monthRange |> List.map (fun monthInd -> (monthInd, season))


 


        // Map of (months * season) pairs


        let monthSeasonList =   (genSeasonTuples  [1 .. 2]  Spring) // Who likes March anyways?


                              @ (genSeasonTuples  [4 .. 6]  Summer)


                              @ (genSeasonTuples  [7 .. 10] Fall)


                              @ (genSeasonTuples [11 .. 12] Winter)


        let monthSeasonMap = Map.ofList monthSeasonList


       


        let currentMonthIndex = DateTime.Now.Month


        monthSeasonMap.[currentMonthIndex]


end


 


// Code in our main module


printfn “(attach debugger and press any key)”


Console.ReadKey(true)


 


let currentSeason = SeasonUtils.CurrentSeason()


printfn “Current season = %s” (SeasonUtils.GetSeasonName(currentSeason))


 


Console.WriteLine(“(press any key)”)


Console.ReadKey(true)


 


If you run it and break into a debugger, all you see is the x86 disassembly with the F# code on top. (As you can see quite a bit gets lost when looking at F# -> x86).


 


000001e0  call        7891820C


000001e5  mov         esi,eax


000001e7  mov         dword ptr ds:[001A3184h],esi


let currentSeason = monthSeasonMap.[currentMonth]


000001ed  call        dword ptr ds:[001A6898h]


000001f3  mov         esi,eax


000001f5  call        dword ptr ds:[001A689Ch]


000001fb  mov         edi,eax


 


What we would like to do is break the source program into its native IL-level. To do this you can use ILDasm.exe, a managed code disassembler. To get the IL code for an assembly, type:


ildasm foo.exe /OUT=foo.exe.il /SOURCE


 


The /SOURCE parameter included the original source code (from foo.exe.pdb) in the output IL. The result is the following code in ‘foo.exe.il’:


 


    .method public static string  GetSeasonName(class Codefile/Season season) cil managed


    {


      // Code size       9 (0x9)


      .maxstack  3


//000024:        


//000025:     // Gets the current name of the season by calling .ToString()


//000026:     static member GetSeasonName (season : Season) = season.ToString()


      IL_0000:  ldarg.0


      IL_0001:  tail.


      IL_0003:  callvirt   instance string [mscorlib]System.Object::ToString()


      IL_0008:  ret


    } // end of method SeasonUtils::GetSeasonName


 


So now we have the IL, but that doesn’t help us debug a live instance of foo.exe. Enter ILAsm.exe, which will ‘round trip’ disassembled IL code and build a new assembly. This seems strange, since we don’t gain anything. (The IL code is exactly the same.) But, ILAsm.exe has a debug flag which will generate a new PDB file which contains debugging information for not the F# lines of code, but the IL instructions themselves. And, since we included the /SOURCE flag when we disassembled the assembly the F# code is included as comments. Type:


ilasm foo.exe.il /DEBUG


 


Now, if we run the program and debug it, rather than using the .fs file as the source language we use foo.exe.il, and the disassembly display should show the F# source code along with the generated IL. For example:


 


//000044:        


//000045:         let currentMonthIndex = DateTime.Now.Month


      IL_010f:  call       valuetype [mscorlib]System.DateTime [mscorlib]System.DateTime::get_Now()


000004bb  lea         ecx,[ebp+FFFFFF40h]


000004c1  call        78EDAAE8


      IL_0114:  stloc.s    ‘$struct-addr@45’


000004c6  lea         edi,[ebp-40h]


000004c9  lea         esi,[ebp+FFFFFF40h]


000004cf  movq        xmm0,mmword ptr [esi]


000004d3  movq        mmword ptr [edi],xmm0


      IL_0116:  ldloca.s   ‘$struct-addr@45’


000004d7  lea         esi,[ebp-40h]


      IL_0118:  call       instance int32 [mscorlib]System.DateTime::get_Month()


000004da  mov         ecx,esi


000004dc  call        78F080BC


000004e1  mov         dword ptr [ebp+FFFFFF3Ch],eax


      IL_011d:  stloc.s    currentMonthIndex


000004e7  mov         eax,dword ptr [ebp+FFFFFF3Ch]


000004ed  mov         dword ptr [ebp-38h],eax


 


Now while debugging you can get a complete picture of what’s going on under the hood. It gives you an appreciate of how much the CLR abstracts the machine and how much F# abstracts the CLR.

Comments (2)

  1. Fantastic! Thanks for the tip Chris.

  2. Gaurav says:

    Awesome

    i didn’t knowed anything about this…