49

我在汇编中使用了堆栈,但我不知道 push ebp 和 pop ebp。

.intel_syntax noprefix

.include "console.i"

.text

askl:   .asciz  "Enter length: "
askb:   .asciz  "Enter breadth: "
ans:    .asciz  "Perimeter = "

_entry:

    push    ebp     # establishing stack-frame
    mov ebp, esp
    sub esp, 12

    Prompt  askl
    GetInt  [ebp-4]     # length
    Prompt  askb
    GetInt  [ebp-8]     # breadth

    mov eax, [ebp-4]    # eax = l
    add eax, [ebp-8]    # eax = l + b
    add eax, eax    # eax = 2 * (l + b)
    mov [ebp-12], eax

    Prompt  ans
    PutInt  [ebp-12]
    PutEoL

    mov esp, ebp
    pop ebp     # unwinding stack-frame
    ret

.global _entry

.end
4

2 回答 2

81

也许你想知道这个:

push    ebp
mov ebp, esp
sub esp, 12

这些行被称为汇编函数序言。前 2 条指令保存前一个基指针 (ebp) 并将 EBP 设置为指向堆栈上的该位置(在返回地址的正下方)。这将 EBP 设置为帧指针

sub esp,12行正在为函数中的局部变量节省空间。该空间可以使用诸如[ebp - 4]. 函数 args 的任何推送/弹出,或call推送返回地址的指令本身,或我们调用的函数的堆栈帧,都将发生在当前 ESP 的此保留空间之下。

最后你有:

mov esp, ebp         ; restore ESP
pop ebp              ; restore caller's EBP
ret                  ; pop the return address into EIP

这与序言(即尾声)相反,因此可以恢复之前的上下文。这有时被称为“拆除”堆栈框架。

(EBP 在所有标准 x86 调用约定中都是非易失性的,即调用保留:如果修改它,则必须恢复调用者的值。)

leave指令与这两条指令完全相同,一些编译器使用该指令来节省代码大小。(enter 0,0非常慢且从未使用过(https://agner.org/optimize/);leave与 mov + pop 一样有效。)


请注意,使用 EBP 作为帧指针是可选的,编译器不会对优化代码中的大多数函数执行此操作。相反,他们保存单独的元数据以允许堆栈展开/回溯。

于 2010-09-03T17:35:57.560 回答
51

ebp被称为基指针或帧指针。在进入您的函数时,您推送它(以保存调用函数的值)。esp然后,将堆栈指针复制到ebp中,以便ebp现在指向函数的堆栈帧。在你的函数结束时,你会弹出ebp,以便恢复调用函数的值。

为了澄清到底发生了什么 -push指令将指定寄存器(ebp在这种情况下)中的值放入堆栈,并将堆栈指针递减适当的量。操作是相反的pop——它增加堆栈指针并从堆栈中获取一个值并将其放入指定的寄存器中。

于 2010-09-03T17:26:57.610 回答