是否有任何CPU对边界检查有硬件支持?

时间:2016-11-22 22:01:11

标签: memory virtual-machine

将范围与内存段关联起来似乎并不困难。然后有一个汇编指令,将2个整数视为" location" &安培; "偏移" (另一个用于"数据"如果设置),并返回数据和错误代码。这意味着在使用数组时不再需要在速度和安全性/安全性之间做出选择。

另一个示例可能是一个函数,它验证源自特定内存范围的指令无法物理访问该范围之外的内存。如果连接到主板的所有硬件都具有此功能(并且彼此兼容),那么制作与物理机几乎相同速度的完美虚拟机将是微不足道的。

达斯汀苏达克

3 个答案:

答案 0 :(得分:4)

几十年前,Lisp machines执行了同步验证检查(例如类型检查和边界检查),因为程序运行时假设程序和状态有效,跳回"回到时间"如果检查失败 - 不幸的是,这种能够获得免费"当传统(即x86)机器占据主导地位时,运行时验证失败了。

  

https://en.wikipedia.org/wiki/Lisp_machine

     

Lisp Machines与更传统的单指令添加并行运行测试。如果同时测试失败,则结果被丢弃并重新计算;这在很多情况下意味着几个因素的速度增加。 这种同步检查方法也被用于测试引用时的数组边界,以及其他内存管理必需品(不仅仅是垃圾收集或数组)。

幸运的是,我们终于从过去慢慢地学习,并且通过零碎的方式重新引入这些创新 - 用于x86的Intel's "MPX" (Memory Protection eXtensions)被引入到Skylake生成的处理器中以进行硬件边界检查 - 尽管它不是&#39完美。

(x86在其他方面也是一种回归:IBM的大型机在20世纪80年代拥有真正的硬件加速系统虚拟化 - 直到2005年我们才在x86上使用英特尔#&# 34; VT-x"以及AMD" AMD-V"扩展)。

x86 BOUND

从技术上讲,x86 确实进行了硬件边界检查:the BOUND instruction于1982年在Intel 80188(以及英特尔286及更高版本,但不是英特尔8086,8088或80186处理器)。

虽然BOUND指令确实提供了硬件边界检查,但我理解它间接导致性能问题,因为它打破了硬件分支预测器(according to a Reddit thread,但我不确定为什么),但是还因为它需要在内存中的元组中指定边界 - 这对于性能来说太糟糕了 - 我在运行时理解它并不比手动执行指令更快"如果index不在范围[x,y]中,则向程序或操作系统发出BR异常信号" (所以你可能会想象BOUND指令的添加是为了方便那些手工编写程序集的人,这在20世纪80年代很常见。)

BOUND指令仍然存在于今天的处理器中,但它不包含在AMD64(x64)中 - 可能出于我上面解释的性能原因,也可能因为很少有人使用它(并且编译器可以通过手动边界检查来替换它,无论如何都可能具有更好的性能,因为它可以使用寄存器)。

将数组边界存储在内存中的另一个缺点是其他地方的代码(不受BOUNDS检查)可能会覆盖以前写入的另一个指针的边界并绕过这样的检查 - 这是主要是代码故意试图禁用安全功能(即恶意软件)的问题,但如果边界存储在堆栈中 - 并且考虑到破坏堆栈有多容易,它的效用更低

英特尔MPX

英特尔MPX于2015年在Skylake架构中推出,应该存在于主流英特尔酷睿系列(包括Xeon和非SoC版本的赛扬和奔腾)的所有Skylake和后续处理器型号中。从2016年起,英特尔还在Goldmont架构(Atler和SoC版本的Celeron和Pentium)中实施了MPX。

MPX优于BOUND,因为它提供了专用寄存器来存储边界范围,因此与要求的BOUND相比,边界检查应该几乎零成本内存访问。在英特尔486上BOUND指令takes 7 cycles(与CMP进行比较,即使操作数是内存地址,也需要only 2 cycles。在Skylake中,MPX等价物(BNDMKBNDCLBNDCU)都是1周期指令,BNDMK可以摊销,因为每个新的只需要调用一次指针)。

我无法找到有关AMD在何处实施自己版本的MPX的任何信息(截至2017年6月)。

对MPX的批判性思考

不幸的是,目前MPX的状态并不乐观 - a recent paper by Oleksenko, Kuvaiskii, et al. in February 2017 "Intel MPX Explained"PDF link:谨慎:尚未经过同行评审)有点关键:

  

我们的主要结论是,英特尔MPX是一种很有前途的技术,对于广泛采用尚不实用。英特尔MPX的性能开销仍然很高(平均约为50%),支持基础架构存在可能导致编译或运行时错误的错误。此外,我们展示了英特尔MPX的设计限制:它无法检测时间错误,可能在多线程代码中有误报和漏报,以及它的限制   在内存布局上需要对某些程序进行大量的代码更改。

另请注意,与以前的Lisp机器相比,英特尔MPX仍然是内联执行的 - 而在Lisp机器中(,如果我的理解是正确的)边界检查在硬件中同时发生并且向后追溯如果检查失败;因此,只要正在运行的程序指针不指向越界位置,那么运行时性能成本就会绝对为零,所以如果你有这个C代码:

char arr[10];
arr[9] = 'a';
arr[8] = 'b';

然后在MPX下执行此操作:

