28

我正在阅读《汇编语言的艺术》(Randall Hyde,Amazon 的链接),并在那本书中试用了一个控制台应用程序。这是一个使用 Win32 API 函数为自己创建新控制台的程序。该程序包含一个名为 的过程LENSTR,它将字符串的长度存储在EBP寄存器中。该函数的代码如下:

LENSTR PROC
ENTER 0, 0
PUSH  EAX
;----------------------
CLD
MOV   EDI, DWORD PTR [EBP+08H]
MOV   EBX, EDI
MOV   ECX, 100 ; Limit the string length
XOR   AL, AL
REPNE SCASB ; Find the 0 character
SUB   EDI, EBX ; String length including 0
MOV   EBX, EDI

DEC   EBX
;----------------------
POP   EAX
LEAVE
RET   4
LENSTR ENDP

你能在这里解释一下enterandleave命令的用法吗?

4

3 回答 3

51

Enter创建一个堆栈帧,并leave销毁一个堆栈帧。使用 上的0,0参数enter,它们基本上相当于:

; enter
push ebp
mov ebp, esp

; leave
mov esp, ebp
pop ebp

尽管您发布的代码中没有使用它,但它enter确实支持比上面显示的简单 push/mov 组合更多的功能。第一个参数enter指定为局部变量分配的空间量。例如,enter 5, 0大致相当于:

push ebp
mov ebp, esp
sub esp, 5

Enter还支持像 Pascal 这样可以使用嵌套函数/过程的语言:

procedure X;
    procedure Y;
    begin
        { ... }
    end
begin
   { ... }
end

在这种情况下,Y不仅可以访问它自己的局部变量,还可以访问所有本地变量X。这些可以嵌套到任意深度,因此您可以有一个Z内部Y可以访问它自己的局部变量,以及 的变量Y和 的变量X。第二个参数enter指定嵌套深度,因此X将使用enter Sx, 0Y将使用enter Sy, 1Z将使用enter Sz, 2(其中SxSy和分别Sz表示局部变量的大小XYZ)。

这将创建一个堆栈帧链,以Z访问Yand的局部变量X,依此类推。如果函数是递归的,这将变得相当重要,因此调用Z不能只是将堆栈向上移动到两个最近的堆栈帧——它需要跳过之前调用自身的堆栈帧,并直接返回为词法父函数/过程堆栈帧,在递归的情况下,这与它的调用者不同。

这种复杂性也是 C 和 C++ 禁止嵌套函数的原因。考虑到进入/离开的存在,它们在英特尔处理器上相当容易支持,但在许多其他缺乏这种直接支持的处理器上可能要困难得多。

这也至少有助于解释另一个...特性enter--for the trivial case is used here (ie, enter 0, 0) 它比使用push/的等价物要慢很多mov

于 2011-05-02T15:30:10.700 回答
16

这是函数的堆栈帧(激活记录)的设置。在内部,它通常看起来像这样:

    push( ebp );         // Save a copy of the old EBP value
     
    mov( esp, ebp );     // Get ptr to base of activation record into EBP
     
    sub( NumVars, esp ); // Allocate storage for local variables.

// ENTER with a non-zero immediate does all 3 of the above things, slowly.

然后,当要再次销毁堆栈帧时,您必须执行以下操作:

   mov( ebp, esp );    // Deallocate locals and clean up stack.
 
   pop( ebp );         // Restore pointer to caller's activation record.
// LEAVE does the above steps; a RET instruction is separate

   ret();              // Return to the caller.

是使用 HLA 对其进行更好的解释。尽管您正在阅读的书中对此进行了很好的解释,因为我也有那本书,并且我已经阅读了解释它的部分。

于 2011-05-02T15:34:50.907 回答
1

进入和离开只是设置堆栈帧。通常编译器会生成直接操作堆栈帧指针的代码,因为进入和离开相对于 mov/sub 并不完全快(尽管它们曾经是,早在 286 天 :-) )。

于 2011-05-02T15:29:11.220 回答