QEMU 内存管理
本节讲述 QEMU 如何管理某个特定 VM 的内存。
Guest VM 视角(GPA)
MemoryRegion:Guest 视角的一块“内存”
在 Qemu 当中使用 MemoryRegion 结构体类型来表示一块具体的 Guest 物理内存区域,该结构体定义于 include/exec/memory.h 当中:
/** MemoryRegion:
*
* 表示一块内存区域的一个结构体.
*/
struct MemoryRegion {
Object parent_obj;
/* private: */
/* The following fields should fit in a cache line */
bool romd_mode;
bool ram;
bool subpage;
bool readonly; /* For RAM regions */
bool nonvolatile;
bool rom_device;
bool flush_coalesced_mmio;
bool global_locking;
uint8_t dirty_log_mask;
bool is_iommu;
RAMBlock *ram_block;
Object *owner;
const MemoryRegionOps *ops;
void *opaque;
MemoryRegion *container; // 指向父 MemoryRegion
Int128 size; // 内存区域大小
hwaddr addr; // 在父 MR 中的偏移量
void (*destructor)(MemoryRegion *mr);
uint64_t align;
bool terminates;
bool ram_device;
bool enabled;
bool warning_printed; /* For reservations */
uint8_t vga_logging_count;
MemoryRegion *alias; // 仅在 alias MR 中,指向实际的 MR
hwaddr alias_offset;
int32_t priority;
QTAILQ_HEAD(, MemoryRegion) subregions;
QTAILQ_ENTRY(MemoryRegion) subregions_link;
QTAILQ_HEAD(, CoalescedMemoryRange) coalesced;
const char *name;
unsigned ioeventfd_nb;
MemoryRegionIoeventfd *ioeventfds;
};在 Qemu 当中有三种类型的 MemoryRegion:
MemoryRegion 根:通过
memory_region_init()进行初始化,其用以表示与管理由多个 sub-MemoryRegion 组成的一个内存区域,并不实际指向一块内存区域,例如system_memory。MemoryRegion 实体:通过
memory_region_init_ram()初始化,表示具体的一块大小为 size 的内存空间,指向一块具体的内存。MemoryRegion 别名:通过
memory_region_init_alias()初始化,作为另一个 MemoryRegion 实体的别名而存在,不指向一块实际内存。
MR 容器与 MR 实体间构成树形结构,其中容器为根节点而实体为子节点:
相应地,基于 OOP 的思想,MemoryRegion 的成员函数被封装在函数表 MemoryRegionOps 当中:
当我们的 Guest 要读写虚拟机上的内存时,在 Qemu 内部实际上会调用 address_space_rw(),对于一般的 RAM 内存而言则直接对 MR 对应的内存进行操作,对于 MMIO 而言则最终调用到对应的 MR->ops->read() 或 MR->ops->write()。
同样的,为了统一接口,在 Qemu 当中 PMIO 的实现同样是通过 MemoryRegion 来完成的,我们可以把一组端口理解为 QEMU 视角的一块 Guest 内存。
几乎所有的 CTF QEMU Pwn 题都是自定义一个设备并定义相应的 MMIO/PMIO 操作。
FlatView:MR 树对应的 Guest 视角物理地址空间
FlatView 用来表示一棵 MemoryRegion 树所表示的 Guest 地址空间,其使用一个 FlatRange 结构体指针数组来存储不同 MemoryRegion 对应的地址信息,每个 FlatRange 表示单个 MemoryRegion 的 Guest 视角的一块物理地址空间以及是否只读等特性信息, FlatRange 之间所表示的地址范围不会重叠。
AddressSpace:不同类型的 Guest 地址空间
AddressSpace 结构体用以表示 Guest 视角不同类型的地址空间,在 x86 下其实就只有两种:address_space_memory 与 address_space_io。
单个 AddressSpace 结构体与一棵 MemoryRegion 树的根节点相关联,并使用一个 FlatView 结构体建立该树的平坦化内存空间。
最终我们可以得到如下总览图:

host VMM 视角(HVA)
RAMBlock:MR 对应的 Host 虚拟内存
RAMBlock 结构体用来表示单个实体 MemoryRegion 所占用的 Host 虚拟内存信息,多个 RAMBlock 结构体之间构成单向链表。
比较重要的成员如下:
mr:该 RAMBlock 对应的 MemoryRegion(即 HVA → GPA)host:GVA 对应的 HVA,通常由 QEMU 通过mmap()获得(如果未使用 KVM)
对应关系如下图所示:

REFERENCE
Last updated