仿真器如何工作以及如何编写? [英] How do emulators work and how are they written?

查看:105
本文介绍了仿真器如何工作以及如何编写?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

仿真器如何工作?当我看到NES/SNES或C64模拟器时,这让我大吃一惊.

您是否必须通过解释特定的组装说明来模拟这些机器的处理器?还有什么呢?它们通常是如何设计的?

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

解决方案

仿真是一个多方面的区域.这里是基本思想和功能组件.我将把它分成几部分,然后通过编辑填写细节.我将要描述的许多内容都需要了解处理器的内部工作原理-组装知识是必需的.如果我在某些方面不太清楚,请提出问题,以便我继续改进此答案.

基本思路:

仿真通过处理处理器和各个组件的行为来工作.您需要构建系统的各个部分,然后像连接硬件中的电线那样连接各个部分.

处理器仿真:

有三种处理处理器仿真的方法:

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

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

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

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

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

  • 程序中不存在的代码(例如压缩,加密,在运行时生成/修改的代码等)将不会重新编译,因此不会运行
  • 已经证明,在给定的二进制文件中查找所有代码等同于停止问题

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

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

  • 处理器计时
  • 中断处理

处理器时间:

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

中断处理:

中断是CPU与硬件通信的主要机制.通常,您的硬件组件会告诉CPU它关心的中断是什么.这非常简单-当您的代码引发给定的中断时,您可以查看中断处理程序表并调用适当的回调.

硬件仿真:

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

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

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

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

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

资源:

我想我在这里做了很好的介绍,但是还有 ton 的其他区域.我很乐意为您解决任何问题;由于非常复杂,我在大多数情况下都非常模糊.

必填Wikipedia链接:

常规仿真资源:

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

要参考的仿真器项目:

  • IronBabel -这是.NET的仿真平台,使用Nemerle编写,并将代码重新编译为C#在飞行中.免责声明:这是我的项目,请原谅无耻的插件.
  • BSnes -一款超棒的SNES模拟器,旨在达到完美的循环精度.
  • MAME -街机模拟器.很好的参考.
  • 6502asm.com -这是一个JavaScript 6502模拟器,带有一个很棒的小论坛.
  • dynarec's 6502asm -这是我在一两天之内所做的一个小技巧.我从6502asm.com取得了现有的模拟器,并对其进行了更改,以将代码动态地重新编译为JavaScript,从而大大提高了速度.

处理器重新编译参考:

  • 由迈克尔·斯蒂尔(上面引用)完成的对静态重新编译的研究最终达到了本文,您可以在此处找到来源.

附录:

提交此答案至今已经一年多了,并且得到了所有人的关注,我认为现在是时候进行一些更新了.

也许现在仿真中最激动人心的事情是 libcpu ,由上述迈克尔·史提尔(Michael Steil)发起.它是一个旨在支持大量CPU内核的库,这些内核使用LLVM进行重新编译(静态和动态!).它具有巨大的潜力,我认为它将为仿真做出巨大的贡献.

emu-docs 也引起了我的注意,该文件中包含大量的系统文档,这非常有用用于仿真目的.我在这里花的时间还不多,但看来他们有很多宝贵的资源.

我很高兴这篇文章对您有所帮助,希望我能在年底或明年年初之前完成有关该主题的书.

How do emulators work? When I see NES/SNES or C64 emulators, it astounds me.

Do you have to emulate the processor of those machines by interpreting its particular assembly instructions? What else goes into it? How are they typically designed?

Can you give any advice for someone interested in writing an emulator (particularly a game system)?

解决方案

Emulation is a multi-faceted area. Here are the basic ideas and functional components. I'm going to break it into pieces and then fill in the details via edits. Many of the things I'm going to describe will require knowledge of the inner workings of processors -- assembly knowledge is necessary. If I'm a bit too vague on certain things, please ask questions so I can continue to improve this answer.

Basic idea:

Emulation works by handling the behavior of the processor and the individual components. You build each individual piece of the system and then connect the pieces much like wires do in hardware.

Processor emulation:

There are three ways of handling processor emulation:

  • Interpretation
  • Dynamic recompilation
  • Static recompilation

With all of these paths, you have the same overall goal: execute a piece of code to modify processor state and interact with 'hardware'. Processor state is a conglomeration of the processor registers, interrupt handlers, etc for a given processor target. For the 6502, you'd have a number of 8-bit integers representing registers: A, X, Y, P, and S; you'd also have a 16-bit PC register.

