【问题标题】:How do emulators work and how are they written? [closed]模拟器如何工作以及它们是如何编写的? [关闭]
【发布时间】:2010-10-01 17:04:00
【问题描述】:

模拟器是如何工作的?当我看到 NES/SNES 或 C64 仿真器时,我感到很震惊。

您是否必须通过解释特定的汇编指令来模拟这些机器的处理器?还有什么内容?它们通常是如何设计的?

您能给有兴趣编写模拟器(尤其是游戏系统)的人提供任何建议吗?

【问题讨论】:

  • 您需要找到的最重要的东西是该系统的“程序员手册”,因为它详细说明了硬件供应商和程序员之间的“合同”,并隐藏了不相关且可能改变。您的机会取决于系统受欢迎程度。
  • 不错的游戏选择。
  • 对于任何想知道Emulation vs Simulation的人
  • 自从我第一次玩那个游戏以来,我一直想知道为什么 Hyrule 到处都是“8 球”巨石 :-)

标签: emulation c64


【解决方案1】:

仿真是一个多方面的领域。以下是基本思想和功能组件。我将把它分成几部分,然后通过编辑填写详细信息。我要描述的许多事情都需要了解处理器的内部工作原理——汇编知识是必要的。如果我对某些事情有点含糊不清,请提出问题,以便我继续改进此答案。

基本思路:

仿真通过处理处理器和各个组件的行为来工作。您构建系统的每个单独部分,然后像硬件中的电线一样连接这些部分。

处理器仿真:

处理处理器仿真的三种方式:

  • 解读
  • 动态重新编译
  • 静态重新编译

通过所有这些路径,您有相同的总体目标:执行一段代码来修改处理器状态并与“硬件”交互。处理器状态是给定处理器目标的处理器寄存器、中断处理程序等的集合。对于 6502,您将有多个 8 位整数表示寄存器:AXYPS;你还有一个 16 位的 PC 寄存器。

通过解释,您从IP(指令指针——也称为PC,程序计数器)开始并从内存中读取指令。您的代码解析此指令并使用此信息来更改处理器指定的处理器状态。解释的核心问题是它非常慢。每次处理给定指令时,都必须对其进行解码并执行必要的操作。

通过动态重新编译,您可以像解释一样对代码进行迭代,但您不仅可以执行操作码,还可以构建一个操作列表。一旦到达分支指令,您就可以将此操作列表编译为主机平台的机器代码,然后缓存此编译代码并执行它。然后,当您再次点击给定指令组时,您只需执行缓存中的代码。 (顺便说一句,大多数人实际上并没有列出指令,而是将它们即时编译为机器代码——这使得优化变得更加困难,但这超出了这个答案的范围,除非有足够多的人感兴趣)

使用静态重新编译,您执行的操作与动态重新编译相同,但您遵循分支。您最终构建了代表程序中所有代码的代码块,然后可以在没有进一步干扰的情况下执行这些代码。如果没有以下问题,这将是一个很好的机制:

  • 开始时不在程序中的代码(例如压缩、加密、在运行时生成/修改等)不会被重新编译,因此不会运行
  • 已证明在给定二进制文件中查找所有代码等同于Halting problem

这些结合在一起,使得静态重新编译在 99% 的情况下完全不可行。有关更多信息,Michael Steil 对静态重新编译进行了一些出色的研究——这是我所见过的最好的。

处理器仿真的另一面是您与硬件交互的方式。这确实有两个方面:

  • 处理器计时
  • 中断处理

处理器时序:

某些平台(尤其是 NES、SNES 等较旧的控制台)要求您的模拟器具有严格的时序才能完全兼容。使用 NES,您拥有 PPU(像素处理单元),它要求 CPU 在精确的时刻将像素放入内存中。如果您使用解释,您可以轻松地计算周期并模拟正确的时间;使用动态/静态重新编译,事情会变得/很多/更复杂。

中断处理:

中断是 CPU 与硬件通信的主要机制。通常,您的硬件组件会告诉 CPU 它关心什么中断。这很简单——当你的代码抛出一个给定的中断时,你查看中断处理程序表并调用正确的回调。

硬件仿真:

模拟给定硬件设备有两个方面:

  • 模拟设备的功能
  • 模拟实际的设备接口

以硬盘驱动器为例。通过创建后备存储、读/写/格式化例程等来模拟功能。这部分通常非常简单。

设备的实际界面要复杂一些。这通常是内存映射寄存器(例如,设备监视更改以执行信令的内存部分)和中断的某种组合。对于硬盘驱动器,您可能有一个内存映射区域,您可以在其中放置读取命令、写入等,然后将这些数据读回。

我会更详细地介绍,但您可以使用一百万种方法。如果您在此处有任何具体问题,请随时提出,我会添加信息。

