虚拟世界英特尔。 第2部分:SMP

在上一篇文章 (链接)中,我讨论了基于Intel硬件虚拟化技术的虚拟机管理程序的基本概念。 现在,我建议通过添加对多处理器体系结构(SMP)的支持来扩展虚拟机监控程序的功能,并考虑一个虚拟机监控程序如何对来宾OS进行更改的示例。

所有进一步的操作将在PC上以以下配置执行:

处理器:Intel Core i7 5820K
主板:华硕X99-PRO
内存:16GB
来宾操作系统:禁用PAE的Windows 7 x32

我将从描述虚拟机管理程序组件在硬盘驱动器上的位置开始(所有值均在扇区中指定)。

图片
加载管理程序的过程与先前版本的区别仅在于存在新模块hypervisor.ap ,其目的是初始化AP处理器。

将模块加载到内存的过程:



SMP支持

我基于对称多处理的原理实现了虚拟机监控程序,这意味着将在存在的所有逻辑处理器上启动相同的VMX副本。 此外,IDT和GDT表以及用于分页内存的表对于所有逻辑处理器都是通用的。 我这样做是因为管理程序将立即为来宾OS的地址空间初始化内存,而无需动态地重新分配各个页面的物理地址。 同样,使用这种方法,您无需在系统管理程序侧监视处理器TLB缓存的对应关系。
BSP和AP的初始化过程将有所不同。 系统管理程序中涉及的所有主要结构都将在BSP初始化期间创建。 另外,vmx非根模式AP处理器的活动状态将设置为HLT状态。 因此,将根据不使用虚拟化的情况来模拟来宾OS环境。

初始化BSP:

  1. 自旋锁初始化
  2. 初始化和加载GDT和IDT表
  3. 初始化分页表
  4. 初始化VMCS结构并创建公用EPT表
  5. 激活AP处理器。 为此,将INIT-SIPI中断序列发送到每个AP。 SIPI中断的向量为0x20,对应于0x20000处的AP控制传输(hypervisor.ap模块)
  6. 在0x7C00(win7.mbr模块)启动来宾操作系统

初始化AP:

  1. 激活AP后,处理器处于实模式。 hypervisor.ap模块初始化内存和分页表以切换到长模式
  2. 下载IDT,GDT以及在BSP初始化阶段创建的分页表的目录
  3. 初始化VMCS结构,并加载在BSP初始化阶段创建的EPT表
  4. 切换到具有活动HLT状态的vmx非root用户模式

可以说,在管理程序中对SMP支持的实现非常简单,但是有几点我想引起注意。

1.USB旧版支持

新的主板型号可能没有PS / 2连接器,因此使用USB传统支持来确保向后兼容。 这意味着您可以使用与PS / 2标准相同的方法(输入/输出端口)使用USB键盘或鼠标。 USB Legacy Support的实现不仅取决于主板的型号,而且还可以吸引不同版本的固件。 在我的Asus X99-PRO主板上,USB传统支持是通过SMI中断实现的,在该处理器中发生PS / 2仿真。 我对此进行了详细介绍,因为在我的情况下(固件版本3801),USB Legacy Support与长模式不兼容,当它从SMM返回时,处理器将进入关闭状态。

在这种情况下,最简单的解决方案是在切换到长模式之前关闭USB Legacy支持。 但是,在Windows中,在选择启动选项的阶段使用PS / 2键盘轮询方法,因此必须在来宾OS开始加载之前再次激活USB Legacy Support。

2.硬件任务开关

在现代操作系统中,通常通过软件方法来实现任务之间的切换。 但是,在Windows7中,将指向TSS的选择器分配给中断2-NMI和8-Double Fault,这意味着此类中断将导致硬件上下文切换。 Intel VMX不支持硬件任务切换,尝试执行它会导致VM退出。 对于这种情况,我编写了任务切换处理程序(GuestTaskSwitch函数)。 仅当由于其他中断的不正确处理导致严重的系统冲突时,才会发生Double Fault中断。 在调试过程中,我没有遇到它。 但是NMI在重新启动Windows时出现在AP处理器上。 这仍然引起我的怀疑,因为不清楚这些NMI是常规重新启动过程的结果还是在以前的某些阶段管理程序的这种不正确操作。 如果您有关于此主题的任何信息,请在评论中讲话或在个人信息中给我写信。

来宾操作系统中的更改

老实说,我很长一段时间都无法确定虚拟机监控程序在来宾OS工作中应该进行哪些更改。 事实是,一方面我想展示一些有趣的东西,例如在基本网络协议中引入我们的处理程序,但另一方面,这全都归结为大量代码,并且与管理程序主题无关。 另外,我不想将管理程序绑定到任何特定的铁上。

结果,发现了以下折衷方案:在此版本的虚拟机管理程序中,实现了对来自用户模式的系统调用的控制,换句话说,可以控制来宾OS中运行的应用程序的操作。 这种控件的实现非常简单,此外,还可以使您获得视觉效果。

对应用程序操作的控制将在系统调用级别执行。 并且主要目标将是更改NtQuerySystemInformation函数的结果,以便在使用SystemProcessInformation0x05 )参数调用时,可以截取进程信息。

在Windows 7中,用于调用系统功能的应用程序使用sysenter汇编器命令,然后将控制权转移到KiFastCallEntry处理器,级别为r0到内核。 要返回到应用程序级别r3,请使用sysexit命令。
若要访问NtQuerySystemInformation函数执行的结果,必须在每次执行sysenter命令时保存被调用函数的编号。 然后,在执行sysexit时,将存储的值与要拦截的函数的编号进行比较,如果匹配,则对函数返回的数据进行更改。
英特尔VMX不提供直接方法来监视sysenter / sysexit的执行,但是,如果将值0写入Guest MSR IA32_SYSENTER_CS ,则sysenter / sysexit命令将引发GP异常,可用于调用VM Exit处理程序。 为了让GP异常调用VM Exit,您需要在VMCS的Exception Bitmap字段中设置13位。

以下结构用于模拟sysenter / sysexit对。

typedef struct{ QWORD ServiceNumber; QWORD Guest_Sys_CS; QWORD Guest_Sys_EIP; QWORD Guest_Sys_ESP; } SysEnter_T; 

ServiceNumber字段包含正在调用的函数的编号,并且每次调用sysenter都会更新。

当来宾OS尝试写入相应的MSR寄存器时将更新Guest_Sys_CS,Guest_Sys_EIP,Guest_Sys_ESP字段。 为此, 设置MSR位图地址中的写掩码。

 // 174H 372 IA32_SYSENTER_CS SYSENTER_CS write mask ptrMSR_BMP[0x100 + (0x174 >> 6)] |= (1UL << (0x174 & 0x3F)); // 175H 373 IA32_SYSENTER_ESP SYSENTER_ESP write mask ptrMSR_BMP[0x100 + (0x175 >> 6)] |= (1UL << (0x175 & 0x3F)); // 176H 374 IA32_SYSENTER_EIP SYSENTER_EIP write mask ptrMSR_BMP[0x100 + (0x176 >> 6)] |= (1UL << (0x176 & 0x3F)); 

来宾操作系统不应看到系统管理程序对系统函数调用的操作所做的更改。 通过设置MSR IA32_SYSENTER_CS的读取掩码可以在读取时将来宾OS返回其原始寄存器值。

 // 174H 372 IA32_SYSENTER_CS SYSENTER_CS read mask ptrMSR_BMP[0x174 >> 6] |= (1UL << (0x174 & 0x3F)); 

以下是sysenter / sysexit命令仿真方案。



sysexit仿真阶段 ,将调用函数的存储编号与NtQuerySystemInformation编号(0x105)进行比较。 在匹配的情况下,将验证是否使用“系统进程信息”参数调用了NtQuerySystemInformation,如果是,则ChangeProcessNames函数(DWORD SPI_GVA,DWORD SPI_size)会对包含有关进程信息的结构进行更改。
SPI_GVASYSTEM_PROCESS_INFORMATION结构的来宾虚拟地址
SPI_size是结构的总大小(以字节为单位)。
SYSTEM_PROCESS_INFORMATION结构本身如下所示:

 typedef struct _SYSTEM_PROCESS_INFORMATION { ULONG NextEntryOffset; ULONG NumberOfThreads; BYTE Reserved1[48]; UNICODE_STRING ImageName; KPRIORITY BasePriority; HANDLE UniqueProcessId; PVOID Reserved2; ULONG HandleCount; ULONG SessionId; PVOID Reserved3; SIZE_T PeakVirtualSize; SIZE_T VirtualSize; ULONG Reserved4; SIZE_T PeakWorkingSetSize; SIZE_T WorkingSetSize; PVOID Reserved5; SIZE_T QuotaPagedPoolUsage; PVOID Reserved6; SIZE_T QuotaNonPagedPoolUsage; SIZE_T PagefileUsage; SIZE_T PeakPagefileUsage; SIZE_T PrivatePageCount; LARGE_INTEGER Reserved7[6]; } SYSTEM_PROCESS_INFORMATION; 

它的解析没有什么复杂的,主要的是不要忘记将来宾虚拟地址转换为物理地址,为此使用了GuestLinAddrToPhysAddr()函数。

为了清楚起见,我将所有进程名称中的前两个字符替换为' :) '符号,这种替换的结果在屏幕截图中可见。



总结

一般而言,本文开头设置的任务已完成。 系统管理程序可确保来宾OS的稳定运行,并从应用程序级别控制系统功能的调用。 我注意到,使用sysenter / sysexit命令仿真的主要缺点是VM Exit调用的显着增加,这会影响性能,而当来宾OS处于单处理器模式时,这一点尤其明显。 如果仅在所选进程的上下文中控制调用,则可以消除此缺点。

到此为止。 文章来源可在此处获取

谢谢您的关注。

Source: https://habr.com/ru/post/zh-CN429918/


All Articles