模拟器是否解析文件中的二进制代码?

3 emulation cpu-architecture

我见过一些模拟器声称他们执行,即使他们这样做,他们的源代码显示他们不直接解析每 1 和 0 来确定指令。

我的问题是,如果模拟器必须模拟真实 CPU 的确切操作码,是否不需要解析游戏的正确二进制操作码格式以合法(或根本不需要)模拟 CPU?

例如,在游戏文件中我存储了一条指令,一个字节,标记如下:

0000 1111
Run Code Online (Sandbox Code Playgroud)

我的程序必须验证这条指令确实意味着(例如“向 A 寄存器加一”),但它是否不需要检查文本文件中的每个 0 和 1 以确保这一点?

然后仿真器将解析整个字节,但整个字节同样是 8 位,波动的模式会改变操作输出。

例如,0000 1111 可能意味着将 A 添加到 A,但 0000 1110 可能意味着将 A 与 A 相加。

all*_*tic 6

阐述——试图直接回答问题

如果您正在阅读模拟器的源代码,但它没有阅读二进制(可执行)文件的某些位,并且仍在忠实地执行代码,那么可能会出现三种结果:

  1. 错误地认为模拟器不会读取文件的每一部分,而实际上它确实读取了,而您只是错了。
  2. 你是正确的,而模拟器不读每一个位,因为它能够承担有关它的模拟,以不是程序的行为的某些事实需要阅读每一位来知道它需要做的(也许是因为它期望运行某种游戏引擎,或某种类型的图形 API,或某种类型的声音 API 等)。
  3. 你是正确的,而模拟器不读每一个位,因为还有一些根本就没有必要正确地执行该程序的可执行的某些位。它们可能是遗留的“垃圾”,或元数据,或任何其他任何实际上并不包含程序功能的额外内容。
  4. 你是正确的,并且仿真器不会读取每一位,因为仿真器正在将代码中的某些操作转换为更高级别的操作,并且完全绕过低级别的、特定于处理器/硬件的指令。例如,如果你被要求在一个人执行复杂操作的视频录像中准确地模仿他正在做什么,并且他们说“现在在盒子的侧面钻一个洞”,你会很想停下来观看视频并使用您现有的如何在事物上钻孔的经验,而不是跟随视频中人的字面动作(假设您配备了适当的钻头并且通常在生活中经验丰富)。类似地,如果模拟器可以推断出程序要求在给定的坐标集上在屏幕上绘制一个 32x32 的图像,

模拟器的工作原理

为另一个平台和/或 CPU(例如,wine)执行代码的模拟器在不同阶段执行操作。有些阶段是模拟器工作绝对需要的;其他阶段是可选的,代表性能优化的可能性。

  • 必需:“解析”可执行代码(机器代码、MSIL、Java 字节码等)解析包括:

    • 读取可执行代码的每一位。
    • 充分了解本机代码的每个位/字节(或您关心使用的任何其他离散信息度量单位)的布局/格式(语法)和用途(语义),以便了解它在做什么。
    • 要了解什么计划说,该仿真器具有理解语法的二进制格式,和语义。语法包括“我们以最小符号位格式表示 32 位有符号整数”;语义包括“当本机代码包含操作码52 时,这意味着进行函数调用”。
    • 助记符(帮助你记住为什么这是必要的):如果我致力于遵循一个食谱,如果我完全忽略那个食谱甚至不阅读它,我不可能永远遵循那个食谱,除非我随机尝试很多事情并运气好采取与食谱所需的相同步骤。同样,除非你有执行随机CPU指令,直到它在卢克斯在程序执行相同功能的随机蒙特卡罗模拟,任何模拟器将不得不明白什么节目说。

  • 要求:将解析出的代码(通常是某种抽象数据模型、状态机、抽象语法树或类似的东西)“翻译”成高级命令(例如 C 或 Java 中的语句)或低级命令命令(例如 x86 处理器的 CPU 指令)。高级命令往往更优化。例如,如果您分析一长串 CPU 指令的代码流并在较高级别确定它要求的是从磁盘播放某个 MP3 文件,您可以跳过整个指令级仿真,而只使用您的本机平台的 MP3 解码器(可能针对您的处理器进行了优化)来播放相同的 MP3 文件。另一方面,如果您要尽可能按字面意思“跟踪”仿真程序的执行,这会更慢且不太理想,因为您将放弃通过本机执行指令而受益​​的大部分优化。

  • 可选:“优化”和分析大量仿真程序代码或整个程序的代码流,以确定完整的执行顺序,并构建一个非常详细和复杂的模型,说明您的仿真器将如何进行仿真与本机平台的设施的行为。Wine 在一定程度上做到了这一点,但它所翻译的代码是 x86 到 x86 的这一事实有所帮助(这意味着在这两种情况下,CPU 是相同的指令集,所以你所要做的就是连接 Windows代码到基于 UNIX 的外部环境,并让它“本地”运行)。


