我了解模拟器的作用,将一种机器语言更改为另一种机器语言,通常是“即时”的。是否可以构建这样的程序,它读取为一种架构编写的二进制文件并为另一种架构保存新的二进制文件。该过程完成后,它将为用户留下二进制文件,以便在给定架构上进行本机执行。这对于那些拥有昂贵的遗留架构专有应用程序的人来说尤其有用。
是否可以提出这样的申请?二进制重新编译不是一个新概念,但我还没有找到任何有用的实现。
在其他一些人的帮助下,我会很高兴开始编写这样一个程序的开源实现,如果这样的编程是可能的话。
我了解模拟器的作用,将一种机器语言更改为另一种机器语言,通常是“即时”的。是否可以构建这样的程序,它读取为一种架构编写的二进制文件并为另一种架构保存新的二进制文件。该过程完成后,它将为用户留下二进制文件,以便在给定架构上进行本机执行。这对于那些拥有昂贵的遗留架构专有应用程序的人来说尤其有用。
是否可以提出这样的申请?二进制重新编译不是一个新概念,但我还没有找到任何有用的实现。
在其他一些人的帮助下,我会很高兴开始编写这样一个程序的开源实现,如果这样的编程是可能的话。
静态重新编译是将二进制文件从外部架构转换为另一个目标架构的一种很有前途的方法。它会比即时 (JIT) 更快,因为它不必在运行之前立即编译代码,而且它可能花费的额外编译时间对于优化生成代码很有用。
然而,JIT 编译使用动态程序分析,而静态重新编译依赖于静态程序分析(因此得名)。
在静态分析中,您没有关于执行的运行时信息。
间接跳转带来了一个主要问题。该术语涵盖了可能从某些switch
语句、函数指针的使用或运行时多态性(想想虚拟表)生成的代码。这一切都归结为以下形式的指令:
JMP reg_A
假设您知道程序的起始地址,并且您决定从这一点开始重新编译指令。当你遇到直接跳转时,你会转到它的目标地址,然后从那里继续重新编译。但是,当您遇到间接跳跃时,您会被卡住。在这个汇编指令中, 的内容reg_A
是静态未知的。因此,我们不知道下一条指令的地址。请注意,在动态重新编译中,我们没有这个问题,因为我们模拟了寄存器的虚拟状态,并且我们知道reg_A
. 此外,在静态重新编译中,您有兴趣找到所有可能的值reg_A
此时,因为您希望编译所有可能的路径。在动态分析中,你只需要当前值来生成你当前正在执行的路径,如果reg_A
改变它的值,你仍然可以生成其他路径。在某些情况下,静态分析可以找到候选列表(如果是,switch
则必须在某处有可能的偏移量表),但在一般情况下,我们根本不知道。
好吧,你说,那让我们重新编译二进制中的所有指令吧!
这里的问题是,在大多数二进制文件中都包含代码和数据。根据架构,您可能无法分辨哪个是哪个。
更糟糕的是,在某些体系结构中没有对齐约束和可变宽度指令,您可能会在某个时候开始反汇编,却发现您已经开始使用偏移量重新编译。
让我们看一个包含两条指令和一个寄存器的简化指令集A
:
41 xx (size 2): Add xx to `A`.
42 (size 1): Increment `A` by one.
让我们看下面的二进制程序:
41 42
假设起点是第一个字节41
。你做:
41 42 (size 2): Add 42 to `A`.
但是如果 41 是一条数据呢?然后你的程序变成:
42 (size 1): Increment `A` by one.
这个问题在旧游戏中被放大了,这些游戏通常直接在汇编中进行优化,并且程序员可能故意期望某些字节被解释为代码和数据,具体取决于上下文!
更糟糕的是,重新编译的程序可能会自己生成代码!想象一下重新编译 JIT 编译器。结果仍然会输出源架构的代码并尝试跳转到它,很可能会导致程序很快死掉。静态重新编译仅在运行时可用的代码需要无限的技巧!
静态二进制分析是一个非常活跃的研究领域(主要是在安全领域,寻找源不可用的系统中的漏洞),实际上我知道尝试生成一个尝试静态重新编译程序的 NES 模拟器。这篇文章很有趣。
JIT 和静态重新编译之间的折衷方案是静态重新编译尽可能多的代码,只保留无法静态翻译的位。
我相信您正在寻找静态与动态重新编译。动态重新编译就是您所说的“实时”仿真或重新编译。代码以块的形式重新编译,允许仿真器准确地反映原始代码的运行时环境。
静态重新编译是您要问的是否可能。正如一些人指出的那样,在许多不同的情况下都是可能的,但是在静态重新编译后,期望非常具体的运行时约束的代码可能无法成功运行。这就是为什么使用静态重新编译的 N64 模拟器 Corn 只能运行极少数高度手动优化的游戏,而其他采用动态重新编译的 N64 模拟器运行的游戏种类要多得多。
对于更复杂和更传统的代码(即 x86 到 PowerPC),静态重新编译确实是可能的,但是这样的工作将被证明是非常乏味的,因为重新编译器必须使用很多技巧才能使生成的静态代码在目标上可靠地运行机器。动态重新编译器可以在运行时即时执行此操作,只需开发工作的一小部分,并且性能成本可以忽略不计。
您必须首先确保使用它重新编译任何引用的库。
这是可能的,但这是一项艰巨的任务。
还要考虑这样做可能存在许可问题;您正在基于原始软件创建衍生作品。大多数允许您执行此操作的许可证也会让您拥有源代码,因此您可以重新编译或移植源代码,这更容易。