虚拟机如何动态生成本机机器代码并执行它?
假设您可以弄清楚您想要发出的本机机器操作码是什么,您将如何实际运行它?
是否像将助记符指令映射到二进制代码,将其填充到 char* 指针中并将其转换为函数并执行一样棘手?
或者您是否会生成一个临时共享库(.dll 或 .so 或其他)并使用标准函数将其加载到内存中LoadLibrary
?
虚拟机如何动态生成本机机器代码并执行它?
假设您可以弄清楚您想要发出的本机机器操作码是什么,您将如何实际运行它?
是否像将助记符指令映射到二进制代码,将其填充到 char* 指针中并将其转换为函数并执行一样棘手?
或者您是否会生成一个临时共享库(.dll 或 .so 或其他)并使用标准函数将其加载到内存中LoadLibrary
?
是否像将助记符指令映射到二进制代码,将其填充到 char* 指针中并将其转换为函数并执行一样棘手?
是的,如果你是用 C 或 C++(或类似的东西)做的,那正是你要做的。
它看起来很老套,但这实际上是语言设计的产物。请记住,您要使用的实际算法非常简单:确定要使用的指令,将它们加载到内存中的缓冲区中,然后跳转到该缓冲区的开头。
但是,如果您真的尝试这样做,请确保在返回 C 程序时获得正确的调用约定。我想如果我想生成代码,我会寻找一个库来为我处理这方面的问题。Nanojit 最近上了新闻;你可以看看那个。
是的。您只需构建一个 char* 并执行它。但是,您需要注意几个细节。char* 必须位于内存的可执行部分中,并且必须具有正确的对齐方式。
除了 nanojit,您还可以查看 LLVM,这是另一个能够将各种程序表示编译为函数指针的库。它的界面很干净,生成的代码往往很高效。
据我所知,它编译内存中的所有内容,因为它必须运行一些启发式算法来优化代码(即:随着时间的推移内联),但您可以查看Shared Source Common Language Infrastructure 2.0转子版本。除了 Jitter 和 GC 之外,整个代码库与 .NET 相同。
除了Rotor 2.0 - 您还可以查看OpenJDK 中的HotSpot 虚拟机。
关于生成 DLL:额外需要的 I/O,加上链接,加上生成 DLL 格式的复杂性,会使这变得更加复杂,最重要的是它们会降低性能;此外,最后你仍然调用一个指向加载代码的函数指针,所以......此外,JIT 编译可以一次发生一个方法,如果你想这样做,你会生成很多小的 DLL。
关于“可执行部分”要求,在 POSIX 系统上调用 mprotect() 可以修复权限(Win32 上有类似的 API)。您需要为一个大内存段执行此操作,而不是每个方法一次,否则它会太慢。
在普通的 x86 上你不会注意到这个问题,在带有 PAE 的 x86 或 64 位 AMD64/Intel 64 位机器上你会得到一个段错误。
是否像将助记符指令映射到二进制代码,将其填充到 char* 指针中并将其转换为函数并执行一样棘手?
是的,这行得通。
要在 Windows 中执行此操作,您必须将 PAGE_EXECUTE_READWRITE 设置为分配的块:
void (*MyFunc)() = (void (*)()) VirtualAlloc(NULL, sizeofblock, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
//Now fill up the block with executable code and issue-
MyFunc();