Compiling in Debug Mode
When a managed compiler compiles source code in debug mode, you can expect at least two occurrences. First, the resulting module has the custom attribute [mscorlib]System.Diagnostics.DebuggableAttribute attached to the Module record or, if it is a prime module, to the Assembly record. Second, the compiler produces a PDB file containing data about the source files and the compiler, the local variable names, and the tables binding source lines and columns to the code offsets. Of course, the compiler can perform other tasks as well in debug mode—for example, emitting different IL code.
When a module is round-tripped, or when a high-level compiler produces the ILAsm source code as an intermediate step, it is usually desirable to preserve the debug information binding the original source code to the final IL code. ILAsm provides two directives facilitating this:
The .language <Language_GUID>[,<Vendor_GUID>[,<Document_ GUID>]] directive defines the source language and, optionally, the compiler vendor and the source document type.
The .line <line_num>[:<column_num> [<file_name>]] directive identifies the line and column in the original source file that are “responsible” for the IL code that follows the .line directive.
For example, the following Visual C# .NET code
using System; public class arr { private static int[,] MakeArray() { return (int[,])Array.CreateInstance(typeof(int), new int[]{2,3}, new int[]{-1, 0}); } private static void Main() { int[,] _aTgt = MakeArray(); foreach (int i in _aTgt) { Console.Write(i + " "); } } }
compiled in debug mode, is disassembled, using the option /LINENUM, into the following ILAsm code:
.class public auto ansi beforefieldinit arr extends [mscorlib]System.Object { .method private hidebysig static int32[0...,0...] MakeArray() cil managed { // Code size 53 (0x35) .maxstack 5 .locals init ([0] int32[0...,0...] CS$00000003$00000000, [1] int32[] CS$00000002$00000001, [2] int32[] CS$00000002$00000002) .language '{3F5162F8-07C6-11D3-9053-00C04FA302A1}', '{994B45C4-E6E9-11D2-903F-00C04FA302A1}', '{5A869D0B-6611-11D3-BD2A-0000F80849BD}' .line 6:3 'C:\\MyDirectory\\arr.cs' IL_0000: ldtoken [mscorlib]System.Int32 IL_0005: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle( valuetype [mscorlib]System.RuntimeTypeHandle) IL_000a: ldc.i4.2 IL_000b: newarr [mscorlib]System.Int32 IL_0010: stloc.1 IL_0011: ldloc.1 IL_0012: ldc.i4.0 IL_0013: ldc.i4.2 IL_0014: stelem.i4 IL_0015: ldloc.1 IL_0016: ldc.i4.1 IL_0017: ldc.i4.3 IL_0018: stelem.i4 IL_0019: ldloc.1 IL_001a: ldc.i4.2 IL_001b: newarr [mscorlib]System.Int32 IL_0020: stloc.2 IL_0021: ldloc.2 IL_0022: ldc.i4.0 IL_0023: ldc.i4.m1 IL_0024: stelem.i4 IL_0025: ldloc.2 IL_0026: call class [mscorlib]System.Array [mscorlib]System.Array::CreateInstance( class [mscorlib]System.Type, int32[], int32[]) IL_002b: castclass int32[0...,0...] IL_0030: stloc.0 IL_0031: br.s IL_0033 .line 7:2 IL_0033: ldloc.0 IL_0034: ret } // End of method arr::MakeArray .method private hidebysig static void Main() cil managed { .entrypoint // Code size 103 (0x67) .maxstack 3 .locals init ([0] int32[0...,0...] _aTgt, [1] int32 i, [2] int32[0...,0...] CS$00000007$00000000, [3] int32 CS$00000264$00000001, [4] int32 CS$00000265$00000002, [5] int32 CS$00000008$00000003, [6] int32 CS$00000009$00000004) .line 10:3 IL_0000: call int32[0...,0...] arr::MakeArray() IL_0005: stloc.0 .line 11:21 IL_0006: ldloc.0 IL_0007: stloc.2 IL_0008: ldloc.2 IL_0009: ldc.i4.0 IL_000a: callvirt instance int32 [mscorlib]System.Array::GetUpperBound(int32) IL_000f: stloc.3 IL_0010: ldloc.2 IL_0011: ldc.i4.1 IL_0012: callvirt instance int32 [mscorlib]System.Array::GetUpperBound(int32) IL_0017: stloc.s CS$00000265$00000002 IL_0019: ldloc.2 IL_001a: ldc.i4.0 IL_001b: callvirt instance int32 [mscorlib]System.Array::GetLowerBound(int32) IL_0020: stloc.s CS$00000008$00000003 IL_0022: br.s IL_0061 IL_0024: ldloc.2 IL_0025: ldc.i4.1 IL_0026: callvirt instance int32 [mscorlib]System.Array::GetLowerBound(int32) IL_002b: stloc.s CS$00000009$00000004 IL_002d: br.s IL_0055 .line 11:12 IL_002f: ldloc.2 IL_0030: ldloc.s CS$00000008$00000003 IL_0032: ldloc.s CS$00000009$00000004 IL_0034: call instance int32 int32[0...,0...]::Get(int32, int32) IL_0039: stloc.1 .line 12:8 IL_003a: ldloc.1 IL_003b: box [mscorlib]System.Int32 IL_0040: ldstr " " IL_0045: call string [mscorlib]System.String::Concat(object, object) IL_004a: call void [mscorlib]System.Console::Write(string) .line 11:3 IL_004f: ldloc.s CS$00000009$00000004 IL_0051: ldc.i4.1 IL_0052: add IL_0053: stloc.s CS$00000009$00000004 IL_0055: ldloc.s CS$00000009$00000004 IL_0057: ldloc.s CS$00000265$00000002 IL_0059: ble.s IL_002f IL_005b: ldloc.s CS$00000008$00000003 IL_005d: ldc.i4.1 IL_005e: add IL_005f: stloc.s CS$00000008$00000003 IL_0061: ldloc.s CS$00000008$00000003 IL_0063: ldloc.3 IL_0064: ble.s IL_0024 .line 14:2 IL_0066: ret } // End of method arr::Main .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { // Code size 7 (0x7) .maxstack 1 IL_0000: ldarg.0 IL_0001: call instance void [mscorlib]System.Object::.ctor() IL_0006: ret } // End of method arr::.ctor } // End of class arr
The .language directive sets the GUIDs for all following code until it is superseded by another .language directive.
You’ll encounter a slight problem with the .line directive in the first release of the ILAsm compiler and disassembler: the directive specifies the starting line and column of the original source statement that has been compiled into ILAsm code following the .line directive. This doesn’t bode well for the Microsoft Visual Studio .NET debugger, which wants to see the line/column interval (starting line and column and ending line and column) for each original source statement. This problem will be corrected in future releases of the ILAsm compiler and disassembler.
In short, if you want the resulting code bound to the original source code, you need to do the following:
If your compiler generates ILAsm source code, it must insert .language and .line directives at appropriate points.
If you are round-tripping a module compiled from a high-level language, use the disassembler option /LINENUM (or /LIN).
In any case, don’t forget to use the option /DEBUG (or /DEB) of the ILAsm compiler.