assembly 什么是pushl/popl %esp的汇编级表示?

1yjd4xko  于 2023-04-30  发布在  其他
关注(0)|答案(2)|浏览(130)

我试图理解堆栈指针寄存器的压入和弹出行为。在AT&T:

pushl %esp

popl %esp

请注意,它们将计算值存储回%esp
我在独立考虑这些指令,而不是按顺序。我知道存储在%esp中的值总是递增/递减之前的值,但我如何用汇编语言表示行为呢?这是我目前为止想到的。
对于pushl %esp(忽略FLAGS和对临时寄存器的影响):

movl %esp, %edx     1. save value of %esp
subl  $4, %esp      2. decrement stack pointer
movl %edx, (%esp)   3. store old value of %esp on top of stack

对于popl %esp

movl (%esp), %esp   You wouldn’t need the increment portion.

是这样吗?如果不是,我错在哪里?

ahy6op9u

ahy6op9u1#

正如它在Intel® 64 and IA-32 Architectures Developer's Manual: Combined Volumes中所说的那样(实际上是在第一卷中)。2,或HTML scraping at https://www.felixcloutier.com/x86/push):
PUSH ESP指令将ESP寄存器的值按执行该指令之前的状态推送。如果PUSH指令使用其中ESP寄存器用于计算操作数地址的存储器操作数,则在ESP寄存器递减之前计算操作数的地址。
关于pop esphttps://www.felixcloutier.com/x86/pop):
POP ESP指令在旧堆栈顶部的数据写入目标之前递增堆栈指针(ESP)。
pop 16(%esp)
如果ESP寄存器用作基址寄存器,用于寻址内存中的目标操作数,则POP指令在递增ESP寄存器后计算操作数的有效地址
所以是的,除了修改FLAGS和%edx之外,您的伪代码是正确的。

ivqmmu1c

ivqmmu1c2#

是的,这些序列是正确的,除了对FLAGS的影响,当然push %esp不会击败%edx。相反,如果你想把它分解成单独的步骤,想象一个内部临时1,而不是考虑一个push原语操作,它在做任何其他事情之前快照它的输入(源操作数)。
(类似地,pop DST可以被建模为pop %temp/mov %temp, DST,pop的所有效果在它评估并写入目的地之前完成,即使这是或涉及堆栈指针。)

push等价物,即使在ESP特殊情况下也有效

(In所有这些,我假设32位compat或保护模式,SS配置正常,堆栈地址大小匹配模式,如果它甚至可能不是这种情况。与%rsp等效的64位模式与-8/+8的工作方式相同。16位模式不允许(%sp)寻址模式,因此您必须将其视为伪代码。)

#push SRC         for any source operand including %esp or 1234(%esp)
   mov  SRC, %temp
   lea  -4(%esp), %esp         # esp-=4 without touching FLAGS
   mov  %temp, (%esp)

mov SRC, %temp;push %temp
或者因为我们描述的是一个不可中断的事务(一条push指令),
我们don't need to move ESP before storing

#push %REG              # or immediate, but not memory source
   mov  %REG, -4(%esp)
   lea  -4(%esp), %esp

(This更简单的版本不会与内存源进行真实的的汇编,仅限于寄存器或立即,以及如果中断或信号处理程序在mov和莱亚之间运行,则不安全。在真实的汇编中,具有两种显式寻址模式的mov mem, mem是不可编码的,但push (%eax)是可编码的,因为内存目的地是隐式的。您可以将其视为伪代码,即使是内存源。但是在临时内存中进行快照是一种更真实的内部模型,比如第一个块或mov SRC, %temp/push %temp。)
如果你说的是在一个真实的程序中实际使用这样的序列,我不认为有一种方法可以 * 精确地 * 复制push %esp而不使用临时寄存器(第一个版本),或者(第二个版本)禁用中断或使用带有红色区域的ABI。(类似于x86-64 System V的非内核代码,因此您可以复制push %rsp。)

pop等价:

#pop DST   works for any operand
  mov  (%esp), %temp
  lea  4(%esp), %esp      # esp += 4 without touching FLAGS
  mov  %temp, DST         # even if DST is %esp or 1234(%esp)

