gpt4 book ai didi

c++ - 使用硬件内存保护在64位硬件上进行数组范围检查

转载 作者:IT老高 更新时间:2023-10-28 22:14:24 26 4
gpt4 key购买 nike

我正在hacks.mozilla.org上阅读有关64位Firefox版本的博客。

作者指出:

For asm.js code, the increased address space also lets us use hardware memory protection to safely remove bounds checks from asm.js heap accesses. The gains are pretty dramatic: 8%-17% on the asmjs-apps-*-throughput tests as reported on arewefastyet.com.



我试图了解64位硬件如何对C/C++进行自动边界检查(假设编译器在硬件支持下进行)。我在SO中找不到任何答案。我找到了 one technical paper on this subject,但是我无法理解它是如何完成的。

有人可以在边界检查中解释64位硬件帮助吗?

最佳答案

大多数现代CPU实现虚拟寻址/虚拟内存-当程序引用特定地址时,该地址是虚拟的;到物理页面的映射(如果有的话)是由CPU的MMU(内存管理单元)实现的。 CPU通过在为当前进程设置的操作系统page table中查找每个虚拟地址,将每个虚拟地址转换为物理地址。这些查询由TLB缓存,因此大多数时候没有额外的延迟。 (在某些非x86 CPU设计中,操作系统通过软件在软件中处理TLB丢失。)

因此,我的程序访问的地址为0x8050,该地址位于虚拟页面8中(假定标准4096字节(0x1000)页面大小)。 CPU看到虚拟页面8映射到物理页面200,因此在物理地址200 * 4096 + 0x50 == 0xC8050上执行读取。

当CPU没有该虚拟地址的TLB映射时会发生什么?由于TLB的大小有限,因此经常发生这种情况。答案是CPU生成页面错误,由操作系统处理。

页面错误可能导致几种结果:

  • 一个,操作系统可以说:“哦,好吧,它不在TLB中,因为我无法容纳它”。操作系统从TLB逐出一个条目,并使用进程的页表映射将新条目填充到新条目中,然后让进程继续运行。在中等负载的计算机上,这种情况每秒发生数千次。 (在具有硬件TLB未命中处理的CPU(例如x86)上,这种情况是在硬件中处理的,甚至不是“次要”页面错误。)
  • 第二,操作系统可以说:“哦,好吧,虚拟页面现在还没有映射,因为它使用的物理页面已交换到磁盘,因为我内存不足。”操作系统暂停该过程,找到一些要使用的内存(也许通过换出其他虚拟映射),将磁盘读取排队以请求的物理内存,并且当磁盘读取完成时,使用新填充的页面表映射恢复该过程。 (这是"major" page fault。)
  • 第三,该进程正在尝试访问不存在映射的内存-它正在读取不应该的内存。这通常称为段错误。

  • 相关案例是数字3。发生段错误时,操作系统的默认行为是中止进程并执行诸如写出核心文件之类的事情。但是,允许进程捕获自己的段错误并尝试处理它们,甚至可能不停止也是如此。这就是事情变得有趣的地方。

    我们可以利用它来发挥优势,以执行“硬件加速”索引检查,但是尝试这样做还有更多绊脚石。

    首先,总体思路是:对于每个数组,我们将其放在自己的虚拟内存区域中,所有包含数组数据的页面都照常进行映射。在实际数组数据的任一侧,我们创建不可读和不可写的虚拟页面映射。如果尝试在数组之外读取,则会生成页面错误。编译器在编写程序时插入其自己的页面错误处理程序,并处理该页面错误,将其转换为“索引超出边界”异常。

    绊脚石是我们只能将整个页面标记为可读或不可读。数组大小可能不是页面大小的偶数倍,因此我们遇到了一个问题-我们无法在数组末尾前后准确放置栅栏。我们能做的最好的事情是在数组的开始之前或在数组的结尾与最近的“栅栏”页面之间留一个小间隙。

    他们如何解决这个问题?好吧,在Java的情况下,编译执行负索引的代码并不容易。如果确实如此,则无论如何都没有关系,因为负索引被视为无符号,这使索引远位于数组开头,这意味着它很可能会遇到未映射的内存,并且无论如何都会导致错误。

    因此,他们要做的是对齐数组,以使数组的末端紧靠页面的末端,就像这样(“-”表示未映射,“+”表示映射):
    -----------++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
    | Page 1 | Page 2 | Page 3 | Page 4 | Page 5 | Page 6 | Page 7 | ...
    |----------------array---------------------------|

    现在,如果索引超出数组的末尾,它将到达第7页,该页未映射,这将导致页面错误,该错误将变成索引超出范围的异常。如果索引在数组的开头之前(即它是负数),则因为它被视为无符号值,所以它将变得非常大且为正数,这使我们远远超出了第7页,从而导致未映射的内存读取,从而导致页面错误,它将再次变成索引超出范围异常。

    第2个绊脚石是,在映射下一个对象之前,我们确实应该在数组末尾留下大量未映射的虚拟内存,否则,如果索引超出范围,但又远远超出了范围,它可能会触及有效页,并且不会导致“越界索引”异常,而是会读取或写入任意内存。

    为了解决这个问题,我们仅使用大量的虚拟内存-我们将每个阵列放入其自己的4 GiB内存区域,其中实际上仅映射了前N个页面。之所以可以这样做,是因为我们在这里只使用地址空间,而不是实际的物理内存。 64位进程具有约40亿块4 GiB内存区域,因此在耗尽之前,我们有足够的地址空间可以使用。在32位CPU或进程上,我们需要处理的地址空间非常小,因此这种技术不太可行。事实是,当今许多32位程序都用尽了虚拟地址空间,只是试图访问实际内存,不要介意尝试在该空间中映射空的“栅栏”页面,以尝试用作“硬件加速”索引范围检查。

    关于c++ - 使用硬件内存保护在64位硬件上进行数组范围检查,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29565312/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com