如何使用动态重新编译?

时间:2010-04-03 02:23:59

标签: virtual-machine emulation implementation vm-implementation

我注意到一些模拟器和虚拟机使用动态重新编译。他们是怎么做到的?在C中我知道如何使用类型转换在ram中调用函数(虽然我从未尝试过)但是如何读取操作码并为其生成代码?该人是否需要预先制作组装块并将它们一起复制/批处理?是用C编写的汇编?如果是这样,您如何找到代码的长度?你如何解释系统中断?

-edit -

系统中断以及如何(重新)编译数据是我最感兴趣的。经过更多的研究,我听说有一个人(没有可用来源)使用js,读取机器代码,输出js源并使用eval '编译'js源码。有趣。

7 个答案:

答案 0 :(得分:1)

很可能 - 尽管显然不是微不足道 - 从内存指针中反汇编代码,以某种方式优化代码,然后将优化后的代码写回原始位置或跳转到新位置并跳转到原来的位置。

当然,仿真器和虚拟机不必重写,他们可以在加载时执行此操作。

答案 1 :(得分:1)

  

听起来我必须知道要动态重新编译的目标平台机器代码

是的,绝对的。这就是为什么必须为每个架构重写Java虚拟机的部分内容(即JIT)。

编写虚拟机时,考虑到特定的主机架构和特定的客户架构。便携式VM最好称为仿真器,因为您将模拟来宾架构的每条指令(来宾寄存器将表示为主机变量,而不是主机寄存器)。

当客户端和主机架构相同时,比如VMWare,你可以做很多(非常简洁的)优化来加速虚拟化 - 今天我们就是这种类型的虚拟机是BARELY比直接在处理器上运行慢。当然,它依赖于架构 - 你可能最好从头开始重写大部分VMWare而不是尝试移植它。

答案 2 :(得分:1)

这是一个广泛的问题,不确定你想要去哪里。维基百科用通用答案涵盖了通用主题。模拟或虚拟化的本机代码将替换为本机代码。代码运行得越多,替换的就越多。

我认为您需要做一些事情,首先要确定您是在谈论仿真还是虚拟机,如vmware或虚拟机。模拟处理器和硬件是使用软件模拟的,因此下一条指令由仿真器读取,操作码被代码拉开,您决定如何处理它。我一直在做一些6502仿真和静态二进制转换,这是动态重新编译但是预处理而不是实时。所以你的模拟器可能需要一个LDA#10,立即加载一个,模拟器看到加载一个立即指令,知道它必须读取下一个字节,即模拟器在A寄存器的代码中有一个变量并且放置该变量的直接值。在完成指令之前,仿真器需要更新标志,在这种情况下,Zero标志清零,N标志为空,C和V未触及。但是,如果下一条指令立即加载X怎么办?没什么大不了的?好吧,加载x也会修改z和n标志,所以下次你执行加载指令时你可能会发现你不必计算标志,因为它们会被破坏,它在模拟中是死代码。您可以继续这种想法,比如你看到将x寄存器复制到寄存器的代码然后将堆栈中的寄存器推送然后将y寄存器复制到寄存器并推送堆栈,你可以替换那个块只需按下堆栈上的x和y寄存器即可。或者,您可能会看到一些带有链接的链接,以执行16位添加并将结果存储在相邻的内存位置。基本上寻找被模拟处理器无法做但在仿真中很容易做的操作。我建议您在动态重新编译之前查看静态二进制转换,在运行代码之前以静态方式执行此分析和转换。例如,您可以将操作码转换为C而不是模拟,而是删除尽可能多的死代码(C编译器可以为您删除更多死代码)。

一旦理解了仿真和翻译的概念,那么你可以尝试动态地进行,这当然不是微不足道的。我建议再次尝试将二进制文件静态转换为目标处理器的机器代码,这是一个很好的练习。我不会尝试动态运行时优化,直到我成功地对二进制文件执行它们。

虚拟化是另一回事,您谈论的是在同一处理器上运行相同的处理器。所以x86上的x86就好了。这里的美妙之处在于,使用非旧的x86处理器,您可以将程序虚拟化并在实际处理器上运行实际操作码,无需仿真。你设置了处理器中内置的陷阱以捕获内容,因此在AX中加载值并添加BX等都会在处理器上实时发生,当AX想要读取或写入内存时,如果地址在内,则取决于您的陷阱机制虚拟机ram空间,没有陷阱,但是让我们说程序写入一个虚拟化uart的地址,你有处理器陷阱然后vmware或任何解码写入和模拟它与真正的串行端口交谈。虽然这一条指令不是实时的,但执行却需要很长时间。如果您选择替换为虚拟化串行端口写入值的指令或指令集,然后可能已写入可能是真实串行端口的其他地址或其他不可能的位置,您可以执行的操作导致错误导致vm管理器必须模拟指令。或者在虚拟内存空间中添加一些代码,在没有陷阱的情况下执行对uart的写入,并将该代码分支到此uart写入例程。下次你点击那段代码时,它现在可以实时运行。

你可以做的另一件事就是例如模拟,当你转换为虚拟中间副本代码时,比如llvm。从那里你可以从中间机器翻译到本机,最终取代大部分程序,如果不是全部的话。您仍然需要处理外围设备和I / O.

答案 3 :(得分:0)

以下是他们如何为'Rubinius'Ruby interpteter进行动态重新编译的解释:

http://www.engineyard.com/blog/2010/making-ruby-fast-the-rubinius-jit/

答案 4 :(得分:0)

此方法通常由具有中间字节代码表示的环境(如Java,.net)使用。字节代码包含足够的“高级”结构(高级别而不是机器代码),因此VM可以从字节代​​码中取出块并用编译的内存块替换它。 VM通常通过计算代码已被解释的次数来决定编译哪个部分,因为编译本身是一个复杂且耗时的过程。因此,仅编译多次执行的部分是有用的。

  

但是如何读取操作码并为其生成代码?

操作码的方案由VM的规范定义,因此VM打开程序文件,并根据规范对其进行解释。

  

此人是否需要预先制作组装块并将它们一起复制/批处理?是用C?

编写的汇编

此过程是VM的实现细节,通常存在嵌入的编译器,其能够将VM操作码流转换为机器代码。

  

您如何解释系统中断?

很简单:没有。 VM中的代码无法与真实硬件交互。 VM与OS交互,并通过跳转/调用解释代码内的特定部分将OS事件传输到代码。代码中或OS中的每个事件都必须通过VM。

硬件虚拟化产品也可以使用某种JIT。 X86世界中的典型用例是将16位实模式代码转换为32或64位保护模式代码,以免被迫在实模式下模拟CPU。此外,仅软件VM通过跳转到VM控制软件来替换执行代码中的跳转指令,VM控制软件在跳转到实际代码目的地之前,在每个分支处跳转指令的以下代码路径进行扫描并替换它们。但我怀疑跳转替换是否符合JIT编译。

答案 5 :(得分:-1)

IIS通过卷影复制实现此目的:在编译之后,它将程序集复制到某个临时位置并从temp运行它们。

想象一下,该用户更改了一些文件。然后IIS将在接下来的步骤中重新编译asseblies:

  1. 重新编译(旧代码处理的所有请求)
  2. 复制新程序集(旧代码处理的所有请求)
  3. 所有新请求都将由新代码处理,所有请求都由旧代码处理。
  4. 我希望这会有所帮助。

答案 6 :(得分:-2)

虚拟机加载“字节代码”或“中间语言”而不是机器代码,因此我认为,只要它有更多的运行时数据,它就会更有效地重新编译字节代码。

http://en.wikipedia.org/wiki/Just-in-time_compilation