蛋糕类比

在考虑模拟器的性能时,请考虑在以下情况下,如果您正在观看某人烤蛋糕的视频(带音频),您需要为自己写下多少张纸的说明:

  • 如果您以前从未动过手或锻炼过身体的任何肌肉;(提示:您需要数千张纸来记录手部运动、手眼协调、角度、速度、位置、抓握、握持器皿、揉捏等基本技术的详细步骤。)

  • 如果您有基本的运动控制能力(您可以自己走路和吃饭),但您以前从未准备过任何食物;(提示:您需要数十张纸来记录各个步骤,并且可能需要大量练习才能掌握诸如揉捏和拿着不熟悉的器皿之类的技巧,但您可以用更少的时间记录下来时间比前一种情况)

  • 如果您以前从未烤过蛋糕,但之前已经做过一些食物准备工作;(提示:您需要几张纸,但不要超过 10 张;您已经熟悉测量成分、搅拌等。)

  • 如果你以前烤过很多次蛋糕,并且非常熟悉这个过程,但你不知道如何烤这种特殊品种/口味的蛋糕(提示:你可能需要半张纸来记下基本的成分和它在烤箱中需要的时间,就是这样)

基本上,在“模拟器能力”的这些不断提高的水平上,模拟器可以“本地”做更多更高级别的事情(使用它已经知道的例程和过程),并且必须做更少的“跟踪”(使用它是的例程和过程)从字面上遵循模拟程序)。

用计算机术语来做这个类比,你可以想象一个仿真器模拟运行被仿真程序的实际硬件,并忠实地“跟踪”该硬件的行为,甚至可能下至硬件(电路)级别;与将程序分析到如此复杂程度的模拟器相比,这将非常慢,以至于它可以理解何时尝试播放声音文件,并且可以“本机”播放该声音文件而无需跟踪模拟程序的指令所以。


关于“追踪”(又名死记硬背)与“本地执行”

最后一件事:跟踪速度很慢,主要是因为您必须使用大量内存来“复制”您正在模拟的事物的非常详细、复杂的组件,而不仅仅是在主机 CPU 上执行指令,您必须执行指令哪个执行指令(参见间接级别?),这会导致效率低下。如果你全神贯注地模拟计算机系统的物理硬件以及程序,那么你将模拟 CPU、主板、声卡等,这些反过来又会“跟踪”程序的执行情况,就像你的程序一样。模拟器“跟踪”CPU 的执行,并且有这么多级别的跟踪,整个过程将非常缓慢和繁琐。

这是一个详细的示例,其中仿真器不需要读取输入程序的每个位/字节来模拟它。

假设我们知道一个用 C 或 C++ 编写的 API(细节不重要)用于模拟软件环境,该 API 有一个函数void playSound(string fileName)。假设我们知道这个函数的语义是打开磁盘上的文件,读取它的内容,找出文件的编码(MP3?WAV?别的什么?),然后在扬声器上播放它普通/预期采样率和音高。如果我们从本机代码中读取一组指令,上面写着“进入 playSound 例程开始播放声音/home/hello/foo.mp3”,我们可以在那里停止读取程序代码,并使用我们自己的(优化!)本机打开该声音文件并播放它的例程。我们是否需要在指令级别上遵循仿真处理器?不,我们真的不知道,如果我们相信我们知道 API 的作用的话。


一个巨大的差异出现了!(高层地麻烦)

当然,通过读取一堆指令并“推断”一个高级执行计划,如上面的示例所示,您可能会冒着可能无法精确模拟在原始硬件上运行的原始程序的行为的风险。例如,原始硬件可能有硬件限制,只能同时播放 8 个声音文件。那么,如果您的新电脑可以同时播放 128 个声音文件就好了,并且您正在playSound高水平地模拟例程,那么有什么能阻止您一次播放 8 个以上的声音文件呢?这可能会导致...在程序的模拟版本中出现奇怪的行为(无论好坏)。这些情况可以通过仔细测试来解决,或者可以通过非常了解原始执行环境来解决。

例如,DOSBox 有一个特性,可以让你故意限制模拟的 DOS 程序的执行速度,因为如果让它们全速运行,某些 DOS 程序会运行不正确;它们实际上取决于 CPU 时钟频率以预期速度执行的时间。这种有意限制执行环境的“特性”可用于在执行的忠实度(即,使仿真程序正常工作)和执行效率(即,构建程序的表示)之间提供良好的折衷。足够高,可以通过最少的跟踪有效地模拟)。