如果 v8 使用“代码”或“文本”内存类型,或者所有内容都在堆/堆栈中

Lan*_*ard 2 memory compiler-construction jit v8

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

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

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

在此处输入图片说明

sep*_*p2k 5

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

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

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

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

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

但幸运的是,操作系统允许您更改页面的权限(在 POSIX 系统上可以使用mprotect,在 Windows 上使用VirtualProtect)。因此,您的第一个想法可能是将生成的代码存储在堆上,然后简单地使包含页面可执行。然而,这可能有些问题:VirtualProtect并且某些实现mprotect需要指向页面开头的指针,但是如果您使用mallocnew或您的语言的等价物)分配数组,则它不一定从页面的开头开始。此外,您的数组可能与您不想执行的其他数据共享一个页面。

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

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