5

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

-编辑-

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

4

7 回答 7

1

听起来我必须了解目标平台机器代码才能动态重新编译

是的,一点没错。这就是为什么 Java 虚拟机的某些部分必须为每个架构重写(即 JIT)。

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

当来宾和主机架构相同时,例如 VMWare,您可以进行大量(非常简洁的)优化来加速虚拟化 - 今天我们处于这种类型的虚拟机几乎比直接在处理器上运行。当然,它非常依赖于体系结构——从头开始重写大部分 VMWare 可能比尝试移植它更好。

于 2010-04-13T04:17:07.920 回答
1

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

当然,模拟器和虚拟机不必重写,它们可以在加载时完成。

于 2010-04-09T22:23:35.227 回答
1

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

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

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

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

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

于 2010-04-16T18:57:05.387 回答
0

这种方法通常用于具有中间字节码表示的环境(如 Java、.net)。字节码包含足够多的“高级”结构(高级比机器码更高的级别),以便虚拟机可以从字节码中取出块并用编译的内存块替换它。VM 通常通过计算代码已经解释了多少次来决定编译哪个部分,因为编译本身是一个复杂且耗时的过程。所以只编译多次执行的部分是很有用的。

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

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

该人是否需要预先制作组装块并将它们复制/批处理在一起?程序集是用 C 编写的吗?

这个过程是虚拟机的一个实现细节,通常嵌入一个编译器,它能够将虚拟机操作码流转换为机器码。

你如何解释系统中断?

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

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

于 2010-04-16T07:26:03.633 回答
0

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

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

于 2010-04-15T17:10:28.437 回答
-1

IIS 通过卷影复制来做到这一点:编译后,它将程序集复制到某个临时位置并从 temp 运行它们。

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

  1. 重新编译(旧代码处理的所有请求)
  2. 复制新程序集(旧代码处理的所有请求)
  3. 所有新请求都将由新代码处理,所有请求 - 由旧代码处理。

我希望这会有所帮助。

于 2010-04-09T13:18:47.607 回答
-2

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

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

于 2010-04-12T06:43:39.753 回答