我没有清楚地理解LEAVE功能,它是这两条指令的缩写:
MOV ESP, EBP
POP EBP
所以MOV ESP, EBP
将ESP向下移动到EBP的水平(堆栈的开始)。
然后POP EBP
,移动ESP指向的值并将其影响到EBP,并将ESP向下移动一步。
但我真的不明白,这两个操作如何与离开函数的事实联系起来(这是LEAVE
的目的)。
你能帮我澄清一下吗?
答案 0 :(得分:5)
一个常见的序言,例程开始时的指令序列,在32位和16位时代是
push ebp
mov ebp, esp
sub esp, <local_var_size>
push <clobbered_reg1>
push <clobbered_reg2>
...
这里没有什么是随意的,指令的顺序很重要,我们最终会
|parN | <-- EBP + 04 + n*4 par1..parN = Routine parameters
... ... ra = Return address
|par2 | <-- EBP + 0ch o ebp = Original (caller) EBP
|par1 | <-- EBP + 08h lvar1..lavarM = Local variables
|ra | <-- EBP + 04h creg1..cregK = Clobbered registers
|o ebp| <-- EBP
|lvar1| <-- EBP - 04h
|lvar2| <-- EBP - 08h
... ...
|lvarM| <-- EBP - m*4
|creg1|
|creg2|
...
|cregK| <-- ESP
了解如何使用ebp
中的合适指针轻松访问所有数据(参数为连续正偏移大于或等于8,局部变量为负偏移小于或等于4)以及此模型的缩放程度对于更多的本地变量或参数。
因此,ebp
称为帧指针。
结语必须撤消所有这一切 一种可能的变体是
pop <clobbered_regK>
...
pop <clobbered_reg1>
add esp, <local_var_size>
pop ebp
ret n*4
然而,这涉及重复<local_var_size>
- 很容易忘记保持两个版本同步
在分配本地变量之前,我们可以利用ebp
是esp
的值这一事实,因此通过恢复该值,我们可以有效地将它们全部解除分配。
pop <clobbered_regK>
...
pop <clobbered_reg1>
mov esp, ebp
pop ebp
ret n*4
但是最后的第三条和第二条指令是leave
指令的作用。所以:
pop <clobbered_regK>
...
pop <clobbered_reg1>
leave
ret n*4
是等同的序幕。
enter
是一条糟糕的指令,而leave
可用于优化代码空间。
答案 1 :(得分:0)
但是我真的不明白,这两个操作如何与离开函数这一事实联系在一起(这是LEAVE的目的)。
这不是LEAVE
的目的。 RET
的目的。离开实际上并不会做任何事情,除了修改栈。实际上,您可以LEAVE
然后设置另一个堆栈框架,并且仍然保留在相同的功能中。