【问题标题】:Need some explanations about LEAVE instruction in assembly language需要一些关于汇编语言中的LEAVE指令的解释
【发布时间】:2017-06-13 22:38:55
【问题描述】:

LEAVE函数我没看清楚,是这两条指令的浓缩:

MOV ESP, EBP
POP EBP

所以MOV ESP, EBP 将 ESP 向下移动到 EBP 级别(堆栈的开始)。

然后POP EBP,将ESP指向的值移动并影响到EBP,同时ESP也向下移动一级。

但我真的不明白,这两个操作如何与离开函数的事实相关联(这是LEAVE 的目的)。

你能帮我澄清一下吗?

【问题讨论】:

  • LEAVE 的目的只是撤销ENTER 所做的事情,即“释放”由ENTER 创建的堆栈帧。
  • 那么,就是把当前栈帧去掉,指向下面的那个?
  • 它平衡了 ENTER 指令的作用。设置堆栈帧并再次将其拆除。您需要忘记这些指令存在,它们仅在 20 多年前处理器还不是超标量时才有意义。

标签: assembly x86


【解决方案1】:

在 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

但是,这涉及到重复 &lt;local_var_size&gt; - 很容易忘记让两个版本保持同步。
我们可以利用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 是一条非常慢的指令 (https://agner.org/optimize),因此编译器从不使用它,但 leave 可用于优化代码空间,而对性能的影响很小(可能会被代码平衡-尺寸节省)。当 pop ebp 本身不够用时,GCC 使用 leave,大多数 -mtune= 设置。

在当前的 Intel CPU(例如 Skylake)上,leave 总共需要 3 个 uop,而 mov esp, ebp / pop ebp 需要 2 个。在一个真实的测试用例中,通过调用(从重复循环中)一个实际的小函数来解释堆栈同步 uops 的可能差异,该函数将 EBP 设置为帧指针并分配一些堆栈空间,然后将其拆除,测量硬件性能计数器leave 函数每次调用比 mov/pop 函数多占用 1 个前端 uop。但是由于某种未知原因,leave 函数运行得稍微快了一点,即使两者都对齐了 32。(@petercordes 运行了这个测试。)

【讨论】:

    【解决方案2】:

    但我真的不明白,这两个操作如何与离开函数的事实相关联(这是 LEAVE 的目的)。

    这不是LEAVE 的目的。那RET的目的。离开实际上并没有做任何事情除了修改堆栈。事实上你可以LEAVE 然后设置另一个堆栈帧,仍然保持在同一个函数中。

    【讨论】:

    • LEAVE 不只是调整堆栈,它还恢复之前被压入堆栈的 RBP/EBP/BP 值。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-27
    • 1970-01-01
    • 2011-04-07
    • 1970-01-01
    • 1970-01-01
    • 2011-01-17
    相关资源
    最近更新 更多