摘要:

分析下Qemu的内部结构,qemu大概是一个使用原始便携动态翻译器的快速机器仿真工具,。它能在很多机器上(x86, ARM PowerPC, Sparc….) 仿真CPU (x86, ARM,PowerPC, Sparc),QEMU支持完整的系统仿真,可以在虚拟机和Linux用户模式下仿真完整的操作系统,同时编译完成后的仿真操作系统可以运行在不同cpu上。

 

介绍:

QEMU是一个机器仿真器:仿真器可以运行在一个正常的操作系统下(比如Windows或者Linux),也可以运行在一个虚拟机里。QEMU本身肯定也可以跑在不同的操作系统上,比如Linux, Windows和Mac OS X。以及不同的物理硬件,比如主机型号和CPU可以不同。


QEMU的主要用法是在一个操作系统里运行另一个操作系统,比如Windows上跑Linux,或者Linux上跑Windows。另一种用法是调试,因为虚拟机很容易就突然停止,所以需要检查,保存和恢复其状态。另外,也可以模拟新的设备,通过这些模拟的设备来测试程序。


QEMU还集成了Linux专用的用户模式仿真器。 它是机器仿真器的一个子集,这个进程可以跑在模拟的CPU上也可以跑在物理CPU上。它主要用于测试交叉编译器的结果或测试CPU仿真器,而无需启动完整的虚拟机。


 QEMU由以下子系统组成:

  • CPU仿真器( X86, PowerPC, ARM, Sparc )

  • 仿真设备( VGA, 串行接口,PS/2 鼠标和键盘,IDE硬盘,网卡,….)

  • 将仿真设备连接到相应主机设备的通用设备(例如,块设备,字符设备,网络设备)

  • 机器描述,实例化仿真设备

  • 调试

  • 用户接口

 

本文将探讨QEMU的动态翻译器的实现。 动态转换器执行VCPU指令到Host指令集的运行时转换。 所得到的二进制代码存储在翻译缓存中,以便可以重用它。 与解释器相比的优点是目标指令仅被取出和解码一次。


通常,动态翻译器难以从一个主机端口传输到另一个主机,因为整个代码生成器必须被重写。它代表与向C编译器添加更多相同的代码。 QEMU简单得多,因为它只是连接了由GNU C编译器在线生成的机器代码


CPU模拟器也面临其他更经典但是困难的问题:

  • 管理翻译的代码缓存

  • 注册分配

  • 条件代码优化

  • 直接块链接

  • 内存管理

  • 自修改代码支持

  • 异常支持

  • 硬件中断

  • 用户模式仿真

 

便携的动态翻译

1.描述

第一步是将每个目标CPU指令分成更简单的简单指令,称为微操作。每个微操作都由一小段C代码实现。这个小C代码由GCC编译为一个对象文件。微操作的选择会使得它们的数量比VCPU的指令和操作数的所有组合小得多(通常是几百个)。从VCPU指令到微操作的转换完全是用代码来完成的(软件的方式)。源代码针对可读性和紧凑性进行了优化,因为此阶段的速度与解释器相比不太重要。

 

编译时工具dyngen使用包含微操作的对象文件作为输入来生成动态代码生成器。这个动态代码生成器在运行时被调用以产生一个连接几个微操作的完整主机功能。

 

在编译时要做更多的工作来获得更好的性能。特别地,一个关键思想是在QEMU中可以给定常数参数进行微操作。为此,为每个常量参数使用GCC生成虚拟代码重定位。这使得dyngen工具能够定位重定位,并在生成动态代码时生成适当的C代码来解决这些错误。还支持重定位,以便在微操作中引用静态数据和其他功能。

 

2.例子:

假设我们要仿真PowerPC,宿主机host为x86,下面说明整个翻译过程:

QEMU动态翻译器_java


在x86上 T0匹配ebx寄存器,cpu状态的上下文信息保存在ebp寄存器上

  

3.Dyngen实现

dyngen是QEMU翻译的关键。在包含微操作的对象文件上运行时执行以下任务:

 

  • 目标文件被解析以获取其符号表,它会重定向入口及其代码段。依赖于主机对象文件格式(dyngen支持ELF(Linux),PE-COFF(Windows)和MACH-O(Mac OS X))。

  • 微操作使用符号表位于代码段中。执行主机特定方法来获取复制代码的开始和结束。通常,跳过功能序言和结尾语。

  • 检查每个微操作的重定位以获取常数参数的数量。通过使用特定符号名称_op_paramN来检测常量参数重定位。

  • 生成C中的存储器副本以复制微操作代码。每个微操作的代码的重定位都用于修补复制的代码,使其被正确地重新定位。迁移修补程序是主机特定的。

  • 对于某些主机(如ARM),必须将常数存储在生成的代码附近,因为它们是通过一个小的位移来访问的。完成主机特定的传递,以在生成的代码中重定位这些常量。

 

 当编译微操作代码时,使用一组GCC标志来将功能序言和结尾码的生成操作为易于解析的形式。 虚拟组装宏强制GCC通过单个返回指令总是终止与每个微操作相对应的功能。 如果在单个微操作中产生几个返回指令,则代码级联将不起作用。