几个月前写的文章了,翻飞书翻到了。可能结构会有点乱,主要是了解一下 PCI,有错误的话请各位大佬指正一下
PCI 设备通过 BAR(Base Address Register,基地址寄存器) 来定义自己的地址空间。一个标准的 PCI 设备最多可以有 6 个 BAR(BAR0-BAR5),每个 BAR 可以映射到两种不同类型的地址空间:
BAR(BASE Address Registers)用来确定设备所需要使用的内存和 I/O 空间的大小,也可以用来存放设备寄存器的地址。
设备可以申请两类的地址空间:
想象 PCI 总线就像是计算机内部的"高速公路系统":
内存和 I/O 设备共享同一个地址空间
端口映射 I/O 通常使用一种特殊的 CPU 指令,专门执行 I/O 操作
I/O 设备有一个与内存不同的地址空间,为了实现地址空间的隔离,要么在 CPU 物理接口上增加一个 I/O 引脚,要么增加一条专用的 I/O 总线。
这里用 edu PCI设备来做解释
EDU = Educational Device(教育用设备)这是QEMU官方提供的一个虚拟PCI设备,专门用于教学目的,帮助学生和开发者学习如何编写设备驱动程序。
FlatView的作用:
我们知道传入的参数为
所以access_fn间接调用memory_region_write_accessor
跳过阶段七(就是正常执行注册的函数)
在后面会仔细解释其含义和实现
一般 Device 与 Function 一起成为 deviceFn
正向和反向运算
deviceFn 为 PCIBus 设备数组的索引
直接查询源代码代码即可
常用的快速查询表

然后通过 lspci 获取 pci 设备信息

config文件 : PCI 配置空间(原始数据)
256 字节 (标准 PCI) 4096 字节 (PCIe)

BAR0 解析

