x86汇编寄存器中使用的push / pop指令的function是什么?

在阅读汇编程序时,我经常遇到一些人,他们会推送一个处理器的某个寄存器,稍后再popup来恢复它以前的状态。

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

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

popup意味着将堆栈顶部的任何内容恢复registry中。 这些是基本的说明:

 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 

这是你如何推注册。 我假设我们正在谈论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寄存器减小到推入值的大小。

它在哪里推?

esp - 4 。 更确切地说:

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

pop反转这一点。

System V ABI告诉Linux,当程序开始运行时, rsp指向一个合理的堆栈位置: https : //stackoverflow.com/a/32967009/895245这是你应该经常使用的。

你怎么能推注册?

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

上面有断言 。

为什么这需要?

这些指令确实可以通过movaddsub轻松实现。

他们之所以存在,是因为这些指令组合太频繁了,英特尔决定为我们提供这些指令。

这些组合之所以如此频繁,是因为它们可以很容易地将寄存器的值暂时保存并恢复到内存中,从而不会被覆盖。

要理解这个问题,请手动编译一些C代码。

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

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

但是,当然,我们可以比寄存器更容易拥有更多的variables,特别是嵌套函数的参数,所以唯一的解决scheme是写入内存。

我们可以写任何内存地址,但是由于函数调用和返回的局部variables和参数适合一个很好的堆栈模式,这可以防止内存碎片 ,这是处理它的最好方法。 比较这与写一个堆分配器的疯狂。

然后我们让编译器为我们优化寄存器分配,因为这是NP完成的,也是编写编译器最困难的部分之一。 这个问题叫做寄存器分配 ,与图着色是同构的。

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

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

我们所能确定的是,英特尔提供了一个pushpop指令,所以它们是这个意义上的一个指令。

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

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

这大部分是非文件logging的:

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

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

堆栈通过堆栈预期的CPU寄存器进行pipe理,也称为堆栈指针,所以当CPU执行POPPUSH时 ,堆栈指针会将一个寄存器或常量加载/存储到堆栈内存中,堆栈指针将自动减less。或者从(堆叠)中取出。

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

  1. CPU寄存器和常量。
  2. 返回函数或过程的地址
  3. 函数/程序input/输出variables
  4. 函数/程序局部variables。

压入和popup寄存器在幕后等同于:

 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语法。

作为一对使用,这可以让您保存在堆栈上的寄存器,并在以后恢复。 还有其他用途。