如果 v8 使用 "code" 或 "text" 内存类型,或者如果所有内容都在 heap/stack

If v8 uses the "code" or "text" memory type, or if everything is in the heap/stack

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

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

我是内存布局的新手,所以我想知道 v8 是一个 JIT 编译器并动态生成代码,是将这段代码存储在内存的 "code" 段中,还是仅将其存储在堆中连同其他一切。我不确定操作系统是否允许您访问 code/text,所以不确定这是否是一个愚蠢的问题。

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

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

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

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

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

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

但值得庆幸的是,操作系统允许您更改页面的权限(在 POSIX 系统上,这可以使用 mprotect 完成,在 Windows 上使用 VirtualProtect).因此,您的第一个想法可能是将生成的代码存储在堆上,然后简单地使包含的页面可执行。然而,这可能有点问题:VirtualProtectmprotect 的某些实现需要指向页面开头的指针,但是如果您使用 malloc(或 new 或您的语言的等效项)。此外,您的数组可能与您不希望可执行的其他数据共享一个页面。

为防止这些问题,您可以使用函数,例如在类 Unix 操作系统上使用 mmap 和在 Windows 上使用 VirtualAlloc,它们会为您提供内存页 "to yourself".这些函数将分配足够的页面以包含您请求的内存和 return 指向该内存开头的指针(将在第一页的开头)。 malloc 将无法使用这些页面。也就是说,即使您的数组明显小于 OS 上页面的大小,该页面也只会用于存储您的数组 - 随后调用 malloc 不会 return 指向该页内存的指针。

所以大多数 JIT 编译器的工作方式是使用 mmapVirtualAlloc 分配读写内存,将生成的机器指令复制到内存中,使用 mprotectVirtualProtect 使内存可执行且不可写(出于安全原因,如果可以避免,您永远不希望内存同时可执行和可写)然后跳入其中。就其(虚拟)地址而言,内存将是堆内存区域的一部分,但在不受 malloc 和 [=25 管理的意义上,它将与堆分开=].