2013-08-28 29 views
1

我有三年.NET(C#和VB)全职工作经验。我对MSIL具有良好的工作知识,可以将其用作调试工具。.NET JIT编译天真

我对编译过程的下一步没有太多的了解,即当Jitter产生汇编代码(显示在拆散窗口中)时。 Hans Passant在这里发布了一个问题的答案:What is the difference between native code, machine code and assembly code?。我的经验更丰富的同事说,这是一个辉煌的答案,但我还是不明白下面的代码:

static void Main(string[] args) { 
      Console.WriteLine("Hello world"); 
00000000 55    push  ebp       ; save stack frame pointer 
00000001 8B EC    mov   ebp,esp      ; setup current frame 
00000003 E8 30 BE 03 6F call  6F03BE38      ; Console.Out property getter 
00000008 8B C8    mov   ecx,eax      ; setup "this" 
0000000a 8B 15 88 20 BD 02 mov   edx,dword ptr ds:[02BD2088h] ; arg = "Hello world" 
00000010 8B 01    mov   eax,dword ptr [ecx]   ; TextWriter reference 
00000012 FF 90 D8 00 00 00 call  dword ptr [eax+000000D8h]  ; TextWriter.WriteLine() 
00000018 5D    pop   ebp       ; restore stack frame pointer 
     } 
00000019 C3    ret          ; done, return 

任何人都可以提供每行发生了什么,更具体为什么每个寄存器例如选择的更多信息为什么选择eax代替edx?或者任何人都可以推荐一本书?

回答

3

我对此有点生疏,但我也对低级别的东西感兴趣。这里所说:

push ebp; save stack frame pointer 

推送存储在EBP压入堆栈,这样,当我们从这个方法返回,我们知道我们从哪里来值。

mov ebp,esp; setup current frame 

将当前堆栈位置值从ESP移到EBP,以便EBP位于当前方法的上下文中。

前面两行代码是确保堆栈中存在固定位置(存储在EBP寄存器中)的约定,用于确定局部变量的相对位置。

call 6F03BE38; Console.Out property getter 

不用猜,这是Console.Out

mov ecx,eax; setup "this" 

从方法返回值的调用存储在EAX,这是调用约定的问题。因此从Console.Out返回的值将被存储在EAX中。在这里,该值被复制到ECX中供以后使用,使EAX可用于其他目的。

mov edx,dword ptr ds:[02BD2088h]; arg = "Hello world" 

寄存器EDX被赋予字符串“Hello World”的内存位置。 dword ptr ds:[02BD2088h]表示取消引用内存位置ds:[02BD2088h],其中dsdata segment(存储了初始化字符串等内容)。 [02BD2088h]是内存区域ds中的偏移量。

mov eax,dword ptr [ecx]; TextWriter reference 

请记住拨打Console.Out?我们把这个返回的值放到ECX中。这里,ECX的内存地址被解除引用,以便TextWriter的内存地址被复制到EAX中。所以EAX现在将包含TextWriter对象的实际内存地址。如果我们做了mov eax,dword ptr ecx;那么EAX将包含指向TextWriter的内存地址的指针,而不是TextWriter的实际内存地址。 (我自己仍然感到困惑)。

call dword ptr [eax+000000D8h]; TextWriter.WriteLine() 

这里打电话给TextWriter.WriteLine()。我假定TextWriter.WriteLine()使用_fastcall调用约定(这里可以找到调用约定的一个很好的解释),这意味着它使用EDX寄存器来查找传递给该方法的参数。

pop ebp; restore stack frame pointer 

我们去掉最上面的(或最底层真的像栈向下实际增长)值到EBP,所以在EBP帧指针现在对应于调用方法。

ret 

返回到堆栈顶部找到的位置,返回到调用方法。在这种情况下,调用Main()时,控件将返回到系统代码,应用程序将退出。

+0

如果我编辑,你做得很好,介意吗? –

+0

继续,请随时:) –

+0

把我的两美分。 –

1

前两行用于设置堆栈帧。 EBP寄存器用于存储当前方法帧的基地址。

push  ebp 

将调用方法帧的基地址保存在堆栈上。这是恢复功能之前与最终ret指令之前,

pop   ebp 

指令退出。

正如评论指出,该

call  6F03BE38 

指令调用Console.Out属性的getter。这是一个静态方法,因此当编译当前方法时,该方法的地址可以直接由JIT插入。

windows上的函数通常使用_stdcall调用约定。调用约定指定参数应该如何传入函数(通过堆栈或通过寄存器),顺序应该传递到堆栈(从左到右还是从右到左)以及谁负责清理呼叫后的堆栈(主叫方或被叫方)。由于没有参数,所以不清楚getter的约定是什么,但是看起来返回值是放在EAX寄存器中的。

以下三行建立呼叫到TextWriter.WriteLine

线:

mov   ecx,eax 

在EAX移动值到ECX。 EAX包含从Console.Out获取器返回的值。

线

mov   edx,dword ptr ds:[02BD2088h] 

的字符串 “Hello World” 的地址移动到EDX寄存器。

线

mov   eax,dword ptr [ecx] 

副本字的地址指向ECX到EAX。 EAX包含从Console.Out返回的值。由于这是一个引用类型,所以这个值是一个指向存储在堆上的对象的指针。所有对象都有一个对象头,由一个同步块索引和一个指向方法表的指针组成。该参考本身直接指向方法表。因此,[ECX]是该方法将被调用的引用的方法表指针的地址。

最后,该方法被调用

call  dword ptr [eax+000000D8h] 

000000D8h是偏移到对应于TextWriter.WriteLine方法的方法表。

由于this指针和string参数存储在ECX和EDX中,因此此方法似乎使用_fastcall约定。