为什么在使用 PUSH 或 POP 指令时不鼓励使用 ESP 寄存器?

arc*_*s50 2 x86 assembly stack-pointer

我正在检查 C 代码,其中我必须计算出特定程序跟踪中使用的寄存器数量。每当遇到推送或弹出命令时,代码都会忽略存储 ESP 寄存器。我还引用了 X86 指令,它那里写着“ESP,堆栈指针,不应该使用”。为什么?

Cod*_*ray 6

在 x86 上,ESP是堆栈指针。最初,在 16 位 8088 和 8086 处理器上,寄存器简称为,SP堆栈指针当 386 处理器中添加 32 位支持时,E所有寄存器名称都会添加前缀(表示“扩展”),因此它变成了ESP. 堆栈指针始终具有与当前模式下处理器的本机字大小相同的位宽度。也就是说,如果您在 32 位保护模式下执行,堆栈指针将是 32 位宽并存储在ESP. 如果您在 16 位实模式下执行,堆栈指针将是 16 位宽并存储在SP.

x86 架构的 64 位扩展(也称为 AMD64、x86-64 或简称 x64)将寄存器扩展至 64 位并添加了前缀R。因此,该RSP寄存器包含堆栈指针,在长(64 位)模式下执行时该指针为 64 位宽。

尽管该寄存器在概念上与其他寄存器(EAXECXEDXEBXESIEDIEBP)相似,但它不能以等效的方式使用。它设计用于保存堆栈指针,不能用作通用寄存器。

您不显式压入或弹出堆栈指针的原因是因为这是由其他指令隐式完成的。事实上,PUSHPOP指令是操纵堆栈指针的指令,因为它们将内容推入堆栈或弹出堆栈。

在x86上,堆栈在内存中总是向下增长,因此PUSH将从堆栈指针中减去适当的字节数(根据操作数的大小),同时POP会添加适当的字节数。

CALL和指令RET还隐式操作堆栈指针。您可以通过阅读此处提供的Intel x86 架构手册来了解更多详细信息。标签 wiki中还有许多其他可用资源。

唯一一次您会看到寄存器被显式操作是在or指令ESP中将其用作目标操作数时。这些通常通过优化编译器、根据需要递增或递减堆栈指针来插入函数的序言和尾声,以腾出额外的空间来存储值或清理堆栈。它们的功能与和类似,只是它们可以具有连续多次推入和弹出的效果。例如:ADDSUBPUSHPOP

push eax
push eax
push eax
push eax
...
pop  eax
pop  eax
pop  eax
pop  eax
Run Code Online (Sandbox Code Playgroud)

可以简单地替换为:

sub esp, 16
...
add esp, 16
Run Code Online (Sandbox Code Playgroud)

(假设您实际上并没有尝试存储的值EAX,而只是使用PUSH来在堆栈上腾出空间)。