资源:

我认为我在这里给出了很好的介绍,但还有 其他领域。我很乐意为您解答任何问题;由于极其复杂,我对其中的大部分内容都非常模糊。

必填维基百科链接:

通用仿真资源:

  • Zophar -- 这是我开始仿真的地方,首先下载仿真器并最终掠夺他们庞大的文档档案。这是您可能拥有的绝对最佳资源。
  • NGEmu -- 直接资源不多,但他们的论坛是无与伦比的。
  • RomHacking.net -- 文档部分包含有关流行控制台机器架构的资源

仿真器项目参考:

  • IronBabel -- 这是一个 .NET 的仿真平台,用 Nemerle 编写,可即时将代码重新编译为 C#。免责声明:这是我的项目,请原谅无耻的插件。
  • BSnes -- 一个很棒的 SNES 模拟器,目标是循环完美的准确性。
  • MAME -- 街机模拟器。很好的参考。
  • 6502asm.com -- 这是一个 JavaScript 6502 模拟器,带有一个很酷的小论坛。
  • dynarec'd 6502asm -- 这是我在一两天内做的一个小技巧。我从 6502asm.com 获取了现有的模拟器并将其更改为将代码动态重新编译为 JavaScript 以大幅提高速度。

处理器重新编译参考:

  • Michael Steil 对静态重新编译的研究(参考上文)在 this paper 中达到高潮,您可以找到源代码等 here

附录:

自从提交此答案以来已经有一年多的时间了,并且受到了所有关注,我认为是时候更新一些东西了。

也许现在仿真中最令人兴奋的事情是libcpu,由前面提到的 Michael Steil 发起。它是一个旨在支持大量 CPU 内核的库,这些内核使用 LLVM 进行重新编译(静态和动态!)。它有巨大的潜力,我认为它会为仿真做很多事情。

emu-docs 也引起了我的注意,它包含一个很棒的系统文档存储库,这对于仿真目的非常有用。我在那里的时间不多,但看起来他们有很多很棒的资源。

我很高兴这篇文章对我有所帮助,我希望我能在今年年底/明年年初之前完成我关于这个主题的书。

