这是一个没有多大价值的小实验,对于大家了解.NET编程等方面几乎没有任何 好处,尽管老赵一直强调“基础”,例如扎实的算法和数据结构能力,并且对一 些必要的支持,例如操作系统,计算机体系结构,计算机网络有足够的了解,拥 有“常识”,在需要的时候有足够的能力去深入了解便可;但是对于还有一些科 目,例如“编译原理”,它虽然可以加强对于一个人对程序的理解,但是我也并 不觉得这是一条“必经之路”。了解黑盒内部肯定是有好处的,但是是否值得学 习还要进行权衡,至少要考虑(1)了解这些对于一个人究竟好处有多大,是否真 那么关键;(2)同样了解这些知识,需要了解到多深,是否我们走的是了解这些 的“必经之路”。同样,对于那种动辄一个问题就深入“IL”,“系统底层”的 做法,老赵对此持保留态度1。当然,对于亲手进行一番尝试和探索 的做法,我总是支持的,这表明了一种严谨的治学态度——但是,前提是我们并 不是“以此为荣”而去搞这些(老赵也一直强调,谁说搞应用层的技术含量就比 搞所谓“底层”要差了),在搞这些之前也已经有必要的根基。我们是为了探索 而去研究,不是为了研究而去研究。
有时候,我们需要查看一个托管方法的汇编指令是怎么样的。记得在大学的时 候,我们使用gcc -s和objdump来获得一个c程序代码的汇编指令。但是对于.NET 程序来说,我们肯定无法轻松地获得这些内容。因为所有的.NET程序都是编译成 IL代码的,而只有在运行时才会被JIT编译成本机代码。因此,我们必须要在程序 运行之后,再使用某种方式去“探得”汇编指令为何——除非我们可以让JIT在不 运行程序的时候编译IL代码,老赵不知道该怎么做,可能需要朋友的提点。
为了进行这个实验,我们先来写一些简单的示例代码:
namespace TestAsm
{
public static class TestClass
{
public static int TestMethod(int i)
{
return i;
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Before JIT.");
Console.ReadLine();
TestClass.TestMethod(1);
Console.WriteLine("After JIT");
Console.ReadLine();
TestClass.TestMethod(1);
}
}
}
大家可以新建一个TestAsm项目,将以上代码复制粘贴,并使用Debug模式编译 (避免TestMethod方法被内联,这会导致TestMethod永远不会被JIT) 2,便可以得到一个TestAsm.exe,这就是我们的试验目标。可以看到 代码中调用了两遍TestClass.TestMethod方法,并且分别在调用前使用 Console.ReadLine中断,这使我们有了有机会使用WinDbg来进行一番探索。我们 先进行一番准备工作:
运行TestAsm.exe,看到Before JIT字样(最好不要在VS里调试运行,因为这 会加入VS的的调试模块——虽然这并不影响试验)。
打开WinDbg(假设您已经设好了Symbol Path),按F6(或File - Attach to a Process),选择TestAsm.exe并确定。
加载SOS(例如.load C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727 \sos.dll)。
现在我们就已经做好了准备。那么我们第一步是什么呢?自然是要找出 TestClass.TestMethod方法的“位置”,于是先使用!name2ee命令获得TestClass 类的信息:
0:003> !name2ee *!TestAsm.TestClass
Module: 70ca1000 (mscorlib.dll)
--------------------------------------
Module: 00942c5c (TestAsm.exe)
Token: 0x02000002
MethodTable: 0094306c
EEClass: 0094133c
Name: TestAsm.TestClass
“!name2ee *!TestAsm.TestClass”命令的含义是“遍历所有已加载模块,查 找TestAsm.TestClass类型”。如果需要的话,您也可以使用“!name2ee modulename typename”的方式来查找指定模块中的指定类型。从输出中我们可以 看到MethodTable的地址是0028306c。于是我们使用!dumpmt -md <address>命令来查看TestClass类型的方法描述符(Method Descriptor) :
0:003> !dumpmt -md 0094306c
EEClass: 0094133c
Module: 00942c5c
Name: TestAsm.TestClass
mdToken: 02000002 (C:\...\TestAsm\bin\Debug\TestAsm.exe)
BaseSize: 0xc
ComponentSize: 0x0
Number of IFaces in IFaceMap: 0
Slots in VTable: 5
--------------------------------------
MethodDesc Table
Entry MethodDesc JIT Name
70e66a70 70ce4934 PreJIT System.Object.ToString()
70e66a90 70ce493c PreJIT System.Object.Equals (System.Object)
70e66b00 70ce496c PreJIT System.Object.GetHashCode()
70ed72f0 70ce4990 PreJIT System.Object.Finalize()
0094c040 00943060 NONE TestAsm.TestClass.TestMethod(Int32)
且看TestMethod的JIT栏的状态:“NONE”,这意味着这个方法还没有经过JIT 的编译,如果我们此时通过!u <address>命令来查看方法的汇编指令就会 看到:
0:003> !u 0094c040
Unmanaged code
0094c040 e8755d9571 call mscorwks!PrecodeFixupThunk (722a1dba)
0094c045 5e pop esi
0094c046 0000 add byte ptr [eax],al
0094c048 60 pushad
0094c049 30940000000000 xor byte ptr [eax+eax],dl
0094c050 0000 add byte ptr [eax],al
0094c052 0000 add byte ptr [eax],al
0094c054 0000 add byte ptr [eax],al
0094c056 0000 add byte ptr [eax],al
0094c058 0000 add byte ptr [eax],al