假设现在你编写了一个Program.cs的代码文件,那么在Visual Studio命令提示工具中执行以下命令:
csc.exe /out:Program.exe /t:exe /r:mscorlib.dll Program.cs
这个命令行将指示C#编译器生成一个名为Program.exe的可执行文件(/out:Program.exe)。生成的文件属于Win32控制台应用程序类型(/t[target]:exe)。
一个托管的PE文件由4个部分构成:PE32(+)头、CLR头、元数据以及IL。使用ILDasm.exe(即IL Disassembler,IL反汇编器)可以检查一个托管PE文件中的元数据。下面讲一下关于ILDasm.exe的使用。
在"开始"菜单中找到"Visual Studio Tools",如图:
打开VS命令提示窗口,输入指令如:
ildasm D:\我的项目\程序测试\ILDasm使用测试\ConsoleApplication1\bin\Debug\ConsoleApplication1.exe
这时会打开ildasm.exe,如图:
注意在指令中要使用绝对路径,否则ildasm.exe会找不到你所要反汇编的exe或dll文件。也可只输入ildasm,此时只会打开不加载任何exe或dll的ildasm.exe窗口:
还可以通过为Visual Studio添加外部工具来更方便地使用ILDasm.exe工具,如图:
"命令"那一栏其实就是选择ILDasm.exe所在的目录,如笔者的:
C:\Program Files\Microsoft SDKs\Windows\v8.1A\bin\NETFX 4.5.1 Tools\ildasm.exe
现在就用一个实际的例子进一步讲解有关ILDasm.exe的使用。新建控制台应用程序,代码很简单:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Console.WriteLine("ILDasm使用测试"); 6 Console.ReadLine(); 7 } 8 }
保存生成后,打开ILDasm.exe,选择”文件”打开控制台应用程序生成的exe文件,如图:
随带附上ILDasm树状图的各种图标的说明:
鼠标双击树状图的子节点,便可看到相关的IL代码:
简单分析一下IL的代码及各种指令:
MANIFEST清单:MANIFEST是一个附加信息列表,主要包含程序集的一些属性,如程序集名称、版本号、哈希算法等;
ConsoleApplication1.Program类:
1 .class private auto ansi beforefieldinit ConsoleApplication1.Program 2 extends [mscorlib]System.Object 3 { 4 } // end of class ConsoleApplication1.Program
1).class,表示Program是一个类。并且它继承自程序集—mscorlib的System.Object类;
2)private,表示访问权限;
3)auto,表示程序的内存加载全部由CLR来控制;
4)ansi,是为了在没有托管代码与托管代码之间实现无缝转换。这里主要指C、C++代码等;
5)beforefieldinit,是用来标记运行库(CLR)可以在静态字段方法生成后的任意时刻,来加载构造器(构造函数);
.otor方法(一个构造器,或者说constructor):
1 .method public hidebysig specialname rtspecialname 2 instance void .ctor() cil managed 3 { 4 // 代码大小 7 (0x7) 5 .maxstack 8 6 IL_0000: ldarg.0 7 IL_0001: call instance void [mscorlib]System.Object::.ctor() 8 IL_0006: ret 9 } // end of method Program::.ctor
1)cil managed:表示其中为IL代码,指示编译器编译为托管代码;
2).maxstack:表示调用构造函数.otor期间的评估堆栈(Evaluation Stack);
3)IL_0000:标记代码行开头;
4)ldarg.0:表示转载第一个成员参数,在实例方法中指的是当前实例的引用;
5)call:call一般用于调用静态方法,因为静态方法是在编译期就确定的。而这里的构造函数.otor()也是在编译期就制定的。而另一指令callvirt则表示调用实例方法, 它是在运行时确定的,因为如前述,当调用方法的继承关系时,就要比较基类与派生类的同名函数的实现方法(virtual和new),以确定调用的函数所属的Method Table;
6)ret:表示执行完毕,返回;
最后是Main()方法:
1 .method private hidebysig static void Main(string[] args) cil managed 2 { 3 .entrypoint 4 // 代码大小 19 (0x13) 5 .maxstack 8 6 IL_0000: nop 7 IL_0001: ldstr bytearray (49 00 4C 00 44 00 61 00 73 00 6D 00 7F 4F 28 75 // I.L.D.a.s.m..O(u 8 4B 6D D5 8B ) // Km.. 9 IL_0006: call void [mscorlib]System.Console::WriteLine(string) 10 IL_000b: nop 11 IL_000c: call string [mscorlib]System.Console::ReadLine() 12 IL_0011: pop 13 IL_0012: ret 14 } // end of method Program::Main
1) .entrypoint指令表示CLR加载程序时,是首先从.entrypoint开始的,即从Main方法作为程序的入口函数;
2)ldstr:表示将字符串压栈,在这里就是将"Hello World." 压栈;
3)hidebysig:表示当把此类作为基类,存在派生类时,此方法不被继承,同上构造函数;
下面总结一下常用的IL指令:
1.newobj: 用于创建引用类型的对象;
2.ldstr:用于创建String对象变量;
3.newarr:用于创建数组型对象;
4.box:在值类型转换为引用类型的对象时,将值类型拷贝纸托管堆上分配内存。