解析
每行三个字段:起始地址 结束地址 标志位(最后一位表示类型:偶数=MMIO,奇数=PMIO)
这里可以看见仅仅是用了 BAR0
标识位解析
Bit 0:Region Type,总是为 0,用于区分此类型为 Memory
Bits 2-1:Locatable,为 0 时表示采用 32 位地址,为 2 时表示采用 64 位地址,为 1 时表示区间大小小于 1MB
Bit 3:Prefetchable,为 0 时表示关闭预取,为 1 时表示开启预取
Bits 31-4:Base Address,以 16 字节对齐基址
┌─────────────────────────────────────────────────────────────────┐
│ QEMU 启动 - main() │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 机器初始化 - pc_init1() │
│ (hw/i386/pc_piix.c) │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 1. 初始化内存区域 │
│ - system_memory (系统内存) │
│ - system_io (I/O 地址空间) │
│ - pci_memory (PCI 地址空间) │
│ │
│ memory_region_init(pci_memory, NULL, "pci", UINT64_MAX) │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 2. 创建 PCI Host Bridge │
│ phb = qdev_new(TYPE_I440FX_PCI_HOST_BRIDGE) │
│ │
│ 设置属性链接: │
│ - RAM_MEM → ram_memory │
│ - PCI_MEM → pci_memory │
│ - SYSTEM_MEM → system_memory │
│ - IO_MEM → system_io │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 3. Realize PCI Host Bridge │
│ sysbus_realize_and_unref(SYS_BUS_DEVICE(phb)) │
│ │
│ 调用: i440fx_pcihost_realize() │
│ (hw/pci-host/i440fx.c) │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 4. 映射 PCI 配置空间端口 │
│ - 0xCF8: PCI 配置地址端口 (conf_mem) │
│ - 0xCFC: PCI 配置数据端口 (data_mem) │
│ │
│ memory_region_add_subregion(io_memory, 0xcf8, conf_mem) │
│ memory_region_add_subregion(io_memory, 0xcfc, data_mem) │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 5. 创建 PCI 根总线 │
│ b = pci_root_bus_new(dev, NULL, pci_address_space, │
│ io_memory, 0, TYPE_PCI_BUS) │
│ │
│ 调用: pci_root_bus_internal_init() │
│ (hw/pci/pci.c) │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 6. 初始化 PCIBus 结构 │
│ - bus->address_space_mem = mem │
│ - bus->address_space_io = io │
│ - bus->flags |= PCI_BUS_IS_ROOT │
│ - bus->devices[256] = {NULL}
│ │
│ pci_host_bus_register(parent) │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 7. 创建 i440FX PCI 设备(设备号 0) │
│ d = pci_create_simple(b, 0, TYPE_I440FX) │
│ │
│ - Vendor ID: 0x8086 (Intel) │
│ - Device ID: 0x1237 (82441) │
│ - Class: PCI_CLASS_BRIDGE_HOST │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 8. 设置内存映射 │
│ - PCI Hole (3.5G ~ 4G) │
│ - SMRAM 区域 │
│ - PAM (可编程属性映射) │
│ │
│ pc_pci_as_mapping_init(system_memory, pci_address_space) │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 9. 设置中断路由 │
│ pci_bus_map_irqs(pcibus, pc_pci_slot_get_pirq) │
│ │
│ 将 PCI INTx (A/B/C/D) 映射到 PIRQ │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 10. 创建南桥 PCI 设备 (PIIX3/PIIX4) │
│ pci_dev = pci_new_multifunction(-1, TYPE_PIIX3) │
│ │
│ 包含: │
│ - ISA Bridge │
│ - IDE Controller │
│ - USB Controller (可选) │
│ - ACPI/Power Management │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 11. PCI 设备实现 (pci_realize_and_unref) │
│ 调用: pci_qdev_realize() → PCIDeviceClass->realize() │
│ │
│ - 分配配置空间 (256/4096 字节) │
│ - 初始化 BAR │
│ - 设置中断 │
│ - 添加到 bus->devices[devfn] │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 12. 创建其他 PCI 设备 │
│ - VGA 显卡 │
│ - 网卡 (e1000, rtl8139, virtio-net) │
│ - 存储控制器 (virtio-blk, nvme) │
│ - 声卡等 │
│ │
│ 通过命令行参数 (-device) 或机器默认配置创建 │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 13. PCI 初始化完成 │
│ - PCI 总线就绪 │
│ - 客户机可以通过 0xCF8/0xCFC 枚举设备 │
│ - 设备 MMIO/PMIO 映射到地址空间 │
│ - 中断路由配置完成 │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ QEMU 启动 - main() │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 机器初始化 - pc_init1() │
│ (hw/i386/pc_piix.c) │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 1. 初始化内存区域 │
│ - system_memory (系统内存) │
│ - system_io (I/O 地址空间) │
│ - pci_memory (PCI 地址空间) │
│ │
│ memory_region_init(pci_memory, NULL, "pci", UINT64_MAX) │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 2. 创建 PCI Host Bridge │
│ phb = qdev_new(TYPE_I440FX_PCI_HOST_BRIDGE) │
│ │
│ 设置属性链接: │
│ - RAM_MEM → ram_memory │
│ - PCI_MEM → pci_memory │
│ - SYSTEM_MEM → system_memory │
│ - IO_MEM → system_io │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 3. Realize PCI Host Bridge │
│ sysbus_realize_and_unref(SYS_BUS_DEVICE(phb)) │
│ │
│ 调用: i440fx_pcihost_realize() │
│ (hw/pci-host/i440fx.c) │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 4. 映射 PCI 配置空间端口 │
│ - 0xCF8: PCI 配置地址端口 (conf_mem) │
│ - 0xCFC: PCI 配置数据端口 (data_mem) │
│ │
│ memory_region_add_subregion(io_memory, 0xcf8, conf_mem) │
│ memory_region_add_subregion(io_memory, 0xcfc, data_mem) │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 5. 创建 PCI 根总线 │
│ b = pci_root_bus_new(dev, NULL, pci_address_space, │
│ io_memory, 0, TYPE_PCI_BUS) │
│ │
│ 调用: pci_root_bus_internal_init() │
│ (hw/pci/pci.c) │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 6. 初始化 PCIBus 结构 │
│ - bus->address_space_mem = mem │
│ - bus->address_space_io = io │
│ - bus->flags |= PCI_BUS_IS_ROOT │
│ - bus->devices[256] = {NULL}
│ │
│ pci_host_bus_register(parent) │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 7. 创建 i440FX PCI 设备(设备号 0) │
│ d = pci_create_simple(b, 0, TYPE_I440FX) │
│ │
│ - Vendor ID: 0x8086 (Intel) │
│ - Device ID: 0x1237 (82441) │
│ - Class: PCI_CLASS_BRIDGE_HOST │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 8. 设置内存映射 │
│ - PCI Hole (3.5G ~ 4G) │
│ - SMRAM 区域 │
│ - PAM (可编程属性映射) │
│ │
│ pc_pci_as_mapping_init(system_memory, pci_address_space) │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 9. 设置中断路由 │
│ pci_bus_map_irqs(pcibus, pc_pci_slot_get_pirq) │
│ │
│ 将 PCI INTx (A/B/C/D) 映射到 PIRQ │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 10. 创建南桥 PCI 设备 (PIIX3/PIIX4) │
│ pci_dev = pci_new_multifunction(-1, TYPE_PIIX3) │
│ │
│ 包含: │
│ - ISA Bridge │
│ - IDE Controller │
│ - USB Controller (可选) │
│ - ACPI/Power Management │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 11. PCI 设备实现 (pci_realize_and_unref) │
│ 调用: pci_qdev_realize() → PCIDeviceClass->realize() │
│ │
│ - 分配配置空间 (256/4096 字节) │
│ - 初始化 BAR │
│ - 设置中断 │
│ - 添加到 bus->devices[devfn] │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 12. 创建其他 PCI 设备 │
│ - VGA 显卡 │
│ - 网卡 (e1000, rtl8139, virtio-net) │
│ - 存储控制器 (virtio-blk, nvme) │
│ - 声卡等 │
│ │
│ 通过命令行参数 (-device) 或机器默认配置创建 │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 13. PCI 初始化完成 │
│ - PCI 总线就绪 │
│ - 客户机可以通过 0xCF8/0xCFC 枚举设备 │
│ - 设备 MMIO/PMIO 映射到地址空间 │
│ - 中断路由配置完成 │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────┐
│ QEMU虚拟机 │
│ ┌──────────────────────────────┐ │
│ │ Guest Linux (虚拟机OS) │ │
│ │ ┌────────────────────────┐ │ │
│ │ │ 学生编写的EDU驱动 │ │ │ ← 学习目标
│ │ └────────┬───────────────┘ │ │
│ │ │ (MMIO/DMA/IRQ) │ │
│ │ ┌────────▼───────────────┐ │ │
│ │ │ EDU虚拟PCI设备 │ │ │ ← edu.c模拟的设备
│ │ └────────────────────────┘ │ │
│ └──────────────────────────────┘ │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ QEMU虚拟机 │
│ ┌──────────────────────────────┐ │
│ │ Guest Linux (虚拟机OS) │ │
│ │ ┌────────────────────────┐ │ │
│ │ │ 学生编写的EDU驱动 │ │ │ ← 学习目标
│ │ └────────┬───────────────┘ │ │
│ │ │ (MMIO/DMA/IRQ) │ │
│ │ ┌────────▼───────────────┐ │ │
│ │ │ EDU虚拟PCI设备 │ │ │ ← edu.c模拟的设备
│ │ └────────────────────────┘ │ │
│ └──────────────────────────────┘ │
└─────────────────────────────────────┘
EDU设备包含4个教学模块:
┌─────────────────────────────────────┐
│ 1. 基础MMIO读写 │
│ - 设备识别寄存器 (0x00) │
│ - 活性检查寄存器 (0x04) │
│ └→ 学习:基本的寄存器访问 │
├─────────────────────────────────────┤
│ 2. 异步计算 + 中断 │
│ - 阶乘计算 (0x08) │
│ - 状态寄存器 (0x20) │
│ - 中断状态 (0x24) │
│ └→ 学习:异步操作、中断处理 │
├─────────────────────────────────────┤
│ 3. 中断控制 │
│ - 触发中断 (0x60) │
│ - 清除中断 (0x64) │
│ └→ 学习:中断管理、INTx/MSI │
├─────────────────────────────────────┤
│ 4. DMA传输 │
│ - DMA源地址 (0x80) │
│ - DMA目标地址 (0x88) │
│ - DMA计数 (0x90) │
│ - DMA命令 (0x98) │
│ └→ 学习:DMA编程、内存管理 │
└─────────────────────────────────────┘
EDU设备包含4个教学模块:
┌─────────────────────────────────────┐
│ 1. 基础MMIO读写 │
│ - 设备识别寄存器 (0x00) │
│ - 活性检查寄存器 (0x04) │
│ └→ 学习:基本的寄存器访问 │
├─────────────────────────────────────┤
│ 2. 异步计算 + 中断 │
│ - 阶乘计算 (0x08) │
│ - 状态寄存器 (0x20) │
│ - 中断状态 (0x24) │
│ └→ 学习:异步操作、中断处理 │
├─────────────────────────────────────┤
│ 3. 中断控制 │
│ - 触发中断 (0x60) │
│ - 清除中断 (0x64) │
│ └→ 学习:中断管理、INTx/MSI │
├─────────────────────────────────────┤
│ 4. DMA传输 │
│ - DMA源地址 (0x80) │
│ - DMA目标地址 (0x88) │
│ - DMA计数 (0x90) │
│ - DMA命令 (0x98) │
│ └→ 学习:DMA编程、内存管理 │
└─────────────────────────────────────┘
static const TypeInfo edu_types[] = {
{
.name = TYPE_PCI_EDU_DEVICE,
.parent = TYPE_PCI_DEVICE,
.instance_size = sizeof(EduState),
.instance_init = edu_instance_init,
.class_init = edu_class_init,
.interfaces = (const InterfaceInfo[]) {
{ INTERFACE_CONVENTIONAL_PCI_DEVICE },
{ },
},
}
};
DEFINE_TYPES(edu_types)
static const TypeInfo edu_types[] = {
{
.name = TYPE_PCI_EDU_DEVICE,
.parent = TYPE_PCI_DEVICE,
.instance_size = sizeof(EduState),
.instance_init = edu_instance_init,
.class_init = edu_class_init,
.interfaces = (const InterfaceInfo[]) {
{ INTERFACE_CONVENTIONAL_PCI_DEVICE },
{ },
},
}
};
DEFINE_TYPES(edu_types)
#define TYPE_PCI_EDU_DEVICE "edu"
.name = "edu"
.parent = TYPE_PCI_DEVICE
.instance_size = sizeof(EduState)
.instance_init = edu_instance_init
.class_init = edu_class_init
.interfaces = CONVENTIONAL_PCI_DEVICE
#define TYPE_PCI_EDU_DEVICE "edu"
.name = "edu"
.parent = TYPE_PCI_DEVICE
.instance_size = sizeof(EduState)
.instance_init = edu_instance_init
.class_init = edu_class_init
.interfaces = CONVENTIONAL_PCI_DEVICE
static void edu_class_init(ObjectClass *class, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(class);
PCIDeviceClass *k = PCI_DEVICE_CLASS(class);
k->realize = pci_edu_realize;
k->exit = pci_edu_uninit;
k->vendor_id = PCI_VENDOR_ID_QEMU;
k->device_id = 0x11e8;
k->revision = 0x10;
k->class_id = PCI_CLASS_OTHERS;
set_bit(DEVICE_CATEGORY_MISC, dc->categories);
}
static void edu_class_init(ObjectClass *class, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(class);
PCIDeviceClass *k = PCI_DEVICE_CLASS(class);
k->realize = pci_edu_realize;
k->exit = pci_edu_uninit;
k->vendor_id = PCI_VENDOR_ID_QEMU;
k->device_id = 0x11e8;
k->revision = 0x10;
k->class_id = PCI_CLASS_OTHERS;
set_bit(DEVICE_CATEGORY_MISC, dc->categories);
}
k->realize = pci_edu_realize;
k->exit = pci_edu_uninit;
k->vendor_id = PCI_VENDOR_ID_QEMU;
k->device_id = 0x11e8;
k->revision = 0x10;
k->class_id = PCI_CLASS_OTHERS;
k->realize = pci_edu_realize;
k->exit = pci_edu_uninit;
k->vendor_id = PCI_VENDOR_ID_QEMU;
k->device_id = 0x11e8;
k->revision = 0x10;
k->class_id = PCI_CLASS_OTHERS;
static void edu_instance_init(Object *obj)
{
EduState *edu = EDU(obj);
edu->dma_mask = (1UL << 28) - 1;
object_property_add_uint64_ptr(obj, "dma_mask",
&edu->dma_mask, OBJ_PROP_FLAG_READWRITE);
}
static void edu_instance_init(Object *obj)
{
EduState *edu = EDU(obj);
edu->dma_mask = (1UL << 28) - 1;
object_property_add_uint64_ptr(obj, "dma_mask",
&edu->dma_mask, OBJ_PROP_FLAG_READWRITE);
}
static void pci_edu_realize(PCIDevice *pdev, Error **errp)
{
EduState *edu = EDU(pdev);
uint8_t *pci_conf = pdev->config;
pci_config_set_interrupt_pin(pci_conf, 1);
if (msi_init(pdev, 0, 1, true, false, errp)) {
return;
}
timer_init_ms(&edu->dma_timer, QEMU_CLOCK_VIRTUAL, edu_dma_timer, edu);
qemu_mutex_init(&edu->thr_mutex);
qemu_cond_init(&edu->thr_cond);
qemu_thread_create(&edu->thread, "edu", edu_fact_thread,
edu, QEMU_THREAD_JOINABLE);
memory_region_init_io(&edu->mmio, OBJECT(edu), &edu_mmio_ops, edu,
"edu-mmio", 1 * MiB);
pci_register_bar(pdev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &edu->mmio);
}
static void pci_edu_realize(PCIDevice *pdev, Error **errp)
{
EduState *edu = EDU(pdev);
uint8_t *pci_conf = pdev->config;
pci_config_set_interrupt_pin(pci_conf, 1);
if (msi_init(pdev, 0, 1, true, false, errp)) {
return;
}
timer_init_ms(&edu->dma_timer, QEMU_CLOCK_VIRTUAL, edu_dma_timer, edu);
qemu_mutex_init(&edu->thr_mutex);
qemu_cond_init(&edu->thr_cond);
qemu_thread_create(&edu->thread, "edu", edu_fact_thread,
edu, QEMU_THREAD_JOINABLE);
memory_region_init_io(&edu->mmio, OBJECT(edu), &edu_mmio_ops, edu,
"edu-mmio", 1 * MiB);
pci_register_bar(pdev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &edu->mmio);
}
pci_config_set_interrupt_pin(pci_conf, 1);
msi_init(pdev, 0, 1, true, false, errp);
timer_init_ms(&edu->dma_timer, QEMU_CLOCK_VIRTUAL, edu_dma_timer, edu);
qemu_thread_create(&edu->thread, "edu", edu_fact_thread, edu, ...);
memory_region_init_io(
&edu->mmio,
OBJECT(edu),
&edu_mmio_ops,
edu,
"edu-mmio",
1 * MiB
);
pci_register_bar(
pdev,
0,
PCI_BASE_ADDRESS_SPACE_MEMORY,
&edu->mmio
);
pci_config_set_interrupt_pin(pci_conf, 1);
msi_init(pdev, 0, 1, true, false, errp);
timer_init_ms(&edu->dma_timer, QEMU_CLOCK_VIRTUAL, edu_dma_timer, edu);
qemu_thread_create(&edu->thread, "edu", edu_fact_thread, edu, ...);
memory_region_init_io(
&edu->mmio,
OBJECT(edu),
&edu_mmio_ops,
edu,
"edu-mmio",
1 * MiB
);
pci_register_bar(
pdev,
0,
PCI_BASE_ADDRESS_SPACE_MEMORY,
&edu->mmio
);
uint64_t (*read)(void *opaque, hwaddr addr, unsigned size);
uint64_t (*read)(void *opaque, hwaddr addr, unsigned size);
static const MemoryRegionOps edu_mmio_ops = {
.read = edu_mmio_read,
.write = edu_mmio_write,
.endianness = DEVICE_NATIVE_ENDIAN,
.valid = {
.min_access_size = 4,
.max_access_size = 8,
},
.impl = {
.min_access_size = 4,
.max_access_size = 8,
},
};
static const MemoryRegionOps edu_mmio_ops = {
.read = edu_mmio_read,
.write = edu_mmio_write,
.endianness = DEVICE_NATIVE_ENDIAN,
.valid = {
.min_access_size = 4,
.max_access_size = 8,
},
.impl = {
.min_access_size = 4,
.max_access_size = 8,
},
};
Guest: mov [0xFEBF1000], 1337
↓ (VM Exit)
KVM捕获访问
↓
传递给QEMU
↓
QEMU内存系统查找对应的MemoryRegion
↓
找到edu->mmio
↓
调用 edu_mmio_write(edu, 0x00, 1337, 4)
↓
设备逻辑处理
Guest: mov [0xFEBF1000], 1337
↓ (VM Exit)
KVM捕获访问
↓
传递给QEMU
↓
QEMU内存系统查找对应的MemoryRegion
↓
找到edu->mmio
↓
调用 edu_mmio_write(edu, 0x00, 1337, 4)
↓
设备逻辑处理
┌─────────────────────────────────────────────────────────────────────┐
│ Guest OS (虚拟机) │
│ │
│ 用户驱动或应用程序 │
│ │ │
│ ▼ │
│ uint32_t val = *(uint32_t*)(mmio_mem + 0x08); │
│ │ │
│ ▼ │
│ CPU执行: MOV EAX, [0xFEBF1008] ← EDU设备BAR0 + 0x08 │
│ │ │
└───────────────────┼───────────────────────────────────────────────────┘
│
▼ (VM Exit - EPT Violation 或 MMIO访问)
┌─────────────────────────────────────────────────────────────────────┐
│ Linux Kernel (Host) │
│ │
│ KVM模块 │
│ │ │
│ ▼ │
│ 检测到Guest访问设备地址 (0xFEBF1008) │
│ │ │
│ ▼ │
│ 填充 kvm_run 结构体: │
│ run->exit_reason = KVM_EXIT_MMIO │
│ run->mmio.phys_addr = 0xFEBF1008 │
│ run->mmio.len = 4 │
│ run->mmio.is_write = 0 │
│ │ │
│ ▼ │
│ return to userspace (ioctl返回) │
│ │
└───────────────────┼───────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ QEMU进程 (用户态) │
│ │
│ [1] kvm_cpu_exec() │
│ ↓ │
│ [2] run_ret = kvm_vcpu_ioctl(cpu, KVM_RUN, 0) │
│ ↓ (ioctl阻塞,直到VM Exit) │
│ [3] switch(run->exit_reason) │
│ ↓ │
│ [4] case KVM_EXIT_MMIO: │
│ ↓ │
│ [5] address_space_rw(&address_space_memory, │
│ run->mmio.phys_addr, │
│ attrs, │
│ run->mmio.data, │
│ run->mmio.len, │
│ run->mmio.is_write) │
│ ↓ │
│ [6] address_space_read_full() │
│ ↓ │
│ [7] fv = address_space_to_flatview(as) │
│ ↓ │
│ [8] flatview_read(fv, addr, attrs, buf, len) │
│ ↓ │
│ [9] mr = flatview_translate(fv, addr, ...) │
│ ↓ │
│ [10] flatview_read_continue() │
│ ↓ │
│ [11] flatview_read_continue_step() │
│ ↓ │
│ [12] memory_region_dispatch_read(mr, mr_addr, &val, ...) │
│ ↓ │
│ [13] memory_region_dispatch_read1() │
│ ↓ │
│ [14] access_with_adjusted_size() │
│ ↓ │
│ [15] memory_region_read_accessor() │
│ ↓ │
│ [16] tmp = mr->ops->read(mr->opaque, addr, size) │
│ ↓ │
│ [17] edu_mmio_read(edu, 0x08, 4) ← 最终调用! │
│ ↓ │
│ [18] switch(addr) { case 0x08: return edu->fact; } │
│ ↓ │
│ [19] 返回值逐层返回到 run->mmio.data │
│ ↓ │
│ [20] VM Enter,Guest继续执行 │
│ │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ Guest OS (虚拟机) │
│ │
│ 用户驱动或应用程序 │
│ │ │
│ ▼ │
│ uint32_t val = *(uint32_t*)(mmio_mem + 0x08); │
│ │ │
│ ▼ │
│ CPU执行: MOV EAX, [0xFEBF1008] ← EDU设备BAR0 + 0x08 │
│ │ │
└───────────────────┼───────────────────────────────────────────────────┘
│
▼ (VM Exit - EPT Violation 或 MMIO访问)
┌─────────────────────────────────────────────────────────────────────┐
│ Linux Kernel (Host) │
│ │
│ KVM模块 │
│ │ │
│ ▼ │
│ 检测到Guest访问设备地址 (0xFEBF1008) │
│ │ │
│ ▼ │
│ 填充 kvm_run 结构体: │
│ run->exit_reason = KVM_EXIT_MMIO │
│ run->mmio.phys_addr = 0xFEBF1008 │
│ run->mmio.len = 4 │
│ run->mmio.is_write = 0 │
│ │ │
│ ▼ │
│ return to userspace (ioctl返回) │
│ │
└───────────────────┼───────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ QEMU进程 (用户态) │
│ │
│ [1] kvm_cpu_exec() │
│ ↓ │
│ [2] run_ret = kvm_vcpu_ioctl(cpu, KVM_RUN, 0) │
│ ↓ (ioctl阻塞,直到VM Exit) │
│ [3] switch(run->exit_reason) │
│ ↓ │
│ [4] case KVM_EXIT_MMIO: │
│ ↓ │
│ [5] address_space_rw(&address_space_memory, │
│ run->mmio.phys_addr, │
│ attrs, │
│ run->mmio.data, │
│ run->mmio.len, │
│ run->mmio.is_write) │
│ ↓ │
│ [6] address_space_read_full() │
│ ↓ │
│ [7] fv = address_space_to_flatview(as) │
│ ↓ │
│ [8] flatview_read(fv, addr, attrs, buf, len) │
│ ↓ │
│ [9] mr = flatview_translate(fv, addr, ...) │
│ ↓ │
│ [10] flatview_read_continue() │
│ ↓ │
│ [11] flatview_read_continue_step() │
│ ↓ │
│ [12] memory_region_dispatch_read(mr, mr_addr, &val, ...) │
│ ↓ │
│ [13] memory_region_dispatch_read1() │
│ ↓ │
│ [14] access_with_adjusted_size() │
│ ↓ │
│ [15] memory_region_read_accessor() │
│ ↓ │
│ [16] tmp = mr->ops->read(mr->opaque, addr, size) │
│ ↓ │
│ [17] edu_mmio_read(edu, 0x08, 4) ← 最终调用! │
│ ↓ │
│ [18] switch(addr) { case 0x08: return edu->fact; } │
│ ↓ │
│ [19] 返回值逐层返回到 run->mmio.data │
│ ↓ │
│ [20] VM Enter,Guest继续执行 │
│ │
└─────────────────────────────────────────────────────────────────────┘
Guest vCPU KVM (Kernel) QEMU (Userspace) EDU Device
│ │ │ │
│ MOV [0xFEBF1008] │ │ │
├───────────────────>│ │ │
│ │ EPT Violation │ │
│ │ (设备地址) │ │
│ │ │ │
│ │ 填充kvm_run │ │
│ │ exit_reason=MMIO │ │
│ │ │ │
│ │ ioctl返回 │ │
│ ├─────────────────────>│ │
│ │ │ │
│ │ │ address_space_rw │
│ │ │ flatview_translate │
│ │ │ 查找MemoryRegion │
│ │ │ │
│ │ │ dispatch_read │
│ │ ├────────────────────>│
│ │ │ │ edu_mmio_read
│ │ │ │ switch(0x08)
│ │ │ │ return fact
│ │ │<────────────────────┤
│ │ │ │
│ │ 数据写入run->mmio │ │
│ │ KVM_RUN ioctl │ │
│ │<─────────────────────┤ │
│ │ │ │
│ │ VM Enter │ │
│ │ 注入读取结果到EAX │ │
│<───────────────────┤ │ │
│ 继续执行 │ │ │
│ │ │ │
Guest vCPU KVM (Kernel) QEMU (Userspace) EDU Device
│ │ │ │
│ MOV [0xFEBF1008] │ │ │
├───────────────────>│ │ │
│ │ EPT Violation │ │
│ │ (设备地址) │ │
│ │ │ │
│ │ 填充kvm_run │ │
│ │ exit_reason=MMIO │ │
│ │ │ │
│ │ ioctl返回 │ │
│ ├─────────────────────>│ │
│ │ │ │
│ │ │ address_space_rw │
│ │ │ flatview_translate │
│ │ │ 查找MemoryRegion │
│ │ │ │
│ │ │ dispatch_read │
│ │ ├────────────────────>│
│ │ │ │ edu_mmio_read
│ │ │ │ switch(0x08)
│ │ │ │ return fact
│ │ │<────────────────────┤
│ │ │ │
│ │ 数据写入run->mmio │ │
│ │ KVM_RUN ioctl │ │
│ │<─────────────────────┤ │
│ │ │ │
│ │ VM Enter │ │
│ │ 注入读取结果到EAX │ │
│<───────────────────┤ │ │
│ 继续执行 │ │ │
│ │ │ │
int kvm_cpu_exec(CPUState *cpu)
{
struct kvm_run *run = cpu->kvm_run;
do {
run_ret = kvm_vcpu_ioctl(cpu, KVM_RUN, 0);
switch (run->exit_reason) {
case KVM_EXIT_MMIO:
address_space_rw(&address_space_memory,
run->mmio.phys_addr,
attrs,
run->mmio.data,
run->mmio.len,
run->mmio.is_write);
break;
}
} while (ret == 0);
}
int kvm_cpu_exec(CPUState *cpu)
{
struct kvm_run *run = cpu->kvm_run;
do {
run_ret = kvm_vcpu_ioctl(cpu, KVM_RUN, 0);
switch (run->exit_reason) {
case KVM_EXIT_MMIO:
address_space_rw(&address_space_memory,
run->mmio.phys_addr,
attrs,
run->mmio.data,
run->mmio.len,
run->mmio.is_write);
break;
}
} while (ret == 0);
}
struct kvm_run {
__u8 request_interrupt_window;
__u8 HINT_UNSAFE_IN_KVM(immediate_exit);
__u8 padding1[6];
__u32 exit_reason;
__u8 ready_for_interrupt_injection;
__u8 if_flag;
__u16 flags;
__u64 cr8;
__u64 apic_base;
struct {
__u64 phys_addr;
__u8 data[8];
__u32 len;
__u8 is_write;
} mmio;
}
struct kvm_run {
__u8 request_interrupt_window;
__u8 HINT_UNSAFE_IN_KVM(immediate_exit);
__u8 padding1[6];
__u32 exit_reason;
__u8 ready_for_interrupt_injection;
__u8 if_flag;
__u16 flags;
__u64 cr8;
__u64 apic_base;
struct {
__u64 phys_addr;
__u8 data[8];
__u32 len;
__u8 is_write;
} mmio;
}
MemoryRegion *flatview_translate(FlatView *fv, hwaddr addr, hwaddr *xlat,
hwaddr *plen, bool is_write,
MemTxAttrs attrs)
{
MemoryRegion *mr;
MemoryRegionSection section;
AddressSpace *as = NULL;
section = flatview_do_translate(fv, addr, xlat, plen, NULL,
is_write, true, &as, attrs);
mr = section.mr;
if (xen_enabled() && memory_access_is_direct(mr, is_write, attrs)) {
hwaddr page = ((addr & TARGET_PAGE_MASK) + TARGET_PAGE_SIZE) - addr;
*plen = MIN(page, *plen);
}
return mr;
}
MemoryRegion *flatview_translate(FlatView *fv, hwaddr addr, hwaddr *xlat,
hwaddr *plen, bool is_write,
MemTxAttrs attrs)
{
MemoryRegion *mr;
MemoryRegionSection section;
AddressSpace *as = NULL;
section = flatview_do_translate(fv, addr, xlat, plen, NULL,
is_write, true, &as, attrs);
mr = section.mr;
if (xen_enabled() && memory_access_is_direct(mr, is_write, attrs)) {
hwaddr page = ((addr & TARGET_PAGE_MASK) + TARGET_PAGE_SIZE) - addr;
*plen = MIN(page, *plen);
}
return mr;
}
【层次化视图】MemoryRegion树形结构
system_memory (容器)
├── ram_below_4g (RAM, 0x00000000-0x7FFFFFFF, 2GB)
├── pci_memory (容器, 0x80000000-0xFFFFFFFF)
│ ├── vga_mmio (VGA设备, 0xA0000000, 16MB)
│ ├── edu_mmio (EDU设备, 0xFEBF1000, 1MB) ← 我们的设备
│ └── e1000_mmio (网卡, 0xFEBD0000, 128KB)
└── ram_above_4g (RAM, 0x100000000+)
【扁平化视图】FlatView - 线性地址映射
FlatRange数组 (按地址排序):
[0] 0x00000000-0x7FFFFFFF → ram_below_4g
[1] 0x80000000-0x9FFFFFFF → (未分配)
[2] 0xA0000000-0xA0FFFFFF → vga_mmio
[3] 0xA1000000-0xFEBCFFFF → (未分配)
[4] 0xFEBD0000-0xFEBEFFFF → e1000_mmio
[5] 0xFEBF0000-0xFEBF0FFF → (gap)
[6] 0xFEBF1000-0xFEBF1FFF → edu_mmio ← 查找目标
[7] 0xFEBF2000-0xFFFFFFFF → (未分配)
[8] 0x100000000-... → ram_above_4g
【层次化视图】MemoryRegion树形结构
system_memory (容器)
├── ram_below_4g (RAM, 0x00000000-0x7FFFFFFF, 2GB)
├── pci_memory (容器, 0x80000000-0xFFFFFFFF)
│ ├── vga_mmio (VGA设备, 0xA0000000, 16MB)
│ ├── edu_mmio (EDU设备, 0xFEBF1000, 1MB) ← 我们的设备
│ └── e1000_mmio (网卡, 0xFEBD0000, 128KB)
└── ram_above_4g (RAM, 0x100000000+)
【扁平化视图】FlatView - 线性地址映射
FlatRange数组 (按地址排序):
[0] 0x00000000-0x7FFFFFFF → ram_below_4g
[1] 0x80000000-0x9FFFFFFF → (未分配)
[2] 0xA0000000-0xA0FFFFFF → vga_mmio
[3] 0xA1000000-0xFEBCFFFF → (未分配)
[4] 0xFEBD0000-0xFEBEFFFF → e1000_mmio
[5] 0xFEBF0000-0xFEBF0FFF → (gap)
[6] 0xFEBF1000-0xFEBF1FFF → edu_mmio ← 查找目标
[7] 0xFEBF2000-0xFFFFFFFF → (未分配)
[8] 0x100000000-... → ram_above_4g
MemTxResult memory_region_dispatch_read(MemoryRegion *mr,
hwaddr addr,
uint64_t *pval,
MemOp op,
MemTxAttrs attrs)
{
unsigned size = memop_size(op);
MemTxResult r;
if (mr->alias) {
return memory_region_dispatch_read(mr->alias,
mr->alias_offset + addr,
pval, op, attrs);
}
if (!memory_region_access_valid(mr, addr, size, false, attrs)) {
*pval = unassigned_mem_read(mr, addr, size);
return MEMTX_DECODE_ERROR;
}
r = memory_region_dispatch_read1(mr, addr, pval, size, attrs);
adjust_endianness(mr, pval, op);
return r;
}
static MemTxResult memory_region_dispatch_read1(MemoryRegion *mr,
hwaddr addr,
uint64_t *pval,
unsigned size,
MemTxAttrs attrs)
{
*pval = 0;
if (mr->ops->read) {
return access_with_adjusted_size(addr, pval, size,
mr->ops->impl.min_access_size,
mr->ops->impl.max_access_size,
memory_region_read_accessor,
mr, attrs);
} else {
return access_with_adjusted_size(addr, pval, size,
mr->ops->impl.min_access_size,
mr->ops->impl.max_access_size,
memory_region_read_with_attrs_accessor,
mr, attrs);
}
}
MemTxResult memory_region_dispatch_read(MemoryRegion *mr,
hwaddr addr,
uint64_t *pval,
MemOp op,
MemTxAttrs attrs)
{
unsigned size = memop_size(op);
MemTxResult r;
if (mr->alias) {
return memory_region_dispatch_read(mr->alias,
mr->alias_offset + addr,
pval, op, attrs);
}
if (!memory_region_access_valid(mr, addr, size, false, attrs)) {
*pval = unassigned_mem_read(mr, addr, size);
return MEMTX_DECODE_ERROR;
}
r = memory_region_dispatch_read1(mr, addr, pval, size, attrs);
adjust_endianness(mr, pval, op);
return r;
}
static MemTxResult memory_region_dispatch_read1(MemoryRegion *mr,
hwaddr addr,
uint64_t *pval,
unsigned size,
MemTxAttrs attrs)
{
*pval = 0;
if (mr->ops->read) {
return access_with_adjusted_size(addr, pval, size,
mr->ops->impl.min_access_size,
mr->ops->impl.max_access_size,
memory_region_read_accessor,
mr, attrs);
} else {
return access_with_adjusted_size(addr, pval, size,
mr->ops->impl.min_access_size,
mr->ops->impl.max_access_size,
memory_region_read_with_attrs_accessor,
mr, attrs);
}
}
static MemTxResult access_with_adjusted_size(hwaddr addr,
uint64_t *value,
unsigned size,
unsigned access_size_min,
unsigned access_size_max,
MemTxResult (*access_fn)
(MemoryRegion *mr,
hwaddr addr,
uint64_t *value,
unsigned size,
signed shift,
uint64_t mask,
MemTxAttrs attrs),
MemoryRegion *mr,
MemTxAttrs attrs)
{
uint64_t access_mask;
unsigned access_size;
unsigned i;
MemTxResult r = MEMTX_OK;
bool reentrancy_guard_applied = false;
if (!access_size_min) {
access_size_min = 1;
}
if (!access_size_max) {
access_size_max = 4;
}
if (mr->dev && !mr->disable_reentrancy_guard &&
!mr->ram_device && !mr->ram && !mr->rom_device && !mr->readonly) {
if (mr->dev->mem_reentrancy_guard.engaged_in_io) {
warn_report_once("Blocked re-entrant IO on MemoryRegion: "
"%s at addr: 0x%" HWADDR_PRIX,
memory_region_name(mr), addr);
return MEMTX_ACCESS_ERROR;
}
mr->dev->mem_reentrancy_guard.engaged_in_io = true;
reentrancy_guard_applied = true;
}
access_size = MAX(MIN(size, access_size_max), access_size_min);
access_mask = MAKE_64BIT_MASK(0, access_size * 8);
if (devend_big_endian(mr->ops->endianness)) {
for (i = 0; i < size; i += access_size) {
r |= access_fn(mr, addr + i, value, access_size,
(size - access_size - i) * 8, access_mask, attrs);
}
} else {
for (i = 0; i < size; i += access_size) {
r |= access_fn(mr, addr + i, value, access_size, i * 8,
access_mask, attrs);
}
}
if (mr->dev && reentrancy_guard_applied) {
mr->dev->mem_reentrancy_guard.engaged_in_io = false;
}
return r;
}
static MemTxResult access_with_adjusted_size(hwaddr addr,
uint64_t *value,
unsigned size,
unsigned access_size_min,
unsigned access_size_max,
MemTxResult (*access_fn)
(MemoryRegion *mr,
hwaddr addr,
uint64_t *value,
unsigned size,
signed shift,
uint64_t mask,
MemTxAttrs attrs),
MemoryRegion *mr,
MemTxAttrs attrs)
{
uint64_t access_mask;
unsigned access_size;
unsigned i;
MemTxResult r = MEMTX_OK;
bool reentrancy_guard_applied = false;
if (!access_size_min) {
access_size_min = 1;
}
if (!access_size_max) {
access_size_max = 4;
}
if (mr->dev && !mr->disable_reentrancy_guard &&
!mr->ram_device && !mr->ram && !mr->rom_device && !mr->readonly) {
if (mr->dev->mem_reentrancy_guard.engaged_in_io) {
warn_report_once("Blocked re-entrant IO on MemoryRegion: "
"%s at addr: 0x%" HWADDR_PRIX,
memory_region_name(mr), addr);
return MEMTX_ACCESS_ERROR;
}
mr->dev->mem_reentrancy_guard.engaged_in_io = true;
reentrancy_guard_applied = true;
}
access_size = MAX(MIN(size, access_size_max), access_size_min);
access_mask = MAKE_64BIT_MASK(0, access_size * 8);
if (devend_big_endian(mr->ops->endianness)) {
for (i = 0; i < size; i += access_size) {
r |= access_fn(mr, addr + i, value, access_size,
[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!