1

我有三年全职使用 .NET(C# 和 VB)的经验。我对 MSIL 有很好的工作知识,可以将它用作调试工具。

我对编译过程的下一步知之甚少,即 Jitter 何时生成汇编代码(显示在 dissassebly 窗口中)。Hans Passant 在这里发布了一个问题的答案:本机代码、机器代码和汇编代码有什么区别?. 我更有经验的同事说这是一个绝妙的答案,但我仍然不明白以下代码:

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?或者有人可以推荐一本书吗?

4

2 回答 2

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 中,这是调用约定的问题。因此 from 的返回值Console.Out将存储在 EAX 中。在这里,该值被复制到 ECX 以供以后使用,从而使 EAX 可用于其他目的。

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

寄存器 EDX 被赋予字符串“Hello World”的内存位置。dword ptr ds:[02BD2088h]意味着取消引用内存位置ds:[02BD2088h]数据段ds在哪里(存储初始化字符串之类的东西)。是 的内存区域中的偏移量。[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()被调用时,控制权将返回给系统代码并且应用程序将退出。

于 2013-08-28T21:04:15.587 回答
1

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

push        ebp

将调用方法的帧的基地址保存在堆栈上。这是在函数退出之前恢复的

pop         ebp

在最后的指令之前的ret指令。

正如评论所暗示的,

call        6F03BE38

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

Windows 上的函数通常使用_stdcall调用约定。调用约定指定应如何将参数传递给函数(通过堆栈或通过寄存器),它们应按什么顺序传递到堆栈(从左到右或从右到左)以及谁负责清理调用后的堆栈(调用者或被调用者)。由于没有参数,因此不清楚 getter 的约定是什么,但似乎返回值位于 EAX 寄存器中。

以下三行设置调用TextWriter.WriteLine

该行:

mov         ecx,eax

将 EAX 中的值移动到 ECX 中。EAX 包含从Console.Outgetter 返回的值。

线

mov         edx,dword ptr ds:[02BD2088h]

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

线

mov         eax,dword ptr [ecx]

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

最后调用该方法

call        dword ptr [eax+000000D8h]

000000D8h是对应于方法的方法表中的偏移量TextWriter.WriteLine

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

于 2013-08-28T21:12:36.670 回答