将范围与内存段相关联似乎并不困难。然后有一条汇编指令,该指令将2个整数视为“位置”和“偏移”(如果设置,则将另一个整数视为“数据”),并返回数据和错误代码。这意味着在处理阵列时,不再需要在速度和安全性/安全性之间做出选择。
另一个示例可能是一个函数,该函数验证源自特定内存范围的指令不能物理访问该范围之外的内存。如果连接到主板的所有硬件都具有此功能(并使其相互兼容),则使完美的虚拟机以与物理机几乎相同的速度运行将是微不足道的。
达斯汀·索达克(Dustin Soodak)
最佳答案
是。
几十年前,Lisp machines在程序以假定程序和状态有效的情况下运行时执行了同时验证检查(例如,类型检查和边界检查),如果检查失败,则“跳回”时间-不幸的是,这种获得“免费”的能力当传统的(即x86)计算机成为主流时,运行时验证就会丢失。
https://en.wikipedia.org/wiki/Lisp_machine
Lisp Machines与更传统的单指令添加并行运行测试。如果同时测试失败,则将结果丢弃并重新计算。在许多情况下,这意味着速度会提高几个因素。这种同时检查方法还用于测试引用时的数组边界以及其他内存管理必需项(不仅是垃圾回收或数组)。
幸运的是,我们终于可以慢慢地从过去中学习,并逐步引入这些创新-在Skylake一代处理器中引入了xaa的Intel's "MPX" (Memory Protection eXtensions)来进行硬件边界检查-尽管它并不完美。
(x86也在其他方面进行了回归:1980年代,IBM的大型机具有真正的硬件加速系统虚拟化-直到2005年,我们才通过Intel的“ VT-x”和AMD的“ AMD-V”扩展在x86上实现它)。
x86 BOUND
从技术上讲,x86确实具有硬件边界检查功能:the BOUND
instruction于1982年在Intel 80188中引入(以及Intel 286及更高版本,但没有Intel 8086、8088或80186处理器)。
尽管BOUND
指令确实提供了硬件边界检查,但我理解它间接导致性能问题,因为它破坏了硬件分支预测器(according to a Reddit thread,但我不确定为什么),而且还因为它需要指定边界在内存中的元组中-这对性能很不利-我知道在运行时,这并不比手动执行“如果index
不在[x,y]
范围内,然后向程序或OS发出BR
异常信号”的指令快(因此,您可以想象添加了BOUND
指令是为了方便手动编写汇编代码的人员,这在1980年代很普遍)。BOUND
指令仍存在于当今的处理器中,但未包含在AMD64(x64)中-可能是由于我上面解释的性能原因,还可能是因为很少有人在使用它(编译器可以用手动边界检查,但无论如何可能都有更好的性能,因为可以使用寄存器)。
将数组边界存储在内存中的另一个缺点是,其他地方的代码(未经BOUNDS
检查)可能会覆盖先前为另一个指针编写的边界,并以此方式规避检查-这主要是故意编写代码的问题试图禁用安全功能(即恶意软件),但是如果界限存储在堆栈中-并且鉴于破坏堆栈的容易程度,它的实用性甚至更低。
英特尔MPX
英特尔MPX于2015年在Skylake体系结构中引入,并且应在主流Intel Core系列(包括Xeon,Celeron和Pentium的非SoC版本)中的所有Skylake及后续处理器模型中使用。从2016年起,英特尔还以Goldmont架构(Atom以及Celeron和Pentium的SoC版本)实施了MPX。
MPX优于BOUND
,因为它提供了专用寄存器来存储边界范围,因此与需要内存访问的BOUND
相比,边界检查的成本几乎为零。在Intel 486上,BOUND
指令takes 7 cycles(与CMP
进行比较,即使操作数是内存地址,该指令也采用only 2 cycles)。在Skylake中,MPX等效项(BNDMK
,BNDCL
和BNDCU
)都是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机器相比,Intel 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 ...),则计算机中的程序读取器硬件可以与“实际”指令同时执行其他“边”指令,从而允许发生错误时,指示计算机忽略“实际”指令结果的“侧面”指令:
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
我了解到“侧边”指令的每个周期指令与“实际”指令不同-因此,仅在“实际”指令已经执行完之后,在
Time=1
处对指令的侧边检查才可能完成到Time=3
-但如果检查失败,则它将失败指令的指令指针传递给异常处理程序,该异常处理程序将指示程序忽略Time=1
之后执行的指令结果。我不知道他们如何在没有大量内存或某些强制执行暂停(也可能是内存隔离)的情况下实现这一目标-这超出了我的回答范围,但至少在理论上是可能的。
(请注意,在这个人为设计的示例中,我使用的是
constexpr
索引值,编译器可以证明该索引值永远不会超出范围,因此将完全省略MPX检查-假装它们是用户提供的变量:))。我不是x86专家(或者没有微处理器设计经验,没有在UW修过CS500级课程,也没有做作业……),但是我不相信并发执行bounds-尽管存在乱序执行的现成实现,但x86的当前设计无法进行检查或“时间旅行”。但是,我可能是错的。我推测如果所有指针类型都提升为3元组(
struct BoundedPointer<T> { T* ptr, T* min, T* max }
-技术上已经在MPX和其他基于软件的边界检查中发生,因为在调用BNDMK
时定义了每个受保护的指针的边界),则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更新1(2015.1)通过
/d2MPX
开关(https://blogs.msdn.microsoft.com/vcblog/2016/01/20/visual-studio-2015-update-1-new-experimental-feature-mpx/)添加了对MPX的“实验”支持。 Visual Studio 2017中仍然提供支持,但Microsoft尚未宣布是否被认为是主流(即非实验性)功能。Clang / LLVM中的MPX
截至2017年6月,Clang和LLVM似乎不再提供英特尔MPX支持。
英特尔C / C ++编译器中的MPX
自15.0版以来,英特尔C / C ++编译器已支持MPX。