Windows Intel X86 VT学习 - VMCS 粗识

Chapter 25 — VMCS 学习笔记

一、从零到 Guest 跑起来的完整流程

先看全貌,后面每个知识点都是在填充这个流程的细节。

第一阶段:准备(普通驱动代码,还没进 VMX 模式)
  1. CPUID(1) 检查 CPU 支持 VT
  2. CR4.VMXE = 1(开启 VMX 指令支持)
  3. 读 IA32_VMX_BASIC MSR(0x480) → 拿到 revision ID

第二阶段:进入 VMX 模式(配置 root)
  4. 分配 VMXON region(4KB物理页)
  5. 写入 revision ID
  6. VMXON → 进入 VMX root operation

第三阶段:配置 Guest
  7. 分配 VMCS region(4KB物理页)
  8. 写入 revision ID
  9. VMCLEAR → 初始化内部数据
  10. VMPTRLD → 设为 current
  11. VMWRITE × N → 填 Guest状态、Host状态、控制字段

第四阶段:运行 Guest
  12. VMLAUNCH → 首次进入 Guest
  13. [VM Exit → VMREAD → 处理 → VMRESUME] 循环

第五阶段:清理
  14. VMCLEAR → 刷回内存
  15. 释放 VMCS region
  16. VMXOFF → 退出 VMX 模式
  17. 释放 VMXON region

二、VMX 模式下的两块内存区域(别搞混!)

VMXON region VMCS region
用途 进入 VMX 模式的"入场券" 存储 vCPU 的配置和运行状态
数量 每个物理CPU一个 每个vCPU一个
操作指令 VMXON / VMXOFF VMPTRLD / VMCLEAR / VMREAD / VMWRITE
revision ID 检查时机 VMXON 时 VMPTRLD 时

两块内存不能混用——VMPTRLD 传入 VMXON region 的地址会直接失败。


三、VMCS 是什么

VMCS:CPU 在 VMX 模式下的核心数据结构,类似 KPROCESS,用来存储一个虚拟核心的状态。

VMCS 负责三件事:

  • VM Entry:从 VMCS 加载 Guest 的 CPU 状态
  • VM Exit:保存 Guest 状态,加载 Host 状态
  • Guest 执行期间:根据控制字段决定哪些操作触发 VM Exit

Q&A:VMCS Region 是我申请的,为什么不能直接读写?

分两个阶段:

VMPTRLD 之前 — 可以随便读写

void* vmcs = MmAllocateContiguousMemory(4096, ...);
*(UINT32*)vmcs = revision_id;   // 完全合法,就是普通内存

此时 VMCS 是 inactive,CPU 不知道它的存在。

VMPTRLD 之后 — CPU 接管了

物理内存:  [VMCS 数据]           ← 你用 MOV 读写的是这里(可能是过期的)
                ↕ 不一定同步!
CPU 内部:  [VMCS 数据的副本]     ← CPU 实际用的是这里

VMPTRLD 后,CPU 可能把 VMCS 数据拷贝到芯片内部的专用存储(不是 L1/L2 cache)。
从此 CPU 读写 VMCS 都走内部副本,物理内存中的数据可能是旧的。

  • 你用 MOV 读物理内存 → 读到旧数据(CPU 内部副本已更新但没写回)
  • 你用 MOV 写物理内存 → CPU 看不到(它从内部副本读,不看物理内存)

所以不是"不让你读写",是"你读写的地方和 CPU 读写的地方不是同一个地方了"。

类比:DMA 缓冲区。硬件通过 DMA 写数据,CPU cache 里还是旧值,直接读会读到过期数据。
VMCS 更极端——数据在芯片内部专用存储里,连 cache flush 都没用,只能用专用指令:

你想做的事 怎么做
读 VMCS 字段 VMREAD(知道去芯片内部读)
写 VMCS 字段 VMWRITE(知道往芯片内部写)
强制同步回物理内存 VMCLEAR(刷回内存 + 解除关联)

四、操作 VMCS 的六条专用指令(暂时了解作用即可)

管理类

指令 作用
VMPTRLD "选中":设为 active + current,后续 VMREAD/VMWRITE 都针对它
VMCLEAR "放下":刷回内存 + launch state→clear + inactive
VMPTRST "查一下":当前 current VMCS 的物理地址是什么

读写类 — 对 current VMCS 读写字段

指令 作用
VMREAD 读取 current VMCS 的某个字段
VMWRITE 写入 current VMCS 的某个字段

执行类 — 用 current VMCS 启动 Guest

指令 作用
VMLAUNCH 首次进入 Guest(要求 launch state = clear,成功后变 launched)
VMRESUME 后续进入 Guest(要求 launched,比 VMLAUNCH 更快)

五、VMCS Region 的内存布局(SDM 25.2)

偏移量    大小      内容
───────────────────────────────────
0x00     4 字节    [30:0] = Revision ID
                   [31]   = Shadow-VMCS 标志位
0x04     4 字节    VMX-abort indicator
0x08     ~4KB      VMCS data(格式不公开)

Revision ID(偏移 0x00, bits 30:0)

  • CPU 的"版本号",从 IA32_VMX_BASIC MSR(0x480) 的 bits 30:0 读取
  • VMXON region 和 VMCS region 都要写同一个 revision ID
  • CPU 自己永远不写这个字段,只在 VMPTRLD 时读一次做校验

VMX-abort indicator(偏移 0x04)

  • 正常运行时永远是 0
  • VM Exit 过程本身出严重错误时 CPU 写错误码到这里,调试用

