reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
169
vendor-new/rev-skills/.claude/skills/re-hypervisor/SKILL.md
Normal file
169
vendor-new/rev-skills/.claude/skills/re-hypervisor/SKILL.md
Normal file
@@ -0,0 +1,169 @@
|
||||
---
|
||||
name: re-hypervisor
|
||||
description: >
|
||||
虚拟化逆向:VT-x/SVM、hypervisor 检测、VMCS/EPT 分析,
|
||||
以及 Xen / QNX Hypervisor / Jailhouse / ACRN / Bao / Hyper-V·VMBus / XtratuM / LynxSecure / Quest-V 的分区与 vdev 语义。
|
||||
触发词:hypervisor、VT-x、SVM、虚拟化检测、EPT、Xen、grant table、event channel、
|
||||
Jailhouse、cell、ACRN、ivshmem、Bao、shmem_id、vdev、GPA/HPA、
|
||||
Hyper-V、VMBus、VSC、VSP、GPADL、SR-IOV、XtratuM、XM_CF、LynxSecure、Quest-V、sandbox kernel
|
||||
capabilities: [hypervisor-analysis]
|
||||
---
|
||||
|
||||
# 虚拟化逆向(VT-x / SVM / 分区 hypervisor)
|
||||
|
||||
<CORE RULE>
|
||||
本域有两条互不相同的分析轴,先分清在哪一条上:
|
||||
|
||||
- **轴 A:分析 hypervisor 本身**(VT-x/SVM 指令、VMCS/VMCB、EPT/NPT)→ 本主文档
|
||||
- **轴 B:目标处在某种虚拟化之下**,判断"看到的地址/设备/中断是不是虚拟化层造出来的"→ 见 [[xen]] / [[qnx-hypervisor]] / [[jailhouse]] / [[acrn]] / [[bao]]
|
||||
|
||||
轴 B 的共同坑是**把本地句柄当全局标识、把虚拟地址当物理地址**:
|
||||
|
||||
```
|
||||
grant ref 是 grant table 的条目索引 ≠ 物理地址
|
||||
event-channel port 是通知端口 ≠ IRQ 号
|
||||
guest BAR / GPA ≠ 物理 BAR / HPA
|
||||
shmem_id 才是共享对象 identity ≠ 两个 VM 里的映射地址
|
||||
```
|
||||
</CORE RULE>
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 前置:**先判是哪一类虚拟化**(宿主厂商串 / 配置格式 / 结构性证据)→ [[re-analyze/system-fingerprints]] 的 Hypervisor 段
|
||||
- 用:目标是 hypervisor / VMM 二进制或驱动(恶意 hypervisor、rootkit 虚拟化、VM-based 保护)
|
||||
- 用:样本/程序检测自己是否运行在虚拟机或嵌套虚拟化中(CPUID 指纹、时序检测)
|
||||
- 用:分析 VT-x(VMX)或 SVM 相关的启动代码、VMCS/VMCB 布局、EPT 相关操作
|
||||
- 用:**目标跑在分区/嵌入式 hypervisor 之下**——PV 设备、静态分区、vdev、跨 VM 共享内存与虚拟中断(见下方分支)
|
||||
- 不用:普通 Windows 驱动/rootkit(走 [[re-kernel]]);只要识别"我在不在 VM 里"(快速判断走 [[re-triage]] 思路或 `virt-what`)
|
||||
- 不用:无 CPU 虚拟化支持 / 无嵌套虚拟化环境时的动态验证(静态分析先行,见坑 1)
|
||||
- 注意:动态实验(QEMU/KVM 嵌套)按 [[re-analyze/platform-tips]] 最高原则在沙箱内进行;hypervisor 样本具有高特权,只在与宿主隔离的实验环境运行
|
||||
|
||||
## 工具准备
|
||||
|
||||
静态分析(CPUID 检查 / 反编译)免沙箱;QEMU/KVM 动态实验属动态执行,默认沙箱 + 快照([[re-analyze/platform-tips]] 最高原则)。
|
||||
|
||||
### CPUID 检查工具(hypervisor 识别)
|
||||
|
||||
- Linux 内置:`grep -E 'vmx|svm' /proc/cpuinfo`——VT-x/SVM 支持标志(零安装,先用它)
|
||||
- `cpuid` 工具(dump 各 CPUID 叶子的完整输出):
|
||||
- Debian/Ubuntu: `apt install cpuid`;Fedora: `dnf install cpuid`
|
||||
- Arch 无独立 cpuid 包 → 用 `kcpuid`(`pacman -S kcpuid`,linux-tools 组)或 libcpuid 附带的 `cpuid_tool`(`pacman -S libcpuid`)
|
||||
- 验证: `cpuid -1` 输出含各叶子详情;`cpuid -1 -l 0x40000000`(hypervisor 厂商字符串叶子)
|
||||
- Windows 侧:WinDbg 内核调试下 `!cpuid`([[re-windbg]] 扩展命令)
|
||||
|
||||
### QEMU / KVM —— 嵌套虚拟化实验环境
|
||||
|
||||
- Debian/Ubuntu: `apt install qemu-system-x86`(**Debian 12+ 已移除 qemu-kvm 过渡包**,直接装 qemu-system-x86 即含 `qemu-system-x86_64`;Ubuntu 的 qemu-kvm 过渡包仍存在,装了等价于 qemu-system-x86;Ubuntu 另加 `libvirt-daemon-system`)
|
||||
- Fedora: `dnf install qemu-system-x86-core libvirt virt-install`(或 `dnf group install virtualization`)
|
||||
- Arch: `pacman -S qemu-system-x86 libvirt virt-manager`(启用 `systemctl enable --now libvirtd`)
|
||||
- macOS: `brew install qemu`(无 KVM,用 HVF)
|
||||
- 验证: `qemu-system-x86_64 --version`;`ls /dev/kvm`(KVM 加速可用);`kvm-ok`(Debian/Ubuntu 专用,来自 cpu-checker 包——先 `apt install cpu-checker`)
|
||||
|
||||
### 反编译工作台([[re-ghidra]] / [[re-ida]])
|
||||
|
||||
- [[re-ghidra]](默认)/ [[re-ida]]:导入 hypervisor 二进制(内核模块 / 裸二进制)
|
||||
- 验证: 导入后能反编译出 VMXON / VMPTRLD / VMREAD / VMWRITE 调用点
|
||||
|
||||
### Intel SDM / AMD APM(VMCS 字段编码参考,无安装)
|
||||
|
||||
- Intel SDM Volume 3C 附录 B(VMCS field encoding 表);AMD APM Volume 2(VMCB 布局)
|
||||
- 用途: VMREAD/VMWRITE 操作数解码、exit reason 编号对照(无独立包,官方文档)
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行,每步产物(CPUID 输出、VMCS 字段表、QEMU 配置)记录证据路径 + sha256(见 [[re-triage]]),供报告引用。
|
||||
|
||||
1. **hypervisor 识别(CPUID 叶子 / VMX 标志)**:
|
||||
```sh
|
||||
grep -E 'vmx|svm' /proc/cpuinfo | head -1 # 宿主 CPU 虚拟化支持
|
||||
cpuid -1 -l 0x1 | grep -i -A1 'hypervisor' # CPUID.1:ECX[31] hypervisor present bit
|
||||
cpuid -1 -l 0x40000000 # 0x40000000 叶子的 hypervisor 厂商字符串
|
||||
# "KVMKVMKVM" = KVM、"Microsoft Hv" = Hyper-V、"VMwareVMware" = VMware、"XenVMMXenVMM" = Xen
|
||||
```
|
||||
- 检测要点:hypervisor present bit(CPUID.1:ECX[31])→ 有 hypervisor;`0x40000000` 返回厂商字符串(12 字符按序分布在 EBX:ECX:EDX 各 4 字节,EAX 返回最大 hypervisor 叶子号)
|
||||
- 恶意样本常在启动早期做此检测决定后续行为([[re-evasion]] 联动,见坑 5)
|
||||
- 记录:宿主支持情况(vmx/svm)、是否已在 VM 内(含嵌套,坑 4)
|
||||
|
||||
2. **VMCS 结构分析(VT-x)**:
|
||||
- 启动路径:`VMXON`(进入 VMX 操作模式)→ `VMPTRLD`(加载当前 VMCS)→ 配置 VMCS 字段 → `VMLAUNCH`/`VMRESUME`(进 guest)→ VM exit 后查 `VM_EXIT_REASON` 字段分派
|
||||
- 反编译定位:搜 `VMXON`/`VMPTRLD`/`VMWRITE`/`VMREAD`/`VMLAUNCH` 指令(Ghidra 反汇编直接可读);VMCS 区域是内存块,先找 VMCS 缓冲区分配与初始化代码
|
||||
- **VMREAD/VMWRITE 的操作数是 VMCS 字段编码**(32 位,Intel SDM 附录 B 表 B-1~B-4):**bit0 = 访问类型**(0=full field、1=high 32 bits);**索引在 bits 9:1**(9 位,不是 bits 9:0);类型在 bits 11:10(00=control、01=read-only、10=guest-state、11=host-state);宽度在 bits 14:13(00=16 位、01=64 位、10=32 位、11=natural);bit 12 与 bits 31:15 保留(必须为 0)。解码示例:0x400C=VM-exit controls(32 位,bits 14:13=10)、0x2000=I/O bitmap A(64 位,bits 14:13=01)、0x681E=GUEST_RIP(natural,bits 14:13=11)、0x681C=GUEST_RSP(natural)、0x4402=VM-exit reason(32 位 read-only)——即 `access=enc&1; index=(enc>>1)&0x1ff; type=(enc>>10)&3; width=(enc>>13)&3`,在 Ghidra/IDA 里建枚举/结构标注
|
||||
- 关注三块:guest-state(保存 guest 寄存器/CR3/RSP)、host-state(VM exit 后宿主现场)、control 字段(execution control 决定哪些事件触发 VM exit)
|
||||
- SVM 对应:VMCB(物理地址经 `VM_HSAVE_PA` MSR),字段是固定偏移——按 AMD APM 布局标注
|
||||
- 产物:VMCS 字段标注表(编码 → 字段名 → 作用)+ 初始化/exit 处理流程
|
||||
|
||||
3. **虚拟化技术检测对抗(EPT 隐藏内存)**:
|
||||
- EPT(Extended Page Tables):guest 物理地址 → 宿主物理地址的第二层页表(VMM 控制);恶意 hypervisor 可用 EPT 把同一 guest 页映射到不同宿主页、或在 EPT 层面修改页内容而 guest 页表看起来不变
|
||||
- 分析思路:反编译里找 EPT 相关操作——`INVEPT`/`INVVPID`(TLB 失效)使用点、EPT 指针字段(VMCS `EPTP`)赋值、EPT 页表构建函数(从 EPT 指针沿页表结构走)
|
||||
- 检测对抗的观察法:guest 内看到的内存内容与宿主侧直接读同一物理页不一致 → EPT 重映射证据(两个视图对照);[[re-kernel]] 内核调试配合在宿主与 guest 两侧各 dump 同一页
|
||||
- **EPT Hook 双视图机制**(无痕 hook 核心):同一 GPA 准备两个 HPA——原始页(RW/NX)与影子页(X-only 或补丁代码)——数据访问映射原始页、指令取指映射影子页;CPU 取指触发 Execute Violation(VM-exit reason 48)后在 VM-exit 里切影子页并 INVEPT 失效缓存;扫描器/CRC 读地址得到干净字节,执行却是补丁代码(读侧无痕)
|
||||
- **EPT 无痕 INT3**:只在影子页放 `0xCC`,原始数据页不变——读内存看到原指令、取指却断下;VMCS Exception Bitmap 拦截向量 3 后匹配地址用 `guest_rip - 1`(一字节 INT3 的 RIP 在断点字节之后);处理完单步恢复再重新布断点;非本框架的 #BP 必须 reinject 回 guest(不能吞其他调试器/系统断点)
|
||||
- **MTF(Monitor Trap Flag)解决"读执行同页"**:影子页指令若读取同页常量(取指要影子页、数据要原始页冲突)→ 临时放开原始页执行并置 MTF,CPU 单步执行一条指令后在 MTF VM-exit 里收紧权限;MTF 是 VM-execution control,不动 guest RFLAGS.TF、不占 DR0-DR7
|
||||
- **EPT 监视 = 模拟无限硬件断点**:DR0-DR3 仅 4 槽,EPT 监视撤销目标页 R/W/X 权限让匹配访问 VM-exit,数量仅受内存/性能限制;硬件断点类需求(含与传统调试器互通,如 hook `SetThreadContext` 探测是否下硬件断点再转发到 EPT 监视)用这套替代
|
||||
- **VMCALL 无痕通信**:guest 执行 VMCALL 产生 VM-exit reason 18(不调用 guest 内任何地址),RAX 放协议标识、RCX 放操作号、其余寄存器传参,是 hypervisor 与 guest 的私有通信通道
|
||||
- 产物:EPT 构建/切换代码路径 + 内存视图差异证据
|
||||
|
||||
4. **嵌套虚拟化(VMM 内调试)**:
|
||||
```sh
|
||||
# KVM 开启嵌套(宿主)——module_param 权限为 0444,sysfs 只读,echo 写入会失败
|
||||
sudo modprobe -r kvm_intel && sudo modprobe kvm_intel nested=1 # Intel(AMD 用 kvm_amd nested=1)
|
||||
# 持久化:/etc/modprobe.d/kvm.conf 写 `options kvm_intel nested=1`(重启后仍生效)
|
||||
cat /sys/module/kvm_intel/parameters/nested # 验证(Y/1 为已开启)
|
||||
# QEMU 启动带 VT-x 透传的嵌套 VM(`-cpu host` 暴露 vmx 标志)
|
||||
qemu-system-x86_64 -enable-kvm -cpu host,+vmx -m 4096 disk.img &
|
||||
# 嵌套 VM 内再验证: grep vmx /proc/cpuinfo 可见 → 可在此跑 hypervisor 样本
|
||||
```
|
||||
- 用法:宿主上的调试器(gdb/lldb 或 [[re-windbg]] 内核调试)直接观察嵌套 VM 内 hypervisor 的执行——样本以为自己在最底层,实际仍在宿主调试视野内
|
||||
- VMware/Hyper-V 同理(虚拟机设置里开"虚拟化引擎/嵌套虚拟化")
|
||||
- 产物:嵌套配置存档 + 调试会话记录
|
||||
|
||||
5. **反虚拟化绕过([[re-evasion]] 联动)**:
|
||||
- 识别检测手段:CPUID 厂商字符串(步骤 1)、时序(RDTSC 指令耗时)、设备名(VM 虚拟设备)、固件/ACPI 特征
|
||||
- 按 [[re-evasion]] 的"规避识别→绕过点定位"框架:hook CPUID(cpuid 是**指令**而非导出符号,`findExportByName` 拿不到——改 hook libc 导出函数 `__get_cpuid`/`__get_cpuid_max` 改返回值,或 Stalker 指令级追踪拦截 cpuid 指令;内核侧 hook/patch 指令)、QEMU `-cpu` 参数伪造厂商字符串、设备名改名
|
||||
- 恶意样本"检测到 VM 就改变行为"(不执行恶意逻辑)也是常见对抗——记录检测点与分支
|
||||
- 产物:检测点清单 + 绕过方案(授权研究场景)
|
||||
|
||||
## 平台分支(references)
|
||||
|
||||
**轴 B**:目标是"跑在某种虚拟化之下的系统",要判断哪些现象是虚拟化层造出来的正常机制。
|
||||
|
||||
- [[xen]] —— **PV 前后端四件套**(XenStore / grant table / shared ring / event channel);**grant ref 是条目索引**(条目含 flags/domid/frame,且**映射期间不支持撤销**);**event-channel port 不是 IRQ 号**(绑定到 `shared_info` 位掩码);**迁移后本地端口会变、稳定的是 remote port**;shared ring 上"有数据流动却无 IPC 调用"属正常
|
||||
- [[qnx-hypervisor]] —— **三层地址**(guest virtual → guest physical/IPA → host physical);**vdev** 使 guest DT ≠ 板级 DT;**shmem vdev** 的工厂页(`name`/`size`/`shmem`/`vector`/`status`,写 `size` 触发创建,故 guest 启动顺序无关)与控制页(`status`/`idx`/`notify`/`detach`);`intr pass` vs `intr vdev`;直通设备同一时刻只允许一个 resident
|
||||
- [[jailhouse]] —— **配置比 inmate ELF 更重要**(CPU/内存/IRQ/PCI 归属);越权访问 → **CPU 被 park**(不是 guest 崩溃);**`JAILHOUSE_MEM_LOADABLE` 映射在 cell 启动时被撤销**(重装走 Cell Set Loadable);**ivshmem 不维护 pending 语义**(MSI-X PBA 恒 0,事件状态从共享内存协议读);hypervisor VA 里扫不到全部 VM 内存
|
||||
- [[acrn]] —— **Service VM / pre-launched / post-launched** 三态与三种 I/O 服务路径;**guest BAR ≠ 物理 BAR**(EPT 映射,MSI-X 表页必须 trap);posted interrupt 使 VM-exit 减少;**ivshmem 的 dm-land 与 hv-land 永不互通**(前缀 `dm:/` vs `hv:/`,且 dm-land 无 doorbell)
|
||||
- [[bao]] —— **静态分区**(CPU/内存/中断独占,vCPU 与 pCPU 1:1,无调度器);**共享对象 identity 是 `shmem_id` 而非地址**;IPC 的 `size ≤ 对应 shmem 大小`;**cache coloring** 抑制干扰但有代价(牺牲大页、增加 TLB 压力)且随平台颜色数截断
|
||||
- [[hyperv-vmbus]] —— **VSC / VSP 经 VMBus**(双向通道 = 两个 ring buffer;高速设备可用多通道);**GPADL 是缓冲区描述句柄不是地址**(共享总量有上限);**SR-IOV 让数据面中途换路**(VF 直通绕开 VMBus 与 hypervisor,控制面仍在 VMBus)
|
||||
- [[xtratum]] —— **XM_CF 配置驱动**(分区/内存/中断/端口/通道/调度全在配置里,运行期无动态对象);**循环计划(MAF + 时间槽)与固定优先级计划**;**Plan 0 = 初始化、Plan 1 = 维护**,切换要等当前计划剩余槽跑完;**IPVI 每分区最多 8 个**
|
||||
- [[lynxsecure]] —— **静态分离内核**:boot 时定义、不可变的硬件分区;内核功能仅限**资源分区 / 控制分区之间的数据流 / 调解系统状态变更入口**;**I/O 与应用支持全部导出到 guest**(内核里没有驱动与 I/O 栈)。含**逆向可操作层**:配置优先于代码、启动阶段是唯一能看到"配置生效"的窗口、**用三项职责反查内核边界**、跨分区找受控通道而非共享页、用官方约束验证 DMA/hypervisor 与 guest 的隔离
|
||||
- [[questv]] —— **多内核 sandbox + 每 sandbox 一个 monitor**:monitor 只在引导/故障/影子页表/建通道时介入;EPT 用于**内存隔离**而非 CPU 虚拟化;中断直接投递 sandbox;**EPT violation → VM-exit 到对应 monitor**(必要时强制触发陷入);本地/远程**在线恢复**;**无全局时钟**。含"哪一段是 monitor"的定位判据
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-evasion]]:反虚拟化检测/绕过框架(CPUID hook、时序对抗);恶意样本 VM 检测行为分析
|
||||
- [[re-kernel]]:hypervisor 驱动/内核模块分析底座(DriverEntry、IRP、内核调试配合)
|
||||
- [[re-windbg]]:Windows 宿主/guest 内核调试(`!cpuid` 查 CPUID 叶子、驱动加载观察)
|
||||
- [[re-ghidra]] / [[re-ida]]:VMCS/VMCB 相关代码反编译与结构标注(SDM 附录 B 建枚举)
|
||||
- [[re-sandbox]] / [[re-analyze/platform-tips]]:QEMU/KVM 实验环境隔离最高原则;嵌套 VM 是默认沙箱形态
|
||||
- [[re-triage]]:初勘阶段"是否在 VM 内 / CPU 虚拟化能力"快速判断(`virt-what` 思路)
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **硬件虚拟化调试环境复杂**:现象——hypervisor 样本在普通 VM 里跑不起来/直接崩溃,调试器附加失败,样本检测到嵌套环境后行为异常;原因——嵌套虚拟化需要宿主 CPU 支持 + 显式开启(KVM nested / Hyper-V / VMware 选项),且样本会检测自己是否"真的在底层";对策——先纯静态积累信息(步骤 1-2 的 CPUID 与 VMCS 分析不需要跑样本),动态前确认三层能力:宿主 vmx/svm 标志(`grep vmx /proc/cpuinfo`)→ 嵌套开关(`/sys/module/kvm_intel/parameters/nested`)→ QEMU `-cpu host` 透传;实验全部在隔离沙箱([[re-analyze/platform-tips]] 最高原则)
|
||||
- **VMCS 内核对象逆向门槛高**:现象——反编译里 VMREAD/VMWRITE 一堆魔数,不知道读写的是什么字段,VM exit 分派逻辑看不懂;原因——VMCS 是硬件定义格式(字段编码不是符号),且各 CPU 架构(VT-x vs SVM)布局完全不同;对策——把 Intel SDM 附录 B 的字段编码表建进反编译器(Ghidra 枚举/结构),VMREAD/VMWRITE 操作数逐一解码;按 guest-state / host-state / control 三块组织分析;AMD 目标改用 VMCB 固定偏移布局(APM Volume 2)
|
||||
- **EPT 使内存断点失效**:现象——调试器在 guest 里下的内存断点/页保护断点不触发或触发后行为诡异(寄存器对不上);原因——EPT 的访问位/脏位独立于 guest 页表,VMM 通过 EPT 控制 guest 看到的内存视图(含隐藏页),普通调试器断点基于 guest 页表视角;对策——区分 EPT violation(VM exit reason 48)与 guest page fault(reason 14);要观察 EPT 层必须看 EPT 页表结构本身(沿 VMCS EPTP 字段展开)而不是 guest 页表;[[re-kernel]] 内核调试下对照宿主/guest 两侧内存视图
|
||||
- **CPU 特性差异(VT-x vs SVM)**:现象——在 Intel 机器上整理的 VMCS 偏移/exit reason 编号拿到 AMD 机器全对不上,或相反;原因——VT-x 与 SVM 是两套独立实现:VMCS(VMREAD/VMWRITE 编码)vs VMCB(固定偏移),exit reason 编号体系不同;对策——先确认目标平台(CPUID vendor + vmx/svm 标志,步骤 1),按平台选对应手册(Intel SDM Vol 3C / AMD APM Vol 2),分析笔记标注目标平台与 CPU 型号,不跨平台复用字段表
|
||||
- **样本检测 VM 后改变行为(影响结论)**:现象——静态分析很清晰的恶意逻辑,动态运行时完全看不到(样本"正常"运行);原因——样本检测到自己在 VM/调试环境里会走"无害分支"(反沙箱/反调试常见手法);对策——按步骤 1 先确定样本视角的虚拟化状态,动态验证必须与样本检测条件一致(或逐项绕过检测);结论以"检测点还原 + 绕过后的行为"为准,单跑一遍就下结论不可信
|
||||
- **AMD NPT 不能照搬 EPT 方案**:现象——Intel 上可用的"只执行页"技巧(影子页 X-only)在 AMD 平台失效;原因——NPT 与 EPT 是两套独立实现,**NPT 不能单独设置只执行属性**(读与执行位绑定);对策——跨平台实现 hook/隐藏前先确认目标是 Intel(EPT)还是 AMD(NPT),AMD 需换用其他手段(如结合 NX + 数据视图)
|
||||
- **"VT 无痕读写"是伪命题**:现象——以为 EPT 能无痕读写任意数据段;原因——影子页表只能无痕改写"代码段"(取指视图切换),数据段无法同时满足两侧视图(读原始 vs 写影子);对策——无痕写仅限代码段场景(hook 场景);数据段读取仍靠遍历四级页表(GVA→GPA→HPA 二阶段翻译)直读物理页,与普通驱动思路无本质区别
|
||||
- **RDTSC 检测 VM-exit 开销可被补偿**:现象——样本用 `__rdtsc/__rdtscp` 测指令耗时差值识别 hypervisor;原因——VM-exit 有固定开销可测量;对策——VM-exit 汇编入口最早记录 `exit_tsc`,`ReadVirtualTsc` 用有界平滑估计补偿(不能把中断/调度长尾全当 exit 成本),并保证每 vCPU 单调(`max(value, last_guest_tsc+1)`);原则:样本检测哪里,就在 VM-exit 里处理哪里
|
||||
- **把跨 VM 的本地句柄当全局标识**:现象——trace 里同一个整数在两个域/VM 里出现,被当成同一个对象;原因——grant ref 是条目索引、event-channel port 是通知端口、shmem_id 才是共享对象 identity,本地端口在重连后还会变且可被重用;对策——按各自的表/配置对齐(grant table 条目、shared_info 位掩码、shmem_id),见 [[xen]] / [[bao]]
|
||||
- **把虚拟地址当物理地址**:现象——guest 里的 MMIO 地址在真实硬件上找不到对应,就判驱动写错或抓错设备;原因——PV 设备没有寄存器窗口;分区 hypervisor 下 guest 的 BAR/GPA 与物理 BAR/HPA 之间隔着 EPT/NPT 或直通映射;vdev 的地址由配置制造;对策——先判"直通还是 vdev/模拟",再谈地址是否一致,见 [[qnx-hypervisor]] / [[acrn]] / [[jailhouse]]
|
||||
- **把 hypervisor 的保护动作当 guest 崩溃**:现象——程序在访问某地址后"突然停住",却找不到常规缺页/总线错误证据;原因——静态分区 hypervisor 会把越权访问的 CPU/cell **park 掉**(Jailhouse);对策——查 hypervisor 侧日志(Unhandled trap / Parking CPU),并对照 cell/VM 配置的资源归属([[jailhouse]])
|
||||
- **跨 VM 共享内存通不了就往设备模型里找**:现象——两侧 ivshmem 都"正常"但完全无法通信;原因——ACRN 的 dm-land 与 hv-land 是两套实现,前缀(`dm:/` 与 `hv:/`)不同则永不互通,且 dm-land 没有 doorbell 通知;对策——先核对实现与前缀是否一致,再看数据面([[acrn]])
|
||||
- **把 GPADL 之类的描述符当内存地址**:现象——trace 里的小整数被当地址或偏移参与推算;原因——GPADL 是"描述并映射一片客户机缓冲区"的句柄,且宿主对其共享总量有限制;对策——按句柄语义还原它指向的缓冲区([[hyperv-vmbus]])
|
||||
- **网络流量中途"消失"却功能正常**:现象——一段时间 VMBus 很忙,之后抓不到包但网络仍通;原因——SR-IOV 下数据面可从合成路径切到 **VF 直通**(数据面绕开 VMBus 与 hypervisor,控制面仍在 VMBus);对策——先判当前数据面走哪条路径([[hyperv-vmbus]])
|
||||
- **没拿到配置文件就分析资源归属**:现象——找不到"为什么这个分区访问不到"的答案;原因——配置驱动的分离系统(XtratuM、LynxSecure、Jailhouse、Bao 等)把资源归属全放在配置里;对策——先取配置,再谈代码([[xtratum]] / [[lynxsecure]])
|
||||
- **把"缺少集中管理代码/VM-exit 很少"当没有隔离**:现象——反汇编里找不到中央调度或重配置逻辑;原因——静态配置模型与多内核 sandbox 本来就不这么做;对策——按该系统的模型解释([[lynxsecure]] / [[questv]])
|
||||
- **跨 sandbox 比较时间戳**:现象——同一次事件在两侧的时间对不上;原因——某些多内核设计**没有全局时钟**,各核用本地定时器与 TSC;对策——把时钟偏差算进去,别按单一时间线断言([[questv]])
|
||||
- **在内核区域里期待驱动与 I/O 栈**:现象——某个映像块里应有尽有(驱动、I/O、应用 API),却被当作内核;原因——极端的静态分离设计把 **I/O 与应用支持全部导出到 guest**,内核只做资源分区/数据流控制/状态变更调解;对策——用这三项职责反查真正的内核边界([[lynxsecure]])
|
||||
- **在运行期找"重配置"逻辑**:现象——找不到动态 MMU/资源重配置引擎;原因——静态配置模型下映射与归属都在启动期确定;对策——把追映射的工作放到启动阶段([[lynxsecure]])
|
||||
@@ -0,0 +1,129 @@
|
||||
# ACRN:Service VM / pre-launched / post-launched + Device Model
|
||||
|
||||
<CORE RULE>
|
||||
ACRN 里"谁在服务这个 I/O"有三个互斥的答案,**必须先判定是哪一个**:
|
||||
|
||||
| 路径 | 谁在服务 |
|
||||
|---|---|
|
||||
| **直通(passthrough)** | 硬件直接给 VM,hypervisor 只做映射与中断重映射 |
|
||||
| **hypervisor 模拟** | hypervisor 自己实现设备 |
|
||||
| **Service VM 的 Device Model** | 由 Service VM 里的用户态设备模型模拟 |
|
||||
|
||||
判定错了,后面所有"这个寄存器为什么这样写"的推理都会跑偏。
|
||||
|
||||
拓扑基本形是:
|
||||
|
||||
```
|
||||
Hypervisor
|
||||
├─ Service VM(直接拥有大量真实硬件,并为 post-launched VM 提供设备模拟/共享)
|
||||
├─ Pre-launched VM(不依赖 Service VM 启动)
|
||||
└─ Post-launched User VM(依赖 Service VM 的设备模型)
|
||||
```
|
||||
</CORE RULE>
|
||||
|
||||
## 一、guest 看到的 BAR 不是物理 BAR
|
||||
|
||||
直通时 hypervisor 会做 **虚拟 BAR ↔ 物理 BAR** 之间的映射:在两者之间建立 EPT 映射让 VM 直接访问 MMIO——**但 MSI-X 表所在的那几页必须被 trap**(不建立 EPT 映射),否则无法在虚拟向量与物理向量之间做重映射。
|
||||
|
||||
同时还有两件事:
|
||||
|
||||
- **VT-d DMA remapping**:把 GPA 翻译成 HPA 供设备 DMA 使用
|
||||
- **VT-d 中断重映射**:ACRN 出于安全考虑要求它;**如果 VT-d 硬件不支持中断重映射,ACRN 会拒绝启动 VM**
|
||||
|
||||
**所以:**
|
||||
|
||||
```
|
||||
Guest BAR0 = 0x80000000
|
||||
Host 物理设备 BAR = 0xD2000000
|
||||
```
|
||||
|
||||
**完全正常**,不要因为地址不一致就认为抓错了设备或驱动写错了。
|
||||
|
||||
## 二、中断:不是每次都有等量的 VM-exit
|
||||
|
||||
- 常规情况下虚拟中断由 hypervisor 注入;在支持时使用 **VT-d posted interrupt(PI)** 以获得更好的中断性能
|
||||
- 因此:
|
||||
|
||||
```
|
||||
guest ISR 明明执行了
|
||||
hypervisor trace 里却找不到预期的完整中断注入路径
|
||||
```
|
||||
|
||||
**不能直接判 trace 缺失**——先确认该设备/该 VM 是否走了 posted-interrupt 一类旁路。
|
||||
|
||||
## 三、ivshmem:dm-land 与 hv-land 是两套机制
|
||||
|
||||
ACRN 的共享内存设备(ivshmem)有两种实现,**可以同时存在,但不同实现的 VM 之间无法通过同一机制互通**:
|
||||
|
||||
| 实现 | 位置 | 共享内存取自 | 适用 VM |
|
||||
|---|---|---|---|
|
||||
| **dm-land** | Service VM 的 Device Model(`acrn-dm`) | Service VM 的内存空间 | **仅 post-launched User VM** |
|
||||
| **hv-land** | hypervisor 内部 | hypervisor 的内存空间 | pre-launched 与 post-launched 都可 |
|
||||
|
||||
- 区分方式看名字前缀:`dm:/`(早期补丁里为 `sos:/`)与 `hv:/`;**前缀不对会导致 ivshmem 初始化失败**
|
||||
- dm-land 由 `acrn-dm` 启动参数 `-s <slot>,ivshmem,<shm_name>,<shm_size>` 定义;hv-land 在构建期配置(`IVSHMEM_ENABLED` + `IVSHMEM_REGION`,格式 `名字,大小,VM ID 列表`),**最多八个 hv-land 区域**
|
||||
- 设备是标准虚拟 PCI 设备:**BAR0 中断相关寄存器、BAR1 MSI-X 表、BAR2 共享内存**
|
||||
- 寄存器语义:`IRQ_MASK(0x0)` 与 `IRQ_STA(0x4)` 保留(不支持传统中断)、`IV_POS(0x8)` 为跨 VM 位置/VM ID(当前为 0)、**`DOORBELL(0xC)` 用于触发对端中断**
|
||||
- **通知(doorbell)只在 hv-land 支持**:发送方把目标 VM ID 与向量号写入 doorbell 寄存器,由 hypervisor 侧查表并**向目标 VM 注入 MSI**(最多 8 个 MSI-X 向量);**dm-land 没有通知机制**(列为待支持)
|
||||
- 已知坑:dm-land 下 **guest 若重新编程 BAR2,共享内存会变得不可用**(该场景的 GPA/HPA 重映射未处理)
|
||||
|
||||
**所以"两个 VM 各自 ivshmem 都正常、就是互相看不见"时,第一件事是查前缀与实现是否一致**,而不是怀疑设备模型。
|
||||
|
||||
## 四、VM 类型决定 MSI-X 的 trap 路径
|
||||
|
||||
ACRN 分别定义三类 VM 的 MSI-X 访问路径:
|
||||
|
||||
| VM 类型 | 路径 |
|
||||
|---|---|
|
||||
| Service VM | hypervisor 的 MMIO handler |
|
||||
| **Post-launched VM** | **Device Model 的 MMIO handler,再涉及 hypervisor** |
|
||||
| Pre-launched VM | hypervisor handler |
|
||||
|
||||
**所以不要做"所有 MSI-X 写都走同一条 hypervisor 路径"的假设。**
|
||||
|
||||
## 五、设备归属会在 VM 之间迁移
|
||||
|
||||
- Post-launched VM 启动时,物理设备的所有权**从 Service VM 移交给该 VM**;VM 关闭时再**移回 Service VM**
|
||||
- 因此"Service VM 里某个 PCI 设备突然消失、过一会又回来"**不一定是热插拔故障**
|
||||
- **pre-launched VM 由 hypervisor 创建**,不经过 Service VM 的 Device Model——**看不到设备模型进程不能推出启动过程缺了一层**
|
||||
- passthrough 下 guest BAR 与 host BAR 不同属正常:guest GPA 经 EPT/VT-d 到 HPA,**DMA 还需 VT-d 的 GPA→HPA 转换**
|
||||
|
||||
## 决策树
|
||||
|
||||
```
|
||||
拿到 ACRN 目标
|
||||
├─ 先分清 VM 类型:Service VM / pre-launched / post-launched
|
||||
│ └─ post-launched 才可能经过 Service VM 的 Device Model
|
||||
├─ 一个 I/O 行为是谁在服务?
|
||||
│ ├─ 直通 → 查虚拟 BAR ↔ 物理 BAR 映射、VT-d DMA/中断重映射
|
||||
│ ├─ hypervisor 模拟 → 查 hypervisor 侧设备实现
|
||||
│ └─ Device Model → 查 Service VM 里的设备模型
|
||||
├─ guest BAR 与物理 BAR 不一致
|
||||
│ └─ 正常(直通本来就做 MMIO remapping);注意 MSI-X 表页必须被 trap
|
||||
├─ 中断发生但 trace 里没有预期的注入路径
|
||||
│ └─ 查是否走了 posted interrupt 等旁路,别判 trace 缺失
|
||||
└─ ivshmem 通不了
|
||||
├─ 查前缀:dm:/ 还是 hv:/(混用永不通)
|
||||
├─ pre-launched 只能用 hv-land
|
||||
├─ dm-land 无 doorbell → 只能共享数据,不能互相通知
|
||||
└─ dm-land 下 guest 重编程 BAR2 → 共享内存不可用(已知未处理)
|
||||
```
|
||||
|
||||
## 工具与验证
|
||||
|
||||
- 拓扑:VM 类型(Service VM / pre-launched / post-launched)与设备归属
|
||||
- 直通:虚拟 BAR 与物理 BAR 的 EPT 映射、MSI-X 表页是否被 trap、VT-d DMA/中断重映射是否启用
|
||||
- 共享内存:ivshmem 的名字前缀(`dm:/` / `hv:/`)、BAR 布局、doorbell 寄存器与 MSI-X 向量
|
||||
- 验证:能同时说清「VM 类型 + 该 I/O 的服务路径(直通/hypervisor/Device Model)+ 地址与中断经过了几层重映射」
|
||||
|
||||
## 该平台的坑(汇总)
|
||||
|
||||
- **不先分清 pre-launched 与 post-launched**:设备可用性和服务路径完全不同
|
||||
- **把 guest 的 PCI BAR 当物理 BAR**:直通本来就有虚拟↔物理映射
|
||||
- **以为 MSI-X 表页也能被 guest 直接访问**:那几页必须被 trap 才能做向量重映射
|
||||
- **中断 trace 里没有 VM-exit 就判 trace 缺失**:posted interrupt 等旁路会减少甚至避免常规 VM-exit
|
||||
- **把 dm-land 与 hv-land 的 ivshmem 混用**:两种实现之间永远不通,且前缀写错会直接初始化失败
|
||||
- **在 pre-launched VM 上指望 dm-land**:dm-land 只支持 post-launched
|
||||
- **指望 dm-land 有 doorbell 通知**:通知只在 hv-land 实现
|
||||
- **忽略 dm-land 下 BAR2 被 guest 重编程的影响**:共享内存会变得不可用
|
||||
- **忽略 VT-d 中断重映射的硬性要求**:不支持该硬件的平台上 ACRN 会拒绝启动 VM
|
||||
@@ -0,0 +1,103 @@
|
||||
# Bao:静态资源分区 / shmem_id / cache coloring
|
||||
|
||||
<CORE RULE>
|
||||
Bao 是**纯粹的静态分区** hypervisor:
|
||||
|
||||
- 内存按两阶段翻译**静态分配**;I/O **只做直通**;**虚拟中断直接映射到物理中断**;vCPU 与物理 CPU **1:1**,**没有调度器**
|
||||
- **没有运行时内存分配、没有完整设备模拟**
|
||||
- **CPU、内存区、中断默认独占、不共享**;只有配置里明确声明的共享内存/IPC 才跨 VM
|
||||
|
||||
因此第一条判断是:
|
||||
|
||||
```
|
||||
两个 VM 都能访问同一段物理 RAM
|
||||
→ 若它不是 IPC/共享资源配置的一部分 → 先怀疑配置错,或你把地址层级看错了
|
||||
```
|
||||
|
||||
**不要用"地址相同/不同"判断两个 VM 是不是在看同一个共享对象。**
|
||||
</CORE RULE>
|
||||
|
||||
## 一、identity 来自配置,不来自地址
|
||||
|
||||
配置是一个 C 源文件,定义全局 `struct config`:
|
||||
|
||||
- **`.shmemlist`**:共享内存区列表。每个 `struct shmem` 含 `size`、`place_phys`、`base`/`phys` 联合体,以及 **`colors`**(L2 共享缓存的颜色位图;默认 `0x0` 表示使用全部颜色)
|
||||
- **`.vmlist`**:VM 列表(必需)。每个 `struct vm_config` 含 guest 镜像(基址/装载地址、大小、`separately_loaded`、`inplace`)、`entry`、**`cpu_affinity`**(期望的物理 CPU 位图,**约定在各 VM 之间互斥**)、`colors`、`platform`
|
||||
- 每个 VM 的 `struct vm_platform` 含:`cpu_num`、内存区(`regions`)、**IPC 通道(`ipcs`)**、MMIO 设备区(`devs`)、`mmu` 等
|
||||
|
||||
**IPC 通道 `struct ipc` 的关键字段**:
|
||||
|
||||
| 字段 | 含义 |
|
||||
|---|---|
|
||||
| `base` | 该 IPC 内存区**在本 VM 中的虚拟基址** |
|
||||
| `size` | 大小,**应小于或等于其 `shmem_id` 所指向共享区的大小** |
|
||||
| **`shmem_id`** | **关联到 `.shmemlist` 中某个共享区的 ID——这才是共享对象的 identity** |
|
||||
| `interrupt_num` / `interrupts` | 分配给该 IPC 通道的中断号数组(doorbell 机制) |
|
||||
|
||||
**所以:**
|
||||
|
||||
```
|
||||
VM A: 0x70000000 → shmem_id 2
|
||||
VM B: 0x90000000 → shmem_id 2
|
||||
```
|
||||
|
||||
**两个地址不同,底层却是同一个通信对象**——按"地址相等 == 同一 buffer"推理会直接出错。(反过来,两个 VM 用了相同虚拟地址也**不**说明是同一对象。)
|
||||
|
||||
## 二、中断与设备:直通、独占、1:1
|
||||
|
||||
- 设备在配置时**独占分配给某个 VM**,中断**直接路由到该 VM**,以避免虚拟化开销
|
||||
- vCPU 按配置的 affinity 分配到物理 CPU(未定义 affinity 时顺序分配)
|
||||
- **Bao 自己只需要一个中断**,用于核间通信/同步的 IPI
|
||||
- 初始化分七个阶段(early boot → CPU init → memory init → platform init → interrupt init → VMM init → VM launch/run),**之后不再有调度或动态重配置**
|
||||
|
||||
## 三、cache coloring:干扰抑制,但不是万能
|
||||
|
||||
- 通过 `colors` 位图把 LLC 分成互不相同的颜色分给各 VM,减少互相干扰
|
||||
- **默认未启用**;取值在运行时会**按平台可用颜色数截断**,所以**效果是平台相关的**
|
||||
- 实测(16 色 × 32 KiB 的平台上):每 VM 分 7 色(224 KiB),另留 2 色给 hypervisor,干扰可降低近一半——**但不能完全消除**
|
||||
- **代价**:coloring 会让**超大页无法使用**(Bao 本来用大页降低 TLB 压力),从而**增加 TLB 压力与开销**
|
||||
|
||||
**做延迟/性能归因时不能假设所有 VM 天然共享整个 LLC。**
|
||||
|
||||
## 四、MPU 目标上的额外细节
|
||||
|
||||
在无 MMU(MPU)目标上,共享内存区需要**跨核同步**:某核映射共享区时会与共享该区的其他核交换消息(类型/ID 与区域配置),以 IPI 通知;**early boot 尚未启用 IPI 时,非主核以轮询方式等待**。vMPU 用内存保护条目替代页表,Arm PMSAv8 的粒度是 64 字节。
|
||||
|
||||
## 决策树
|
||||
|
||||
```
|
||||
拿到 Bao 目标
|
||||
├─ 先取配置(struct config:shmemlist + vmlist)
|
||||
│ ├─ CPU 亲和、内存区、设备、中断各自归属哪个 VM
|
||||
│ └─ 是否存在跨 VM 共享:看 shmem 条目与 ipc 的 shmem_id
|
||||
├─ 两个 VM 访问同一物理内存
|
||||
│ ├─ 是 IPC/共享配置的一部分?→ 正常(按 shmem_id 对齐,不看地址)
|
||||
│ └─ 不是?→ 先怀疑配置错误或地址层级判断错误
|
||||
├─ 共享区地址在两个 VM 里不同
|
||||
│ └─ 正常:identity 是 shmem_id,base 只是本 VM 的映射地址
|
||||
├─ 性能/延迟归因
|
||||
│ ├─ 是否启用 cache coloring(colors 位图)
|
||||
│ ├─ coloring 生效会牺牲大页 → TLB 压力上升
|
||||
│ └─ 效果随平台可用颜色数截断,不能跨平台照搬结论
|
||||
└─ 无 MMU(MPU)目标
|
||||
└─ 共享区映射靠跨核消息 + IPI(early boot 期非主核轮询)
|
||||
```
|
||||
|
||||
## 工具与验证
|
||||
|
||||
- 配置:`struct config`(`.shmemlist` / `.vmlist`)、`vm_platform`(regions / ipcs / devs)、`struct ipc`(base / size / shmem_id / interrupts)
|
||||
- 共享:按 `shmem_id` 对齐不同 VM 的映射,而不是按地址
|
||||
- 隔离:CPU 亲和与设备/中断的独占归属;七个初始化阶段之后的运行期无重配置
|
||||
- 验证:能同时说清「共享对象的 identity(shmem_id)+ 各 VM 的映射地址 + 中断归属」
|
||||
|
||||
## 该平台的坑(汇总)
|
||||
|
||||
- **用地址相等判断共享对象 identity**:identity 是 `shmem_id`,各 VM 的 `base` 可以不同
|
||||
- **看到两个 VM 访问同一物理内存就判"配置泄漏"**:先看它是不是 IPC/共享配置的一部分
|
||||
- **以为资源可以运行时申请**:静态分区,初始化七阶段之后没有动态重配置
|
||||
- **期待有调度器**:vCPU 与物理 CPU 1:1,没有调度器
|
||||
- **以为中断会被虚拟化/模拟**:中断直接路由给所属 VM,I/O 只做直通
|
||||
- **忽略 IPC 的 `size ≤ 对应 shmem 大小` 约束**:配置不满足时行为不可预期
|
||||
- **在性能分析里假设 VM 共享整个 LLC**:cache coloring 会改变这一点,且受平台颜色数限制
|
||||
- **为了 coloring 忽略大页损失**:会抬高 TLB 压力,可能抵消收益
|
||||
- **在 MPU 目标上忽略跨核共享同步**:共享区映射需要跨核消息与 IPI
|
||||
@@ -0,0 +1,156 @@
|
||||
# Hyper-V / VMBus:VSC·VSP / GPADL / SR-IOV 数据面切换
|
||||
|
||||
<CORE RULE>
|
||||
Hyper-V 的合成设备(synthetic device)不走"硬件 MMIO + 中断",而走 VMBus:
|
||||
|
||||
```
|
||||
子分区: VSC(guest 侧驱动,如 netvsc / storvsc)
|
||||
↕ VMBus(双向通道 = 两个 ring buffer)
|
||||
根分区: VSP(宿主侧实现合成设备)+ 硬件
|
||||
```
|
||||
|
||||
所以:
|
||||
|
||||
```
|
||||
设备 I/O 看不到明显的硬件 MMIO
|
||||
→ 可能只是合成设备流量走 VMBus
|
||||
```
|
||||
|
||||
第二条:**GPADL 不是内存地址**——它是"描述并映射一片客户机缓冲区"的句柄。
|
||||
</CORE RULE>
|
||||
|
||||
## 一、VMBus 的机制
|
||||
|
||||
- VMBus 通过**映射到多个分区的内存页**实现——数据经总线传递**不需要 hypervisor 拷贝**,减少上下文切换
|
||||
- 一条通道是**双向**的,由**两个 ring buffer** 组成(进/出);高速设备(合成 SCSI、网卡)可用**多条通道**提高并行度
|
||||
- 在 Linux 侧它是总线模型(`/sys/bus/vmbus`),主要用户是合成设备:存储、网络、视频、工具服务
|
||||
|
||||
## 二、GPADL:客户机物理地址描述符列表
|
||||
|
||||
- ring buffer 空间有限,性能关键路径用 **GPADL**:让根分区把**额外的客户机内存页直接映射进自己的地址空间**
|
||||
- ring buffer 本身是 guest 分配、由 guest 把 GPA 列表**经 VMBus 控制路径**告知宿主;更大量数据传输另用独立的 GPA 列表描述数据缓冲区
|
||||
- Hyper-V 对**经 GPADL 共享的客户机内存总量有限制**(Windows Server 2019+ 约 1280 MB,更早约 384 MB),防止恶意 guest 耗尽宿主资源
|
||||
|
||||
**RE 含义**:
|
||||
|
||||
```
|
||||
GPADL = 37
|
||||
→ 不是 GPA 0x25000
|
||||
→ 是一个句柄,背后是"一片被描述并映射的客户机缓冲区"
|
||||
```
|
||||
|
||||
## 三、SR-IOV:数据面会"中途换路"
|
||||
|
||||
带 SR-IOV 的网卡上:
|
||||
|
||||
- **数据面**(VF):从 VM 内的 VF **直接走硬件到网卡**——**不经过 VMBus,也不经过 hypervisor**
|
||||
- **控制面**:VF 的控制仍然**经 VMBus** 回到根分区的 PF 驱动
|
||||
|
||||
**VF 拆除时的迁移顺序**:
|
||||
|
||||
```
|
||||
虚拟化栈通知 VPCI VSP(根分区管理 OS)
|
||||
→ 经 VMBus 通知 guest 内的 VPCI VSC
|
||||
→ NetVSC 从 VF 网卡驱动解绑
|
||||
→ 子分区流量从 VF 数据面迁移到软件合成数据面
|
||||
→ 后续:过滤规则搬到 PF 上的默认 VPort、删除 VF 的 VPort、请求函数级复位、释放 VF 资源
|
||||
```
|
||||
|
||||
**所以**:
|
||||
|
||||
```
|
||||
一开始 VMBus 很忙 → 后来包捕获里流量突然消失 → 网络却仍然正常
|
||||
```
|
||||
|
||||
**不一定是数据丢失**——很可能已经切到 SR-IOV VF 直通路径(或反向迁回)。
|
||||
|
||||
## 四、GPADL 的删除会阻塞
|
||||
|
||||
- **若该 GPADL 当前仍被 server 映射,删除调用会被阻塞,直到它被解除映射**
|
||||
- 从 buffer 创建 GPADL 时,**该 buffer 会被 probe 并锁定,直到 GPADL 被销毁**
|
||||
- 所以 teardown 里"调用删除之后线程卡住"**先查远端映射的生命周期**,而不是马上判死锁
|
||||
|
||||
## 五、packet 的生命周期:回调返回 ≠ packet 结束
|
||||
|
||||
- **完成调用可以在处理回调中立即调用,也可以稍后调用**
|
||||
- **直到完成调用被发起**,系统才会:清理未完成的映射、释放占用的缓冲区、在**对端要求完成通知时**发送完成 packet
|
||||
- 因此:
|
||||
|
||||
```
|
||||
callback returned ≠ packet lifetime ended
|
||||
```
|
||||
|
||||
**逆向资源生命周期时不能把"回调返回"当释放边界。**
|
||||
|
||||
## 六、大块 payload 不在 ring packet 里
|
||||
|
||||
- 一个 packet 可以只有 **inline 控制缓冲**,大块 payload 通过**外部内存描述**提供
|
||||
- 所以只 dump ring 时看到:
|
||||
|
||||
```
|
||||
header / command / 几个字段
|
||||
但找不到真正的数据
|
||||
```
|
||||
|
||||
**很正常**——要继续解析外部的内存描述。
|
||||
|
||||
## 七、取外部数据可能返回"待决",而且会再次回调
|
||||
|
||||
- 若为描述这段内存需要把对应区域**分页进来**,取外部数据的调用会返回 **STATUS_PENDING**
|
||||
- 此时**必须从 packet 处理回调返回**;**稍后回调可能以不同的 IRQL(例如 DPC)再次被调用**,届时再调一次就会成功并返回数据
|
||||
- 于是 packet handler 的控制流实际是:
|
||||
|
||||
```
|
||||
callback #1 → 取数据 → STATUS_PENDING → return
|
||||
…稍后,可能在不同 IRQL…
|
||||
callback #2 → 再次取数据 → 成功 → 继续处理
|
||||
```
|
||||
|
||||
**普通的同步 CFG 很容易漏掉这条边。**
|
||||
|
||||
返回的 MDL 有两个易错点:
|
||||
|
||||
- **它描述的内存已被锁定**——不需要再 probe/lock
|
||||
- **它既没有用户态也没有内核态虚拟地址**——需要虚拟地址时必须显式映射(并在之后解除映射),或者直接把 MDL 交给做 DMA 的驱动
|
||||
- **MDL 在"完成"被调用时失效**,之后由内核库释放;**驱动不需要自己释放它**
|
||||
|
||||
需要把处理放到被动级别时,可用"延迟到被动级"的接口把 packet 交过去。
|
||||
|
||||
## 决策树
|
||||
|
||||
```
|
||||
拿到 Hyper-V 环境目标
|
||||
├─ 设备 I/O 找不到硬件 MMIO
|
||||
│ └─ 先按合成设备(VSC/VSP 经 VMBus)解释
|
||||
├─ trace 里出现小整数句柄
|
||||
│ └─ 可能是 GPADL(客户机缓冲区描述),不是地址
|
||||
├─ 网络流量中途"消失"但功能正常
|
||||
│ ├─ 查是否启用 SR-IOV:VF 数据面不过 VMBus
|
||||
│ └─ 查 VF 拆除/迁移路径(VPCI VSP → VMBus → VPCI VSC → NetVSC 解绑)
|
||||
├─ 通道拥塞 / 性能异常
|
||||
│ └─ 看是否使用了多通道(高速设备常有)
|
||||
└─ 共享内存用量相关
|
||||
└─ GPADL 总量存在上限(宿主侧限制),不是无限共享
|
||||
```
|
||||
|
||||
## 工具与验证
|
||||
|
||||
- 通道与设备:VMBus 总线视图、VSC/VSP 配对关系、合成设备类型
|
||||
- 缓冲区:GPADL 建立与使用点;对照"这是句柄不是地址"
|
||||
- 网络路径:VF 是否存在、控制面是否仍在 VMBus、数据面当前走 VF 还是合成路径
|
||||
- 验证:能同时说清「这条 I/O 走合成路径还是 VF 直通 + 共享内存经 GPADL 如何描述 + 控制面在谁那里」
|
||||
|
||||
## 该平台的坑(汇总)
|
||||
|
||||
- **把 GPADL 当内存地址**:它是缓冲区描述符句柄
|
||||
- **在合成设备上找硬件 MMIO**:VMBus 走共享 ring,不走寄存器窗口
|
||||
- **把 SR-IOV 切换当数据丢失或抓包失败**:数据面可以从 VMBus 切到 VF 直通(并可能切回)
|
||||
- **以为 VF 的一切都绕开 VMBus**:数据面绕开,**控制面仍走 VMBus**
|
||||
- **忽略多通道**:高速设备可能并行使用多条通道
|
||||
- **以为 GPADL 共享无上限**:宿主对共享总量有限制
|
||||
- **把删除 GPADL 时的阻塞当死锁**:它会等 server 解除映射;从 buffer 创建的 GPADL 还会锁定该 buffer 直到销毁
|
||||
- **把处理回调返回当 packet 结束**:完成调用才是释放边界,且可能被延后
|
||||
- **在 ring packet 里找大块 payload**:大块数据走外部内存描述
|
||||
- **把取外部数据返回"待决"当失败**:需要分页时就是它会返回的,之后会在可能不同的 IRQL 再次回调
|
||||
- **从外部数据 MDL 直接取虚拟地址**:它不带任何虚拟地址,需要显式映射并解除
|
||||
- **自己去释放外部数据 MDL**:它在完成调用时失效,由内核库释放
|
||||
@@ -0,0 +1,107 @@
|
||||
# Jailhouse:cell 配置 / park / loadable 内存 / ivshmem
|
||||
|
||||
<CORE RULE>
|
||||
Jailhouse 是**静态分区** hypervisor:**配置文件比 inmate 的 ELF 本身更重要**。
|
||||
|
||||
它没有"VM 运行时申请资源"这回事——`.cell` 配置直接规定 CPU 集合、内存区、IRQ 芯片、PIO、PCI 与权限。
|
||||
|
||||
所以分析一个 inmate:
|
||||
|
||||
```
|
||||
inmate 镜像 + .cell 配置 + root cell 配置 + 宿主 DT/ACPI
|
||||
```
|
||||
|
||||
**缺了配置那一半,关于"它为什么能/不能访问某个地址"的判断基本都是猜。**
|
||||
</CORE RULE>
|
||||
|
||||
## 一、资源归属在配置里,不在运行时协商
|
||||
|
||||
- cell 描述符包含:CPU set、内存区(含 `phys_start` / `virt_start` / `size` / 标志)、IRQ、PIO、PCI 归属与权限
|
||||
- 内存区标志是判据:**`READ` / `WRITE` / `EXECUTE` / `DMA` / `COMM_REGION` / `LOADABLE`**(注意 **`DMA` 标志缺失会让非 root Linux cell 不把这些区识别为 RAM**)
|
||||
- `jailhouse cell load` 会把镜像装进配置指定的内存区,**装载地址必须落在映射区内**(例如未设 `virt_start` 而镜像落在低内存之外,就会以 "Invalid argument" 失败——这是配置问题,不是镜像问题)
|
||||
|
||||
## 二、越权访问 → CPU 被 park(不是 guest 崩溃)
|
||||
|
||||
- 访问未映射给该 cell 的内存/外设时,hypervisor 记录 **"Unhandled data read" / "FATAL: unhandled trap"**,随后 **"Parking CPU n (Cell: ...)"**
|
||||
- **被 park 的 CPU/cell 等于停止运行**
|
||||
|
||||
所以看到"程序突然不动了"、又找不到 page fault/总线错误的常规证据,**先对照 `cell.mem_regions`、PCI 与 IRQ 归属**:**非法访问被 hypervisor 拦下并停机**是一等公民的解释。
|
||||
|
||||
## 三、`JAILHOUSE_MEM_LOADABLE`:装载窗口是一次性的
|
||||
|
||||
- 标记为 `LOADABLE` 的内存区**可以映射进 root cell,用于装载/重装镜像**
|
||||
- **cell 启动时,这些映射会被撤销**("对标记为 loadable 的区域的 root cell 访问被收回")
|
||||
- 需要重装时走 **Cell Set Loadable** 超话:**关停运行中的 cell,并把其 loadable 区域重新映射回 root cell**
|
||||
- 相关的错误码有区分度:非 root cell 发起或被目标 cell 拒绝关停 → `-EPERM`;cell 不存在 → `-ENOENT`;**root cell 不能被设为 loadable** → `-EINVAL`
|
||||
- 后来还加入了在 Cell Set Loadable 时**复位 PCI 设备**的行为(此前活跃的设备先静默、停止 DMA,避免与装载冲突)
|
||||
|
||||
**典型现象**:
|
||||
|
||||
```
|
||||
root Linux 启动前能直接访问 0xXXXX
|
||||
inmate 启动后再访问 → fault
|
||||
```
|
||||
|
||||
**这是设计如此**,不是"内存被保护起来了/被 hook 了"。
|
||||
|
||||
## 四、ivshmem:状态从共享内存协议里读,不要指望 pending 位
|
||||
|
||||
ivshmem v2 的寄存器语义与标准 PCI 设备**故意不同**:
|
||||
|
||||
- **Interrupt Control Register**:bit 0 使能中断;**若启用 one-shot 模式,设备在每次投递中断时把 bit 0 清零**——这是给 UIO 类驱动的自动节流,免去一次 VM-exit
|
||||
- **设备没有 pending interrupt 的概念**:**读 MSI-X 的 Pending Bit Array 永远返回 0**;INTx 方面 **Status Register 的 Interrupt Status 位从不置位**(与 PCI 规范有偏差),没有"pending INTx"信息
|
||||
- 规范的做法是:**事件状态从共享内存里的协议信息推导**
|
||||
- **Doorbell Register**:只写;低 16 位是向量号,高 16 位是目标 ID;写一个目标未使能的向量没有效果,写不存在的目标也没有效果;**读该寄存器未定义**
|
||||
- **State Register**:写入会更新本地状态,并在远端设备上触发 MSI-X 向量 0(或 INTx),前提是新值与旧值不同;对端通过比较本地副本区分"状态变化"与"doorbell 事件"
|
||||
- **State Table**:共享内存起始处的只读区,每个 peer 一个 32 位状态值;复位/断开时清零
|
||||
- 协议类型编码在 PCI Class Code 的 interface/sub-class 里(例如虚拟点对点以太网、用户定义区间、Virtio over shared memory)
|
||||
|
||||
**推论**:
|
||||
|
||||
- **"收到中断但 PBA 恒为 0" 不是 hypervisor bug**;**"没有 pending 位可查"也不能推导出"没有事件"**
|
||||
- 排查要以**共享内存里的协议状态**为准,必要时结合 State Table/State Register
|
||||
|
||||
## 五、hypervisor 地址空间里扫不到 inmate 内存是正常的
|
||||
|
||||
Jailhouse **不会把整个 cell 永久映射进 hypervisor 地址空间**:只有显式共享页、以及处理 MMIO 等场景下临时映射的页才可见,而且临时映射**可能是 per-CPU 的**。
|
||||
|
||||
所以"用 hypervisor VA 直接遍历所有 VM RAM"的取证/调试工具会漏掉大量内容——**这不是内存被藏了**。
|
||||
|
||||
## 决策树
|
||||
|
||||
```
|
||||
拿到 Jailhouse 目标
|
||||
├─ 先拿配置:.cell / root cell config / 宿主 DT·ACPI
|
||||
│ ├─ 内存区标志(含 DMA)与 virt_start 是否与镜像匹配
|
||||
│ └─ PCI / IRQ / PIO 归属
|
||||
├─ 访问某地址后程序停住
|
||||
│ ├─ 查 hypervisor 日志:Unhandled trap → Parking CPU n
|
||||
│ └─ 对照该地址是否属于本 cell(不是 → 违规,不是驱动 bug)
|
||||
├─ root cell 能访问 → cell 启动后不能
|
||||
│ └─ LOADABLE 映射在 cell 启动时被撤销;重装需 Cell Set Loadable
|
||||
├─ ivshmem 中断相关
|
||||
│ ├─ PBA 恒 0 / Status 位不置位 → 设备本就不维护 pending 语义
|
||||
│ ├─ 事件状态去共享内存协议里读(State Table / State Register)
|
||||
│ └─ one-shot 模式下 Interrupt Control bit0 会被设备清零
|
||||
└─ 在 hypervisor VA 里找不到某 cell 的内存
|
||||
└─ 只有共享页与临时映射可见,且临时映射可能是 per-CPU
|
||||
```
|
||||
|
||||
## 工具与验证
|
||||
|
||||
- 配置:cell 描述符(内存区与标志、PCI/IRQ/PIO 归属)、root cell 配置、宿主 DT/ACPI
|
||||
- 违规判定:hypervisor 控制台输出(Unhandled trap / Parking CPU)
|
||||
- ivshmem:寄存器区(Interrupt Control / Doorbell / State)、State Table、共享内存协议状态
|
||||
- 验证:能同时说清「资源归属(配置里怎么分的)+ 访问为何被拦(违规 vs 映射撤销)+ 事件状态从哪里读」
|
||||
|
||||
## 该平台的坑(汇总)
|
||||
|
||||
- **只看 inmate ELF 不看 cell 配置**:资源归属全在配置里
|
||||
- **把"访问后停机"当 guest 崩溃/驱动 bug**:越权访问会被 hypervisor park 掉 CPU
|
||||
- **把 cell 启动后 root cell 失去访问当异常**:`LOADABLE` 映射在启动时被撤销,重装走 Cell Set Loadable
|
||||
- **忘了内存区要标 `DMA`**:否则非 root cell 不把该区当 RAM,症状像"内存不见了"
|
||||
- **用 MSI-X PBA 判断有无事件**:ivshmem 不维护 pending 语义,PBA 恒 0
|
||||
- **把 Status Register 的 Interrupt Status 位当 pending 依据**:该位从不置位
|
||||
- **读 Doorbell 寄存器**:它只写,读未定义
|
||||
- **用 hypervisor VA 遍历所有 VM 内存**:只有共享页与临时映射可见
|
||||
- **把 one-shot 模式下 bit0 被清零当"中断被吞"**:那是设备端的自动节流设计
|
||||
@@ -0,0 +1,140 @@
|
||||
# LynxSecure:静态分离内核 / 该恢复什么 / 边界怎么找
|
||||
|
||||
<CORE RULE>
|
||||
这是一个**极端的静态分离**设计,而且**没有中央管理内核**:
|
||||
|
||||
```
|
||||
boot 时定义、不可变的硬件分区
|
||||
不可变的硬件强制隔离(从加电到断电持续保持)
|
||||
内核空间功能仅限:资源分区、控制分区之间的数据流、调解对系统状态变更函数的访问
|
||||
I/O 与应用支持【全部导出到用户态 guest】
|
||||
```
|
||||
|
||||
于是最反直觉的一条是:
|
||||
|
||||
```
|
||||
运行态找不到完整的"VM 配置器 / 动态 MMU 配置引擎"
|
||||
→ 与该模型自洽
|
||||
→ 不要得出"固件被裁剪 / 被破坏"
|
||||
```
|
||||
</CORE RULE>
|
||||
|
||||
**说明**:该系统多数实现细节不公开。下面区分两类内容——**【官方表述】**(可直接引用)与**【该恢复什么】**(方法论,用于指导分析,不是对实现的断言)。
|
||||
|
||||
## 一、官方表述(判断"缺什么算正常"的依据)
|
||||
|
||||
- **boot 时定义、不可变的硬件分区**;**不可变的硬件强制隔离**,从加电到断电持续保持
|
||||
- **最小特权**:内核空间功能**有限**——**资源分区、控制分区之间的数据流、调解对系统状态变更函数的访问**
|
||||
- **按开发者要求,把 I/O 与应用支持全部导出到用户态 guest**——**内核里没有驱动、I/O 栈与应用 API**
|
||||
- 运行期约束(官方列举):
|
||||
- **没有 hypervisor/管理控制台登录**
|
||||
- **没有动态系统修改**
|
||||
- 一个 guest 不能访问另一个 guest 的资源
|
||||
- **hypervisor 自身也不能访问 guest 的资源**
|
||||
- **可 DMA 设备不能访问 guest 资源**
|
||||
|
||||
(说明:公开资料**未直接证实**"用于初始配置的特权 setup code 随后被丢弃"这一具体机制;上面列的是官方明确表述的部分——判断"缺什么算正常"用它们即可。)
|
||||
|
||||
## 二、该恢复什么(逆向工作台层)
|
||||
|
||||
### 1. 配置是最重要的 artifact,优先于代码
|
||||
|
||||
资源在 **boot 时**被规定成固定、不可变的划分。所以第一优先级是**恢复"谁拥有什么"**:
|
||||
|
||||
```
|
||||
CPU 核归属 / 内存区归属 / I/O 与设备归属
|
||||
```
|
||||
|
||||
**做法**:配置本身(如果拿得到)是最高价值;拿不到就从**各 guest 可见的资源范围**反推——把每个 guest 能看到的物理范围并起来,**缺口就是别的分区**,重叠就是可疑点。
|
||||
|
||||
### 2. 启动阶段是唯一能看到"配置生效"的窗口
|
||||
|
||||
因为运行期**没有动态系统修改**,映射与归属都是**启动期建立**的:
|
||||
|
||||
```
|
||||
追映射、追资源分配 → 在启动阶段做
|
||||
运行期只应看到"已经确定"的状态
|
||||
```
|
||||
|
||||
**所以**:如果运行期看到"某资源在动态重新分配",先怀疑你看到的是别的东西(例如 guest 内部的分配),而不是内核在重配置。
|
||||
|
||||
### 3. 边界怎么找:从"三项职责"反查
|
||||
|
||||
官方把内核功能限定为三件事,**这三件事出现在哪里,哪里就是分界**:
|
||||
|
||||
| 职责 | 观察点 |
|
||||
|---|---|
|
||||
| **资源分区** | 谁在读/写"划分表"一类的结构 |
|
||||
| **控制分区之间的数据流** | 跨分区的数据通路在哪被拦截/校验 |
|
||||
| **调解对系统状态变更函数的访问** | 系统状态变更入口的守门代码 |
|
||||
|
||||
**推论**:**内核里不应有驱动、I/O 栈与应用 API**——所以
|
||||
|
||||
```
|
||||
在一个映像里看到完整驱动/I/O 栈
|
||||
→ 那一块大概率不是内核
|
||||
```
|
||||
|
||||
### 4. 跨分区通信在内存里长什么样
|
||||
|
||||
- 设计上是**无共享内存**的:硬件强制的隔离意味着**不存在"两个分区共享同一块物理内存"的常规通路**
|
||||
- 跨分区的数据流由内核**控制**——所以找的是**受控通道**(数据流被拦截/校验的路径),**不是共享页**
|
||||
- 这与"共享内存 + 通知"型系统(见 [[re-hypervisor/xen]]、[[re-hypervisor/hyperv-vmbus]])**方向相反**:先找控制点,再找数据通路
|
||||
|
||||
### 5. IOMMU / 设备归属怎么验证
|
||||
|
||||
官方约束可以直接当**验证判据**:可 DMA 设备不能访问 guest 资源、hypervisor 自身不能访问 guest 资源。
|
||||
|
||||
于是:
|
||||
|
||||
```
|
||||
拿一条 DMA 通路问:它的源与目标分别属于哪个分区?
|
||||
若答案是"跨了不该跨的边界" → 与模型矛盾,值得深挖
|
||||
若答案是"同一分区内" → 与模型自洽
|
||||
```
|
||||
|
||||
### 6. fault path 与"8 MB 里哪一段是 monitor"
|
||||
|
||||
- 最小内核意味着**异常入口很窄**:找"分区被 violated 之后控制权交给谁",那一段就是核心
|
||||
- 结合第 3 节的三项职责定位:**读划分表 / 拦数据流 / 守系统状态变更入口**的代码块属于内核;周围的驱动与 I/O 栈属于 guest
|
||||
|
||||
## 决策树
|
||||
|
||||
```
|
||||
拿到一份静态分离内核的映像
|
||||
├─ 先问:配置拿得到吗
|
||||
│ ├─ 拿得到 → 据它恢复 CPU/内存/I/O 归属,再解释代码
|
||||
│ └─ 拿不到 → 从各 guest 可见范围反推,缺口=别的分区
|
||||
├─ 找不到"VM 配置器/动态重配置"
|
||||
│ └─ 与"静态配置 + 无动态系统修改 + 无管理登录"自洽
|
||||
├─ 要追映射
|
||||
│ └─ 在启动阶段追;运行期不应有重配置
|
||||
├─ 要定位内核本体
|
||||
│ ├─ 找三项职责:读划分表 / 跨分区数据流拦截 / 系统状态变更守门
|
||||
│ └─ 内核里不该有驱动与 I/O 栈 → 有驱动的块大概率不是内核
|
||||
├─ 跨分区通信
|
||||
│ └─ 找受控通道,不是共享页(与共享内存型系统方向相反)
|
||||
├─ 验证隔离
|
||||
│ └─ 用官方约束当判据:DMA 设备 / hypervisor 自身都不应触及 guest 资源
|
||||
└─ 运行期看到"资源被重新分配"
|
||||
└─ 先怀疑那是 guest 内部行为,内核本不该做这件事
|
||||
```
|
||||
|
||||
## 工具与验证
|
||||
|
||||
- 配置:CPU/内存/设备归属的静态划分(配置本身或从 guest 可见范围反推)
|
||||
- 启动:启动阶段建立映射与归属的过程(唯一能看到"配置生效"的窗口)
|
||||
- 边界:三项职责的落点;驱动/I/O 栈的存在与否
|
||||
- 通信:受控通道而非共享页
|
||||
- 验证:能同时说清「谁拥有哪块资源 + 这段代码属于内核还是 guest + 这条数据流是否跨了分区」
|
||||
|
||||
## 该平台的坑(汇总)
|
||||
|
||||
- **把"缺少集中管理代码"当代码被裁剪**:静态配置模型本来就如此
|
||||
- **在运行期找"重配置"逻辑**:没有动态系统修改;该在启动阶段找
|
||||
- **把 guest 内部的资源分配当成内核在重配**
|
||||
- **在内核区域里期待驱动**:I/O 与应用支持被导出到 guest
|
||||
- **按"共享内存 + 通知"找跨分区通信**:这里是受控通道、无共享内存
|
||||
- **拿"hypervisor 能读 guest 内存"当默认假设做取证**:官方明确相反
|
||||
- **把可 DMA 设备的中介缺失当漏洞**:隔离模型要求它不能触及 guest 资源
|
||||
- **以为有管理控制台入口**:官方明确没有
|
||||
@@ -0,0 +1,118 @@
|
||||
# QNX Hypervisor:三层地址 / vdev / 虚拟中断
|
||||
|
||||
<CORE RULE>
|
||||
guest 里的"物理地址"根本不是宿主物理地址。地址至少三层:
|
||||
|
||||
```
|
||||
guest virtual → guest physical(ARM 术语 IPA)→ host physical
|
||||
```
|
||||
|
||||
而且 guest 看到的那台"设备"**可能是由配置人工制造出来的**:
|
||||
|
||||
```
|
||||
guest driver 的 MMIO 地址 = 0x1c090000
|
||||
≠ 真实 SoC 上那里就有该设备
|
||||
```
|
||||
|
||||
如果那是 **vdev**,这个地址完全由 VM 配置决定。**先判"这是直通、还是 vdev",再谈驱动写得对不对。**
|
||||
</CORE RULE>
|
||||
|
||||
## 一、地址模型
|
||||
|
||||
- guest RAM 由创建 VM 的进程(qvm)配置;对 guest 看起来是物理内存,**但仍需再翻译一层**
|
||||
- **直通设备**:guest 看到的设备物理地址与宿主物理地址**没有直接对应关系**(与共享内存不同)——虚拟化层做转换
|
||||
- **shmem vdev**:配置里那个"工厂页地址"(例如放在所分配 RAM 之外的某处)**明确是 guest physical,不是真实硬件地址**
|
||||
|
||||
**所以"guest 里的 MMIO 地址在真实 SoC 上找不到东西"不是错误**。
|
||||
|
||||
## 二、vdev:guest 驱动必须匹配被暴露出来的虚拟硬件
|
||||
|
||||
- hypervisor 配一个 PL011 vdev,guest 就**应当认为那里真的有一个 PL011**——匹配的对象是 vdev 暴露的硬件模型,不是板子上的原件
|
||||
- 推论:**guest 的 device tree ≠ 物理板级 device tree**,完全可能不同
|
||||
|
||||
## 三、shmem vdev:工厂页 + 控制页
|
||||
|
||||
共享内存 vdev 在 guest 侧表现为"一组映射在 guest-physical 里的寄存器 + 能触发中断",可以是 MMIO(直接在配置里给 `loc`/`intr`),也可以是 PCI(默认;位置与中断由 hypervisor 分配)。
|
||||
|
||||
**工厂页(4 KB)** 是它的虚拟寄存器页:
|
||||
|
||||
| 字段 | 作用 |
|
||||
|---|---|
|
||||
| `name` | 区域名 |
|
||||
| `size` | 大小——**写入即触发创建** |
|
||||
| `shmem` | 返回控制页的 guest-physical 地址 |
|
||||
| `vector` | 中断向量 |
|
||||
| `status` | 状态 |
|
||||
|
||||
- **创建是首次使用时完成的**,因此**各 guest 可以任意顺序启动**——"先启动的那个也正常"不需要额外解释
|
||||
- 每个共享区前面还有自己的**控制页**:`status`(活跃客户端 / notify 位)、`idx`(连接 ID)、`notify`(写位集以通知其他 guest)、`detach`
|
||||
- guest 之间就是**通过写控制页**通信的
|
||||
|
||||
## 四、虚拟中断:`intr pass` 与 `intr vdev` 是两条不同的路
|
||||
|
||||
VM 配置里的 `intr` 关键字决定中断来源:
|
||||
|
||||
| 形式 | 含义 |
|
||||
|---|---|
|
||||
| `pass` | 由**宿主**把物理设备的中断路由给 guest(可给 guest 中断号,或引用一张把宿主向量映射到 guest 中断号的宿主 PIC) |
|
||||
| `vdev` | 中断由**运行在宿主侧的 vdev 代码**产生(引用 guest vdev 向其发中断的宿主 PIC,或直接指定 guest 中断控制器与线号) |
|
||||
|
||||
- **x86 上 LAPIC 自动提供**(vdev 只写 `intr apic`,无需线号),例如 `vdev ioapic` + `intr apic`、`vdev ser8250` + `intr myioapic:4`
|
||||
- **ARM 上 GIC 自动提供**(也可显式指定)
|
||||
- shmem 的通知走 `notify` 位集:接收方 guest 的 IRQ 处理程序去读 `status`(低位表示谁通知了它,高位表示活跃连接)
|
||||
|
||||
**所以"driver 的 ISR 很短,但中断延迟明显变大"不一定是 driver 慢**:路径可能是
|
||||
|
||||
```
|
||||
物理设备 → hypervisor(中断归属/路由) → 虚拟中断 → guest ISR
|
||||
```
|
||||
|
||||
做延迟分析时必须把虚拟化层算进去。
|
||||
|
||||
## 五、guest exit 何时发生(决定了你能看到什么 trace)
|
||||
|
||||
Halt、**vdev 访问**(例如对 shmem 工厂页 size 寄存器的 MMIO 写会被 trap,用于创建/接入区域)、宿主侧中断、虚拟定时器、CPUID 类指令。
|
||||
|
||||
## 六、直通设备:一次只能有一个 resident 访问
|
||||
|
||||
- 直通时 **hypervisor 不需要设备驱动**:它只负责识别并放行物理设备到 guest 的中断、把 guest 的信号直接交给设备,其余交互都是 guest 对设备
|
||||
- **同一时刻只允许一个 resident(host 或 guest)访问某个直通设备**——"host 与 guest 同时操作同一设备"不是可选项,是错误状态
|
||||
|
||||
## 决策树
|
||||
|
||||
```
|
||||
拿到 QNX Hypervisor 目标
|
||||
├─ 先判设备形态
|
||||
│ ├─ 直通(pass)→ GPA 与 HPA 无直接对应;一次只能一个 resident 访问
|
||||
│ └─ vdev → 地址/寄存器布局由 VM 配置制造,guest DT ≠ 板级 DT
|
||||
├─ 地址对不上真实硬件
|
||||
│ └─ 正常:至少三层地址;shmem 工厂页地址明确是 guest physical
|
||||
├─ 共享内存相关
|
||||
│ ├─ 找工厂页(name/size/shmem/vector/status),写 size 才触发创建
|
||||
│ ├─ 首次使用时创建 → 各 guest 启动顺序无所谓
|
||||
│ └─ 数据面写控制页(status / idx / notify / detach)
|
||||
├─ 中断相关
|
||||
│ ├─ 分清 intr pass(宿主路由)与 intr vdev(宿主侧 vdev 产生)
|
||||
│ ├─ 延迟变大 → 把 hypervisor 的中断归属/路由算进路径
|
||||
│ └─ shmem 通知 → 读 status 判定谁通知、谁活跃
|
||||
└─ trace 里 VM-exit 很多
|
||||
└─ 检查是否有 vdev 寄存器写(如工厂页 size)被 trap
|
||||
```
|
||||
|
||||
## 工具与验证
|
||||
|
||||
- 配置:VM 配置里的 RAM 布局、`loc`/`intr` 关键字、vdev 类型(MMIO 还是 PCI)
|
||||
- 共享内存:工厂页字段与控制页字段(`status` / `idx` / `notify` / `detach`)
|
||||
- 中断:区分 `pass` 与 `vdev` 两条来源,再看 guest 侧中断控制器(x86 LAPIC / ARM GIC 自动提供)
|
||||
- 验证:能同时说清「地址处于哪一层 + 设备是直通还是 vdev + 通知与中断来自哪条路径」
|
||||
|
||||
## 该平台的坑(汇总)
|
||||
|
||||
- **拿 guest 的 MMIO 地址去真实 SoC 上找设备**:三层地址;vdev 的地址完全由配置决定
|
||||
- **把 guest device tree 当板级 device tree**:vdev 暴露的是虚拟硬件模型
|
||||
- **以为共享区一定要先由某个 guest 创建**:工厂页写 size 时按需创建,启动顺序无关
|
||||
- **把 shmem 当普通 PCI 设备**:它可以是 MMIO 也可以是 PCI,寄存器语义由工厂页/控制页定义
|
||||
- **分不清 `intr pass` 与 `intr vdev`**:一个由宿主路由物理中断,一个由宿主侧 vdev 代码产生
|
||||
- **把延迟归因于 driver**:先看中断是否经过 hypervisor 的归属与路由
|
||||
- **以为 host 与 guest 能同时用同一台直通设备**:同一时刻只允许一个 resident 访问
|
||||
- **把 vdev 寄存器写引发的 VM-exit 当异常**:那是 vdev 的正常工作机制(例如创建共享区)
|
||||
@@ -0,0 +1,126 @@
|
||||
# Quest-V:多内核 sandbox / 每 sandbox 一个 monitor / 故障与在线恢复
|
||||
|
||||
<CORE RULE>
|
||||
不是"一个 hypervisor 管所有 VM",而是**多个 sandbox kernel,每个配一个自己的 monitor**:
|
||||
|
||||
```
|
||||
sandbox domain = 一个或多个 CPU + 一块主机物理内存
|
||||
(内含【本地 monitor】+ sandbox kernel + 本地应用)
|
||||
|
||||
monitor 只为【一个】sandbox 维护 EPT 映射
|
||||
→ 省掉传统 hypervisor 调度/切换 guest 地址空间的开销
|
||||
```
|
||||
|
||||
于是两条容易误判成"缺失"的现象其实是设计:
|
||||
|
||||
```
|
||||
没有大量 VM-exit → sandbox 自己管 I/O
|
||||
没有中央调度器/全局时钟 → 每 sandbox 自己调度、自己的物理时钟
|
||||
```
|
||||
</CORE RULE>
|
||||
|
||||
## 一、monitor 什么时候才被调用
|
||||
|
||||
**只在四件事上介入**:
|
||||
|
||||
```
|
||||
① 引导各内核(bootstrapping)
|
||||
② 处理故障(fault)
|
||||
③ 管理与发起影子页表(shadow page tables)
|
||||
④ 建立 sandbox 间通信通道
|
||||
```
|
||||
|
||||
其余时间 **sandbox 直接在硬件上运行**。
|
||||
|
||||
- monitor 可以按**每核**建立,也可以为一个**核的集群**建立("域/单元")——**不是天然一核一 monitor**
|
||||
- monitor 跟踪**影子页表映射**,这些映射表达**不可变的内存访问能力**,所以 **sandbox 代码无法修改"管辖主机内存访问的 EPT 映射"**
|
||||
|
||||
## 二、EPT 用于内存隔离,不是 CPU 虚拟化
|
||||
|
||||
- 用 EPT/嵌套页表主要是为了**内存隔离**(额外一层逻辑保护环),**指令直接在硬件执行**
|
||||
- **sandbox kernel 的代码段实际是只读的**——因为 sandbox 代码**没有访问自己 EPT 映射的途径**
|
||||
- 每个 sandbox 做**自己的本地调度与设备管理**
|
||||
|
||||
## 三、中断与 I/O:直接交给 sandbox
|
||||
|
||||
- **中断被定向到 sandbox kernel**(I/O APIC 广播或 IPI),并在驱动里做**早期解复用**——避免 monitor 介入与复杂的 I/O 虚拟化
|
||||
- **部分驱动数据结构跨 sandbox 共享**,另一些是复制或同步的
|
||||
|
||||
## 四、sandbox 间通信
|
||||
|
||||
- **显式消息传递 + 共享内存通道**,传**缓存行大小**的消息
|
||||
- 一个 sandbox 里的服务可以访问另一个 sandbox 里的服务
|
||||
- **IPI 用于 sandbox 间通信**;APIC 机制**也用于重路由中断以做远程故障恢复**
|
||||
|
||||
## 五、故障路径(定位 monitor 的第一线索)
|
||||
|
||||
- 故障被检出时会产生 **EPT violation → 控制权转移(VM-exit)到对应的 monitor**
|
||||
- **若某故障不会自动触发 VM-exit,故障处理器可以强制触发它**(例如用某条必然陷入的指令)
|
||||
- sandbox kernel **只读段**里的故障检测代码被**假定安全**
|
||||
- **抢占超时可周期性强制陷阱到 monitor**——这样**检测逻辑就位于 monitor 内**,与被篡改的 sandbox 隔离
|
||||
|
||||
## 六、恢复:本地与远程,都是在线的
|
||||
|
||||
- **本地恢复**:monitor 释放故障组件的内存、**可能重新初始化整个 sandbox**、或用**替代实现**替换组件(功能/实现多样性),并**通过调整该 sandbox 的 EPT 映射**来激活替代实现
|
||||
- **远程恢复**:本地 monitor 选一个目标 sandbox(随机、轮转、或按负载),**经 IPI 通知**;远端 monitor 执行恢复,**经消息传递从共享内存取状态**
|
||||
- 恢复**在线完成,不重启整机**;未受影响的 sandbox 继续正常运行
|
||||
|
||||
## 七、实操:手里这份映像,哪一段是 monitor
|
||||
|
||||
几个可用的判据(按可靠性排序):
|
||||
|
||||
1. **谁持有并修改 EPT 映射/影子页表**——这是 monitor 独有的能力(sandbox 代码无法访问自己的 EPT 映射)
|
||||
2. **故障入口**:VM-exit 的目标、以及"强制触发陷入"的那条路径
|
||||
3. **抢占超时的定时逻辑**:它被刻意放在 monitor 内与被篡改的 sandbox 隔离
|
||||
4. **引导/通道建立代码**:只在启动与建通道时出现
|
||||
|
||||
反之,**大量设备驱动与本地调度逻辑属于 sandbox kernel,不是 monitor**。
|
||||
|
||||
## 八、时间与迁移
|
||||
|
||||
- **没有全局时钟**:每个 sandbox 有自己的物理时钟,事件计时由**每核本地定时器**管理,**不保证同步**
|
||||
- 既没有全局调度器也没有全局时钟 → **跨 sandbox 迁移与通信的时序分析必须把时钟偏差算进去**(相关研究专门推导过含时钟偏差的通信界限)
|
||||
- 跨 sandbox 的时间戳**不能按单一时间线比较**
|
||||
|
||||
## 决策树
|
||||
|
||||
```
|
||||
拿到多内核 sandbox 系统的映像
|
||||
├─ 找不到中央 hypervisor
|
||||
│ └─ 正常:每 sandbox 一个 monitor
|
||||
├─ VM-exit 很少
|
||||
│ └─ 正常:sandbox 自己调度与管 I/O,中断直接投递
|
||||
├─ 要定位 monitor
|
||||
│ ├─ 谁持有/修改 EPT 与影子页表
|
||||
│ ├─ VM-exit 目标与"强制触发陷入"路径
|
||||
│ ├─ 抢占超时的定时逻辑
|
||||
│ └─ 引导/建通道代码
|
||||
├─ 故障与恢复
|
||||
│ ├─ EPT violation → VM-exit 到对应 monitor
|
||||
│ ├─ 本地恢复可重初始化整个 sandbox 或换实现(改 EPT 激活)
|
||||
│ └─ 远程恢复经 IPI + 共享内存传状态,全程在线
|
||||
├─ 跨 sandbox 通信
|
||||
│ └─ 显式消息 + 共享内存通道(缓存行大小消息)+ IPI
|
||||
└─ 时间线对不上
|
||||
└─ 无全局时钟:各 sandbox 时钟不保证同步,别按单一时间线断言
|
||||
```
|
||||
|
||||
## 工具与验证
|
||||
|
||||
- 资源:sandbox domain 到 CPU/内存/设备的映射
|
||||
- monitor:EPT/影子页表、故障入口、抢占超时、引导与通道建立代码
|
||||
- 通信:共享内存通道的消息流与 IPI
|
||||
- 时间:各 sandbox 的本地时钟与偏差
|
||||
- 验证:能同时说清「这段代码属于 monitor 还是 sandbox kernel + 该故障经哪条路径到 monitor + 这条跨域数据的时序基准是谁的时钟」
|
||||
|
||||
## 该平台的坑(汇总)
|
||||
|
||||
- **把"没有中央 hypervisor"当系统缺失**:每 sandbox 一个 monitor 是设计
|
||||
- **把"VM-exit 很少"当没有虚拟化隔离**:指令直接在硬件跑是设计
|
||||
- **以为 monitor 参与每次 I/O**:它只在引导、故障、影子页表、建通道时介入
|
||||
- **把设备驱动当 monitor 的一部分**:驱动与本地调度属于 sandbox kernel
|
||||
- **以为能改 EPT 映射**:sandbox 代码无法访问自己的 EPT 映射
|
||||
- **把不同 sandbox 的时间戳按单一时间线比较**:没有全局时钟
|
||||
- **以为恢复一定重启整机**:本地/远程恢复都在线进行
|
||||
- **把"替代实现替换组件"当异常**:那是设计内的恢复手段
|
||||
- **假设一核一 monitor**:monitor 可以按核集群建立
|
||||
@@ -0,0 +1,213 @@
|
||||
# Xen:PV 前后端 / grant table / event channel / XenStore
|
||||
|
||||
<CORE RULE>
|
||||
**不要把 Xen 的 PV 设备当普通 PCI/MMIO 驱动。**
|
||||
|
||||
典型 PV I/O 没有寄存器窗口,只有四件套:
|
||||
|
||||
```
|
||||
frontend
|
||||
├─ XenStore :交换配置(含 ring-ref / event-channel)
|
||||
├─ grant table :共享 page
|
||||
├─ shared ring :请求/响应
|
||||
└─ event channel :通知
|
||||
│
|
||||
backend
|
||||
```
|
||||
|
||||
由此产生两类高频误判:**把 grant ref 当地址**、**把 event port 当 IRQ 号**。两者都只是**索引/句柄**,不是地址空间里的东西。
|
||||
</CORE RULE>
|
||||
|
||||
## 一、XenStore:配置交换处,值以文本存放
|
||||
|
||||
- 前端在 XenStore 里记录后端域(`backend-id`,通常为 0),并把**共享 ring 的 grant 引用**写进去:`ring-ref`、`tx-ring-ref`、`rx-ring-ref`、`ctrl-ring-ref` 等
|
||||
- 通知端口同样写进 XenStore:`event-channel`(网络控制环用 `event-channel-ctrl`)
|
||||
- **值是字符串**(XenStore 是文本式的)——"ring-ref=42" 存的是文本 `"42"`,不是二进制字段
|
||||
- **前端把内存共享给后端**(不是反过来):前端用类似 `xenbus_grant_ring()` 的方式把本地分配的 ring 共享出去,再把引用告诉后端;后端拿到引用后在自己内核地址空间里分配页并映射
|
||||
|
||||
**RE 含义**:拿到一个 PV 前端,**先读它在 XenStore 里写了什么**——那里有后端域、ring 引用、事件端口,是恢复拓扑的现成入口。
|
||||
|
||||
## 二、grant reference 是**条目索引**,不是物理地址
|
||||
|
||||
```
|
||||
ring-ref = 42
|
||||
≠ page 0x2a / 物理地址 0x2a000
|
||||
|
||||
ring-ref = 42
|
||||
→ 前端 grant table 的第 42 号条目
|
||||
→ { flags, domid, frame(MFN) }
|
||||
→ 后端据条目去映射那一页
|
||||
```
|
||||
|
||||
- 每个域有自己的 grant table(一块与 hypervisor 共享的内存,由 guest 分配/初始化后经 hypercall 交给 Xen)
|
||||
- 条目三个字段:**`flags`(操作与权限)、`domid`(被授权的远端域)、`frame`(被授权页的 MFN,注意是 MFN 而非虚拟地址)**
|
||||
- **grant reference 就是条目下标**;下标被传给远端域,远端用它"激活"该条目
|
||||
- 引用可以内嵌在 ring 消息里传递(如块设备的 `blkif_request_segment.gref`、网络的 `netif_tx_request.gref`)——**在 ring 里看到一个小整数,很可能是 gref,不是偏移或长度**
|
||||
|
||||
**权限与状态位(决定"能不能撤销")**:
|
||||
|
||||
| 名称 | 含义 |
|
||||
|---|---|
|
||||
| `GTF_permit_access` | 允许远端域访问本域某页 |
|
||||
| `GTF_accept_transfer` | 接受把一页转移进本域 |
|
||||
| `GTF_readonly` | 只读导出/导入 |
|
||||
| `GTF_reading` / `GTF_writing` | hypervisor 置位,表示该页**当前正被远端映射**(读/写) |
|
||||
| `GTF_transfer_committed` | 转移已完成 |
|
||||
|
||||
- **映射期间 Xen 不支持撤销**:结束外来访问的调用**只是阻止后续映射**,不会撤销已建立的映射;发送方必须**等 reading/writing 标志清零**(用查询接口确认)之后才能真正回收
|
||||
- 两种用法要分清:**保留所有权的 grant access**(块前端/后端:A 授权、B 映射、B 用完 unmap、A 再撤权)与**所有权的 transfer**(只适用于 PV;较老的网络前后端用它,新设计改为经 hypervisor 拷贝以避开 TLB 开销)
|
||||
|
||||
## 三、event-channel port 不是 IRQ number
|
||||
|
||||
```
|
||||
event-channel = 17 ≠ hardware IRQ 17
|
||||
```
|
||||
|
||||
- event channel 是 Xen 的通知原语(`event_channel_op` hypercall),类型包括 **VCPU/IPI、VIRQ、PIRQ、域间(interdomain)**
|
||||
- 域间通道常用 `alloc_unbound` / `bind_interdomain` 建立,端口号通常经 XenStore 传给对端
|
||||
- **端口整数绑定到每 guest `shared_info` 里的全局位掩码**(32 位 guest 为 1024 位,64 位 guest 为 4096 位)——这是"端口"在这个系统里的真实表示
|
||||
- 用户态接口另有 `/dev/xen/evtchn`
|
||||
|
||||
**所以 trace 里出现 `port 17` 时,不要去 IOAPIC/GIC 里找 IRQ 17。**
|
||||
|
||||
## 四、迁移:**本地端口不稳定,远端端口才是稳定标识**
|
||||
|
||||
- 域间 event channel 本质是**一对端口**:本地端口 + 远端端口
|
||||
- 持有者退出会关闭 `/dev/xen/evtchn`,内核随之关闭本地端口;**重新连接时是用原来的远端端口去绑定一个全新的本地端口**,新本地端口号**可能与旧值不同**
|
||||
- 因此**要记录/对齐的是 remote port**;把本地端口号当作持久 object identity 会出错
|
||||
- 更危险的一点:**本地端口号在解绑后可被重用**,残留引用会指向完全不同的通道
|
||||
|
||||
**这与 seL4 的 CPtr、Zircon 的 handle 是同一类坑**(见 [[re-kernel/sel4-kernel]]、[[re-kernel/zircon-kernel]]):**整数句柄不是全局标识**。
|
||||
|
||||
## 五、shared ring 不是普通共享内存
|
||||
|
||||
典型流程:
|
||||
|
||||
```
|
||||
前端拥有页 → grant 给后端 → gref 写入 XenStore
|
||||
→ 双方各自映射同一批页 → event channel 只负责 kick
|
||||
```
|
||||
|
||||
所以看到 **ring 内存持续变化、却找不到 `memcpy`/socket IPC** 是正常的——这是共享页 + 生产者/消费者索引的模型,通知只是"踢一下"。每个 ring 配一个 event channel:环变非空时唤醒接收方,变非满时唤醒发送方。
|
||||
|
||||
## 六、XenStore 是控制面,不是数据面
|
||||
|
||||
- **`state = Connected` 只表示协议协商完成**,不代表数据经 XenStore 传
|
||||
- 真正的数据面可以是 **grant 共享 ring + event channel**:
|
||||
|
||||
```
|
||||
XenStore 几乎没流量,设备 I/O 却很大
|
||||
→ 正常
|
||||
```
|
||||
|
||||
- **backend 不一定在 Dom0**:不要硬编码 `/local/domain/0/...`;前端里提供 **`backend` 与 `backend-id`** 用于明确定位 service domain——**正常 backend 区域不应当靠假设推导**
|
||||
|
||||
## 七、通知抑制:ring 前进却没有 kick 是正常的
|
||||
|
||||
- 生产侧的推送宏会**判断对端是否真的需要通知**:**只有新推进的请求越过了对端设置的 `req_event` 阈值时才发通知**(响应侧对称,用 `rsp_event`)
|
||||
- 所以:
|
||||
|
||||
```
|
||||
ring producer 前进 + 没有 event 发送
|
||||
→ 完全正常
|
||||
→ 对端已有工作在飞,它之后会自己再查 ring
|
||||
```
|
||||
|
||||
- 顺序要求:写请求 → 写屏障(对端先看到请求)→ 更新 producer 索引 → 全屏障(对端先看到新请求)→ 再判断 `req_event`
|
||||
|
||||
## 八、"睡前再检查一次"是协议核心,不是奇怪 busy-loop
|
||||
|
||||
消费侧在准备睡眠时的标准形态:
|
||||
|
||||
```
|
||||
发现无工作
|
||||
→ 设置"下一个事件通知我"(req_event = req_cons + 1)
|
||||
→ 全屏障
|
||||
→ 再查一次 ring
|
||||
```
|
||||
|
||||
- 这个屏障覆盖的竞态是:**producer 已经读过 `req_event`(因此决定不通知),而 consumer 随后才更新它**——不重新检查就会**丢唤醒**
|
||||
- 已知副作用:按原样使用会带来"每 2^32 次事件一次多余唤醒"(索引回绕),实现里常见把事件指针推到过去的修法
|
||||
- 另一个副作用:该宏**会推进 ring 的事件指针**,在 NAPI 类场景里可能"提前招来下一次中断",所以有些路径只在真正切回中断模式时才用它
|
||||
|
||||
**所以不要把"双重检查"优化掉,也别把相关的强屏障当冗余。**
|
||||
|
||||
## 九、event channel 的掩码/待决语义:不是"使能位置 1"
|
||||
|
||||
- event channel 是**"一位事件状态"**模型;**unmask 不是简单地打开使能位**
|
||||
- FIFO ABI 的 unmask 路径:清除 MASKED(连带 BUSY),**若 PENDING 置位则发起 unmask 调用**;该调用的语义要求:**若事件处于 pending,必须向对应 VCPU 投递通知**
|
||||
- 因此"masked 期间事件到达、unmask 之后行为诡异"应从 **pending / masked / FIFO 语义**解释,而不是按普通中断使能理解
|
||||
|
||||
## 十、FIFO event ABI:优先级,以及两种 ABI 不能混
|
||||
|
||||
- **优先级 0 最高、15 最低**,默认 7;**每 VCPU 有 16 个事件队列**
|
||||
- 每个事件是带标志的字:**PENDING / MASKED / LINKED / BUSY** 加上指向队列下一项的 LINK 字段;每 VCPU 控制块含 ready 字与 16 个队头
|
||||
- 含义:**不同 event 的投递延迟可能来自 FIFO 优先级,而不是 vCPU 调度**
|
||||
- **同一个 Xen build 里,事件记账布局可能完全不同**——存在 **2-level ABI 与 FIFO ABI**:
|
||||
|
||||
```
|
||||
拿 shared_info 的 pending 位图解析器去解释 FIFO 控制块 → 全错
|
||||
```
|
||||
|
||||
- 一个具体陷阱:**初始化 FIFO 控制块的操作不会重新投递当前 pending 的事件(它们会丢)**,因此应当在绑定任何事件之前完成
|
||||
|
||||
## 十一、迁移后的身份变化
|
||||
|
||||
迁移可能要求前后端**重新协商协议**,因为 **domid 与 event channel 状态不一定保留**:
|
||||
|
||||
```
|
||||
迁移前:domid 7 / port 10
|
||||
迁移后:domid 12 / 新连接
|
||||
```
|
||||
|
||||
**不应当判设备劫持。(与 [[re-kernel/zircon-kernel]]、[[re-kernel/sel4-kernel]] 的句柄本地性同源。)**
|
||||
|
||||
## 十二、内存屏障是协议的一部分
|
||||
|
||||
ring 生产者索引更新的前后都有屏障,保证请求/响应数据**先对另一端可见**。**这些屏障不是编译器噪声,删除即破坏协议。**
|
||||
|
||||
## 决策树
|
||||
|
||||
```
|
||||
拿到 Xen 目标
|
||||
├─ 先判:PV 前端 / 后端 / 全虚拟化(HVM)设备?
|
||||
│ ├─ PV → 找四件套:XenStore 条目 + grant ref + shared ring + event channel
|
||||
│ └─ 找不到寄存器窗口/MMIO 循环 → 正常,PV 本来就没有
|
||||
├─ 看到 `ring-ref = N`
|
||||
│ └─ N 是 grant table 条目索引 → 去读条目 {flags, domid, frame},不是地址
|
||||
├─ 看到 `event-channel = N`
|
||||
│ └─ N 是 event channel 端口(绑定到 shared_info 位掩码),不是 IRQ 号
|
||||
├─ 页被远端访问 / 想回收页
|
||||
│ ├─ 有 GTF_reading / GTF_writing → 映射仍在,撤销未生效
|
||||
│ └─ 结束外来访问只阻止后续映射,必须等标志清零
|
||||
└─ 迁移 / 重连后端口号变了
|
||||
└─ 正常:本地端口会重新分配,稳定的标识是 remote port
|
||||
```
|
||||
|
||||
## 工具与验证
|
||||
|
||||
- 配置来源:XenStore 树(`~/device/...` 与 `~/backend/TYPE/DOMID/DEVID`)里的 ring 引用与事件端口;前端树里的 `backend-id`
|
||||
- 内存路径:grant table 条目(flags / domid / frame)与 reading/writing 状态位
|
||||
- 通知路径:event channel 类型(IPI / VIRQ / PIRQ / 域间)与端口号;用户态经 `/dev/xen/evtchn`
|
||||
- 验证:能同时说清「拓扑(谁在前、谁在后、经哪个 XenStore 路径连接)+ 内存靠哪组 gref + 通知靠哪个端口」
|
||||
|
||||
## 该平台的坑(汇总)
|
||||
|
||||
- **把 grant ref 当物理地址/页码**:它只是本域 grant table 的条目下标,条目里才有 MFN
|
||||
- **把 event-channel port 当 IRQ 号**:它是 Xen 的通知端口,绑定在 `shared_info` 的位掩码上
|
||||
- **以为结束外来访问就能立刻回收页**:映射期间不支持撤销,必须等 `reading`/`writing` 清零
|
||||
- **把 event port 整数当持久对象标识**:迁移/重连后本地端口会变;解绑后同一号码还会被重用
|
||||
- **拿本地端口(而非远端端口)做对齐**:重连时稳定的是 remote port
|
||||
- **在 ring 消息里把小整数当偏移或长度**:很可能是内嵌的 gref
|
||||
- **看到 ring 内存变化却无 IPC 调用就认为分析有漏**:PV 的数据面本来就走共享页,通知只负责 kick
|
||||
- **在前端/后端之间搞反共享方向**:是前端把 ring 共享给后端,再由后端映射
|
||||
- **在 XenStore 里期待二进制字段**:它是文本式的,引用与端口都以字符串存放
|
||||
- **把 XenStore 的 `Connected` 当数据面在传**:它只表示协商完成;数据在 grant 共享 ring 上
|
||||
- **硬编码 backend 在 Dom0**:应读前端的 `backend` / `backend-id`
|
||||
- **把"ring 前进却没通知"当丢事件**:通知抑制是协议设计
|
||||
- **删掉消费侧的双重检查或强屏障**:那正是防丢唤醒的部分
|
||||
- **按"中断使能位"理解 event unmask**:masked/pending 语义不同,pending 必须投递
|
||||
- **用一套事件解析器解释两种 ABI**:2-level 与 FIFO 的记账布局不同
|
||||
- **在绑定事件之后才初始化 FIFO 控制块**:当前 pending 的事件会丢
|
||||
- **把 FIFO 优先级造成的延迟归因于 vCPU 调度**
|
||||
- **把迁移后的 domid/端口变化判为劫持**:需要重新协商属正常
|
||||
@@ -0,0 +1,121 @@
|
||||
# XtratuM:XM_CF 配置驱动 / 调度计划 / IPVI
|
||||
|
||||
<CORE RULE>
|
||||
XtratuM 是**配置驱动**的分离 hypervisor:资源不是运行时协商的,而是由 **XM_CF 配置文件**静态规定:
|
||||
|
||||
```
|
||||
分区 / 内存 / 中断 / I/O 端口 / 通信端口 / 调度
|
||||
```
|
||||
|
||||
**运行期不能新增动态对象。** 所以看到"找不到运行时资源分配代码",先怀疑**你手上没拿到配置文件**,而不是代码被裁剪。
|
||||
</CORE RULE>
|
||||
|
||||
## 一、XM_CF:系统集成者与分区开发者的契约
|
||||
|
||||
配置是 XML,经校验后**翻译成二进制形式**(配置表)并集成进最终系统镜像。
|
||||
|
||||
主要内容:
|
||||
|
||||
| 章节 | 内容 |
|
||||
|---|---|
|
||||
| **内存** | 板级内存布局、给 hypervisor 与各分区的内存;分区内存区可带标志(未映射 / 映射到某处 / 共享 / 只读 / 不可缓存 / IOMMU) |
|
||||
| **处理器** | CPU 如何分配给分区,含**调度计划** |
|
||||
| **外设** | 不由 XtratuM 管理的外设、I/O 端口范围、每个分区的**中断线**与设备标识 |
|
||||
| **健康监控** | 检出错误后如何处理(直接动作 / 交给出错分区 / 记日志 / 复位…) |
|
||||
| **分区间通信** | 端口与通道(**采样**与**队列**两类),连接源端口与目的端口 |
|
||||
| **跟踪** | 跟踪消息存放位置与跟踪范围 |
|
||||
|
||||
配置解析器会做语法与语义双重校验:内存区重叠/包含关系、分区名与 ID 重复、CPU 分配、端口重复、**循环调度计划的一致性**(分区是否都被分配、时间槽是否重叠、槽的分区 ID 是否有效)、硬件中断分配、I/O 端口对齐与占用、通道分配等。
|
||||
|
||||
## 二、两种调度:循环与优先级
|
||||
|
||||
**循环调度(ARINC 653 风格)**:`(分区, 虚拟 CPU)` 对被**固定循环**调度;时间槽在配置里以**起始时间 + 时长**定义;**major frame(MAF)**周期性重复,通常取各分区/线程周期的最小公倍数。
|
||||
|
||||
**优先级调度**:按配置里给分区的优先级调度(**优先级 0 最高**),抢占式;配成固定优先级的 CPU **不受其他 CPU 循环计划影响**。
|
||||
|
||||
**多个调度计划**(关键细节):
|
||||
|
||||
- **Plan 0 保留给初始化**:系统从 Plan 0 启动,直到某系统分区请求切换
|
||||
- **Plan 1 保留作维护计划**:健康监控检出处境时可立即切到它,或由系统分区请求
|
||||
- 设计者可定义 **Plan X(X>1)**
|
||||
- **计划切换发生在当前计划的剩余槽全部执行完之后**
|
||||
- 多核下:只有**循环计划**与多计划关联;优先级 CPU 独立于计划;涉及多个处理器的计划**必须定义相同的 MAF**
|
||||
|
||||
调度可视为状态机:每个状态对应某分区按计划执行;**`next_slot` 定时器中断导致上下文切换**;槽时长定时器由 XtratuM 独占使用。
|
||||
|
||||
## 三、中断与分区间虚拟中断(IPVI)
|
||||
|
||||
- 配置规定分区可访问哪个设备、设备标识与该分区使用的中断线;XtratuM 负责映射设备所在的内存区
|
||||
- **IPVI 模拟真实的处理器间中断**:任何分区都可通过 hypercall 发出,向目的分区投递一个虚拟中断;**每个分区最多 8 个 IPVI**,实现为最后八个扩展虚拟中断
|
||||
- 分区事件通常由分区自己处理;hypervisor 按配置中的动作提供通知机制;向端口写入/发送新消息时也可触发扩展中断(XtratuM 在分区控制表里维护端口状态位图)
|
||||
|
||||
## 四、运行期权限
|
||||
|
||||
**只有系统分区**才被允许控制系统与其他分区的状态、以及查询它们的状态。看到"某分区想改全局状态却做不到",先看它是不是系统分区。
|
||||
|
||||
## 五、计划切换的**两种时机语义**
|
||||
|
||||
- **Plan 0 是复位后的初始计划**,而且**不能像普通用户计划那样再切回来**;Plan 1 是维护计划;用户定义的计划通常从 >1 开始
|
||||
- **普通请求的切换不是立即的**:系统分区请求切换时,**当前计划的槽全部跑完后**(major frame 结束)才切到新计划
|
||||
|
||||
```
|
||||
请求切换之后旧计划还跑了一段时间 → 完全正常
|
||||
```
|
||||
|
||||
- **但健康监控切维护计划可以立即发生**:它属于"终止当前槽、立刻开始维护计划"一类动作
|
||||
|
||||
**所以同一个"计划变了"事件存在两种完全不同的时机语义——不能用一个模型解释全部。**
|
||||
|
||||
- **同一 major frame 内多次请求**:最终可以是**最后一次请求**指定的计划生效——**不要把中间计划没有出现标成"请求被丢弃"**
|
||||
|
||||
## 六、采样端口的刷新与失效
|
||||
|
||||
- 采样端口是 **latest value**:新消息覆盖旧消息、没有队列;**刷新周期决定数据的"新鲜度",旧数据可能被标记为无效**
|
||||
- 队列端口才是真正的"带缓冲的单播消息"——两者必须严格区分
|
||||
- **写端口之后 guest 没收到通知**:XtratuM 的对象通知使用扩展中断,而**对应扩展中断在分区启动时默认是掩蔽的**——**"消息存在但没有中断"先查掩蔽状态**
|
||||
|
||||
## 七、故障后的控制流不由出错分区决定
|
||||
|
||||
- 健康监控按 **事件 / 范围 / 分区标识 / 故障地址或设备** 映射到预定义动作
|
||||
- 所以"fault 之后系统自动切了调度"必须从健康监控策略解释,**不能只看出错 guest 自己**
|
||||
|
||||
## 决策树
|
||||
|
||||
```
|
||||
拿到 XtratuM 目标
|
||||
├─ 先要配置文件(XM_CF)
|
||||
│ ├─ 分区/内存/中断/I/O/通道/调度都在里面
|
||||
│ └─ 没有它,关于"为什么访问不到"的判断都是猜
|
||||
├─ 调度相关
|
||||
│ ├─ 循环计划:MAF + 槽(起始 + 时长)+ 分区/vCPU 对
|
||||
│ ├─ 优先级计划:优先级 0 最高,抢占式
|
||||
│ └─ 计划切换在当前剩余槽执行完之后才发生
|
||||
├─ 分区不运行
|
||||
│ ├─ 查当前处于哪个 Plan(Plan 0 = 初始化,Plan 1 = 维护)
|
||||
│ ├─ 查该分区是否在当前时间槽
|
||||
│ └─ 查健康监控是否触发了切换
|
||||
├─ 通信相关
|
||||
│ ├─ 采样端口 vs 队列端口(覆盖语义不同)
|
||||
│ └─ 通知可经 IPVI(每分区最多 8 个)
|
||||
└─ 运行期改配置
|
||||
└─ 不可能:无动态对象;只有系统分区能控制系统状态
|
||||
```
|
||||
|
||||
## 工具与验证
|
||||
|
||||
- 配置:XM_CF 的章节与生成后的配置表;解析器的语义校验项(可当"这份配置合法吗"的检查清单)
|
||||
- 调度:处理器章节里的循环计划表/固定优先级表;当前 Plan 与时间槽
|
||||
- 通信:端口表与通道定义;IPVI 的源/目的与编号
|
||||
- 验证:能同时说清「该分区被分配了什么 + 当前处于哪个计划与时间槽 + 通信走哪个端口/通道」
|
||||
|
||||
## 该平台的坑(汇总)
|
||||
|
||||
- **没拿到 XM_CF 就分析资源归属**:一切都在配置里
|
||||
- **以为可以运行时新增对象**:静态配置,无动态对象
|
||||
- **把计划切换当请求后立即生效**:要等当前计划剩余槽执行完
|
||||
- **忽略 Plan 0(初始化)与 Plan 1(维护)的保留语义**
|
||||
- **多核下把优先级 CPU 也套循环计划**:优先级 CPU 独立于计划
|
||||
- **多处理器计划未定义相同的 MAF**:配置校验会失败
|
||||
- **把采样端口当队列端口**:覆盖语义不同
|
||||
- **以为任何分区都能控制系统状态**:只有系统分区可以
|
||||
- **忽略健康监控的配置动作**:它可以直接导致计划切换或分区状态变化
|
||||
Reference in New Issue
Block a user