(x86)虚拟机通常如何处理标志?

时间:2016-10-27 19:11:35

标签: assembly x86 virtual-machine virtualization

对于副项目,我正在尝试编写一个半可编程的x86虚拟机。

我理解这些格式,因此大多数设计都相对简单,但在执行带有操作数的指令后,标志经常会改变。检查每个潜在的位是非常低效的,所以我想把标志寄存器弹出到VM中,然后设置它,然后设置VM的标志寄存器。但是,这仍然是很多的开销。

这是一种立场主张,但我有什么遗漏?

2 个答案:

答案 0 :(得分:6)

如果您希望模拟器按原样模拟处理器,那么是的,您需要完全模拟标记。

这意味着清除需要清零的位(使用AND),设置需要设置的位(使用OR),以及在需要时复制/计算位(即Z标志需要测试结果是否为零,携带需要知道你是否有溢出等。)

没有办法解决它。

这就像解码R/M mod字节一样。您无法加载该字节,检查模式以确定这是寄存器还是内存访问,并相应地应用这些...

实际上,这意味着你的模拟器将“慢得多”(除非你用3Ghz现代处理器模拟旧的10Mhz处理器,当你无论如何有时间执行300个周期的指令时...所以你应该是很好。)

如果您有兴趣,我写了6502 emulator并用Apple 2 ROM测试了它。我不得不添加睡眠以使其无法以100Mhz或更高速度运行...(该处理器最初运行1Mhz ......)

答案 1 :(得分:5)

您似乎在询问是否要模拟x86,而不是虚拟化它。从modern x86 hardware supports virtualization开始,CPU在本地运行访客代码并且只为虚拟机管理程序捕获某些特权指令,这就是“虚拟化”这个术语的含义。通常意味着。

懒惰的旗帜评估是典型的。而不是实际计算所有标志,只需保存设置标志的最后一条指令的操作数。然后,如果某些内容实际上读取了标志,请找出标志值需要的内容。

这意味着你每次写入时几乎不需要计算PF和AF(几乎所有指令),只有在每次读取时才会计算(主要是只有PUSHF或中断,几乎没有代码永远读取PF(除了FP分支,它意味着NaN))。在每个整数指令之后计算PF在纯C中是昂贵的,因为它需要在低8位结果上弹出一个数量。 (我认为C编译器通常无法识别该模式并自己使用setp,更不用说pushflahf来存储多个标志,如果编译x86模拟器的话但是,在定位具有该功能的主机CPU(例如popcnt)时,它们有时会识别填充计数模式并发出-march=nehalem指令。

BOCHS使用此技术,并在此简短pdf的“懒惰标志”部分详细描述了实现:How Bochs Works Under the Hood 2nd edition 。它们保存结果,因此它们可以导出ZF,SF和PF,以及CF和OF的高2位进位,以及AF的3位进位。有了这个,他们就不需要重放一条指令来计算它的标志结果。

一些指令没有写入所有标志(即部分标志更新),并且可能来自BSF之类的指令,根据输入设置ZF而非输出。

进一步阅读

This paper on emulators.com提供了很多有关如何有效保存足够状态以重建标志的细节。它有一个用于CPU仿真的" 2.1懒惰算术标志"。

其中一位作者是Darek Mihocka(长期模拟器作家,现在在英特尔工作)。他撰写了许多有趣的内容,关于使非JIT模拟器快速运行,以及一般的CPU性能,大部分都发布在他的网站http://www.emulators.com/上。例如。 this article关于避免模拟器的解释器循环中的分支错误预测,该循环调度到实现每个操作码的函数是非常有趣的。 Darek也是那篇关于我之前联系过的BOCHS内部的文章的合着者。

gozy hit for lazy flag eval也可能相关:https://silviocesare.wordpress.com/2009/03/08/lazy-eflags-evaluation-and-other-emulator-optimisations/

上次出现类似x86标志的仿真时,我的懒人标志答案中的discussion in comments有一些有趣的东西:例如@Raymond Chen建议链接到Mihocka& Troeger论文和@amdn指出,JIT动态翻译可以产生比解释更快的仿真。