VMCS data(偏移 0x08)

  • 所有 VMWRITE 写入的字段都存在这里(Guest RIP、Host CR3、控制位等)
  • 内存布局 Intel 不公开,不能用偏移量直接读写,必须用 VMREAD/VMWRITE

六、VMCS 字段怎么写入?(步骤 11 详解)

核心规则

  • Revision ID 是唯一一个用 MOV 写的字段(步骤 8,此时还没 VMPTRLD,还是普通内存)
  • 其余所有字段都用 VMWRITE 一个一个写(步骤 11,VMPTRLD 之后)
  • 每个字段有一个编号(field encoding),VMWRITE 靠编号找到要写哪个字段

代码长什么样

// 实际代码用宏定义,不需要记编号
#define VMCS_GUEST_RIP           0x681E
#define VMCS_GUEST_RSP           0x681C
#define VMCS_GUEST_CR3           0x6802
#define VMCS_HOST_RIP            0x6C16
#define VMCS_HOST_RSP            0x6C14
#define VMCS_PIN_BASED_CONTROLS  0x4000

__vmx_vmwrite(VMCS_GUEST_RIP, 0x1000);         // Guest 从哪开始执行
__vmx_vmwrite(VMCS_GUEST_RSP, 0xFFFF);         // Guest 的栈指针
__vmx_vmwrite(VMCS_HOST_RIP, &ExitHandler);    // VM Exit 后跳到哪
__vmx_vmwrite(VMCS_PIN_BASED_CONTROLS, xxx);   // 中断控制
// ... 几十个 VMWRITE

六组分类(SDM 25.3-25.9)

VMCS data 区域里存了几十个字段。Intel 按用途把它们分成六组,每组是什么、存的是什么:

是什么 存的是什么 谁写 谁读
① Guest-state Guest CPU 的完整快照 Guest 的寄存器值:RIP/RSP/CR3/RFLAGS/段寄存器等 初始化时 VMM 写;运行中 CPU 自动更新 CPU(Entry时加载)
② Host-state VMM CPU 的恢复点 VMM 的寄存器值:RIP/RSP/CR3/段选择子等 VMM 写一次,基本不改 CPU(Exit时加载)
③ Execution Controls 拦截规则表 一堆开关位:拦不拦 CPUID?拦不拦 IO?拦哪些 MSR? VMM 写一次,基本不改 CPU(Guest运行时持续检查)
④ Exit Controls Exit 过程的配置 开关位:Host 是 64-bit?要保存哪些 MSR? VMM 写一次 CPU(Exit时按配置执行)
⑤ Entry Controls Entry 过程的配置 开关位:Guest 是 64-bit?要注入中断? VMM 写(可能每次 Entry 前改) CPU(Entry时按配置执行)
⑥ Exit Info Exit 的报告单 退出原因编号、触发地址、指令长度等 CPU 自动写(每次 Exit 时) VMM 读(决定怎么处理)

六组不是六个独立功能,是给 VM Entry → Guest 运行 → VM Exit 这个循环的三个阶段用的:

  ⑤ Entry Controls ─┐
  ① Guest-state ────┤  VM Entry      Guest 开始跑
                    └─ ═══════════ →     │
                                         │  ③ Execution Controls
                                         │ (持续监控:拦什么?)
                                         │
                                         ↓ 触发拦截条件
  ② Host-state ────┐
  ④ Exit Controls ──┤  VM Exit       VMM 接管
  ⑥ Exit Info ─────┘  ═══════════ →     │
  (CPU自动写)                           ↓
                                    VMM 读⑥知道为什么被拦
                                    处理完 → VMRESUME → 回到顶部
阶段 用到哪些组 回答什么问题
A. 进入 Guest ① Guest-state Guest 的 CPU 长什么样?(RIP/RSP/CR3/段寄存器)
⑤ Entry Controls 进入时怎么配置?(64-bit?注入中断?)
B. Guest 在跑 ③ Execution Controls Guest 做什么时要拦下来?(CPUID?IO?MSR?)
C. 退出到 VMM ② Host-state VMM 的 CPU 长什么样?(RIP/RSP/CR3)
④ Exit Controls 退出时怎么配置?(64-bit?保存哪些 MSR?)
⑥ Exit Info 为什么退出的?(exit reason,CPU 自动填,只读)

每组写什么(具体字段举例)

核心字段 说明
① Guest-state RIP, RSP, CR0/CR3/CR4, RFLAGS, CS/SS 等段寄存器, GDTR/IDTR, SYSENTER MSRs, Activity State VM Entry 加载、VM Exit 保存(双向)
② Host-state RIP, RSP, CR0/CR3/CR4, CS/SS/GS 等段选择子, GS Base, GDTR/IDTR base 只在 VM Exit 时加载(单向)
③ Execution Controls Pin-Based(中断/NMI), Primary(HLT/CPUID/IO/MSR拦截), Secondary(EPT/VPID), Exception Bitmap, I/O Bitmap, MSR Bitmap Guest 运行期间持续生效
④ Exit Controls Host 64-bit 模式, 保存/加载哪些 MSR 控制 Exit 过程
⑤ Entry Controls Guest 64-bit 模式, Event Injection(给 Guest 注入中断/异常) 控制 Entry 过程
⑥ Exit Info Exit Reason, Exit Qualification, Instruction Length 不用写!CPU 自动填,VMM 只读

总结:分组不是什么机制,就是同一个循环里三个阶段各自需要的数据。

posted @ 2026-03-11 11:22  BitWarden  阅读(3)  评论(0)    收藏  举报