31

在反汇编 .NET 函数时,我注意到它们都以类似的模式开始。这个初始代码有什么作用?

此代码出现在函数应该执行的实际代码之前。是某种参数计数验证吗?

功能1

private static void Foo(int i)
{
   Console.WriteLine("hello");
}

00000000  push        ebp 
00000001  mov         ebp,esp 
00000003  push        eax 
00000004  mov         dword ptr [ebp-4],ecx 
00000007  cmp         dword ptr ds:[005C14A4h],0 
0000000e  je          00000015 
00000010  call        65E0367F 
//the console writleline code follows here and is not part of the question

功能2

static private void Bar()
{
   for (int i = 0; i < 1000; i++)
   {
      Foo(i);
   }
}

00000000  push        ebp 
00000001  mov         ebp,esp 
00000003  push        eax 
00000004  cmp         dword ptr ds:[006914A4h],0 
0000000b  je          00000012 
0000000d  call        65CC36CF 
// the for loop code follows here

功能3

private static void Foo()
{
   Console.WriteLine("hello");
}

00000000  push        ebp 
00000001  mov         ebp,esp 
00000003  cmp         dword ptr ds:[005614A4h],0 
0000000a  je          00000011 
0000000c  call        65E3367F 

[编辑] 那么这是对它的正确描述吗?

//fix stackframe
00000000  push        ebp 
00000001  mov         ebp,esp 
//store eax so it can be used locally
00000003  push        eax 
//ensure static ctor have been called
00000004  cmp         dword ptr ds:[006914A4h],0 
//it has been called, ignore it
0000000b  je          00000012
//it hasn't been called, call it now 
0000000d  call        65CC36CF 

或者?

4

3 回答 3

19

这个序言有两个部分。

设置堆栈帧

这会将当前的 EBP 寄存器存储在堆栈中,然后将堆栈指针 (ESP) 的值分配给 EBP。

push        ebp 
mov         ebp,esp

如果堆栈中存储了局部变量(即可用寄存器中没有足够的空间),则 ESP 将按它们的大小移动以构建当前函数的堆栈帧。

在函数结束时,您会看到这些操作被反转,因此前一个函数的堆栈帧被恢复。

EBP 应该始终指向当前函数
ESP 的堆栈帧的开头到末尾(在 x86 上具有较低的地址,因为堆栈向下增长)。

这是常见调用约定的一部分,并且在抛出异常时需要展开堆栈。这不是 .net 特定的,并且被 windows/x86 上的大多数调用约定使用。

设置堆栈帧后,通常在堆栈上存储一些寄存器。那是因为您可能希望将某些寄存器用作临时变量,但调用约定要求您的函数保存它们。因此,您需要将它们备份到堆栈上。哪些寄存器必须保留,哪些可以修改取决于您使用的调用约定。

当引用堆栈上的局部变量时,您可以使用[ebp-x]whereebp指向堆栈帧的开头,而 x 是一个偏移量,它指示变量存储在堆栈帧中的位置。或者,您可以使用[esp+y]与堆栈帧末尾的偏移量。

调用静态构造函数/初始化程序

正如danbystrom注意到的那样,第二部分很可能是对静态构造函数/初始化程序的调用。由于静态构造函数不是在程序启动时调用,而是在第一次访问时调用,所以每次抖动不能保证静态构造函数已经执行的访问都需要检查它是否被调用,如果没有调用它。

00000004  cmp         dword ptr ds:[006914A4h],0 
0000000b  je          00000012 
0000000d  call        65CC36CF

这有点像if (globalVar!=0) Call Function_65CC36CF。全局变量最有可能指示静态构造函数是否已运行,并且调用是对静态构造函数的调用。


据我所知,您对拆卸的评论是正确的。


检查堆栈帧上的此 OldNewThing 博客条目:如何挽救损坏的堆栈跟踪:恢复 EBP 链

于 2011-02-10T13:46:21.387 回答
2

由于您的方法都是静态的:代码用于检查类的静态初始化程序是否尚未执行。

00000007  cmp    dword ptr ds:[005C14A4h],0  ; test if static initializer has executed
0000000e  je     00000015                    ; skip call to initializer if already done
00000010  call   65E0367F                    ; call static initializer
00000015  ....                               ; continue with the method's code
于 2011-02-10T13:54:45.667 回答
0

如果你在谈论 push 和 mov,它只是修复了调用堆栈。我不确定这些部分的其余部分在做什么。

于 2011-02-10T13:47:47.407 回答