在第 3.4.2 节中,IA32 popl 指令被描述为从 将堆栈顶部移动到目标寄存器,然后递增堆栈 指针。所以,如果我们有一条 popl REG 形式的指令,它将是等价的 代码顺序:
movl (%esp),REG //Read REG from stack
addl $4,%esp //Increment stack pointer
A.根据问题4.7中的分析,这段代码顺序是否正确 描述指令 popl %esp? 的行为解释一下。
B.如何重写代码序列才能正确描述两者 REG 是 %esp 以及任何其他寄存器的情况?
问题4.7:
下面的汇编代码函数让我们可以确定指令的行为 IA32 的 popl %esp:
1 .text
2 .globl poptest
3 poptest:
4 pushl %ebp
5 movl %esp, %ebp
6 pushl $0xabcd Push test value
7 popl %esp Pop to stack pointer
8 movl %esp, %eax Set popped value as return value
9 leave Restore stack and frame pointers
10 ret
我们发现这个函数总是返回0xabcd。这对 popl %esp 的行为意味着什么?还有哪些其他 Y86 指令会具有完全相同的行为?
对于第一个问题中的代码序列是否正确描述了指令 popl %esp 的行为,我一直很困惑。起初我以为是的,因为它从堆栈中获取 REG,就像 popl 一样返回值(我可能在这一点上是错误的),然后它将 esp 增加 4 以从堆栈中删除该实例。
但后来我遇到了这样一句话:“在旧堆栈顶部的数据写入目标之前,popl %esp 指令会递增堆栈指针。”
如果是这种情况,那么在将值放入目标寄存器之前应该先将 esp 加 4,从而使得
movl (%esp),REG //Read REG from stack
addl $4,%esp //Increment stack pointer
popl %esp 的错误表示。
任何人都可以澄清它是否确实没有正确描述行为或 popl %esp?
最佳答案
事实上,这与 pop
是错误的等价形式。有趣的是,这也是英特尔在官方指令集引用中使用的一个。但至少他们确实在文本中把事情说清楚了。更好的等效代码是:
leal 4(%esp), %esp ; use lea to preserve flags (thanks to @Sparky)
movl -4(%esp), REG
然而,这只是逻辑上的等价物,因为实际上有人(例如中断或信号处理程序)可能会破坏两条指令之间堆栈上的值。原始代码不会遇到这个问题。
请注意,这也适用于内存操作数,手册中说:“如果 ESP 寄存器用作寻址内存中目标操作数的基址寄存器,则 POP 指令计算操作数的有效地址在它增加 ESP 寄存器之后。”。我们也解决了这个问题。
关于assembly - popl %esp 的替代方案,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26389596/