【讨论】:

  • 这已经准备好成为一个史诗般的答案。如果你能在最后向我指出任何资源,我将不胜感激。我正在研究可能要模拟的 SNES 或 NES 系统,并将其作为我的学期项目。
  • 当然可以。我将收集一份不错的资源清单。如果你们有什么具体要求,我会尽力满足。
  • @thenonhacker,我的资源部分中引用的 IronBabel 项目是我的。 (无耻的插头有标记;))
  • “已经证明在给定二进制文件中查找所有代码等同于停机问题”——请参考?还是应该是“已经证明在 any 给定二进制文件中找到所有代码等同于停机问题”?也无法访问 Steil 的论文 :-(
  • 你提到你正在写一本书;你能告诉我们最新情况吗?一方面,我有兴​​趣阅读它。
【解决方案2】:

一个名叫 Victor Moya del Barrio 的人就这个主题写了他的论文。 152 页上有很多很好的信息。您可以下载PDFhere

如果您不想注册scribd,可以在 Google 上搜索 PDF 标题 "Study of the techniques for emulation programming"。 PDF 有几个不同的来源。

【讨论】:

    【解决方案3】:

    模拟可能看起来令人生畏,但实际上比模拟要容易得多。

    任何处理器通常都有一个写得很好的规范来描述状态、交互等。

    如果您根本不关心性能,那么您可以使用非常优雅的面向对象程序轻松模拟大多数旧处理器。例如,X86 处理器需要一些东西来维护寄存器的状态(简单),一些东西来维护内存的状态(简单),以及接收每个传入命令并将其应用于机器的当前状态的东西。如果您真的想要准确性,您还可以模拟记忆翻译、缓存等,但这是可行的。

    事实上,许多微芯片和 CPU 制造商会先针对芯片的仿真器测试程序,然后再针对芯片本身测试程序,这有助于他们找出芯片规格或芯片实际实现中是否存在问题在硬件中。例如,编写一个会导致死锁的芯片规范是可能的,当硬件出现最后期限时,重要的是查看它是否可以在规范中重现,因为这表明存在比芯片实现中的问题更大的问题。

    当然,视频游戏模拟器通常关心性能,因此它们不会使用幼稚的实现,并且它们还包含与主机系统的操作系统接口的代码,例如使用绘图和声音。

    考虑到旧视频游戏(NES/SNES 等)的性能非常缓慢,在现代系统上进行仿真非常容易。事实上,更令人惊奇的是,您可以下载所有 SNES 游戏或任何 Atari 2600 游戏的套装,考虑到当这些系统流行时,免费访问每个卡带将是梦想成真。

    【讨论】:

    • 仿真和模拟有什么区别?
    • @Wei:一般来说,模拟器应该像它模拟的系统一样“在外部”表现,但没有什么可以说它必须以类似的方式实现。模拟器以模仿模拟系统的方式实现,因此其行为类似于它。
    • 当你看到“模拟器”时认为它与模拟器“模拟”相似
    【解决方案4】:

    我知道这个问题有点老了,但我想在讨论中添加一些内容。这里的大多数答案都围绕着解释他们所模拟系统的机器指令的模拟器。

    但是,有一个众所周知的例外,称为“UltraHLE”(WIKIpedia article)。 UltraHLE 是有史以来最著名的模拟器之一,它模拟了商业 Nintendo 64 游戏(在家用计算机上具有不错的性能),当时人们普遍认为这是不可能的。事实上,当 UltraHLE 创建时,任天堂仍在为 Nintendo 64 制作新游戏!

    我第一次在印刷杂志上看到关于模拟器的文章,以前我只在网上看到过它们的讨论。

    UltraHLE 的概念是通过模拟 C 库调用而不是机器级调用来使不可能成为可能。

    【讨论】:

      【解决方案5】:

      值得一看的是 Imran Nazar 尝试用 JavaScript 编写 Gameboy 模拟器。

      【讨论】:

      • 我们如何获得Gameboy游戏的原始操作码指令?
      • “灰色市场”上有许多设备可供出售。您不会在发达国家的任何主要商店中找到它们。这些设备能够将指令从游戏卡复制到通常称为“ROM”的文件中。谷歌“Gameboy Roms”,但要注意不安全的链接和攻击网站!
      【解决方案6】:

      创建了我自己的 80 年代 BBC 微型计算机模拟器(在 Google 中输入 VBeeb),有很多事情要知道。

      • 你不是在模仿真实的东西,那将是一个复制品。相反,您正在模拟 State。一个很好的例子是计算器,实物有按钮、屏幕、外壳等。但是要模拟计算器,您只需要模拟按钮是向上还是向下、LCD 的哪些段打开等。基本上,一组数字表示在计算器中可以改变的所有可能的事物组合。
      • 您只需要模拟器的界面出现并表现得像真实的东西。越有说服力,仿真就越接近。幕后发生的事情可以是您喜欢的任何事情。但是,为了便于编写模拟器,在真实系统(即芯片、显示器、键盘、电路板和抽象计算机代码)之间存在一种心理映射。
      • 要模拟计算机系统,最简单的方法是将其分成更小的块并单独模拟这些块。然后将整个批次串在一起以获得成品。就像一组带有输入和输出的黑匣子,非常适合面向对象编程。您可以进一步细分这些块以使生活更轻松。

      实际上,您通常希望为仿真的速度和保真度而写作。这是因为目标系统上的软件将(可能)比源系统上的原始硬件运行得更慢。这可能会限制编程语言、编译器、目标系统等的选择。
      除此之外,您还必须限制您准备模拟的内容,例如不需要模拟微处理器中晶体管的电压状态,但可能需要模拟微处理器寄存器组的状态。
      一般来说,仿真的细节级别越小,您对原始系统的保真度就越高。
      最后,旧系统的信息可能不完整或不存在。所以掌握原始设备是必不可少的,或者至少要分清另一个别人写的好模拟器!

      【讨论】:

        【解决方案7】:

        是的,您必须“手动”解释整个二进制机器代码。不仅如此,大多数时候您还必须模拟一些在目标机器上没有等效硬件的奇异硬件。

        简单的方法是逐一解释说明。这很好用,但速度很慢。一种更快的方法是重新编译——将源机器代码翻译成目标机器代码。这更复杂,因为大多数指令不会一对一映射。相反,您将不得不做出涉及额外代码的复杂变通方法。但最终它要快得多。大多数现代模拟器都这样做。

        【讨论】:

        • 目前最糟糕的是缺少文档。当您发现 GameBoy Color 中修改后的 Z80 内核具有您正在测试的游戏使用的未记录的标志操作时,您才真正开始失去信心。
        • 小毛病:这是机器 code(单数),而不是机器 codes(复数);就像它是 莫尔斯电码 而不是 莫尔斯电码
        • @Vilx:实际上没有——术语“机器代码”,指的是 CPU 的指令集,自软件问世以来就一直在使用,并不是复数。它指的是单数形式的“指令set”,而不是复数形式的“指令”。与程序代码、摩尔斯电码等相同。复数形式的使用已因误用而逐渐普及,通常是那些以英语为第二语言的人。
        • @Software Monkey - 但是我不能用“代码”这个词来指代集合中的单个项目吗?例如:“... --- ... - 这三个摩尔斯电码代表三个字母 S、O、S。”因为... 是代表字母“S”的代码。没有?
        • 不,code是不可数名词,它没有像water或sand这样的复数形式..
        【解决方案8】:

        当您开发模拟器时,您正在解释系统正在处理的处理器组件(Z80、8080、PS CPU 等)。

        您还需要模拟系统具有的所有外围设备(视频输出、控制器)。

        您应该开始为 simpe 系统编写模拟器,例如老式的 Game Boy(使用 Z80 处理器,我没有弄错)或 C64。

        【讨论】:

        • C64 是一个“简单”的系统?虽然 6510 相对简单(一旦您了解了未列出的操作码),声音 (SID) 和视频 (VIC) 芯片就简单了。为了达到任何体面的兼容性水平,您需要模拟它们 - 硬件错误等等。
        【解决方案9】:

        模拟器很难创建,因为有很多 hack(如在不寻常的 效果)、时间问题等,您需要模拟。

        有关这方面的示例,请参阅http://queue.acm.org/detail.cfm?id=1755886

        这还将向您展示为什么您“需要”多 GHz CPU 来模拟 1MHz 的 CPU。

        【讨论】:

          【解决方案10】:

          还可以查看 Darek Mihocka 的 Emulators.com,了解有关 JIT 指令级优化的重要建议,以及有关构建高效仿真器的许多其他好东西。

          【讨论】:

            【解决方案11】:

            我从来没有做过任何花哨的事情来模拟游戏机,但我确实参加过一次课程,任务是为 Andrew Tanenbaums Structured Computer Organization 中描述的机器编写模拟器。这很有趣,给了我很多惊喜。在开始编写真正的模拟器之前,您可能需要先拿起那本书。

            【讨论】:

              【解决方案12】:

              关于模拟真实系统或您自己的东西的建议? 我可以说模拟器通过模拟整个硬件来工作。也许不是电路(就像硬件一样移动位。移动字节是最终结果,所以复制字节就可以了)。模拟器很难创建,因为您需要模拟许多技巧(如不寻常的效果)、时间问题等。如果一个(输入)部分出错,整个系统可能会出现故障,或者充其量会出现错误/故障。

              【讨论】:

                【解决方案13】:

                Shared Source Device Emulator 包含可构建到 PocketPC/Smartphone 模拟器的源代码(需要 Visual Studio,在 Windows 上运行)。我在二进制版本的 V1 和 V2 上工作。

                它解决了许多仿真问题: - 从客户虚拟到客户物理到主机虚拟的有效地址转换 - 来宾代码的 JIT 编译 - 模拟网络适配器、触摸屏和音频等外围设备 - UI 集成,用于主机键盘和鼠标 - 保存/恢复状态,用于模拟从低功耗模式恢复

                【讨论】:

                  【解决方案14】:

                  添加@Cody Brocious提供的答案
                  在虚拟化环境中,您正在向虚拟机模拟新系统(CPU、I/O 等),我们可以看到以下类别的模拟器。

                  解释:bochs 是解释器的一个例子,它是一个 x86 PC 仿真器,它将来自客户系统的每条指令翻译成另一组指令(主机 ISA)以产生预期的效果。是的,它很慢,它不缓存任何东西,因此每条指令都经过相同的周期。

                  动态仿真器:Qemu 是一个动态仿真器。它对客户指令进行即时翻译也缓存结果。最好的部分是直接在主机系统上执行尽可能多的指令,以便仿真更快。也正如 Cody 所提到的,它将代码分成块(1 个单一的执行流程)。

                  静态模拟器:据我所知,没有静态模拟器可以帮助虚拟化。

                  【讨论】:

                    【解决方案15】:

                    我将如何开始仿真。

                    1.获取基于低级编程的书籍,您将需要它来“伪装”任天堂的操作系统......游戏男孩......

                    2.获取专门的仿真书籍,也许还有操作系统开发。 (您不会制作操作系统,而是最接近它的操作系统。

                    3.看看一些开源模拟器,尤其是那些你想为之制作模拟器的系统。

                    4.将更复杂的代码的 sn-ps 复制到您的 IDE/编译器中。这将节省您编写长代码的时间。这是我做os开发的,用的是linux的一个区

                    【讨论】:

                      【解决方案16】:

                      我写了一篇关于模仿Chip-8 system in JavaScript的文章。

                      这是一个很好的起点,因为系统不是很复杂,但您仍然可以了解操作码、堆栈、寄存器等的工作原理。

                      我很快就会为 NES 写一个更长的指南。

                      【讨论】:

                        猜你喜欢
                        • 2019-01-08
                        • 2010-12-31
                        • 2011-01-06
                        • 1970-01-01
                        • 2018-01-25
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        相关资源
                        最近更新 更多