Time    Instruction              Notes
1       BNDMK arr, arr+9         Set bounds 0 to 9.
2       BNDCL arr                Check `arr` meets lower-bound.
3       BNDCU arr                Check `arr` meets upper-bound.
4       MOV 'a' arr+9            Assign 'a' to arr+9.
5       MOV 'a' arr+8            Assign 'a' to arr+8.

但是在Lisp机器上(如果将C编译成Lisp的神奇可能......),那么计算机中的程序读取器硬件就能够执行额外的" side"与"实际"同时发出的指令说明,允许" side"说明指示计算机忽视"实际"发生错误时的说明:

Time    Actual instruction    Side instruction
1       MOV 'A' arr+9         ENSURE arr+9 BETWEEN arr, arr+9
2       MOV 'A' arr+8         ENSURE arr+8 BETWEEN arr, arr+9

我理解" side"的每个周期的说明。说明与" Actual"不同。说明 - 因此Time=1处的指令的侧面检查可能仅在" Actual"之后完成。指令已经进展到Time=3 - 但是如果检查失败,那么它会将失败指令的指令指针传递给异常处理程序,该异常处理程序将指示程序忽略{{1}之后执行的指令的结果。 }。我不知道如果没有大量内存或一些强制执行暂停,他们可以实现这一目标,也可能是内存限制 -  这超出了我的答案范围,但至少在理论上是可行的。

(注意在这个人为的例子中我使用Time=1索引值,编译器可以证明它永远不会超出范围,因此会完全省略MPX检查 - 所以假装它们是用户提供的变量:))。

我不是x86的专家(或者在微处理器设计方面有任何经验,不需要我在UW学习的CS500级课程而且没有做过功课......)但是我没有&# 39; t相信边界检查的并发执行,以及&#34;时间旅行&#34;尽管现在实现了无序执行,但是x86的当前设计是可能的 - 但是我可能错了。我推测如果所有指针类型都被提升为3元组(constexpr - 这在技术上已经发生在MPX和其他基于软件的边界检查中,因为每个有保护的指针在struct BoundedPointer<T> { T* ptr, T* min, T* max }时都定义了它的边界然后保护可以由MMU免费提供 - 但是现在指针将消耗24个字节的内存,而不是当前的8个字节 - 或者与32位x86下的4个字节相比 - RAM很多,但仍然是一种不应该被浪费的有限资源。

GCC中的MPX

GCC在版本5.0(https://gcc.gnu.org/wiki/Intel%20MPX%20support%20in%20the%20GCC%20compiler)中添加了对MPX的支持,但不支持用于C可变长度数组的MPX。请阅读该文章,了解如何启用MPX,因为它不是自动的。

Visual Studio / Visual C ++中的MPX

Visual Studio 2015 Update 1(2015.1)添加了&#34; experimental&#34;使用BNDMK开关(https://blogs.msdn.microsoft.com/vcblog/2016/01/20/visual-studio-2015-update-1-new-experimental-feature-mpx/)支持MPX。 Visual Studio 2017仍然存在支持,但微软尚未宣布它是否被认为是主流(即非实验性)功能。

Clang / LLVM中的MPX

截至2017年6月,Clang和LLVM似乎不提供英特尔MPX支持。

英特尔C / C ++编译器中的MPX

英特尔C / C ++编译器自版本15.0起支持MPX。

答案 1 :(得分:3)

Little Endian Linux,Big Endian Linux或AIX操作系统上的IBM POWER处理器上提供的XL编译器具有不同的数组边界检查实现。
使用-qcheck或其同义词-C选项可以打开各种检查。 -qcheck = bounds检查数组边界。使用它时,编译器会检查每个数组引用是否都有一个有效的下标。
使用的硬件指令是条件陷阱,将下标与上限进行比较,如果下标太大或太小则捕获。在C和C ++中,下限为0.在Fortran中,它默认为1,但可以是任何整数。当它不为零时,从被检查的下标中减去下限,并且检查将其与上限减去下限进行比较。
当在编译时知道限制并且足够小时,条件陷阱立即指令就足够了。当在执行时计算限制或大于65535时,需要比较两个寄存器的条件陷阱指令。
性能影响很小,原因如下:
1.条件陷阱指令很快。
2.它们在标准整数管道中执行。由于大多数POWER CPU都有2或4个整数流水线,因此通常会有一个空槽来放置陷阱,因此通常基本上是零成本。
3.如果编译器优化器可以将条件陷阱移出循环,那么它只执行一次,立即检查所有循环迭代。
4.当可以证明实际下标不能超过限制时,优化器会丢弃该指令。
5.此外,当它可以证明下标也无效时,优化器使用无条件陷阱。
6.如果需要,可以在测试期间使用-qcheck并跳过生产版本,但是开销很小,通常不需要。
如果我的记忆是正确的,那么很久以前的一篇论文报告说,一个案例减速2%,另一个案例减少0%。由于该CPU只有一个整数管道,因此现代CPU的速度应该会明显减慢。
使用相同机制的其他检查可用于检测解除引用的NULL指针,将整数除以零,使用未初始化的自动变量,特别编写的断言等。
这并不包括各种无效的内存使用,但它确实处理最常见的类型,非常有效,并且非常易于使用。

答案 2 :(得分:0)

GCC支持-fbounds-check用于类似目的,但此时它仅适用于Fortran前端(gfortran)。