【问题标题】:Why does emulation have to be done in real time?为什么必须实时进行仿真?
【发布时间】:2012-06-26 20:48:49
【问题描述】:

我了解模拟器的作用,将一种机器语言更改为另一种机器语言,通常是“即时”。是否可以构建这样的程序,它读取为一种架构编写的二进制文件并为另一种架构保存新的二进制文件。该过程完成后,它将为用户留下二进制文件,以便在给定架构上进行本机执行。这对于那些拥有昂贵的遗留架构专有应用程序的人来说尤其有用。

这样的申请有可能吗?二进制重新编译不是一个新概念,但我还没有找到任何有用的实现。

在其他人的帮助下,如果有可能的话,我会很高兴开始编写此类程序的开源实现代码。

【问题讨论】:

  • 已经做了好几次了。例如,DEC 的 FX!32 重新编译 x86 二进制文件以在 DEC Alpha 上运行,通常比任何 x86 都快。但这还不足以弥补 DEC 的(错误)管理,康柏/惠普也不太在意。
  • 如果是这种情况,为什么还要存在模拟器?这真的比编写模拟器更难吗?

标签: architecture binary emulation cpu


【解决方案1】:

静态重新编译是将二进制文件从外部架构转换为另一个目标架构的有前途的方法。它会比即时 (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.

这个问题在老游戏中被放大了,这些游戏通常直接在汇编中进行优化,程序员可能故意expect some byte to be interpreted as both code and data, depending on the context!

更糟糕的是,重新编译的程序可能会自己生成代码!想象一下重新编译 JIT 编译器。结果仍然会输出源架构的代码并尝试跳转到它,很可能导致程序很快死掉。静态重新编译仅在运行时可用的代码需要无限的技巧!

静态二进制分析是一个非常活跃的研究领域(主要是在安全领域,以寻找来源不可用的系统中的漏洞),实际上我知道有人尝试生成NES emulator that tries to statically recompile programs。 这篇文章很有趣。

JIT 和静态重新编译之间的折衷方案是静态重新编译尽可能多的代码,只保留无法静态翻译的位。

【讨论】:

  • 很好的解释。我想提一下静态/动态重新编译之间的另一种可能的折衷方案——将动态重新编译的代码块缓存(到文件中)。也许这种方法会从静态重新编译中获得很多好处,并通过运行时分析的“代码路径发现”能力得到增强。
【解决方案2】:

我相信您正在寻找静态与动态重新编译。动态重新编译就是您所说的“实时”仿真或重新编译。代码以块的形式重新编译,使模拟器能够准确地反映原始代码的运行时环境。

静态重新编译是您要问的是否可能。正如一些人指出的那样,在许多不同的情况下都是可能的,但是需要非常特定的运行时约束的代码在静态重新编译后可能无法成功运行。这就是为什么使用静态重新编译的 N64 模拟器 Corn 只能运行极少数高度手部优化的游戏,而其他使用动态重新编译的 N64 模拟器可以运行更多种类的游戏。

对于更复杂和传统的代码(即 x86 到 PowerPC),静态重新编译确实是可能的,但是这样的工作将被证明是非常乏味的,因为重新编译器必须使用很多技巧才能使生成的静态代码可靠地运行在目标机器上。动态重新编译器可以在运行时即时执行此操作,只需开发工作的一小部分,而且性能成本可以忽略不计。

【讨论】:

  • 我也相信不同架构中的浮点实现之间存在差异,但我不确定。
【解决方案3】:

您必须首先确保所有引用的库也使用它重新编译。

这是可能的,但这是一项艰巨的任务。

还要考虑这样做可能会出现许可问题;您正在基于原始软件创建衍生作品。大多数允许您这样做的许可证也会让您拥有源代码,因此您可以重新编译或移植源代码,这更容易。

【讨论】:

  • 所以,这不会完全算作逆向工程,对吗?我很想制造这样的野兽,这样法庭上就会有某种先例。一个有用的应用程序是在 OS X Mountain Lion 上运行闭源 PPC 二进制文件,其中许多所需的库位于操作系统中,或者可以从 Snow Leopard 或 Lion 移植。另一个是控制台仿真,其中大部分库都在游戏中。
  • 并非如此,您的重新编译器会:“读取->翻译->编写新版本”。这听起来更像是衍生作品而不是逆向工程。逆向工程是了解它的作用,并编写具有相同作用的新代码。
  • 一个有用的应用程序是在 OS X Mountain Lion 上运行闭源 PPC 二进制文件,其中许多所需的库位于操作系统中,或者可以从 Snow Leopard 或 Lion 移植。另一个是控制台仿真,其中大部分库都在游戏中。我会遇到法律问题吗?
  • 如果作者追你,可能。虽然我不是律师,但我只是阅读了很多关于这方面的内容。如果您打算将任何严肃的工作投入到这样的事情中,您应该就此咨询律师。
  • 谢谢!这比模拟器难多少?
猜你喜欢
  • 2013-11-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多