我正在浏览C代码,其中我必须弄清楚特定程序跟踪中使用的寄存器数量。无论何时遇到push或pop命令,代码都忽略了存储ESP寄存器。我还提到了X86指令,它写在那里“ESP,堆栈指针,不应该使用”。为什么?
答案 0 :(得分:2)
在x86上,ESP
是堆栈指针。最初,在16位8088和8086处理器上,寄存器简称为SP
,用于 S 大头钉 P ointer。当在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中有许多其他可用资源。
只有在ESP
或ADD
指令中将SUB
寄存器用作目标操作数时,才会看到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
的值,而只是使用collect_set
在堆栈中腾出空间。)
答案 1 :(得分:1)
不应将其用作通用寄存器。请谨慎使用它作为堆栈指针。例如。您可以使用'sub esp,...'指令为堆栈上的本地变量保留一些内存,但必须在ret指令之前恢复其原始值
答案 2 :(得分:0)
ESP 是推送/弹出所依赖的堆栈指针。
ESP是保留呼叫的(在调用方将args弹出堆栈的约定中,大约是这样),但是您可以通过平衡推送和弹出来实现这一点,因此它最终指向到达时调用方推送的返回地址ret
指令。
您实际上并没有保存/恢复ESP,因为您在执行函数时将其指向堆栈。
[esp]
是push ebx
写入的隐式目标。 (将ESP减4之后。
您不能使用push
/ pop
来保存/恢复ESP并将其用于其他用途,因为ESP 是您将内容推送到何处的指针。
pop esp
is equivalent至mov esp, [esp]
;它取决于旧值,如果销毁了它,则对“恢复”没有用。
如果您想将ESP用作额外的临时寄存器,则可以将其保存在mm0
中,或者(在非线程安全功能中)保存在静态存储位置中。通常,您应该不执行此操作;了解您可以可以作为练习来了解机器的工作原理非常有用。
在ESP指向堆栈以外的任何地方尝试运行的任何信号处理程序或异常处理程序都会引起很大的问题。 (但是在多任务操作系统下的用户空间中,中断处理程序将在内核堆栈上运行。只有在内核代码中,硬件才会异步使用ESP之下的内存。)