x86中,LEA是唯一一个带有内存操作数但不访问内存的指令吗?

7
我正在使用来自the bastard的 x86 反汇编库 libdis,并尝试查找哪些指令访问内存。
参考这两个指令:
mov eax, [ebx + 10]
lea eax, [ebx + 10]

libdis中,这两种情况都列为指令类型insn_mov,并且地址操作数在两种情况下具有相同的标志。因此,我唯一能判断内存是否被访问的方法是查看指令助记符。
因此我的问题是:LEA是唯一使用不会实际访问内存的内存操作数的指令吗?任何参考链接都可以。

我想是这样的...不过不太确定。 - user541686
荣誉提及bt/btc/btr/bts,这些指令确实访问内存,但不一定在指令中指定的有效地址处访问。 - Nate Eldredge
3个回答

12
prefetch指令系列(包括prefetcht1、prefetcht2、prefetcht3和prefetchnta)要求处理器将那些即将需要的内存行拉入高速缓存中。然而,英特尔的文档明确表示,错误地址传递给预取指令也不会导致任何故障。这是为了使软件能够在未经检查的情况下传递潜在的越界地址,以便数据可以在执行这些检查的同时被预先获取。
LEA不同,预取指令也没有“输出”。

10
Intel有一条multi-byte "NOP"指令,操作码为0F 1F /0,它采用内存寻址操作数。根据英特尔的手册:
多字节NOP指令不会改变寄存器的内容,也不会执行内存操作。
评论中的讨论是关于将nop的操作码字节放在未映射页面的末尾,并且如果无法读取包括ModR/M和位移字节在内的完整指令,则代码提取故障。这与此问题无关。
您可以将长NOP视为以下方式运作:
  • 指令解码硬件知道如何找到需要使用ModRM(这意味着可选的SIB和/或位移)的指令的结尾。
  • 基于特定的操作码是NOP,CPU的其余部分不会对由ModRM编码的寻址模式进行任何操作。
这使得软件可以通过使用各种寻址模式和前缀来编码多字节NOP,而无需除识别一个以上的操作码为nop之外的任何特殊硬件即可处理它。 整个指令格式与大多数指令相同。

2
根据Peter Ferrie所说,它似乎确实可以访问内存:“有趣的是,尽管它的名称是“无操作”,但如果Mod / RM字节告诉它这样做,它确实可以访问内存,因此这个“无操作”可能会引起页面错误。 总之,它并不完全是NOP。” - Igor Skochinsky
1
我看起来得到了不同的故事:http://mail.openjdk.java.net/pipermail/hotspot-compiler-dev/2010-September/003881.html。我的代码生成器在可以是任意值的上下文中生成建议的涉及EAX的代码。如果这些代码确实读取内存,我会遇到陷阱。但是我使用这个没有遇到任何问题。Ferrie或Dabbs其中一个是错误的。 - Ira Baxter
这与Mod/RM如何解码有关。将其放置在页面末尾,使其跨越到下一页,例如使用偏移量。使下一页无法访问。现在尝试执行它。指令获取将导致页面错误,即使CPU应该“知道”它是nop并且偏移量对其执行无关紧要。 - peter ferrie
3
这句话的意思是CPU在执行指令前有规则要求获取完整的指令,这是一个不相关的话题。问题是,如果NOP尝试获取由mod/rm字节形成的地址,你是否有除刚才给出的示例以外的具体证据? - Ira Baxter
1
@IgorSkochinsky:幸运的是,英特尔的手册消除了那个误导性引用所带来的任何疑虑。我编辑了Ira的答案以澄清。 - Peter Cordes
显示剩余2条评论

4

不需要有效地址

prefetch/ prefetchw 和其他答案中提到的nop

任何使用全零掩码进行AVX512掩码加载或存储的指令,例如vmovaps [rdi]{k1}, zmm1。或者使用AVX的vmaskmovps/vpmaskmovdAVX2 gather/AVX512 gather或scatter。所有这些指令都对无效地址执行故障抑制。(速度较慢,但没有实际的内存访问。)

invlpg m8接受一个ModRM,它指定了一个虚拟地址。(特权指令)。它不会从该地址中加载数据,而是使该地址的TLB条目以及存储在页行走器中的更高级别的页目录条目无效。

verr/verw - 验证读取或写入段:它们采用ModRM寻址模式,并检查地址是否符合段限制,设置FLAGS。(并且有最近的微代码更新。verw还清除内部CPU缓冲区,以便操作系统可用于缓解L1TF/MDS漏洞)。

rep cmpsb或其他RCX = 0的字符串指令执行零次迭代,不访问隐式内存操作数[RDI][RSI]。我认为这意味着即使出现错误的地址,它也不会出错。微码肯定足够慢了。

cldemote(Intel Tremont中新引入的指令)- 是一种相反于预取的性能提示,用于将数据推送到共享L3以加速另一个核心对其的首次访问。在不支持此功能的硬件上,该指令解码为NOP。预取不会因地址无效而故障(尽管它们可能在需要微代码帮助抑制故障时变慢);手册对于cldemote没有100%明确的说明,但确实将其称为一种推测提示。

在某些处理器实现中,CLDEMOTE指令可能会在页表中设置A位但不设置D位。

如果在缓存中未找到该行,则该指令将被视为NOP。

MPX bndcl bnd, r/m64 / bndcu / bndcn / bndmk - 内存源形式内置了LEA:操作部分的伪代码甚至说 TEMP ← LEA(mem);。寄存器源形式直接使用寄存器值作为地址。正如手册所说,该指令不会引起任何内存访问,并且不读/写标志位。(它会在越界时引发#BR异常)。请注意,MPX已经被弃用。


需要有效地址但并非严格的加载或存储操作。

clflush / clflushopt / clwb都采用内存操作数来指定要刷新或回写到DRAM的缓存行,对于使用非易失性DIMMs以确保提交到NV存储器的情况很有用(因此与cldemote不同,这些操作不仅仅是CPU可以随意丢弃的提示)。它们确实需要一个有效的虚拟地址,并且确实会影响相应缓存行的MESI状态。但如果L1d缓存中没有该缓存行,则不会将其带入并再次刷新。我认为它会驱逐所有核心的缓存,因此一个核心在一条线上不断使用clflush会影响另一个核心对其进行读写。

CLFLUSHOPT指令可以在所有权限级别下使用,并且与字节加载相关的所有许可检查和故障都适用(此外,CLFLUSHOPT指令允许刷新执行-只读段中的线性地址)。类似于加载操作,CLFLUSHOPT指令在页表中设置A位,但不设置D位。

MONITOR将内存地址作为隐式DS:RAX/EAX/AX,而不是编码在ModRM中。它实际上并没有从中加载数据,只是设置核心以注意到其他核心何时更改该内存。然而,它的工作原理类似于加载操作。(假设将行置于MESI共享状态,以便在写入之前,它可以注意到另一个核心使其无效。)

MONITOR指令按照加载操作的顺序与其他内存事务排序。该指令受到与字节加载相关的许可检查和故障的约束。类似于加载操作,MONITOR在页表中设置A位,但不设置D位。

umonitor(用户空间版本)与此相同。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接