Lup*_*upe 2 virtualization x86 assembly virtual-machine
对于侧面项目,我试图编写一个半可编程的x86虚拟机.
我理解格式,因此大部分设计都相对简单,但在执行带有操作数的指令后,标志经常会改变.检查每个潜在的位是非常低效的,所以我想把标志寄存器弹出到VM中,然后设置它,然后设置VM的标志寄存器.但是,这仍然是很多开销.
这是一种立场的观点,但我有什么遗漏?
如果您希望模拟器按原样模拟处理器,那么是的,您需要完全模拟标志.
这意味着清除需要清零的位(使用AND),设置需要设置的位(使用OR),以及在需要时复制/计算位(即Z标志需要测试结果是否为零,进位需要知道你是否有溢出等)
没有其他办法了.
这就像解码R/M mod
字节一样.您无法加载该字节,检查模式以确定这是寄存器还是内存访问,并相应地应用这些...
实际上,这意味着你的模拟器将"慢得多"(除非你用3Ghz现代处理器模拟旧的10Mhz处理器,当你无论如何都有时间执行300个周期的指令时...所以你应该没问题. )
如果你有兴趣,我写了一个6502模拟器并用Apple 2 ROM进行了测试.我不得不添加睡眠以使其不以100Mhz或更高速度运行...(该处理器最初运行1Mhz ......)
您似乎在询问模拟x86,而不是虚拟化它.由于现代x86硬件支持虚拟化,其中CPU本地运行访客代码并且仅针对某些特权指令捕获到管理程序,这就是术语"虚拟化"通常意味着什么.
懒惰的旗帜评估是典型的.而不是实际计算所有标志,只需保存设置标志的最后一条指令的操作数.然后,如果某些内容实际上读取了标志,请确定标志值需要是什么.
这意味着你每次写入时几乎不需要计算PF和AF(几乎每条指令),只有每次读取它们时(大多数只有PUSHF或中断,几乎没有任何代码读取PF(FP分支除外)这意味着NaN)).在每个整数指令之后计算PF在纯C中是昂贵的,因为它需要在低8位结果上使用popcount.(我认为C编译器一般不设法识别模式,并使用setp
自己,更不用说pushf
或lahf
存储多个标志,如果编译一个x86模拟器在x86主机上运行.它们有时会认识人口数的模式和发射popcnt
但是,在指向具有该功能的主机CPU时(例如-march=nehalem
)).
BOCHS使用这种技术,并在这篇简短的pdf的Lazy Flags部分中详细描述了实现:Bochs如何在引擎盖第2版下工作.它们保存结果,因此它们可以导出ZF,SF和PF,以及CF和OF的高2位进位,以及AF的3位进位.有了这个,他们永远不需要重放指令来计算其标志结果.
某些指令没有写入所有标志(即部分标志更新),并且可能来自BSF之类的指令,根据输入而不是输出设置ZF .
进一步阅读:
emulators.com上的这篇论文给出了很多关于如何有效地保存足够的状态来重建标志的细节.它有一个"用于CPU仿真的2.1懒惰算术标志".
其中一位作者是Darek Mihocka(长期模拟器作家,现在在英特尔工作).他编写了很多有趣的东西,关于使非JIT仿真器快速运行,以及一般的CPU性能,大部分都发布在他的网站http://www.emulators.com/上.例如,本文关于避免模拟器的解释器循环中的分支错误预测,该循环调度到实现每个操作码的函数非常有趣.Darek也是我之前链接的关于BOCHS内部的文章的合着者.
谷歌搜索懒惰的旗帜eval也可能是相关的:https://silviocesare.wordpress.com/2009/03/08/lazy-eflags-evaluation-and-other-emulator-optimisations/
上次仿真类似x86的标志出现了,我对懒惰标志的评论中的讨论有一些有趣的东西:例如@Raymond Chen建议链接到Mihocka&Troeger论文,@ amdn指出JIT动态翻译可以产生比解释更快的仿真.