19

虚拟机如何动态生成本机机器代码并执行它?

假设您可以弄清楚您想要发出的本机机器操作码是什么,您将如何实际运行它?

是否像将助记符指令映射到二进制代码,将其填充到 char* 指针中并将其转换为函数并执行一样棘手?

或者您是否会生成一个临时共享库(.dll 或 .so 或其他)并使用标准函数将其加载到内存中LoadLibrary

4

7 回答 7

8

您可以让程序计数器指向您要执行的代码。请记住,数据可以是数据或代码。在 x86 上,程序计数器是 EIP 寄存器。EIP的IP部分代表指令指针。调用 JMP 指令跳转到一个地址。跳转后的 EIP 会包含这个地址。

是否像将助记符指令映射到二进制代码,将其填充到 char* 指针中并将其转换为函数并执行一样棘手?

是的。这是一种方法。生成的代码将被强制转换为指向C中函数的指针。

于 2008-09-05T08:15:05.247 回答
6

是否像将助记符指令映射到二进制代码,将其填充到 char* 指针中并将其转换为函数并执行一样棘手?

是的,如果你是用 C 或 C++(或类似的东西)做的,那正是你要做的。

它看起来很老套,但这实际上是语言设计的产物。请记住,您要使用的实际算法非常简单:确定要使用的指令,将它们加载到内存中的缓冲区中,然后跳转到该缓冲区的开头。

但是,如果您真的尝试这样做,请确保在返回 C 程序时获得正确的调用约定。我想如果我想生成代码,我会寻找一个库来为我处理这方面的问题。Nanojit 最近上了新闻;你可以看看那个。

于 2008-09-16T00:42:11.200 回答
4

是的。您只需构建一个 char* 并执行它。但是,您需要注意几个细节。char* 必须位于内存的可执行部分中,并且必须具有正确的对齐方式。

除了 nanojit,您还可以查看 LLVM,这是另一个能够将各种程序表示编译为函数指针的库。它的界面很干净,生成的代码往往很高效。

于 2008-09-18T10:43:17.477 回答
1

据我所知,它编译内存中的所有内容,因为它必须运行一些启发式算法来优化代码(即:随着时间的推移内联),但您可以查看Shared Source Common Language Infrastructure 2.0转子版本。除了 Jitter 和 GC 之外,整个代码库与 .NET 相同。

于 2008-09-05T08:14:54.543 回答
1

除了Rotor 2.0 - 您还可以查看OpenJDK 中的HotSpot 虚拟机

于 2008-09-16T00:23:58.497 回答
1

关于生成 DLL:额外需要的 I/O,加上链接,加上生成 DLL 格式的复杂性,会使这变得更加复杂,最重要的是它们会降低性能;此外,最后你仍然调用一个指向加载代码的函数指针,所以......此外,JIT 编译可以一次发生一个方法,如果你想这样做,你会生成很多小的 DLL。

关于“可执行部分”要求,在 POSIX 系统上调用 mprotect() 可以修复权限(Win32 上有类似的 API)。您需要为一个大内存段执行此操作,而不是每个方法一次,否则它会太慢。

在普通的 x86 上你不会注意到这个问题,在带有 PAE 的 x86 或 64 位 AMD64/Intel 64 位机器上你会得到一个段错误。

于 2009-01-15T00:32:48.927 回答
1

是否像将助记符指令映射到二进制代码,将其填充到 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();
于 2010-07-01T18:29:59.590 回答