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


100

在阅读有关汇编程序的信息时,我经常遇到人们写信,他们压入处理器的某个寄存器并稍后再次弹出以恢复其先前的状态。

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

3
警告:所有当前答案均以Intel的汇编语法给出;推入-弹出在AT&T语法例如使用后修复程序等bwl,或q以表示存储器的被操纵的大小。例如:pushl %eaxpopl %eax
霍肯(Hawken)2012年

5
@hawken在大多数可以吞并AT&T语法的汇编程序中(尤其是gas),如果可以从操作数大小中推导出操作数大小,则可以省略大小缀。对于您给出的示例,%eax就是这种情况,大小总是32位。
冈瑟·皮埃兹

Answers:


153

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

弹出意味着将堆栈顶部的所有内容恢复寄存器中。这些是基本说明:

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

5
推送和弹出操作的显式操作数r/m不仅是注册,还可以注册push dword [esi]。甚至pop dword [esp]加载后将相同的值存储回相同的地址。(github.com/HJLebbink/asm-dude/wiki/POP)。我之所以仅提及这一点,是因为您说的“不一定是寄存器”。
彼得·科德斯

2
您还可以pop进入一个内存区域:pop [0xdeadbeef]
SS Anne

您好,push / pop和pushq / popq有什么区别?我在
macOS

45

这是您推送寄存器的方式。我假设我们正在谈论x86。

push ebx
push eax

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

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

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

Apush是x86中的一条指令,它在内部执行两项操作。

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

@vavan刚刚发送了一个解决问题的请求
jgh fun-run

1
@vavan:我进行了编辑。
Nate Eldredge

38

它在哪里推?

esp - 4。更确切地说:

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

pop 扭转这一点。

rsp当程序开始运行时,System V ABI告诉Linux指向一个合理的堆栈位置:程序启动时(asm,linux)的默认寄存器状态是什么?这通常是您应该使用的。

你怎么能推寄存器?

最小的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.
    */

上面的代码在GitHub上带有可运行的断言

为什么需要这个?

这是事实,这些指令可以通过轻松实现movaddsub

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

这些组合之所以如此频繁,是因为它们使得可以轻松地将寄存器的值临时保存和恢复到内存中,从而不会被覆盖。

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

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

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

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

我们可以写入任何内存地址,但是由于函数调用和返回的局部变量和参数适合一个好的堆栈模式,因此可以防止内存碎片,这是处理内存的最佳方法。将其与编写堆分配器的精神错乱相提并论。

然后,让编译器为我们优化寄存器分配,因为这已完成NP,并且是编写编译器中最难的部分之一。这个问题称为寄存器分配图形着色是同构的。

当强制编译器的分配器将内容存储在内存中而不是仅将寄存器存储在内存中时,这称为溢出

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

我们唯一可以确定的是,英特尔记录了pushpop指令,因此从某种意义上来说它们是一条指令。

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

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

这大部分是未记录的:


4
您无需猜测如何push/pop解码为uops。借助性能计数器,可以进行实验测试,而Agner Fog做到了这一点并发布了说明表。奔腾M和更新的CPU具有单UOP push/pop感谢堆栈引擎(SEE昂纳的microarch PDF)。由于采用了Intel / AMD专利共享协议,其中包括最近的AMD CPU。
彼得·科德斯

@PeterCordes太棒了!英特尔是否记录了性能计数器来对微操作进行计数?
西罗Santilli郝海东冠状病六四事件法轮功

而且,如果实际上使用了任何从regs溢出的局部变量,它们通常在L1缓存中仍然很热。但是从寄存器读取实际上是免费的,零延迟。因此,它要比L1缓存无限快,具体取决于您要如何定义术语。对于溢出到堆栈中的只读局部变量,主要成本只是额外的负载(有时是内存操作数,有时带有单独的mov负载)。对于溢出的非const变量,存储转发往返有很多额外的延迟(与直接转发相比,额外的〜5c且存储指令并不便宜)。
彼得·科德斯

是的,在几个不同的流水线阶段(发出/执行/退休)有总计数的计数器,因此您可以计算融合域或未融合域。例如,请参见此答案。如果我现在正在重写该答案,则可以使用ocperf.py包装脚本来获得计数器的简单符号名称。
彼得·科德斯

23

推入和弹出寄存器在后台等效于此:

push reg   <= same as =>      sub  $8,%rsp        # subtract 8 from rsp
                              mov  reg,(%rsp)     # store, using rsp as the address

pop  reg    <= same as=>      mov  (%rsp),reg     # load, using rsp as the address
                              add  $8,%rsp        # add 8 to the rsp

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

成对使用,这使您可以将寄存器保存在堆栈中,并在以后还原。也有其他用途。


4
是的,这些序列可以正确模拟推入/弹出。(除了推/弹出不影响标志)。
彼得·科德斯

2
您最好使用lea rsp, [rsp±8]代替add/sub来更好地模拟push/pop对标志的影响。
罗斯兰

12

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

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

堆栈是通过堆栈专用的CPU寄存器(也称为堆栈指针)来管理的,因此,当CPU执行POPPUSH时,堆栈指针会将寄存器或常量加载/存储到堆栈存储器中,并且堆栈指针将根据被压入的字数自动减少或增加或从堆栈弹出。

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

  1. CPU寄存器以及常量。
  2. 函数或过程的返回地址
  3. 函数/过程输入/输出变量
  4. 函数/过程局部变量。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.