在典型的内存布局中有4个项目:
答案 0 :(得分:2)
以下适用于当今常用的主要CPU上运行的主要操作系统。旧的或某些嵌入式操作系统的情况会有所不同(特别是在没有虚拟内存的操作系统上更简单),或者在没有操作系统的情况下运行代码或在没有内存保护的情况下运行CPU时。
您问题中的图片有点简化。它没有显示的一件事是(虚拟)内存由操作系统提供给您的页面组成。每个页面都有自己的权限,控制您的进程是否可以读取,写入和/或执行该页面中的数据。
二进制文本部分将加载到可执行但不可写的页面上。只读数据部分将加载到既不可写也不可执行的页面上。图片中的所有其他内存((未)初始化数据,堆,堆栈)将存储在可写但不可执行的页面上。
这些权限可防止安全漏洞(例如缓冲区溢出),否则攻击者可能会通过使程序跳转到攻击者提供的代码或让攻击者覆盖文本部分中的代码来执行任意代码。
现在,关于JIT编译,这些权限的问题在于你无法执行你的JIT编译代码:如果你将它存储在堆栈或堆上(或在一个全局变量中),它就赢了t在可执行页面上,因此当您尝试跳转到代码时程序将崩溃。如果您尝试将其存储在文本区域中(通过在最后一页上使用剩余内存或通过覆盖部分JIT编译器代码),程序将崩溃,因为您尝试写入只读存储器中。
但幸运的是,操作系统允许您更改页面的权限(在POSIX系统上,这可以使用mprotect
完成,在Windows上使用VirtualProtect
)。所以你的第一个想法可能是将生成的代码存储在堆上,然后简单地使包含的页面可执行。但是这可能有些问题:VirtualProtect
并且mprotect
的某些实现需要指向页面开头的指针,但是如果使用{分配它,则数组不一定从页面的开头开始{1}}(或malloc
或您的语言相当于)。此外,您的阵列可能与其他数据共享页面,您不希望这些数据可执行。
为了防止出现这些问题,您可以使用类似Unix的操作系统上的new
和Windows上的mmap
等功能,为您自己提供内存页面。这些函数将分配足够的页面以包含您请求的内存,并返回指向该内存开头的指针(将在第一页的开头)。 VirtualAlloc
无法使用这些页面。也就是说,即使您的数组明显小于操作系统上页面的大小,该页面也只会用于存储您的数组 - 后续调用malloc
将不会返回指向该页面内存的指针
因此大多数JIT编译器的工作方式是使用malloc
或mmap
分配读写内存,将生成的机器指令复制到该内存中,使用VirtualAlloc
或{ {1}}使内存可执行且不可写(出于安全原因,你永远不希望内存在可以避免的情况下同时执行和写入),然后跳转到它。就其(虚拟)地址而言,内存将是内存堆的一部分,但它将与堆分开,因为它不会由mprotect
和{{1 }}
答案 1 :(得分:0)
堆和堆栈是程序可以在运行时分配的内存区域。这不是V8或JIT编译器特有的。有关更多细节,我谦卑地建议您阅读插图来自的任何书籍; - )