我没有清楚地理解LEAVE函数,它是这两条指令的缩写:
MOV ESP, EBP
POP EBP
Run Code Online (Sandbox Code Playgroud)
因此MOV ESP, EBP将ESP向下移动到EBP(堆栈的开始)的水平.
然后POP EBP,移动ESP指向的值并将其影响到EBP,并将ESP向下移动一步.
但我真的没有看到,这两个操作如何与离开函数的事实相关联(这是目的LEAVE).
你能帮我澄清一下吗?
一个常见的序言,例程开始时的指令序列,在32位和16位时代
push ebp
mov ebp, esp
sub esp, <local_var_size>
push <clobbered_reg1>
push <clobbered_reg2>
...
Run Code Online (Sandbox Code Playgroud)
这里没有什么是随意的,指令的顺序很重要,我们最终会
|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
Run Code Online (Sandbox Code Playgroud)
看看如何使用合适的指针ebp(参数为连续正偏移大于或等于8,本地变量为负偏移小于或等于4)轻松访问所有数据,以及此模型如何适应更多数量的本地变量或参数.
因此ebp,称为帧指针.
结语必须撤消所有这一切.
一种可能的变体是
pop <clobbered_regK>
...
pop <clobbered_reg1>
add esp, <local_var_size>
pop ebp
ret n*4
Run Code Online (Sandbox Code Playgroud)
然而,这涉及重复<local_var_size>- 很容易忘记保持两个版本同步.
我们可以利用这一事实,ebp即在esp分配本地变量之前的价值,因此通过恢复该值,我们有效地将它们全部解除分配.
pop <clobbered_regK>
...
pop <clobbered_reg1>
mov esp, ebp
pop ebp
ret n*4
Run Code Online (Sandbox Code Playgroud)
但是从结尾开始的第三条和第二条指令就是leave指令的作用.所以:
pop <clobbered_regK>
...
pop <clobbered_reg1>
leave
ret n*4
Run Code Online (Sandbox Code Playgroud)
是等同的序幕.
enter是一个很差的指令,leave可用于优化代码空间.