pop %temp/mov %temp, DST。这准确地反映了DST是涉及ESP的内存寻址模式的情况:使用增量后ESP * 的值。我用push $5验证了Intel的文档;pop -8(%esp) .当我在Skylake CPU上的GDB中单步执行push时,它将dword 5复制到push写入的dword的正下方。如果-8(%esp)地址计算在该指令执行之前使用ESP进行,则会有4字节的间隙。
pop %esp的特殊情况下,是的,它会逐步增加,简化为:

#pop %esp  # 3 uops on Skylake, 1 byte
   mov  (%esp), %esp             # 1 uop on Skylake.  3 bytes of machine-code size

英特尔手册有误导性伪代码

英特尔在其指令集手册条目的操作部分中的伪代码(SDM vol.2)不能准确地反映堆栈指针的特殊情况。只有描述部分中的额外段落(在@nrz的回答中引用)才能做到这一点。
https://www.felixcloutier.com/x86/pop显示(对于StackAddrSize = 32和OperandSize = 32)加载到DEST,然后 * 递增ESP

DEST ← SS:ESP; (* Copy a doubleword *)
     ESP ← ESP + 4;

但这对于pop %esp来说是误导性的,因为它意味着ESP += 4发生在ESP = load(SS:ESP)之后。正确的伪代码将使用

if ... operand size etc.
     TEMP ← SS:ESP; (* Copy a doubleword *)
     ESP ← ESP + 4;

 ..
 // after all the if / else size blocks:
 DEST ← TEMP

英特尔对其他指令(如pshufb)也有这种权利,其中伪代码以TEMP ← DEST开始,以快照读写目标操作数的原始状态。
类似地, www.example.com 。只有文本描述部分中的额外段落才能正确处理这种特殊情况。

AMD手册第三册:通用和系统指令(2021年3月)在这方面同样错误(我的重点):

将堆栈指针(SS:rSP)指向的值复制到指定的寄存器或内存位置**,然后**将rSP递增2(16位弹出)、4(32位弹出)或8(64位弹出)。
与Intel不同,它甚至没有记录弹出堆栈指针本身或涉及rSP的内存操作数的特殊情况。至少这里没有,在push rsppush esp上搜索没有找到任何东西。
(AMD根据SS选择的当前堆栈大小属性,使用rSP表示SP / ESP / RSP。)
AMD没有像英特尔那样的伪代码部分,至少不是用于像push/pop这样的简单指令。(pusha有一个。)

脚注1:这甚至可能发生在一些CPU上(尽管我不这么认为)。例如,在Skylake上,Agner Fog measuredpush %esp作为前端的2个uop与1个微熔丝存储器,用于推送任何其他寄存器。

我们知道英特尔CPU确实有一些寄存器被重命名,就像架构寄存器一样,但这些寄存器只能通过微码访问。例如https://blog.stuffedcow.net/2013/05/measuring-rob-capacity/提到“一些用于内部使用的额外架构寄存器。“所以mov %esp, %temp/push %temp理论上可能是它解码的方式。

但一个更可能的解释push %esp指令的长 * 序列 * 中额外测量的uop只是堆栈同步uop,就像我们在推送/弹出操作后OoO后端显式读取ESP时得到的任何情况一样。例如push %eax/mov %esp, %edx也会导致堆栈同步uop。(“stack engine”是避免pushesp -= 4部分需要额外uop的原因)

push %esp有时是有用的,e.例如,推送您刚刚保留的某个堆栈空间的地址:

sub   $8, %esp
  push  %esp
  push  $fmt         # "%lf"
  call  scanf
  movsd 8(%esp), %xmm0

  # add $8, %esp    # balance out the pushes at some point, or just keep using that allocated space for something.  Or clean it up just before returning along with the space for your local var.

pop %esp在Skylake上花费3个uops,一个负载(p23)和两个ALU用于任意整数ALU端口(2 p0156)。所以它的效率更低,但它基本上没有用例。您不能有效地保存/恢复堆栈上的堆栈指针;如果你知道如何到达你保存它地方,你可以用add还原它。

相关问题