代码之家  ›  专栏  ›  技术社区  ›  Lance Pollard

如果v8使用“code”或“text”内存类型,或者所有内容都在堆/堆栈中

  •  3
  • Lance Pollard  · 技术社区  · 7 年前

    在典型的内存布局中,有4项:

    1. 代码/文本(程序本身的编译代码所在的位置)
    2. 数据
    3. 堆栈

    我不熟悉内存布局,所以我想知道v8是一个JIT编译器,它可以动态生成代码,是将此代码存储在内存的“代码”段中,还是将其与其他所有内容一起存储在堆中。我不确定操作系统是否允许您访问代码/文本,因此不确定这是否是一个愚蠢的问题。

    enter image description here

    2 回复  |  直到 7 年前
        1
  •  7
  •   sepp2k    7 年前

    以下是目前常用的主要CPU上运行的主要操作系统。在旧的或一些嵌入式操作系统上(特别是在没有虚拟内存的操作系统上,情况要简单得多),或者在没有操作系统的情况下运行代码,或者在不支持内存保护的CPU上运行代码时,情况会有所不同。

    你问题中的情况有点简单。它没有显示的一点是(虚拟)内存是由操作系统提供给您的页面组成的。每个页面都有自己的权限,控制您的进程是否可以读取、写入和/或执行该页面中的数据。

    二进制文件的文本部分将加载到可执行但不可写的页面上。只读数据节将加载到既不可写也不可执行的页面上。图片中的所有其他内存((未)初始化数据、堆、堆栈)将存储在可写但不可执行的页面上。

    这些权限可防止安全缺陷(如缓冲区溢出),否则攻击者可以通过使程序跳入攻击者提供的代码或让攻击者覆盖文本部分中的代码来执行任意代码。

    现在,关于JIT编译,这些权限的问题是,您无法执行JIT编译的代码:如果您将其存储在堆栈或堆(或全局变量内),它将不在可执行页上,因此当您尝试跳入代码时,程序将崩溃。如果试图将其存储在文本区域(通过使用最后一页上的剩余内存或覆盖JIT编译器代码的部分),程序将崩溃,因为您试图写入只读内存。

    但谢天谢地,操作系统允许您更改页面的权限(在POSIX系统上,可以使用 mprotect 和在Windows上使用 VirtualProtect )因此,您的第一个想法可能是将生成的代码存储在堆上,然后简单地使包含的页面可执行。然而,这可能有点问题: 虚拟保护 以及 M保护 需要指向页面开头的指针,但如果使用 malloc (或 new 或您的语言等效)。此外,您的阵列可能与其他数据共享一个页面,而您不希望这些数据是可执行的。

    要防止这些问题,可以使用函数,例如 mmap 在类Unix操作系统和 VirtualAlloc 在Windows上,它为您提供“给自己”的内存页。这些函数将分配足够的页面来容纳您所请求的内存,并返回指向该内存开头(将位于第一页开头)的指针。这些页面将不可用于 马洛克 。也就是说,即使您的数组明显小于操作系统上的页面大小,该页面也将仅用于存储您的数组-随后调用 马洛克 不会返回指向该页中内存的指针。

    因此,大多数JIT编译器的工作方式是使用 mmap公司 VirtualAlloc公司 ,将生成的机器指令复制到该内存中,使用 M保护 虚拟保护 使内存可执行且不可写(出于安全原因,如果可以避免的话,您永远不希望内存同时可执行和可写),然后跳转到内存中。就其(虚拟)地址而言,内存将是内存堆区域的一部分,但它将与堆分离,因为它不会由 马洛克 free

        2
  •  -1
  •   jmrk    7 年前

    堆和堆栈是程序在运行时可以分配的内存区域。这并不特定于V8或JIT编译器。关于更多细节,我谦恭地建议您阅读该插图来自的任何书籍;-)