Emitting and Debugging MSIL - Debugging Emitted Code (
Page 2 of 3 )
Debugging Emitted Code Against Source Code
Suppose you have written an emitter and you want to debug it. You can debug it using a variety of techniques. You can run the command line tools ildasm and ilasm to generate a .il source file as follows:
ildasm HelloWorld.exe /out=HelloWorld.il /source
ADVERTISEMENT
which will generate an MSIL source file, and
ilasm helloworld_il.il /debug
which will run the IL assembler will produce output similar to the text shown in Listing 2.
Listing 2: Output from ilasm.
Microsoft (R) .NET Framework IL Assembler. Version 2.0.50727.3053
Copyright (c) Microsoft Corporation. All rights reserved.
Assembling 'helloworld.il' to EXE --> 'helloworld.exe'
Source file is ANSI
Assembled method MyType::Main
Assembled method MyType::.ctor
Creating PE file
Emitting classes:
Class 1:MyType
Emitting fields and methods:
Global
Class 1Methods: 2;
Emitting events and properties:
Global
Class 1
Writing PE file
Operation completed successfully
You can also run the command line debugger MDbg.exe that will debug IL. Type
MDbg HelloWorld.exe “Hello World!”
At the Visual Studio Command Prompt. MDbg works a lot like the old DOS debug.exe command. MDbg will let you explore memory, CPU registers, steps through the code at the IL level, examine the stack and all sorts of low-level goodies (see Figure 1).
Figure 1: A sample MDbg command line session showing a register dump.
You can also look at address-level debugging in Visual Studio by enabled Address-level debugging in Tools|Options as shown in Figure 2.
Figure 2: Enabling address-level debugging in Visual Studio.
Last but not least here is a very interesting way to debug emitted code. The technique is to approximate the MSIL as source code and associate the pseduo source code with the MSIL using an ISymbolDocumentWriter. Here are two lines extraced from Listing 1:
The preceding two lines indicate that Source2.txt contain symbol information and this information is associated with the ModuleBuilder. MarkSequencePoint preserves offset points for lines of MSIL code. MarkSequencePoint above is indicating that there is an MSIL sequence point at Line 1. This makes way for a breakpoint in Source2.txt. The net effect of the two lines of code is that you are approximating a symbol file (.PDB) for your MSIL. Listing 3 shows the debuggable code in Source2.txt and Figure 3 shows a debugging session with a break on that line—which is the only on the file—and QuickWatch demonstratng that this psedo-source code file can be treated like actual source code.
Listing 2: PSeudo-source code in a text file that is debuggable if an ISombolDocumentWriter is associated with a ModuleBuilder.
Console.WriteLine(args[0]);
Figure 3: Console.WriteLine(args[0]); is debuggable all by itself because using an ISymbolDocumentWriter to indicate that the text file contains source associated with the MSIL.
Even if Source2.txt were empty you could put a breakpoint at line and explore the MSIL from Visual Studio’s debugger. However, by writing pseudo-source code it will be easier to approximate the relationships between the MSIL and what individual chunks are doing. Listing 4 better approximates the output from the MSIL.
Listing 4: A better approximation of the MSIL from Listing 1.
class MyType
{
//args[0] = "Welcome to Valhalla Tower Material Defender!"
public static void Main(string[] args)
{
Console.WriteLine(args[0]);
return;
}
}
If you use the pseudo-source in Listing 4 then the sequence point for Console.WriteLine is row 6. With MarkSequencePoint rows start with 1 and columns start with 0. To indicate Console.WriteLine is you preserved sequence point you could use ILGenerator.MarkSequencePoint(doc, 6, 0, 6, 100). This represents row 6 and column 0 to 100.