模拟器能解析文件内的二进制代码吗?

模拟器能解析文件内的二进制代码吗?

我见过一些声称可以执行的模拟器,但即使可以执行,它们的源代码显示它们不会直接解析每个 1 和 0 来确定指令。

我的问题是,如果模拟器必须模拟真实 CPU 的精确操作码,那么是否需要解析游戏的正确二进制操作码格式才能合法地(或完全)模拟 CPU?

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

0000 1111

我的程序必须验证该指令确实意味着(例如“将一添加到 A 寄存器”),但它是否需要检查文本文件中的每个零和一来确保这一点?

然后,模拟器会解析整个字节,但整个字节再次是八位,并且波动的模式会改变操作输出。

例如,0000 1111 可能表示在 A 上加一,但 0000 1110 可能表示在 A 上加 A。

答案1

解释——试图直接回答问题

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

  1. 你是错误的认为模拟器不会读取文件的每个部分,并且它事实上,你就错了。
  2. 你是正确的,并且模拟器不会读取每一个位,因为它能够假设关于它所模拟的程序的行为的某些事实,以便不需要读取每一个位来知道它需要做什么(可能是因为它期望运行某个游戏引擎,或者某种类型的图形 API,或者某种类型的声音 API 等)。
  3. 你是正确的,并且模拟器不会读取每一个位,因为可执行文件中的某些位对于正确执行程序来说根本不是必需的。它们可能是遗留的“垃圾”或元数据,或者任何其他不构成程序功能的额外内容。
  4. 你是正确的,而模拟器不会读取每一个位,因为模拟器正在将代码中的某些操作转换为更高级的操作,并且完全绕过低级、处理器/硬件特定的指令。例如,如果有人要求你模仿某人在视频录像中执行复杂操作时所做的一切,而他们说“现在在盒子的侧面钻一个洞”,你很想停止观看视频,利用你现有的钻孔经验,而不是跟随视频中那个人的动作(假设你配备了合适的钻头,并且在生活中经验丰富)。同样,如果模拟器可以推断出程序要求在给定的一组坐标上在屏幕上绘制 32x32 的图像,那么只要它了解了图像是什么、图像的格式是什么以及在哪里绘制它,它就可以停止读取代码——它不需要查看模拟系统是如何绘制它的。

模拟器的工作原理

执行其他平台和/或 CPU 代码的模拟器(例如,葡萄酒) 在不同阶段执行任务。有些阶段对于模拟器的工作来说是必不可少的;其他阶段则是可选的,代表了性能优化的可能性。

  • 必需的:“解析”可执行代码(机器码、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 时钟速率的时序以预期速度执行。这种有意限制执行环境的“功能”可用于在两者之间提供良好的权衡忠诚执行(即使模拟程序正常工作)和效率执行(即构建一个足够高级的程序表示,以便能够通过最少的跟踪进行有效的模拟)。

答案2

我的问题是,如果模拟器必须模拟真实 CPU 的精确操作码,那么是否需要解析游戏的正确二进制操作码格式才能合法地(或完全)模拟 CPU?

“解析”的意思是“浏览文本并弄清楚内容的含义。”文本和语言类语法很复杂,因为不仅单个“单词”有意义,而且其含义还会根据其位置和与其他单词的接近程度而改变。

可能更准确的术语是“解码”来描述 CPU 对指令流所做的操作(这比解析简单得多),而且是的,模拟器必须以相同的方式“解码”指令。不过,我猜“解析”并不是一个很糟糕的术语,因为 x86 指令集非常复杂(如果你说的是这个)。

我的程序必须验证该指令确实意味着(例如“将一添加到 A 寄存器”),但它是否需要检查文本文件中的每个零和一来确保这一点?

CPU 并不真正验证指令。任何 CPU 都有一个指向 RAM 中内存位置的内部寄存器。CPU 读取它,如果需要,再读取几个字节,然后尝试执行它。如果指令是非法的,现代 CPU 将启动“异常进程”。

旧 CPU,例如旧的 8 位 6502,甚至没有这样做 - 一些非法指令会锁定 CPU,其他指令会执行奇怪的、未记录的事情。(一些搜索会发现在 x86 万神殿中的所有 CPU 中发现了许多未记录的 x86 指令 - 例如,CPUID在英特尔正式记录之前,该指令就存在于一些 486 CPU 上。)

一个好的、准确的 CPU 模拟器会像真正的 CPU 一样盲目地执行指令流 - 尽管它可能会在导致系统锁定的情况下执行不同的事情,例如显示一个对话框告诉您虚拟 CPU 已死而不是锁定您的模拟器。

此外,您说“它需要检查文本文件中的每个零和一”,但通常您不会向模拟器提供文本文件。CPU 模拟器需要模拟内存 - 而且由于许多平台使用内存映射 I/O,因此也需要模拟 I/O 设备。模拟固件之类的东西需要您拥有该固件的二进制映像 - 真实固件的合法副本或替代开源固件。模拟整个平台(例如 x86 CPU 是其组成部分的“PC 平台”)需要的不仅仅是 CPU 模拟。

相关内容