【问题标题】:Assembly x86 - "leave" Instruction汇编 x86 - “离开”指令
【发布时间】:2015-06-29 16:11:40
【问题描述】:

据说“离开”指令类似于:

movl %ebp, %esp
popl %ebp

我了解movl %ebp, %esp 部分,它的作用是释放存储的内存(如this question 中所述)。

但是popl %ebp 代码的目的是什么?

【问题讨论】:

  • 弹出ebp
  • 我投票决定关闭它,因为它混淆了所问的内容。您专门询问pop %ebp,这在LEAVE 之外具有意义,这使得这个问题变得非常复杂。你在LEAVE 上的问题也是一个明确的骗局。所以我们需要知道这里的问题到底是什么,并保持其原子性。
  • 有趣的是,这几乎是对这个问题的直接欺骗*.com/questions/41907672/…
  • 仅供参考:movl %ebp, %esp 是 AT&T 语法,表示 esp=ebp。 movl %esp, %ebp 将是 Intel 语法等价物。

标签: c assembly


【解决方案1】:

LEAVEENTER 的对应物。 ENTER 指令通过首先将EBP 压入堆栈然后将ESP 复制到EBP 来设置堆栈帧,因此LEAVE 必须执行相反的操作,即将EBP 复制到ESP 和然后从堆栈中恢复旧的EBP

如果您想详细了解ENTERLEAVE 的工作原理,请参阅Intel's Software Developer's Manual 第1 卷中名为块结构语言的过程调用 部分。


enter n,0 完全等同于(并且应该替换为)

push  %ebp
mov   %esp, %ebp     # ebp = esp,  mov  ebp,esp in Intel syntax
sub   $n, %esp       # allocate space on the stack.  Omit if n=0

leave 完全等价于

mov   %ebp, %esp     # esp = ebp,  mov  esp,ebp in Intel syntax
pop   %ebp

enter 非常慢,编译器不使用它,但leave 很好。 (http://agner.org/optimize)。编译器确实使用leave,如果他们制作了一个堆栈帧(至少 gcc 是这样)。但是如果esp 已经等于ebp,那么只使用pop ebp 是最有效的。

【讨论】:

  • 好像不太懂EBP和ESP的概念。您能帮我解释一下这两个术语吗?
  • EBPESP 都只是 32 位通用寄存器。虽然ESP 有一个特殊的功能,即充当堆栈指针,并且它会被某些指令隐式修改(例如pushpopcall)。 EBP 按照惯例通常用作函数内的堆栈帧指针。
  • 为了扩展这个答案,LEAVEENTER 不相互依赖。也就是说,您可以有一个没有另一个,但关联的 ASM 必须存在(使用这两个指令或显式包含等效指令)。
  • 否(见我的回答)。基指针是栈底,栈指针是栈顶。
【解决方案2】:

popl 指令恢复基指针,movl 指令恢复堆栈指针。基指针是栈底,栈指针是栈顶。在离开指令之前,堆栈是这样的:

----Bottom of Caller's stack----
...
Caller's
Variables
...
Function Parameters
----Top of Caller's Stack/Bottom of Callee's stack----   (%ebp)
...
Callee's
Variables
...
---Bottom of Callee's stack----    (%esp)

movl %ebp %esp 之后,释放被调用者的堆栈,堆栈如下所示:

----Bottom of Caller's stack----
...
Caller's
Variables
...
Function Parameters
----Top of Caller's Stack/Bottom of Callee's stack----   (%ebp) and (%esp)

在恢复调用者堆栈的popl %ebp之后,堆栈如下所示:

----Bottom of Caller's stack----    (%ebp)
...
Caller's
Variables
...
Function Parameters
----Top of Caller's Stack----   (%esp)

enter 指令保存调用者堆栈的底部并设置基指针,以便被调用者可以分配他们的堆栈。

另外请注意,虽然大多数 C 编译器都以这种方式分配堆栈(至少在关闭优化的情况下),但如果您编写汇编语言函数,则可以根据需要使用相同的堆栈帧,但是你必须确保pop 堆栈中的所有东西都在你push 上,否则当你返回时你会跳转到垃圾地址(这是因为call <somewhere> 表示push <ret address>[或push %eip] 、jmp <somewhere>ret表示跳转到栈顶地址[或pop %eip]。%eip是保存当前指令地址的寄存器)。

【讨论】:

  • 对于您的 ASCII 图表来说,代码块可能比粗糙的反引号线看起来更好。最后一段的反引号中有一些 HTML 元素。顺便说一句,大多数 C 编译器根本不会浪费指令来制作堆栈帧。 -fomit-frame-pointer 多年来一直是 gcc 的默认值,即使在 32 位 x86 上也是如此(当然启用了优化)。
  • 我并没有说这是浪费指令,只是指出你不必这样做(很多 C 引用都说它是这样做的)。另外,我确实为 ASCII 图表使用了 标签。
  • 不,我的意思是堆栈溢出代码格式:选择您的块并按 control-k,或单击 {} 图标。回复:编译器:我在评论你的 虽然 C 编译器几乎总是以这种方式分配堆栈,但 编译器 不这样做。顺便说一句,您可能不应该对 pop 堆栈外的所有内容 使用代码格式,因为您不应该使用 pop 指令将 esp 增加超过 4,只是add $12, %esp 或其他。不过,如果您只需要一次,pop %ecx 通常会很好。
  • 我为您修复了格式以使用 Stack Overflow 的降价。它支持一些 HTML,但例如 < 不会在反引号中处理,并且可能根本不会被处理。欢迎来到 SO :)