With interpretation, you start at the IP (instruction pointer -- also called PC, program counter) and read the instruction from memory. Your code parses this instruction and uses this information to alter processor state as specified by your processor. The core problem with interpretation is that it's very slow; each time you handle a given instruction, you have to decode it and perform the requisite operation.

With dynamic recompilation, you iterate over the code much like interpretation, but instead of just executing opcodes, you build up a list of operations. Once you reach a branch instruction, you compile this list of operations to machine code for your host platform, then you cache this compiled code and execute it. Then when you hit a given instruction group again, you only have to execute the code from the cache. (BTW, most people don't actually make a list of instructions but compile them to machine code on the fly -- this makes it more difficult to optimize, but that's out of the scope of this answer, unless enough people are interested)

With static recompilation, you do the same as in dynamic recompilation, but you follow branches. You end up building a chunk of code that represents all of the code in the program, which can then be executed with no further interference. This would be a great mechanism if it weren't for the following problems:

  • Code that isn't in the program to begin with (e.g. compressed, encrypted, generated/modified at runtime, etc) won't be recompiled, so it won't run
  • It's been proven that finding all the code in a given binary is equivalent to the Halting problem

These combine to make static recompilation completely infeasible in 99% of cases. For more information, Michael Steil has done some great research into static recompilation -- the best I've seen.

The other side to processor emulation is the way in which you interact with hardware. This really has two sides:

  • Processor timing
  • Interrupt handling

Processor timing:

Certain platforms -- especially older consoles like the NES, SNES, etc -- require your emulator to have strict timing to be completely compatible. With the NES, you have the PPU (pixel processing unit) which requires that the CPU put pixels into its memory at precise moments. If you use interpretation, you can easily count cycles and emulate proper timing; with dynamic/static recompilation, things are a /lot/ more complex.

Interrupt handling:

Interrupts are the primary mechanism that the CPU communicates with hardware. Generally, your hardware components will tell the CPU what interrupts it cares about. This is pretty straightforward -- when your code throws a given interrupt, you look at the interrupt handler table and call the proper callback.

Hardware emulation:

There are two sides to emulating a given hardware device:

  • Emulating the functionality of the device
  • Emulating the actual device interfaces

Take the case of a hard-drive. The functionality is emulated by creating the backing storage, read/write/format routines, etc. This part is generally very straightforward.

The actual interface of the device is a bit more complex. This is generally some combination of memory mapped registers (e.g. parts of memory that the device watches for changes to do signaling) and interrupts. For a hard-drive, you may have a memory mapped area where you place read commands, writes, etc, then read this data back.

I'd go into more detail, but there are a million ways you can go with it. If you have any specific questions here, feel free to ask and I'll add the info.

Resources:

I think I've given a pretty good intro here, but there are a ton of additional areas. I'm more than happy to help with any questions; I've been very vague in most of this simply due to the immense complexity.

Obligatory Wikipedia links:

General emulation resources:

  • Zophar -- This is where I got my start with emulation, first downloading emulators and eventually plundering their immense archives of documentation. This is the absolute best resource you can possibly have.
  • NGEmu -- Not many direct resources, but their forums are unbeatable.
  • RomHacking.net -- The documents section contains resources regarding machine architecture for popular consoles

Emulator projects to reference:

  • IronBabel -- This is an emulation platform for .NET, written in Nemerle and recompiles code to C# on the fly. Disclaimer: This is my project, so pardon the shameless plug.
  • BSnes -- An awesome SNES emulator with the goal of cycle-perfect accuracy.
  • MAME -- The arcade emulator. Great reference.
  • 6502asm.com -- This is a JavaScript 6502 emulator with a cool little forum.
  • dynarec'd 6502asm -- This is a little hack I did over a day or two. I took the existing emulator from 6502asm.com and changed it to dynamically recompile the code to JavaScript for massive speed increases.

Processor recompilation references:

  • The research into static recompilation done by Michael Steil (referenced above) culminated in this paper and you can find source and such here.

Addendum:

It's been well over a year since this answer was submitted and with all the attention it's been getting, I figured it's time to update some things.

Perhaps the most exciting thing in emulation right now is libcpu, started by the aforementioned Michael Steil. It's a library intended to support a large number of CPU cores, which use LLVM for recompilation (static and dynamic!). It's got huge potential, and I think it'll do great things for emulation.

emu-docs has also been brought to my attention, which houses a great repository of system documentation, which is very useful for emulation purposes. I haven't spent much time there, but it looks like they have a lot of great resources.

I'm glad this post has been helpful, and I'm hoping I can get off my arse and finish up my book on the subject by the end of the year/early next year.

这篇关于仿真器如何工作以及如何编写?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