我正在阅读汇编语言艺术(Randall Hyde,link to 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
您能在这里解释一下enter
和leave
命令的用法吗?
答案 0 :(得分:39)
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
的变量和{{1的变量}}。 X
的第二个参数指定了嵌套深度,因此enter
将使用X
,enter Sx, 0
将使用Y
而enter Sy, 1
将使用Z
1}}(其中enter Sz, 2
,Sx
和Sy
分别表示Sz
,X
和Y
本地变量的大小。 / p>
这将创建一个堆栈帧链,以便Z
访问Z
和Y
的本地变量,依此类推。如果函数是递归的,那么这变得相当重要,因此调用X
不能只是将堆栈向上移动到最近的两个堆栈帧 - 它需要跳过之前调用自身的堆栈帧,并直接返回到词汇父级函数/过程的堆栈帧,这与递归时的调用者不同。
这种复杂性也是C和C ++禁止嵌套函数的原因。鉴于进入/离开的存在,它们在英特尔处理器上相当容易支持,但在缺乏这种直接支持的许多其他处理器上可能要困难得多。
这至少也有助于解释Z
的另一个......特征 - 对于这里使用的琐碎案例(即enter
),它比使用{enter 0, 0
的等价物慢得多1}} / push
。
答案 1 :(得分:13)
这是该功能的堆栈帧(激活记录)的设置。在内部,它通常看起来像这样:
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.
然后当要再次销毁堆栈帧时,您必须执行以下操作:
mov( ebp, esp ); // Deallocate locals and clean up stack.
pop( ebp ); // Restore pointer to caller's activation record.
ret(); // Return to the caller.
Here是使用HLA更好地解释它。虽然你正在阅读的书中有很好的解释,因为我也有这本书,我已经阅读了解释它的部分。
答案 2 :(得分:0)
输入并离开,只需设置堆栈帧。通常编译器会生成直接操作堆栈帧指针的代码,因为进入和离开相对于mov / sub并不是很快(过去它们曾经是286天之内:-))。