为什么CPU推测执行不会导致OOB程序崩溃?

Why CPU speculative execution does not cause OOB program crash?

问题源于阅读Spectre attack paper。如果我理解正确的话,攻击源于 CPU 启发式推测执行(错误的)代码分支的可能性。 考虑示例(在 C 中):

int arr[42];
if (i < 42) {
    int j = arr[i];
}

如果我对论文的理解正确,int j = arr[i] 即使在 i >= 42 时也可以(在某些情况下)推测执行。我的问题是 - 当我访问超出其范围的数组时,我的程序经常会崩溃([=24= 上的分段错误]、Windows 上的 "The program performed an illegal operation" 错误)。

为什么在数组越界访问时推测执行不会导致程序崩溃?

关键在于,在现代 CPU 年代,动词 executing 并不代表您认为的意思。

执行指令是计算其输出和副作用(如果有)的行为。
但是,这 不会更改 程序状态。
乍一看这似乎很难理解,但实际上并没有什么奇怪的。

CPU 有一个相当大的内部内存,由它的所有寄存器组成,大部分内存是 程序员可见的,那部分是被称为建筑状态
体系结构状态 (AS) 是 CPU 手册中记录的内容,以及由一系列指令(例如程序)更改的内容。

由于更改 AS 只能通过 ISA(手册)中给出的语义进行,并且 ISA 指定了串行语义(指令按程序顺序一个接一个地完成),因此不允许并行。
然而,现代 CPU 拥有大量可以独立完成工作的资源(称为执行单元)。

为了利用所有这些资源,CPU 的前端(负责从内存层次结构中读取指令并将其提供给执行单元的部分)能够到达、解码和输出每个周期多于一条指令。
前端和后端(执行单元所在的位置)之间的边界不再真正处理指令(而是使用 uops),但这是 x86 CISC 的麻烦。

所以现在 CPU 一次给 "execute" 4/6 微指令,但是如果 ISA 是串行的,除了排队这些微指令它还能做什么?
好吧,前端是这样设计的,这些 uops 不在 AS 上运行,而是在 shadow state(SS,我的术语在这里)上,它们的操作数被重命名,由部分组成CPU.
的巨大无形记忆 并行或无序更改都可以,因为它不是 AS。
这就是执行:改变 SS。

真的值得吗?毕竟重要的是AS。
好吧,将SS转移到AS,相对于执行来说,真的是很快,所以值得。
这是"renaming back"(反转之前的重命名)的事情,它被称为指令的retiring

其实退休还不止这些
由于执行不会影响 AS,因此副作用也应该不会影响它。
这些副作用包括异常,但是推测性地处理异常太麻烦了(需要协调很多资源)所以异常处理延迟直到退休
这也具有在处理异常时拥有正确 AS 的优势,以及仅在实际必须出现时才引发异常的优势。

推测执行的要点是打赌,CPU打赌指令序列不会产生任何异常(包括页面错误),从而执行它取消大多数检查(我无法排除,无论如何都不会进行某些检查)从而获得很多优势。
当需要取消这些指令时,将检查投注,如果有任何失败,SS 将被丢弃。

这就是推测性执行不会使您的程序崩溃的原因。

Spectre 所依赖的事实是,推测性执行确实在某种意义上改变了 AS:缓存没有失效(再次出于性能原因,当赌注关闭时,SS 根本不会复制到 AS 中)和定时攻击是可能的。
这可以通过多种方式纠正,包括在从 TLB 读取时执行基本权限检查(毕竟只使用权限 0 和 3,因此逻辑很简单)或向缓存行添加一位以将其标记为推测性(被非推测代码视为无效)。