我正在查看 C 代码,其中我必须找出在特定程序跟踪中使用的寄存器数量。每当遇到推送或弹出命令时,代码都会忽略存储 ESP 寄存器。我还提到了 X86 指令,它写在那里“ESP,堆栈指针,不应该使用”。为什么?
3 回答
在 x86 上,ESP
是堆栈指针。最初,在 16 位 8088 和 8086 处理器上,该寄存器被简单地称为SP
S tack P指针。在 386 处理器中添加 32 位支持时,E
前缀(用于“扩展”)被添加到所有寄存器名称中,所以它变成了ESP
. 在当前模式下,堆栈指针始终具有与处理器的本机字长相同的位宽。也就是说,如果您在 32 位保护模式下执行,堆栈指针将是 32 位宽并存储在ESP
. 如果您在 16 位实模式下执行,堆栈指针将为 16 位宽并存储在SP
.
x86 架构的 64 位扩展(也称为 AMD64、x86-64 或只是 x64)将寄存器扩大到 64 位并添加了R
前缀。因此,RSP
寄存器包含堆栈指针,在长(64 位)模式下执行时为 64 位宽。
尽管此寄存器在概念上与其他寄存器(EAX
、ECX
、EDX
、EBX
、ESI
、EDI
和EBP
)相似,但它不能以等效方式使用。它仅用于保存堆栈指针,不能用作通用寄存器。
您不显式推送或弹出堆栈指针的原因是因为这是由其他指令隐式完成的。实际上,PUSH
andPOP
指令是操纵堆栈指针的指令,因为它们将东西压入堆栈并将其弹出。
在 x86 上,堆栈在内存中总是向下增长,因此PUSH
会从堆栈指针中减去适当数量的字节(根据操作数的大小),同时POP
会添加适当数量的字节。
CALL
and指令还隐式地RET
操作堆栈指针。您可以通过阅读此处提供的 Intel x86 架构手册找到更多详细信息。x86标签 wiki中还有许多其他可用资源。
唯一一次您会看到ESP
寄存器被显式操作是当它用作ADD
orSUB
指令中的目标操作数时。这些通常通过优化编译器、根据需要增加或减少堆栈指针以腾出额外空间来存储值或清理堆栈来插入函数的序言和结尾。它们的功能类似于PUSH
and POP
,除了它们可以具有连续多次推送和弹出的效果。例如:
push eax
push eax
push eax
push eax
...
pop eax
pop eax
pop eax
pop eax
可以简单地替换为:
sub esp, 16
...
add esp, 16
(假设您实际上并没有尝试存储的值,EAX
而只是使用PUSH
来在堆栈上腾出空间)。
它不应该用作通用寄存器。随意将其用作堆栈指针,并小心谨慎。例如,您可以使用 'sub esp, ...' 指令为堆栈上的本地变量保留一些内存,但您必须在 ret 指令之前恢复其原始值
ESP是push/pop 依赖的堆栈指针。
ret
ESP 是保留调用的(或者在调用者将 args 从堆栈中弹出的约定中大致如此),但是您可以通过平衡推送和弹出来实现这一点,因此当您到达指令时,它最终指向调用者推送的返回地址。
您实际上并没有保存/恢复 ESP,因为您在函数执行时让它指向堆栈。
[esp]
是push ebx
写入的隐含目的地。(将 ESP 减 4 后)。
您不能使用push
/pop
来保存/恢复 ESP 并将其用于其他用途,因为 ESP是指向您推送内容的位置的指针。
pop esp
相当于mov esp, [esp]
; _ 它取决于旧值,如果您将其销毁,则对“恢复”没有用处。
如果您想将 ESP 用作额外的临时寄存器,您可以将其保存在mm0
, 或(在非线程安全函数中)静态存储位置。您通常不应该这样做;了解您可以作为了解机器如何工作的练习非常有用。
任何在 ESP 指向堆栈以外的地方时尝试运行的信号处理程序或异常处理程序都会导致大问题。(但在多任务操作系统下的用户空间中,中断处理程序将在内核堆栈上运行。只有在内核代码中,硬件才会异步使用低于 ESP 的内存)。