【发布时间】:2012-09-12 07:14:03
【问题描述】:
我正在查看 C 代码,其中我必须找出在特定程序跟踪中使用的寄存器数量。每当遇到推送或弹出命令时,代码都会忽略存储 ESP 寄存器。我还参考了 X86 指令,而且上面写着“ESP,stack pointer,should not be used”.why??
【问题讨论】:
标签: assembly x86 stack-pointer
我正在查看 C 代码,其中我必须找出在特定程序跟踪中使用的寄存器数量。每当遇到推送或弹出命令时,代码都会忽略存储 ESP 寄存器。我还参考了 X86 指令,而且上面写着“ESP,stack pointer,should not be used”.why??
【问题讨论】:
标签: assembly x86 stack-pointer
ESP是push/pop依赖的栈指针。
ESP 是保留调用的(或者在调用者从堆栈中弹出 args 的约定中大致如此),但是您可以通过平衡推送和弹出来实现这一点,因此它最终指向调用者推送的返回地址ret 指令。
您实际上并没有保存/恢复 ESP,因为您在函数执行时让它指向堆栈。
[esp] 是 push ebx 写入的隐式目的地。 (将 ESP 减 4 后)。
您不能使用push/pop 来保存/恢复 ESP 并将其用于其他用途,因为 ESP 是指向您推送内容的位置的指针。强>
pop espis equivalent 到 mov esp, [esp];它取决于旧值,如果您将其销毁,则对“恢复”没有用处。
如果您想将 ESP 用作额外的临时寄存器,您可以将其保存在 mm0 中,或者(在非线程安全函数中)保存在静态存储位置中。你通常应该不这样做;了解您可以作为了解机器工作原理的练习非常有用。
任何信号处理程序或异常处理程序在 ESP 指向堆栈以外的某个位置时尝试运行都会导致大问题。 (但在多任务操作系统下的用户空间中,中断处理程序将在内核堆栈上运行。只有在内核代码中,硬件才会异步使用 ESP 以下的内存)。
【讨论】:
在 x86 上,ESP 是堆栈指针。最初,在 16 位 8088 和 8086 处理器上,该寄存器简称为 SP,用于 Stack Pointer。在 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 和 POP 指令是操作堆栈指针的指令,因为它们会将内容推送和弹出堆栈。
在 x86 上,堆栈在内存中总是向下增长,因此PUSH 将从堆栈指针中减去适当数量的字节(根据操作数的大小),而POP 将添加适当数量的字节.
CALL 和 RET 指令也隐式地操作堆栈指针。您可以通过阅读英特尔 x86 架构手册available here 找到更多详细信息。 x86 标签 wiki 中还有许多其他可用资源。
只有当ADD 或SUB 指令中用作目标操作数时,才会看到ESP 寄存器被显式操作。这些通常通过优化编译器、根据需要增加或减少堆栈指针以腾出额外空间来存储值或清理堆栈来插入函数的序言和结尾。它们的功能类似于PUSH 和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 指令之前恢复其原始值
【讨论】: