Hypervisor(二):VMCS region

😄

1 VMCS region

在 VMX 架构下,至少需要实现一个被称为 VMXON region,以及一个被称为 VMCS region 的物理区域。

  • VMXON 区域对应于 VMM,VMM 使用 VMXON 区域对一些数据进行记录和维护。
  • 每个 VMCS(Virtual Machine Control Structure,虚拟机控制结构)区域对应着一个 VM。VMM 使用 VMCS 区域来配置 VM 的运行环境,以及控制 VM 的运行。
  • 在进入 VMX operation 模式前,必须先为 VMM 准备一份 VMXON 区域,同样在进入 VM 前也必须准备相应的 VMCS 区域,并配置 VM 的运行环境。

1.1 VMCS 概述

VMM 通过虚拟机控制结构 VMCS 来设置和保存 VMX root、VMX non-root 模式切换中涉及的数据、寄存器值等。VMCS 具有以下几点特征:

  1. 一个逻辑处理器对应 VMM 中的一块 VMCS(该区域称为 VMCS region),而不是一个 VM 对应着一个 VMCS。如果一个 VM 使用了几个逻辑处理器 vCPU,则就应该有几个对应的 VMCS 结构。可以使用 MSR IA32_VMX_BASIC 来确定 VMCS 区域的大小。
  2. 每个逻辑处理器中都有一个 VMCS 指针(64 bits)指向 VMCS 块的物理地址,应与 4KB 边界对齐。使用 VMPTRSTVMPTRLD 读取和写入 VMCS 指针的值。如果 IA32_VMX_BASIC[48] = 1,则这些指针不得设置 63:32 范围内的任何位,见附录 A.1。
  3. VMM 使用 VMREADVMWRITEVMCLEAR 指令配置 VMCS。

1.2 VMCS 的状态

在 VMX root、VMX non-root 模式切换中,从 VMX root 切换到 VMX non-root 模式被称为 VM-entry;从 VMX non-root 切换到 VMX root 模式被称为 VM-exit


注意⚠️:VM-exit 和 VM-entry 不会改变 VMCS 的状态


VMCS 的三类属性状态:

  1. active — inactive,活动的—非活动的。
  2. current — not current,当前的—非当前的。在一个逻辑处理器上一个时刻只能有一个 VMCS 处于 current(current-VMCS)。
  3. launched — clear,已启动—干净。

启动 VMCS region 区域顺序

  1. VMCS region 区域初始化之后要先试用 VMCLEAR 使该 VMCS 处于 inactive、not current、clear。
  2. 使用 VMPTRLD 使 VMCS 变成 active、current。
  3. 使用 VMLAUNCH 使 VMCS 变成 launched。

制约关系:

  • 使用 VMLAUNCH 指令前,必须先使用 VMCLEAR 使 VMCS region 处于 clear 状态(此时 current-VMCS pointer == 0xFFFFFFFFFFFFFFFF),然后使用 VMPTRLD 将 VMCS 变成 active、current,最后使用 VMLAUNCH 指令。
  • 使用 VMRESUME 进入 VM 前,该 VM 必须已经使用 VMLAUNCH 启动过了。
  • VMREADVMWRITEVMLAUNCHVMRESUMEVMPTRST 指令的操作都是实施在 current-VMCS 之上的

注意

  1. 假设先使用 VMPTRLD 使得 VMCS-A 变成 active、current,current-VMCS point 是 VMCS-A 的。
  2. 然后又使用 VMPTRLD 使得 VMCS-B 变成 active、current,current-VMCS point 是 VMCS-B 的。那么此时 VMCS-A 的状态是 active(活动状态不变)、not current
  3. 在软件中没有任何方法来读取某个 VMCS 处在哪一个状态,CPU 维护这些状态。
  4. VMCLEARVMPTRLD 指令的操作数都是物理地址。

下图描述了 VMCS 的状态变化,在 Intel 手册中使用 “The VMCS” 表示 “Current VMCS”。

7.png

1.3 申请 VMCS region

申请 VMCS region 和申请 VMXON region 类似。

  1. 通过 IA32_VMX_BASIC[44:32] 得到 VMCS 所需空间大小(0x1000,4096 bytes)。
  2. 通过 IA32_VMX_BASIC[53:50] 得到 VMCS 所需内存类型,如 write-back。
  3. 调用 MmAllocateContiguousMemorySpecifyCache 函数申请连续指定大小和类型的物理内存。
  4. 判断返回地址是否在边界对齐 4KB(bit 11:0 为 0)。
  5. 将 IA32_VMX_BASIC[31:0] 的 VMCS revision identifier 写入 VMCS 前 4 字节的 [bit30:0]。
  6. 将 VMCS 的 bit 31 = 0(不是 shadow VMCS)。
  7. 使用 VMCLEAR 使 VMCS region 处于 clear 状态。
  8. 使用 VMPTRLD 使 VMCS 变成 active、current。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// 
// 1. 通过 IA32_VMX_BASIC[44:32] 得到 VMCS 所需空间大小(0x1000,4096 bytes)。
// 2. 通过 IA32_VMX_BASIC[53:50] 得到 VMCS 所需内存类型,如 write-back。
// 3. 调用 MmAllocateContiguousMemorySpecifyCache 函数申请连续指定大小和类型的物理内存。
// 4. 判断返回地址是否在边界对齐 4KB(bit 11:0 为 0)。
// 5. 将 IA32_VMX_BASIC[31:0] 的 VMCS revision identifier 写入 VMCS 前 4 字节的 [bit30:0]。
// 6. 将 VMCS 的 bit 31 = 0(不是 shadow VMCS)。
// 7. 使用 VMCLEAR 使 VMCS region 处于 clear 状态。
// 8. 使用 VMPTRLD 使 VMCS 变成 active、current。
//
BOOLEAN VmxAllocateVmcsRegion(VIRTUAL_MACHINE_STATE* CurrentGuestState)
{
IA32_VMX_BASIC_MSR MsrVmxBasic = { 0 };
ULONG VmcsRegionSize = 0;
UCHAR VmcsRegionType = 0;
PVOID pVmcsRegion_VA = NULL;
PHYSICAL_ADDRESS pVmcsRegion_Phy = { 0 };
PHYSICAL_ADDRESS Lowphys = { 0 }, Highphys = { 0 };
ULONG64 uAlignVirtualAddress = 0;
ULONG64 uAlignPhysicalAddress = 0;

// get the Vmcs region size and type.
MsrVmxBasic.All = __readmsr(MSR_IA32_VMX_BASIC);
if (MsrVmxBasic.All)
{
VmcsRegionSize = (MsrVmxBasic.All >> 32) & 0x1FFF;
VmcsRegionType = (MsrVmxBasic.All >> 50) & 0xF;
}

if (VmcsRegionSize != PAGE_SIZE)
{
DbgPrint("[*] Error : The VMCS region size is error getting from IA32_VMX_BASIC[44:32].");
return FALSE;
}

do
{
Lowphys.QuadPart = 0;
Highphys.QuadPart = -1;

switch (VmcsRegionType)
{
case 0: // uncache
pVmcsRegion_VA = MmAllocateContiguousMemorySpecifyCache(VmcsRegionSize * 2, Lowphys, Highphys, Lowphys, MmNonCached);
break;
case 6: // write-back
pVmcsRegion_VA = MmAllocateContiguousMemorySpecifyCache(VmcsRegionSize * 2, Lowphys, Highphys, Lowphys, MmCached);
break;
}

if (pVmcsRegion_VA == NULL)
{
DbgPrint("[*] Error : Couldn't Allocate Buffer for VMCS Region.");
return FALSE;
}

RtlSecureZeroMemory(pVmcsRegion_VA, VmcsRegionSize * 2);
pVmcsRegion_Phy = MmGetPhysicalAddress(pVmcsRegion_VA);
uAlignVirtualAddress = (ULONG64)pVmcsRegion_VA;
uAlignPhysicalAddress = (ULONG64)pVmcsRegion_Phy.QuadPart;

// align the physical address to 0x1000(bit 11:0 to 0)
// the address align formula: if align to n bytes,(address + n-1) & (~(n-1))
// https://catecat.top/post/WinXP-Struct/
uAlignPhysicalAddress = ((ULONG64)uAlignPhysicalAddress + PAGE_SIZE - 1) & (~(PAGE_SIZE - 1));

while (((uAlignPhysicalAddress & 0xFFF) != 0) && ((ULONG64)uAlignPhysicalAddress - (ULONG64)pVmcsRegion_Phy.QuadPart <= PAGE_SIZE))
{
uAlignPhysicalAddress++;
uAlignPhysicalAddress = ((ULONG64)uAlignPhysicalAddress + PAGE_SIZE - 1) & (~(PAGE_SIZE - 1));
}

if((uAlignPhysicalAddress & 0xFFF) != 0)
{
MmFreeContiguousMemory(pVmcsRegion_VA);
}

} while ((uAlignPhysicalAddress & 0xFFF) != 0);

if (uAlignVirtualAddress == 0)
{
DbgPrint("[*] Error : MmGetVirtualForPhysical have a fault in VmxAllocateVmcsRegion function.\n");
return FALSE;
}

pVmcsRegion_Phy.QuadPart = uAlignPhysicalAddress;
uAlignVirtualAddress = (ULONG64)MmGetVirtualForPhysical(pVmcsRegion_Phy);

CurrentGuestState->VmvsRegionAllocVirtualAddress = (ULONG64)pVmcsRegion_VA;
CurrentGuestState->VmcsRegionAlignedVirtualAddress = uAlignVirtualAddress;
CurrentGuestState->VmcsRegionAlignedPhysicalAddress = uAlignPhysicalAddress;

DbgPrint("[*] Virtual allocated buffer for VMCS at %llx\n", (ULONG64)pVmcsRegion_VA);
DbgPrint("[*] Virtual aligned allocated buffer for VMCS at %llx\n", uAlignVirtualAddress);
DbgPrint("[*] Aligned physical buffer allocated for VMCS at %llx\n", uAlignPhysicalAddress);

*(PULONG64)CurrentGuestState->VmcsRegionAlignedVirtualAddress = MsrVmxBasic.Fields.RevisionIdentifier;
_bittestandreset64((PULONG64)CurrentGuestState->VmcsRegionAlignedVirtualAddress, 31);


return TRUE;
}

1.4 VMCS 结构

15.png

16.png

偏移 内容
0 Bits 30:0 使用 VMCS revision identifier 来填充。来自于 IA32_VMX_BASIC[30:0]
Bit 31 指示当前 VMCS 是否是 shadow VMCS。0 表示普通的 VMCS,1 表示 shadow VMCS(只有在)VM-execution control fields 的 VMCS shadowing == 1 时该位才有效。
4 当进行 VM-exit 操作遇到一个错误时,会产生 VMX-abort,然后处理器会将一个非零的值填充到当前的 VMX-abort indicator。通常导致 VMX Abort 事件的原因包括保存客户虚拟机 MSR 奇存器失败、当前 VMCS 区域损坏、加载 Hypervisor 的 MSR 寄存器失败等。
8 VMCS 数据区包含 6 个区域,每个区域都有若干字段。用于 VMM root operation 和 VMM non-root operation 转换时提供/保存信息。
1. Guest-state area:VM-exit 时,处理器的当前状态保存在该区域。VM-entry 时从该区域加载处理器的状态值。
2. Host-state area:VM-exit 时从该区域加载处理器的状态值。这个区域记录了所有有关 Hypervisor 的状态信息
3. VM-execution control fields:重点关注该区域。控制着处理器在 Guest 的行为,设置 VM-exit 的条件。
4. VM-exit control fields:该区域定义了在 VM-exit 事件发生后硬件立即要做的事情。
5. VM-entry control fields:该区域定义了在 VM-entry 事件发生后硬件立即要做的事情。
6. VM-exit information fields:记录引起 VM-exit 事件的原因及接收相关的明细信息。在许多处理器中,该区域都是只读的。如果 MSR IA32_VMX_MISC[bit29] = 1 则表示该区域可使用 VMWRITE 写。

注意:由于目前 VMCS 中部分区域需要是 write-back 内存类型,目前申请 VMCS 的内存类型都是 write-back,但是未来可能需要不同的内存类型(Intel 卷3 24.2)。

17.png

参考资料:

  • 处理器虚拟化技术(邓志)
  • New Blue Pill 深入理解硬件虚拟机
  • 加密与解密第 4 版 Chapter 10
  • Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 3 (3A, 3B, 3C & 3D): System Programming Guide Chapter 23

1.5 VMCS 字段 ID

VMCS 分为 6 个区域,每个区域里面都有很多字段,每个字段由一个 ID 值来表示(该 ID 值为 32 bits)。每个字段一般有以下几种宽度:

  1. 16 bits(2 bytes)
  2. 32 bits(4 bytes)
  3. 64 bits(8 bytes)
  4. natural-width(在 32 位系统为 32 bits,在 64 位系统为 64 bits)

对于 VMCS 字段的读写,只能使用 VMREADVMWRITE,不能使用 MOV

ID 字段格式:

20.png

每个字段对应一个 ID 值,参看 HyperPlatform—VmcsField

GuestEsSelector = 0x00000800GuestEsSelector 表示 ES 段寄存器的选择子,其对应的 ID 值为 0x00000800。后续对该 16 bits 大小的字段读写就可以使用 VMREAD/VMWRITE 指令了。

1.6 Control 字段保留值

控制区:VM-execution control fields、VM-exit control fields、VM-entry control fields 中有许多值都必须使用保留值,保留值取 0/1,需要在 IA32_VMX_BASIC[55](0x480) 的影响下进行取值。

影响的 VMCS字段 bit 55 = 0 bit 55 = 1
Pin-Based VM-Execution Controls IA32_VMX_PINBASED_CTLS(0x481) IA32_VMX_TRUE_PINBASED_CTLS(0x48D)
primary processor-based VM-execution control IA32_VMX_PROCBASED_CTLS(0x482) IA32_VMX_TRUE_PROCBASED_CTLS(0x48E)
secondary processor-based VM-execution control IA32_VMX_PROCBASED_CTLS2(0x48B) IA32_VMX_PROCBASED_CTLS2(0x48B)
tertiary VM-execution controls IA32_VMX_PROCBASED_CTLS2(0x492) IA32_VMX_PROCBASED_CTLS2(0x492)
VM-exit contorl IA32_VMX_EXIT_CTLS(0x483) IA32_VMX_TRUE_EXIT_CTLS(0x48F)
VM-entry control IA32_VMX_ENTRY_CTLS(0x484) IA32_VMX_TRUE_ENTRY_CTLS(0x490)

上表中的 10 个 MSR 寄存器都是 64 bits,需要将其分为高/低 32 bits,如下图。

23.png

用法

  • 当 MSR 高 32 bits [63:32] 中的位为 0 时,对应的控制字段相应的位必须为 0。
  • 当 MSR 低 32 bits [31:0] 中的位为 1 时,对应的控制字段相应的位允许为 1。
1
2
3
4
5
6
7
8
9
ULONG AdjustControls(ULONG Ctl/*预设的初始值*/, ULONG Msr)
{
MSR MsrValue = { 0 };

MsrValue.Content = __readmsr(Msr);
Ctl &= MsrValue.High; /* bit == 0 in high word ==> must be zero */
Ctl |= MsrValue.Low; /* bit == 1 in low word ==> must be one */
return Ctl;
}

备注:由于上面的控制字段都是 32 bits 的,所以将 MSR 的高 32 位类似于低 32 位一样使用。

举例说明:

  1. 假设将 Pin-Based VM-Execution Controls 初始值设置为 0。
  2. 此时 IA32_VMX_BASIC[55] = 1
  3. IA32_VMX_TRUE_PINBASED_CTLS 高 32 bits 为 0x0000003F,低 32 bits 为 0x00000016
  4. 计算 0 & 0x0000003F == 00 | 0x00000016 == 0x00000016
  5. 则 bit-1,bit-2,bit-4 必须为 1,其余位都可以为 0。

VM-function control字段,另说,P153。

2 Guest-state area

Guest-state area 包含寄存器类和非寄存器类字段。VM-exit 时,处理器的当前状态保存在该区域,VM-entry 时从该区域加载处理器的状态值。

18.png

2.1 寄存器类字段

  • 控制寄存器:CR0、CR3、CR4。
  • 调试寄存器:DR7。
  • 堆栈、指令寄存器:RSP、RIP、RFLSGS。
  • 段寄存器:CS、SS、DS、ES、FS、GS、TR。
  • 描述符寄存器:GDTR、LDTR、IDTR。
  • MSR 寄存器。

说明:对于 MSR 寄存器,只有在 VM-exit controlVM-entry control 区域中对应的寄存器字段设置为 1 时, Guest-state area 区域中的寄存器字段才有效。

