在x86汇编中寄存器上使用的push / pop指令的功能是什么?

时间:2011-01-03 11:36:03

标签: assembly x86 stack terminology

当阅读有关汇编程序的文章时,我经常会遇到人们在写推送处理器的某个寄存器并弹出以后再次恢复它以前的状态。

  • 你怎么推注册?它在哪里推?为什么需要这个?
  • 这可归结为单处理器指令还是更复杂?

5 个答案:

答案 0 :(得分:110)

推送一个值(不一定存储在寄存器中)意味着将其写入堆栈。

弹出意味着将堆栈顶部的任何内容恢复到寄存器中。这些是基本的指示:

push 0xdeadbeef      ; push a value to the stack
pop eax              ; eax is now 0xdeadbeef

; swap contents of registers
push eax
mov eax, ebx
pop ebx

答案 1 :(得分:36)

以下是推送寄存器的方法。我假设我们正在谈论x86。

push ebx
push eax

它被推入堆栈。随着堆栈在x86系统中向下增长,ESP寄存器的值减小到推送值的大小。

需要保留这些值。一般用法是

push eax           ;   preserve the value of eax
call some_method   ;   some method is called which will put return value in eax
mov  edx, eax      ;    move the return value to edx
pop  eax           ;    restore original eax

push是x86中的单个指令,它在内部执行两项操作。

  1. 将推送值存储在ESP寄存器的当前地址。
  2. ESP寄存器减少到推送值的大小。

答案 2 :(得分:25)

它在哪里推进?

esp - 4。更确切地说:

  • esp减去4
  • 该值被推送到esp

pop改变了这一点。

System V ABI告诉Linux在程序开始运行时rsp指向一个合理的堆栈位置:What is default register state when program launches (asm, linux)?这是你应该经常使用的。

如何推送注册表?

Minimal GNU GAS示例:

.data
    /* .long takes 4 bytes each. */
    val1:
        /* Store bytes 0x 01 00 00 00 here. */
        .long 1
    val2:
        /* 0x 02 00 00 00 */
        .long 2
.text
    /* Make esp point to the address of val2.
     * Unusual, but totally possible. */
    mov $val2, %esp

    /* eax = 3 */
    mov $3, %ea 

    push %eax
    /*
    Outcome:
    - esp == val1
    - val1 == 3
    esp was changed to point to val1,
    and then val1 was modified.
    */

    pop %ebx
    /*
    Outcome:
    - esp == &val2
    - ebx == 3
    Inverses push: ebx gets the value of val1 (first)
    and then esp is increased back to point to val2.
    */

以上on GitHub with runnable assertions

为什么需要这样做?

这些说明确实可以通过movaddsub轻松实施。

他们认为存在这些指令的组合是如此频繁,以至于英特尔决定为我们提供它们。

这些组合如此频繁的原因是它们可以很容易地将寄存器的值保存并暂时恢复到存储器中,这样它们就不会被覆盖。

要了解问题,请尝试手动编译一些C代码。

一个主要的困难是决定每个变量的存储位置。

理想情况下,所有变量都适合寄存器,这是访问速度最快的内存(目前约为RAM 100x faster)。

但是,当然,我们可以轻松拥有比寄存器更多的变量,特别是嵌套函数的参数,因此唯一的解决方案是写入内存。

我们可以写入任何内存地址,但由于函数调用和返回的局部变量和参数适合一个很好的堆栈模式,这会阻止memory fragmentation,这是处理它的最佳方法。将其与编写堆分配器的精神错误进行比较。

然后我们让编译器为我们优化寄存器分配,因为这是NP完成的,也是编写编译器最困难的部分之一。此问题称为register allocation,与graph coloring同构。

当编译器的分配器被强制存储在内存而不是寄存器时,这就是所谓的溢出

这是归结为单处理器指令还是更复杂?

我们所知道的是,英特尔记录了pushpop指令,因此它们就是这方面的一条指令。

在内部,它可以扩展为多个微码,一个用于修改esp,另一个用于执行内存IO,并且需要多个周期。

但是单个push也可能比其他指令的等效组合更快,因为它更具体。

这主要是未记录的:

答案 3 :(得分:15)

推送和弹出寄存器在幕后等同于此:

undefined.balance

请注意,这是x86-64 At& t语法。

作为一对使用,可以将堆栈中的寄存器保存并稍后恢复。还有其他用途。

答案 4 :(得分:11)

几乎所有CPU都使用堆栈。程序堆栈是LIFO技术,支持硬件管理。

堆栈是通常在CPU内存堆顶部分配的程序(RAM)内存量,并且在相反方向上增长(在PUSH指令处堆栈指针减少)。插入堆栈的标准术语是 PUSH ,从堆栈中删除 POP

堆栈通过堆栈预期的CPU寄存器管理,也称为堆栈指针,因此当CPU执行 POP PUSH 时,堆栈指针将加载/存储寄存器或常量堆栈内存和堆栈指针将自动减少xor根据推入或插入(从)堆栈的字数增加。

通过汇编程序指令我们可以存储到堆栈:

  1. CPU寄存器和常量。
  2. 返回函数或的地址 程序
  3. 进/出的功能/程序 变量
  4. 本地的功能/程序 变量