2.2 非寄存器类字段

  1. activity state 字段(32 bits,表示虚拟处理的状态)

    该字段指示在 VM-entry 和 VM-exit 时,虚拟处理器的当前活动状态。包括 active(活动)与 inactive(非活动)两大类状态。目前 VMX 架构支持的状态如表 3-18 所示。

    • active:逻辑处理器可以正常执行 Guest 的指令。
    • inactive:逻辑处理器不可以正常执行 Guest 的指令,需要 VMM 进行处理才可以执行 Guest 的指令。

    19.png

    可以查看 IA32_VMX_MISC(0x485)[8:6],可以看到以上所有状态都支持。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    kd> rdmsr 0x485
    msr[485] = 00000000`5004c1e7

    kd> .formats 0x00000000`5004c1e7
    Evaluate expression:
    Hex: 00000000`5004c1e7
    Decimal: 1342489063
    Octal: 0000000000012001140747
    Binary: 00000000 00000000 00000000 00000000 01010000 00000100 11000001 11100111
  2. Interruptibility state 字段(32 bits)

    该字段指示当前 Guest 中有没有被阻塞的中断,x86/64 体系中一共有 5 种中断阻塞状态。如下图:

    82.png

    • cli 指令禁止中断发生,使 rflags.IF = 0,关中断,屏蔽可屏蔽中断(NMI 不可屏蔽)。
    • sti 指令允许中断发生,使 rflags.IF = 1,开中断,响应中断。

    x86 架构允许某些事件在一段时间内被阻止的功能。该字段的每一个位指示了当前有哪种状态被阻塞

    bit 中断被阻塞 描述
    0 blocking by STI 指示当前有 STI 阻塞状态。

    rflags.IF = 0(不响应可屏蔽的中断)情况下,执行 sti 指令以响应可屏蔽中断时,在 sti 指令的下一条指令执行完毕前发生的外部中断,会被阻塞起来,当下一条指令执行完毕后,阻塞状态会被自动解除。这个时候才开始 delivery 刚才被阻塞的外部中断。
    如果在执行 sti 指令前 eflags.IF = 1 ,那么,这条 sti 指令的下一条指令不会存阻塞的状态。


    举例如下:
    sti;
    mov [eax], ebx;
    如上代码,假设执行 sti 指令前 rflags.IF = 0,那么,执行 sti 指令后,下一条 mov 指令执行完毕前如果发生外部中断将被阻塞(某些处理器也可能会阻塞 NMI)。下一条 mov 指令执行完毕后这个阻塞状态将被自动解除,如果此时有被阻塞的外部中断,将会开始进行 delivery。
    但是,如果此时执行的 mov [eax], ebx 指令产生了 #PF 异常(页面错误),且 exception bitmap 字段的 bit 14 为 1 时将直接导致 VM-exit 发生。VM-exit 时会设置 interruptibility state—blocking by STI = 1 ,表示 VM-exit 时在 Guest 中存在 blocking by STI


    Intel 设计 blocking by STI 阻塞状态的初衷是用在下面的场合:
    sti;
    ret;
    sti 指令最后是一条 ret 指令时(假设之前 rflags.IF = 0),blocking by STI 阻塞状态的存在避免了在返回前产生中断(阻塞期间不 delivery 可屏蔽的中断)。
    1 blocking by MOV-SS 指示当前有 MOV-SS 阻塞状态。

    使用 MOV 或者 POP 指令更新 SS 寄存器时,下一条指令执行完毕前会产生了 blocking by MOV-SS 阻塞状态。这个阻塞状态将阻塞外部中断NMI#DB 异常。
    在更新 SS 栈段时,需要保证栈指针(典型为内核中的栈)能够得到更新而不被中断打断(外部中断,NMI 或者 #DB 断点异常),避免在中断服务例程中使用了不适当的栈指针。如果软件使用 LSS 指令来更新 SS 寄存器和 RSP 栈指针,则不存在 blocking by MOV-SS 阻塞状态。因此,Intel 推荐使用 LSS 指令代替 MOVPOP 来更新 SS 寄存器。

    如:MOV SS,AX; MOV RSP, KernelStack;POP SS; MOV [RAX], RBX;

    注意:在一个指令流里,不可能同时出现 blocking by STIblocking by MOV-SS两种阻塞状态。因此,这两个位不能同时为 1 值。
    注意:如果在 VM-entry 时存在 blocking by MOV-SS 阻塞状态,那么这个阻塞状态会因为注入事件的delivery 而被解除。
    blocking by SMI 指示当前有 SMI 阻塞状态。

    处理器切换到 SMM 模式执行后,新的 SMI 请求会被阻塞,直到执行 RSM 指令退出 SMM 模式后 blocking by SMT 阻塞状态被解除。

    由于这个原因,VM-entry control 字段 enter to SMM = 1 时,表示 VM-entry 将进入 SMM 模式,此时 interruptibility state 字段的 blocking by SMI 位必须为 1。反 之,VM-entry 进入非 SMM 模式时,interruptibility state 字段的 blocking by SMI 位必须为 0 值。
    3 blocking by NMI 指示当前有 NMI 阻塞状态。

    当处理器响应 NMI 请求时,NMI 服务例程得到 delivery 后将阻塞另一个 NMI 请求,直到执行 iret 指令后解除阻塞SMI 请求也是这样。因此,blocking by NMI 阻塞状态的存在依赖于 NMI 是否得到 delivery。(See Section 6.7.1, “Handling Multiple NMls” in the Inte/® 64 and IA-32 Architectures Software Developer’s Manual, Volume 3A and Section 31.8, “NMI Handling While in SMM.”)

    是否存在 blocking by NMI 阻塞状态的区分方法(主要看 NMI 是否得到 delivery):
    1、当 NMI 得到 delivery 执行时,即使在 delivery 期间发生某些错误而产生 VM-exit(在 NMI handler 过程,VM-exit 完成前),处理器就会存在 blocking by NMI 阻塞状态。
    2、 当 NMI 由于 NMI exiting = 1 没有得到 delivery 直接 VM-exit 时,处理器在 VM-exit 完成前并不存在 blocking by NMI 阻塞状态。

    关于 iret 指令对 blocking by NMI 阻塞状态的解除:
    如果 NMI 或 virtual-NMI 已经被 delivery,此时再产生 NMI 或 virtual-NMI 时将会被阻塞。在执行 iret 指令后,阻塞状态将会被解除。不管执行 iret 指令会不会产生异常,都不影响阻塞状态的解除(处理器虚拟化技术 P235)。

    关于 VM-exit interruption information 字段 NMI unblocking(bit 12)的说明:
    只要 iret 指令执行,不管执行过程中是否会发生异常,blocking by NMIblocking by virtual-NMI 阻塞状态都会被解除。但是并不是状态被解除就会将 NMI unblocking 位置 1。
    只有在执行 iret 指令产生的异常直接导致 VM-exit 时,VM-exit interruption information 字段或 VM-exit qualification field 字段中的 NMI unblocking due to IRET 位才会置为 1 值,指示 NMI 或 virtual-NMI 阻塞状态已经被解除。间接导致的 VM-exit 不会将该位置 1。

    在以下的几种情形中,NMI unblocking(bit 12) 是未定义的值:
    1、在 Pin-Based VM-Execution Control(32 bits) 字段的 exiting NMI = 1virtual NMIs = 0 时,当产生一个 NMI 时将会直接 VM-exit,不会存在阻塞状态。所以 iret 指令并不会对 blocking by NMI 阻塞状态的解除产生任何影响。
    2、当一个 NMI 在delivery 期间,执行一个 iret 指令,但是这个 iret 指令产生一个异常,并且这个异常并不会直接引发 VM-exit,而是在这个异常的 delivery 期间引发的另一个异常导致的 VM-exit。虽然此时的 blocking by NMI 阻塞状态已经被解除,但是 NMI unblocking(bit 12) 位并不会置 1。


    举例:
    (1)当 NMI exiting 为 1 时,一个 NMI 请求直接产生 VM-exit,NMI 并没有得到 delivery。处理器在 VM-exit 过程中就不存在 blocking by NMI 阻塞状态。此时, interruptibility state 字段的 blocking by NMI 位为 0 值。
    (2)当 VMM 注入一个 NMI 事件(或 virtual-NMI 事件)时,NMI 通过 guest-IDT 进行 deliver 执行。此时,处理器也存在 blocking by NMI(或 blocking by virtual NMI)阻塞状态。
    (3)当 pin-based VM-execution control 字段的 NMI exitingvirtual-NMIs 位同时为 1 时,产生 virtual-NMI 概念,blocking by NMI 此时被视为 blocking by virtual NMI 位。当注入一个 virtual-NMI 事件 deliver 执行后,处理器就存在 blocking by virtual-NMI 阻塞状态。当注入的 virutal-NMl 事件由于 NMIl-window exiting 位为 1 而 产生VM-exit 时,并不存在 blocking by virtual-NMI 阻塞状态。
    (4)在 NMI 或 virtual-NMI 服务例程中,执行 iret 指令后,这个 NMI 或 virtual-NMI 阻塞状态将解除。如果执行 iret 指令而发生某些错误,并不影响它解除 NMI 或 virtual NMI 的阻塞状态阻塞状态。执行 iret 指令发生一个异常而直接导致 VM-exit 时,在 VM-exit interruption information 字段中的 NMI unblocking 位(bit 12) 将为 1 值,指示 NMI 或 virtual-NMI 阻塞状态已经被解除(见 3.10.2.1节)。

    说明:
    如果 NMI 直接引发 VM-exit,NMI 不被 deliver 执行,也就不会解除阻塞状态。
    但是,如果 iret 指令产生了一个异常,但这个异常并不直接引发 VM-exit,而后来由于异常 delivery 期间导致 VM-exit (间接引发),此时 blocking by NMI 阻塞状态已经被解除。
    4 Enclave interruption 逻辑处理器处于 enclave 模式时发生 VM exit,则设置为 1。
    同时会设置 VM-exit information fields—Basic VM-Exit Information(基本信息类字段)的 Exit reason bit 27 = 1(表示与 enclave 模式相关的 VM-exit)。
    31:5 Reserved 设置为 0。
  3. pending debug exception 字段。

    x86 支持延迟发送一些调试异常,如某些情况下的单步调试。 pending debug exceptions 字段用来记录和设置 guest 存在未处理(阻塞)而 pending(悬挂)的 #DB异常。属于 natural-width 类型字段,在 64 位架构处理器上是 64 位,否则为 32 位。

    pending debug exception 字段只支持记录 trap 类型 #DB 异常(由硬件断点或者 I/O 断 点触发的 #DB 异常,以及由单步调试而触发的 #DB 异常),对于 fault 类型的 #DB 异常不能记录

    该字段的格式基本和 DR6 寄存器格式很相似:

    83.png

    当遇到一个硬件断点(指令执行、数据读写)或 single-step(单步调试)时,处理器将组织一个 #DB 异常,并提交处理器执行。

    在 x86/x64 体系上的 #DB(调试)异常使用 1 号中断向量,分为 fault 和 trap 两类,共有 6 个触发方式。

    • fault 类型的 #DB 有 2 类触发方式:
      • 由执行断点触发。
      • 由访问调试寄存器触发(DR7.GD = 1 时)。当断点对应的 DR7.R/Wx = 00 时,使用执行断点,在断点寄存器 DR0-DR3 里设置断点的地址。当 DR7.GD = 1 时,使用 DR 寄存器访问触发方式。在后续的指令中访问任何一个 DR 寄存器将产生 #DB 异常。
    • trap 类型 #DB 异常有 4 类触发方式:
      • 读写硬件断点(根据 DR7 的 R/Wx 位来区分)。
      • 单步调试断点,设置 Rflags.TF = 1 时产生。单步调试分为指令单步调试及分支单步调试两种(单步调试在将 eflags.TF 置为 1 后,下一条指令执行完毕后触发,分支单步调试则在目标指令执行前触)
      • I/O 断点,I/O 断点是指令访问的 I/O 端口地址。在 CR4.DE = 1 时,DR7 的 R/Wx = 10,产生的就是 I/O 读写断点。(见 卷 3A—Chapter 17 Debug, Branch Profile, TSC, and Intel® Resource Director Technology. (Intel® RDT) Features—17.2 Debug Registers。)
      • 在任务切换时,任务切换的 #DB 异常在执行完任务切换后,处理器检测到 TSS.T 为 1 时触发。(在 VMX 架构下,并不支持在 non-root operation 环境中使用任务切换机制。一旦发生任务切换,将产生 VM-exit 行为。因此,在 pending debug exception 字段中并不支持记录任务切换时产生的 #DB 异常。)

    fault 和 trap 两类异常触发的位置不同,fault 类型如 DR 奇存器访问的 #DB 异常,在执行 MOV-DR 指令前触发,异常处理后还要继续执行该指令;trap 类型的异常,在执行完每一条指令后触发,异常处理后执行下一条指令。

    上面提到的 blocking by MOV-SS 阻塞状态,会阻塞外部中断、NMI 及 #DB 异常。这里主要对阻塞的 #DB 异常进行说明。

    1. pending debug exception 字段中只能记录:(数据读写的硬件断点、 I/0 断点、单步调试)产生的 #DB 异常。。不会记录指令指令而导致的硬件断点,这是因为 blocking by MOV-SS 阻塞状态不会阻塞因为指令执行硬件断点触发的 #DE 异常。
    2. blocking by MOV-SS 阻塞状态并不能阻塞由 DR 寄存器访问而产生的 #DB 异常。当 DR7.GD = 1 时,遇到 MOV-DR 指令将直接提交 #DB 异常执行(参考硬件断点),或者产生 VM-exit 行为。

    总结就是:对于指令执行的硬件断点的 #DB 异常和 DR 寄存器访问的 #DB 异常,它们不会被悬挂(pending)。

    在 VM-exit 时存在 #DB 异常的 pending 状态,有如下 2 种可能。

    1. 在 VM-exit 之前,一条指令产生 trap 类型异常。
    2. 在 VM-exit 前,已经存在由于 blocking by Mov-SS 阻塞状态而被 pending 的 #DB 异常。

    VM-exit information fields—exit qualification 字段记录的 debug 异常信息与 pending debug exception 字段记录的 debug 异常信息是不同的。它们的用途也不同:

    • exit qualification 字段记录的是由 #DB 异常引发 VM-exit 时的 debug 异常信息;
    • pending debug exception 字段记录的是由其他原因导致 VM-exit,而 #DB 异常未处理而被 pending(悬挂)的信息。
  4. 其余字段。

    字段 描述
    VMCS link pointer (1)如果 VM-execution controlVMCS shadowing = 1,此时该区域表示 VMCS region 的物理地址,可以通过该指针读写 VMCS 的字段。
    (2)如果 VM-execution controlVMCS shadowing = 0,此时该区域应该设置为 0xFFFFFFFFFFFFFFFF
    VMX-preemption timer value 保存定时器的计数值。
    (1)如果 VM-execution controlactive VMX-preemption timer = 1,则该区域有效。表示一个定时器。
    (2)每 2^TSC 增加时,该值递减 1,递减为 0 时将会产生 VMX exit。
    填充该字段时,该字段的值来自于 IA32_VMX_MISC[4:0]
    PDPTEs 仅在启用 EPT 的 PAE 分页模式下可用。
    该区域包含 4 个 64 bits 的值:PDPTE0,PDPTE1,PDPTE2,PDPTE3。
    (1)只有在支持 EPT,即 VM-execution controlenable EPT = 1 时,且 Guest OS 在 PAE 分页模式下,需要填充这 4 个字段,以便在 VM-entry 时将这些字段的值加载到内部的 PDPTE 寄存器中、此时,CR3 寄存器将被忽略
    (2)VM-execution controlenable EPT = 0 时,如果 Guest OS 使用 PAE 分页,CR3 寄存器指向的 4 个 PDPTEs 会被加载到内部的 PDPTE 寄存器,此时忽略 VMCS 中的 PDPTEs 字段
    注意:只有 Guest OS 在 32 位系统的 PAE 分页模式下,且启用 EPT 时才有用,IA-32e 模式不使用。
    Guest interrupt status 只有在 VM-execution controlvirtual-interrupt delivery = 1 时,该字段表示虚拟 local APIC,处理器使用该字段来维护虚拟中断的状态。
    PML index 只有在 VM-execution controlenable PML = 1 时,该字段为一个 index 索引值,用在 EPT page-modification log。

2.3 字段填充

3 Host-state area

每一次 VM-exit 时,处理器的状态从 Host-state area 加载。

21.png

3.1 寄存器类字段

Host-state area 区域中只有寄存器的的字段,没有其他字段:

  • 控制寄存器:CR0、CR3、CR4。
  • 堆栈、指令寄存器:RSP、RIP。
  • 段寄存器:CS、SS、DS、ES、FS、GS、TR。
  • 描述符寄存器:GDTR、LDTR、IDTR。
  • MSR 寄存器。

说明

  1. RIP 应该填充为 VMM 中的一个管理例程,使得每次 VM-exit 时都进入到该函数。
  2. 对于 MSR 寄存器,只有在 VM-exit control 区域中对应的寄存器字段设置为 1 时, Host-state area 区域中的寄存器字段才有效。

3.2 字段填充

4 VM-execution control fields

VM-execution 控制类字段主要控制处理器在 VMX non-root operation 模式下的行为能力:

  1. 典型地可以控制某些条件引发 VM-exit 事件;
  2. 也控制着 VMX 的某些虚拟化功能的开启,例如 APIC 的虚拟化及 EPT 机制。

将 VM-execution control fields 分为控制域和数据域,控制域决定着对应的数据域中是否有数值。

  • 控制域:
    • Pin-Based VM-Execution Control(32 bits),设置异步事件来导致 VM-exit 的条件。
    • Processor-Based VM-Execution Control
      • primary processor-based VM-execution controls(32 bits),设置同步事件来导致 VM-exit 的条件。
      • secondary processor-based VM-execution controls(32 bits),设置 Enable 的开关。
      • tertiary VM-execution controls(64 bits),设置 HLAT 分页信息。
  • 数据域:剩下的其余字段。

24.png

4.1 字段解释

将 VM-execution control fields 分为控制域和数据域,控制域决定着对应的数据域中是否有数值。

  • 控制域:
    • Pin-Based VM-Execution Control(32 bits),设置异步事件来导致 VM-exit 的条件。
    • Processor-Based VM-Execution Control
      • primary processor-based VM-execution controls(32 bits),设置同步事件来导致 VM-exit 的条件。
      • secondary processor-based VM-execution controls(32 bits),设置 Enable 的开关。
      • tertiary VM-execution controls(64 bits),设置 HLAT 分页信息。
  • 数据域:剩下的其余字段。
  1. Pin-Based VM-Execution Control(32 bits)

    25.png

  2. Processor-Based VM-Execution Control

    Processor-Based VM-Execution Control 包含以下三个字段:

    1. primary processor-based VM-execution controls(32 bits),设置同步事件来导致 VM-exit 的条件。
    2. secondary processor-based VM-execution controls(32 bits),设置 Enable 的开关。
    3. tertiary VM-execution controls(64 bits),设置 HLAT 分页信息。

    27.png

  3. 以下数据域。

    控制字段 解释
    Exception Bitmap Exception Bitmap 是一个 32 bits 的字段,每一位对应一个中断号(例如 bit-14 对应 #PF 页面异常)。
    在 VMX non-root operation 中,如果发生异常,处理器会检查 Exception Bitmap 中 bit-INT Number 位是否为 1,如果为 1 则会产生 VM-exit,为 0 则会通过 Guest-IDT 执行对应的 ISR。
    备注:如果需要进行 IDT Hook,则必须要注意对应的位。只有 32 bits,也就只能 Hook 前 32 个中断号对应的中断。
    I/O-Bitmap Addresses 包含两个 64KB 大小的物理地址(I/O bitmaps A、I/O bitmaps B。每个地位为 64bits)。
    当 primary processor-based VM-execution controls 中的 use I/O bitmaps = 1 时,使用该区域来存放 I/O bitmaps 的地址,该地址指向相应的 64 KB(注意不是像 Exception Bitmap 直接存放数据)。
    I/O bitmap 中的每个位对应一个 I/O 地址,当位为 1 时,访问相应的位会产生 VM-exit。
    Time-Stamp Counter offset and Multiplier 当 primary processor-based VM-execution controls 中的 use TSC offseting = 1 时,当前字段提供一个 64 bits 的偏移值。
    启用后,使用 RDTSC、RDTSCP、RDMSR 等指令读取 TSC 时,返回的值为 TSC+TSC offset。
    该位启用的条件是:
    (1)use I/O bitmaps = 1
    (2)使用 RDTSC 指令时,RDTSC exiting = 0
    (3)使用 RDTSCP 指令时,enable RDTSCP = 1
    (4)使用 RDMSR 指令时,MSR read bitmap 相应的位为 0。
    Guest/Host Masks and Read Shadows for CR0 and CR4 用来保护 CR0、CR4 寄存器。共包含 4 个 natural-width 的字段。
    (1)CR0 奇存器的 guest/host mask 与 read shadow 字段。
    (4)CR4 奇存器的 guest/host mask 与 read shadow 字段。
    使用规则:
    guest/host mask 中为 1 的位,表示属于 Host OS 所有,Guest OS 中不能将其修改。也就是说 Guest 从 read shadow 字段读出来的值,对应于 guest/host mask 为 1 的位,不可以进行修改。否则将会导致 VM-exit。39.png
    图片引自Day 3: The VMCS, Component Encoding, And Multiprocessor Initialization
    CR3-Target Controls 包括 1 个 CR3-target count 字段(32 bits)与 4 个 CR3-target value 字段(natural-width,从 CR3 target value 0 到 CR3-target value 3 字段)。
    软件可以查询 IA32_VMX_MISC[24:16] 的值来获得支持 CR3-target value 的数量。
    CR3-load exiting = 1,CR3-target count 与 CR3-target value 将决定写 CR3 寄存器是否会产生 VM-exit。以 CR3-target count 字段值作为 N(N ≤ 4):
    (1)当向 CR3 写入的值等于这 N 个 CR3-target 值的其中一个时,不会产生 VM-exit。
    (2)当向 CR3 写入的值不等于 N 个 CR3-target 值中的任何一个时,则产生 VM-exit。
    Controls for APIC Virtualization use TPR shadow = 1 时,当前字段有效,需要提供一个物理地址作为 4K 的 virtual-APIC page 页面。
    用来虚拟化 local APIC,包括以下字段:
    (1)APIC-access address (64 bits)
    (2)Virtual-APIC address (64 bits)
    (3)TPR threshold (32 bits)
    (4)EOI-exit bitmap (4 fields; 64 bits each)
    (5)Posted-interrupt notification vector (16 bits)
    (6)Posted-interrupt descriptor address (64 bits)
    MSR-Bitmap Address primary processor-based VM-execution controls(32 bits)中 use MSR bitmaps = 1 时,当 MSR bitmap 的某位为 1 时,访问该位所对应的 MSR 将产生 VM-exit
    当前字段提供一个 64 bits 的物理地址,指向一个 4 个 1KB 大小的 MSR bitmap 区域。每一个位决定对应的 MSR 地址是否可读(RDMSR),或者是否可写(WRMSR)。
    (1)Read bitmap for low MSRs,表示可读的 MSR 地址范围为 00000000H-00001FFFH
    (2)Read bitmap for high MSRs,表示可读的 MSR 地址范围为 C0000000H-C0001FFFH
    (3)Write bitmap for low MSRs,表示可写的 MSR 地址范围为 00000000H-00001FFFH
    (4)Write bitmap for high MSRs,表示可写的 MSR 地址范围为 C0000000H-C0001FFFH
    Executive-VMCS Pointer 这个字段用于 SMM dual-monitor treatment(SMM 双重监控处理)机制下,当发生 SMM VM-exit 时,这个字段用来保存 executive-monitor 的 VMCS pointer。
    Extended-Page-Table Pointer (EPTP) enable EPT = 1 时,表示开启 EPT,指向 EPT PML4 table(类似于普通分页 CR3 指向 PLM4 页表)。
    28.png
    Virtual-Processor Identifier (VPID) enable VPID = 1 时,该字段表示虚拟机标示符,16 bits。
    (1)这个 VPID 用来标识虚拟处理器的 cache 域。
    (2)处理器会在 cache 中维护 VPID 对应的一份 cache。
    (3)如果每次 VM-entry 使用不同的 VPID 值,这样就为 VM 指定多个虚拟处理器 cache 域。
    (4)另外,VMX 架构引入了 INVVPID 指令来刷新由 VPID 对应的 cache 信息。
    Controls for PAUSE-Loop Exiting 功能的引进主要是解决在一个长时间的 spin lock(自旋锁)等待循环里浪费处理器的时间。在这个自旋锁等待循环里处理器可以产生 VM-exit, 从而去执行其他的任务。
    VM-Function Controls 64 bits。用于管理 VMX non-operation 中 VMFUNC 指令的使用。在 secondary processor-based VM-execution controls 中 Enable VM functions = 1 时该字段才有效。
    VM-functions control 字段每个位对应一个 VM 功能号,最大功能号为 63。VMFUNC 指令执行时,VM 功能号提供在 EAX 奇存器中,当提供的功能号大于63 时,将产生 #UD 异常。
    在 VMX non-root operation 内执行 VMFUNC 指令时,如果使用的功能号在 VM functions control 字段相应位为 0,将产生 VM-exit
    软件应该检查 IA32_VMX_VMFUNC 寄存器,确定支持哪个 VM 功能号。 IA32_VMX_VMFUNC 寄存器在 enable VM functions = 1 允许被置位下有效。
    29.png
    VMCS Shadowing Bitmap Addresses 在 VM-execution control 的 VMCS shadowing = 1 时当前字段才有效。该字段包含两个 64 bits 的物理地址:VMREAD bitmap address、VMWRITE bitmap address。每个 bitmap 都是 4KB 大小,
    ENCLS-Exiting Bitmap 如果 secondary processor-based VM-execution controls 中 Enable ENCLS exiting = 1 时,如果 bitmap 为 1 的位对应的 EAX 中对应的位也为 1,则会产生 VM-exit。
    ENCLV-Exiting Bitmap 如果 secondary processor-based VM-execution controls 中 Enable ENCLV exiting = 1 时,如果 bitmap 为 1 的位对应的 EAX 中对应的位也为 1,则会产生 VM-exit。
    PCONFIG-Exiting Bitmap 如果 secondary processor-based VM-execution controls 中 Enable PCONFIG = 1 时,如果 bitmap 为 1 的位对应的 EAX 中对应的位也为 1,则会产生 VM-exit。
    Control Field for Page-Modification Logging secondary processor-based VM-execution controls 中 Enable PML = 1 时,该字段才有效。它是页面修改日志的 4 KB 对齐地址。
    Controls for Virtualization Exceptions secondary processor-based VM-execution controls 中 Enable EPT-violation #VE = 1 时,该字段才有效。包含以下两个信息:
    (1)该字段表示虚拟化异常信息地址(64 bits)。当逻辑进程遇到虚拟化异常时,将虚拟化异常信息保存在虚拟化异常信息地址中(Virtualization-exception information address)。
    Virtualization-exception information address 是 Virtualization-exception information area 的地址。
    (2)EPTP index。当 EPT violation 导致虚拟化异常时,处理器将此字段的值写入虚拟化异常信息区域。 EPTP-Switching VM 功能更新这个字段。
    XSS-Exiting Bitmap secondary processor-based VM-execution controls 中 Enable XSAVES/XRSTORS = 1 时,该字段才有效。这两个指令的执行可能会参考该位图。
    Sub-Page-Permission-Table Pointer (SPPTP) 如果启用了 EPT 的子页面写权限特性,则可以以 128 字节的粒度确定 EPT 写权限(参见第 28.3.4 节)。 这些权限是使用内存中的子页面权限结构的层次结构来确定的。
    Fields Related to Hypervisor-Managed Linear-Address Translation tertiary VM-execution controls 中 Enable HLAT = 1 表示启用启用 HLAT 分页。当前字段表示分页转译相关信息。

4.2 字段填充

5 VM-entry control fields

该区域中的字段控制着 VM-entry 时处理器对 Guest state area 区域操作的行为。在 VM-entry 时处理器会检查这些字段,如果检查不通过,则会产生 VMfailValid,在 VM-exit information fields 的 VM-instruction error field 中保存错误号。
包含三个区域:

  1. VM-entry Controls(32bits)
  2. VM-entry Controls for MSRs(32bits)
  3. VM-entry Controls for Event Injection
    • VM-entry interruption-information field(32 bits)
    • VM-entry exception error code(32 bits)
    • VM-entry instruction length(32 bits)

30.png

进行 VM-entry 操作时处理器会执行严格的检查,可以分为以下几个阶段。

  1. 第一阶段:对 VMLAUNCHVMRESUME 指令的执行进行基本检查。
  2. 第二阶段:对当前 VMCS 内的 VM-execution、VM-exit 以及 VM-entry 控制区域和 host-state 区域进行检查。
  3. 第三阶段:对当前 VMCS 内的 guest-state 区域进行检查,并加载 MSR。
  4. 在所有的检查都通过后,处理器从 guest-state 区域里加载处理器状态和执行环境信息。如果设置需要加载 MSR,则接着从 VM-entry MSR-load MSR 列表区域里加载 MSR。
  5. VM-entry 操作附加动作会清由执行 MONITOR 指令而产生的地址监控,这个措施可以防止 guest 软件检测到自己处于虚拟机内
  6. 在成功完成 VM-entry 后,如果注入了一个向量事件,则通过 guest-IDT 立即 deliver 这个向量事件执行。如果存在 pending debug exception 事件,则在注入事件完成后 deliver 一个 #DB 异常执行。

在整个 VM-entry 操作流程里,如果 VM-entry 失败可能产生以下三种结果:

  1. VMLAUNCHVMRESUME 指令产生异常(仅能产生 #UD#GP 异常),从而执行相应的异常服务例程(如执行这两个指令的权限不够时会产生 #GP 异常)。《处理器虚拟化技术 P304》
  2. VMLAUNCHVMRESUME 指令产生 VMfailInvalidVMfailValid 类型失败,处理器接着执行下一条指令(Hypervisor 中)。
  3. VMLAUNCHVMRESUME 指令的执行由于检查 guest-state 区域不通过,或者在加载 MSR 阶段失败而产生 VM-exit,从而转入 host-RIP 的入口点执行(进入VM-exit 处理函数)。

更多具体的字段检查,详看《处理器虚拟化技术 P300》。

5.1 字段解释

类似于上面的 VM-execution control fields 字段一样,只要是 Control Field 都有控制域(前半部分)和数据域(后半部分)。

31.png

  • bit 31 是有效位,为 1 时指示 VM-entry interuption information 字段有效,为 0 时无无效。bits 10:8 设置事件类型,包括7 个事件类型,如表 3-10 所示。
  • bits 7:0 设置中断或异常的向量号(IDT),当事件类型是NMI 时,向量号必须为2。事件类 型为 other 时,向量号必须为0值。
  • VM-entry interruption information 字段 bit 11 == 1 时,指示有错误码需要提交。在注入事件 delivery 时,错误码会被压入栈中。这个位只有在注入硬件异常事件时才能被置 1,否则会产生 VMfailValid 失败。能产生错误码的硬件异常是:#DF,#TS,#NP,#SS,#GP,#PF 及 #AC 这 7 类。注入其余的硬件异常不能将此位置 1。

说明:事件注入机制目的是向 VM 中注入一个事件,使得每次 VM-entry 之后立即得到执行。而至于注入的事件会不会产生 VM-exit,需要根据注入事件(中断)类型查 VM-execution Control 等控制区域是否开启了对该类中断的监控,如果命中了就会产生 VM-exit(直接向量事件)。如果控制区域没有对注入的事件进行监控,那就不会马上产生 VM-exit,但是如果在中断/异常分发的过程中产生了错误,那就会导致 VM-exit,这种 VM-exit 就叫做间接向量事件导致的 VM-exit。

中断和异常的区别:

中断/异常类型 描述 备注
硬件中断 外部中断,使用 32~255 号中断。
硬件异常 (1)0~31 号中,除了 #BP 与 #OF 异常以外的所有异常
(2)能产生 ErrorCode 的硬件异常:双重错误 (#DF)、无效 TSS (#TS)、段不存在 (#NP)、栈段错误 (#SS)、通用保护异常 (#GP)、页错误 (#PF)、对齐检查 (#AC)。
产生 ErrorCode 的硬件异常,要向堆栈压入错误码 ErrorCode
软件中断 软件中断指由 INT 指令执行的中断。 中断时需要进行段权限检查 CPL(在 delivery 期间)
软件异常 软件异常指由 INT3INTO 指令产生的 #BP 与 #OF 异常。64 位模式下 INTO 指令是无效的。 中断时需要进行段权限检查 CPL(在 delivery 期间)

5.2 向量化事件

允许在 VM-entry 完成后,在执行任何 Guest 指令之前,处理器执行由 VMM 设置的注入的事件。这个事件可以是一个中断或异常,甚至 pending MTF VM-exit 事件,他们被称为向量化事件。而含有向量化事件的 VM-entry 被称为向量化的 VM-entry

向量化事件通过 VM-entry control fields 区域的以下 3 个字段来进行设置。

  1. VM-entry interruption-information field(32 bits):设置中断或异常的中断向量号(IDT)、事件类型、事件有效位、错误码的 delivery 标志位。
  2. VM-entry exception error code(32 bits) :当 VM-entry interruption-information field bit 31 = 1 时,注入的是硬件异常,且包含有 #DF、#TS、#NP、#SS、#GP、#PF、#AC 时才会产生错误码。因此只有注入这几个硬件异常时才需要提供错误码,对于其他异常或事件可以忽略该字段。
  3. VM-entry instruction length(32 bits):当注入事件属于软件异常、软件中断以及特权级软件中断时,必须在这个字段提供注入的指令长度(指令长度一般在 1~15 之间,为 0 时会产生 VMfailValid 失败)。以便注入的事件引发 VM-exit 时能够在 VM-exit instruction length 找到这些指令的长度。

6 VM-exit control fields

这些字段用来控制发生 VM-exit 时的处理器行为,决定如何进行 VM-exit 操作。

即在 VM-exit 时决定着如何保存和填充寄存器的值,以便在 VMM 中使用。

包含以下 3 个字段:

  1. VM-exit control 字段。
  2. VM-exit MSR-store count 与 VM-exit MsR-store address 字段。
  3. VM-exit MSR-load count 与 VM-exit MSR-load address 宇段。

33.png

6.1 字段解释

字段 描述
VM-exit control 字段 在 VM-exit 时决定着如何保存和填充寄存器的值。如下图。
VM-exit MSR-store count
VM-exit MSR-store address
VM-exit 时将相关 MSR 寄存器的值存储在 VM-exit MSR-store address 指向的对应区域中。
VM-exit MSR-load count
VM-exit MSR-load address
VM-exit 时从 VM-exit MSR-store address 指向的对应区域中加载值到 MSR 寄存器。
类似于 VM-entry control fields——VM-entry Controls for MSRs——VM-entry MSR-load count(32 bits)VM-entry MSR-load address(64 bits)

32.png

6.2 字段填充

7 VM-exit information fields

VMCS 的 VM-exit 信息类字段用来保存发生 VM-exit 事件的原因及明细信息,VMM 利用这些信息来决定如何管理和控制 VM。

VM-exit 信息类字段的 VM-instruction error 字段保存着 VMX 指令执行失败而发生 VMfailValid 的原因值。当VMX 指令发 VMfaillnvalid 失败时,不会产生错误原因值。

VM-exit information fields 包含以下几类信息:

  1. Basic VM-Exit Information,基本信息类。
  2. Information for VM Exits Due to Vectored Events,直接向量事件类。
  3. Information for VM Exits That Occur During Event Delivery,间接向量事件类。
  4. Information for VM Exits Due to Instruction Execution,指令信息类。
  5. VM exits due to SMIS。
  6. VM-Instruction Error Field,指令失败类。

34.png

注意:一般 VM-exit information fields 是只读的,当 MSR IA32_VMX_MISC[29](0x485) = 1 表示可写。

37.png

7.1 基本信息类字段

基本信息类字段包括:

  • Exit reason(32 bits)
  • Exit qualification(natural-width)
  • Guest-linear address(natural-width)
  • Guest-physical address(64 bits)

如果我们需要输出 VM-exit 信息区域所有字段的信息,当根据 exit reason 字段值输出相应的 VM-exit 原因信息时,需要先检查 VM-exit instruction error 字段,确定 VMX 指令是成功的,然后再输出具体的 VM-exit 原因信息

每类导致 VM-exit 的事件都对应一个原因编号,VMM 根据这个原因编号值来确定由什么事件导致 VM-exit 发生,再结合其余字段进一步提供的明细信息做出相应处理。

以下两个特殊值:

  • VM-exit 原因码 0 值是有效的,它表示由异常或 NMI 导致的 VM exit。
  • VM-exit instruction error 字段的值为 0 时,表明 VMX 指令执行成功。
  1. Exit reason(32 bits)。32 位的 Exit reason 退出原因格式如下,bit[15:0] 具体指代原因见《Intel 卷3合集 24.9.1》 和《处理器虚拟化技术 3.10.1.2 VM-ext 原因》。

    81.png

    由 VM-entry 失败而导致的 VM-exit 与在 guest 里产生的 VM-exit 的区分:

    • exit reason 字段 bits 31 = 1 表示 VM-entry 失败而导致的 VM-exit。
    • exit reason 字段 bits 31 = 0 表示在 guest 里产生的 VM-exit。

    备注:在每次 VM-exit 时,需要先检查 VM-exit instruction error 字段,确定 VMX 指令是成功的,然后再输出具体的 VM-exit 原因信息。

    导致 VM-exit 发生的原因中,主要分无条件 VM-exit 与有条件 VM-exit 两大类。另外在 VMX non-root operation 模式中收到某些事件也会直接产生 VM-exit(例如 INIT, SMI,以及 SIPI 消息)。当 Guest 发生 triple fault 时,也会无条件地产生 VM-exit。

    类型 描述
    指令类导致 VM-exit 无条件指令类:
    (1)所有 VMX 指令(除 VMFUNC 指令外)。
    (2)CPUID,GETSEC,INVD,XSETBV 指令。

    有条件指令类:
    取决于 primary processor-based VM-execution controls、secondary processor-based VM-execution controls 和 tertiary VM-execution controls 中设置的条件。例如当 INVLPG exiting 为 1 时,尝试执行 INVLPG 指令将导致 VM-exit。
    HLT, INVLPG, INVPCID, RDPMC, RDTSC, RDTSCP,RSM,MOV from CR3,MOV to CR3,MOV from CR8,MOV to CR8,CLTS,LMSW,MOV to CRO,MOV to CR4,MOV-DR,IN/OUT,INS/OUTS,RDMSR,WRMSR,MWAIT,MONITOR,PAUSE,LGDT,LIDT,LLDT,LTR,SGDT,SIDT,SLDT,STR,RDRAND,WBINVD 指令。
    事件类导致 VM-exit (1)primary processor-based VM-execution controls、secondary processor-based VM-execution controls 和 tertiary VM-execution controls 中设置的条件。如当发生异常时,异常向量号在 exception bitmap 对应的位为 1 时将引发 VM-exit。
    比如:包括 INT3INTO 指令引起的软件异常、BOUNDUD2 指令引起的硬件异常、对于 #PF 异常有些特殊,当 exception bitmap 的 bit 14 为 1,并且 PFEC & PFEC_MASK = PFEC_ MATCH 时,#PF 异常会导致 VM-exit。
    (2)注入机制中(VM-entry Controls for Event Injection)注入的事件导致的 VM-exit。
    (3)三重错误(triple fault)。
    (4)VM-entry 失败而导致的 VM-exit。不同于上面三种在 VMX non-root operation 模式下引发的,这种 VM-exit 是在 VMX root operation 模式下引发的,包括:
    1、在检查 guest-state 区域的字段时,由于无效的 guest-state 字段而导致 VM-exit。
    2、在加载 guest-state 区域的 MSR 时失败而导致 VM-exit。
    3、在 VM-entry 期间可能由于遇到 machine-check 事件而失败导致 VM-exit。
  2. Exit qualification(natural-width 类型)。在 VM-exit 时,这个字段记录由于某些原因导致 VM-exit 的明细信息,具体内容见《处理器虚拟化技术 3.10.1.3》

7.2 直接与间接向量事件

这两个字段记录的是事件注入机制导致的 VM-exit 详细的信息。

  • Information for VM Exits Due to Vectored Events,直接向量事件类。
  • Information for VM Exits That Occur During Event Delivery,间接向量事件类。

直接向量事件

所谓的 直接向量事件 是指直接引发 VM-exit 的向量事件(中断或异常没有机会得到 delivery 而直接 VM-exit)。主要包含以下 4 类:

  1. 硬件异常。由于异常向量号(IDT 号)在 exception bitmap 对应的位为 1 而导致的 VM-exit。
  2. 软件异常(#BP, #OF)。由于异常向量号(IDT 号)在 exception bitmap 对应的位为 1 而导致的 VM-exit。
  3. 外部中断(硬件中断)。发生外部中断请求时,由于 external-interrupt exiting = 1 而直接导致的 VM-exit。
  4. NMI。发生 NMI 请求时,由于 NMI exiting = 1 而直接导致的 VM-exit。

注意:直接向量事件不包括软件中断和特权级软件中断(但是他们可以属于间接向量事件)。

  1. VM-exit interruption information(32 bits)。

    32 bits 的 VM-exit interruption information 字段记录的是向量事件的 delivery 信息,格式如下图。注意:VM-exit interruption information 字段仅支持以上提到的硬件异常、软件异常(#BP, #OF)、外部中断(硬件中断)、NMI。这是因为 Guest 中不会因为软件中断和特权级软件中断而直接引发 VM-exit(只会在 delivery 的过程中引发)。

    注意:bit 31 为 1 时,VM-exit interruption information 字段才有效。

    84.png

    能产生错误码的硬件异常是:#DF,#TS,#NP,#SS,#GP,#PF 及 #AC 这 7 类。其他硬件异常、外部中断、NMI 及软件异常并不存在错误码。

    注意:只有在异常直接导致 VM-exit 发生时,bit 12 才被置位,表示 IRET 指令执行

    • 如果 NMI 直接引发 VM-exit,NMI 不被 deliver 执行,也就不会解除阻塞状态。
    • 但是,如果 iret 指令产生了一个异常,但这个异常并不直接引发 VM-exit,而后来由于异常 delivery 期间导致 VM-exit (间接引发),此时 blocking by NMI 阻塞状态已经被解除。
  2. VM-exit interruption error code(32 bits)。

    如果 VM-exit 由硬件异常引发,当 VM-exit interruption information 字段的 bit 11 为 1 时,VM-exit interruption error code 字段记录异常的错误码。错误码仅存在于前面所述的 7 种硬件异常里,包括:#DF,#TS,#NP,#SS,#GP,#PF 及 #AC 异常。其他类型的事件 这个字段清为 0 值。

间接向量事件

所谓的 间接向量事件 是指:当一个向量事件发生后它不直接产生 VM-exit,在通过 Guest-IDT delivery 期间由于遇到了某些错误而导致 VM-exit。

间接导致 VM-exit 的向量事件可以为:硬件异常、软件异常、软件中断、特权级软件中断、外部中断及 NMI、注入的向量化事件(因此,这个字段的中断类型也可以为软件中断或者特权级软件异常(由事件注入来实现))。

常见的间接向量事件:

  • 在向量事件 delivery 期间引发了一个异常,这个异常由于向量号在 exception bitmap 字段对应的位为 1 而导致 VM-exit(包括转变为 #DF 异常)。
  • 在向量事件 delivery 期间引发连串异常,最终转变为 triple fault 而导致 VM-exit。
  • 向量事件 delivery 期间由于向量号在 Guest-IDT 内对应的描述符为 task-gate 描述符,尝试进行任务切换而导致 VM-exit。
  • 向量事件 delivery 期间访问了 APIC-access page 页面(包括线性访问和 guest-physical address 访问)而导致 VM-exit(在启用 virtualize APIC-accesses 功能时)。
  • 向量事件在 delivery 期间发生了 EPT violation 或者 EPT misconfiguration 而导致 VM-exit(在启用 enable EPT 功能时)。
  1. IDT-vectoring information 字段(32 bits),只记录间接向量事件的信息,它与 VM-exit interruption information 字段极为相似。

    85.png

    bit 11 为 1 时,表明间接向量事件存在错误码,只有 #DF,#TS,#NP,#SS,#GP,#PF, #AC 7 类硬件异常才会产生错误码。

  2. IDT-vectoring error code 字段(32 bits)。它的作用和 VM-exit interruption error code 字段是一样的,所不同的是记录的向量事件对象不同。

说明:事件注入机制目的是向 VM 中注入一个事件,使得每次 VM-entry 之后立即得到执行。而至于注入的事件会不会产生 VM-exit,需要根据注入事件(中断)类型查 VM-execution Control 等控制区域是否开启了对该类中断的监控,如果命中了就会产生 VM-exit(直接向量事件)。如果控制区域没有对注入的事件进行监控,那就不会马上产生 VM-exit,但是如果在中断/异常分发的过程中产生了错误,那就会导致 VM-exit,这种 VM-exit 就叫做间接向量事件导致的 VM-exit。

7.3 指令信息类

Information for VM Exits Due to Instruction Execution 包含以下两个字段:

  • VM-exit instruction length(32 bits),记录引起 VM-exit 指令的长度,指令长度在 1 到 15 之间。注意:如果软件异常,软件中断或者特权级软件中断是通过事件注入方式 deliver, 它们间接导致了 VM-exit 发生,那么,VM-exit instruction length 字段记录的指今长度等于 VM-entry instruction length 字段的值。

    比如在 VM 中执行一个 CPUID 指令无条件导致 VM-exit,guest 中的 CPUID 的执行被 VMM 拦截,VMM 进行了虚拟化后(模拟执行该指令),然后返回一个虚拟化的结果给 guest。那么,VMM 重新进入VM 后,guest 应该从哪里开始执行呢?肯定不是指令本身。 guest 应该从引|起 VM-exit 的指令的下一条指令开始执行。这就是 VM-exit instruction length 字段的作用。所有指令类引起的 VM-exit 在 VM 中都是 fault 类型,也就是说:guest-state 区域的 guest-RIP 字段是指向引起 VM-exit 的指令的地址,VM-exit instruction length 字段保存这条指令的长度,VMM 在重新进入 VM 前,根据 VM-exit instruction length 字段的值来计算 guest 下一条指令的地址,然后设置 geust-RIP 字段的值。

    新的 geust-RIP = geust-RIP + VM-exit instruction length

  • VM-exit instruction information(32 bits)。如果 VM-exit 是由下面的指令引起的:INS,OUTS,LGDT,LIDT,LLDT,LTR,SGDT,SIDT,SLDT,STR,RDRAND,INVPCID,INVEPT,INVVPID,VMCLEAR,VMPTRLD,VMPTRST,VMREAD,VMWRITE 以及 VMXON 指令,此时,VM-exit qualification 字段记录这些指令(除 INS/OUTS 和 RDRAND 指令外)内存操作数的偏移量(参见 3.10.1.4 节),32 位宽的 VM-exit instruction information 字段进一步补充指令的明细信息。只有结合这两个字段才能够完整地分析指令信息,给 VMM 提供管理帮助。

7.4 指令错误类

当执行一条 VMX 指令时,如果发生 VMfailValid 失败,32 位的 VM-instruetion error 字段将记录指令的错误码。注意:如果 VMX 指令执行时产生异常,处理器会执行异常处理。此时,不会存在 VMfailValid 失败。

VMX 指令执行的结果(判断 rflags 的 CF 或 ZF):

  1. 执行成功:VMsuccess,指令会清所有的 rflags 寄存器标志位,例如 CF 与 ZF 标志。
  2. 执行失败:
    • VMfailInvalid,表示因 current-VMCS 指针无效或 VMCS 内的 ID 值是无效时而失败,失败原因不会被记录。
    • VMfailValid,表示遇到某些原因而执行失败,失败原因记录在 VM-exit instruction error 字段。

如果我们需要输出 VM-exit 信息区域所有字段的信息,当根据 exit reason 字段值输出相应的 VM-exit 原因信息时,需要先检查 VM-exit instruction error 字段,确定 VMX 指令是成功的,然后再输出具体的 VM-exit 原因信息。如果 VMX 指令执行失败则会产生 VMfailValid。

下表 2-5,列举了所有可能产生 VMfailValid 失败的原因,每个原因有唯一的指令错误码(VM-instruetion error 的值)。

36.png

注意:上表是 如果 VMX 指令执行失败产生 VMfailValid 时对应的 VM-instruetion error。而 VMfaillnvalid:表示因 current-VMCS 指针无效而失败。当 current-VMCS 指针或 VMCS 内的 ID 值是无效时,VMX 指令会置 CF 标志位,指示执行失败。

7.5 VM-exit 分类

导致 VM-exit 发生的原因中,主要分无条件 VM-exit有条件 VM-exit两大类。另外在 VMX non-root operation 模式中收到某些事件也会直接产生 VM-exit(例如 INIT, SMI,以及 SIPI 消息)。当 guest 发生 triple fault 时,也会无条件地产生 VM-exit。

类型 描述
指令类导致 VM-exit 无条件指令类:
(1)所有 VMX 指令(除 VMFUNC 指令外)。
(2)CPUID,GETSEC,INVD,XSETBV 指令。

有条件指令类:
取决于 primary processor-based VM-execution controls、secondary processor-based VM-execution controls 和 tertiary VM-execution controls 中设置的条件。例如当 INVLPG exiting 为 1 时,尝试执行 INVLPG 指令将导致 VM-exit。
HLT, INVLPG, INVPCID, RDPMC, RDTSC, RDTSCP,RSM,MOV from CR3,MOV to CR3,MOV from CR8,MOV to CR8,CLTS,LMSW,MOV to CRO,MOV to CR4,MOV-DR,IN/OUT,INS/OUTS,RDMSR,WRMSR,MWAIT,MONITOR,PAUSE,LGDT,LIDT,LLDT,LTR,SGDT,SIDT,SLDT,STR,RDRAND,WBINVD 指令。
事件类导致 VM-exit (1)primary processor-based VM-execution controls、secondary processor-based VM-execution controls 和 tertiary VM-execution controls 中设置的条件。如当发生异常时,异常向量号在 exception bitmap 对应的位为 1 时将引发 VM-exit。
比如:包括 INT3INTO 指令引起的软件异常、BOUNDUD2 指令引起的硬件异常、对于 #PF 异常有些特殊,当 exception bitmap 的 bit 14 为 1,并且 PFEC & PFEC_MASK = PFEC_ MATCH 时,#PF 异常会导致 VM-exit。
(2)注入机制中(VM-entry Controls for Event Injection)注入的事件导致的 VM-exit。
(3)三重错误(triple fault)。
(4)VM-entry 失败而导致的 VM-exit。不同于上面三种在 VMX non-root operation 模式下引发的,这种 VM-exit 是在 VMX root operation 模式下引发的,包括:
1、在检查 guest-state 区域的字段时,由于无效的 guest-state 字段而导致 VM-exit。
2、在加载 guest-state 区域的 MSR 时失败而导致 VM-exit。
3、在 VM-entry 期间可能由于遇到 machine-check 事件而失败导致 VM-exit。