kvmtool阅读笔记(四) | 设置BIOS

0X01 设置BIOS

在将镜像加载到内存之后设置BIOS加载内核以及设置中断向量表。参考如下内存使用区域图

 

 

结合代码可以很清晰地看对应组建初始化

p = guest_flat_to_host(kvm, 0x00000400);
memset(p, 0, 0x000000ff);

p = guest_flat_to_host(kvm, 0x0009fc00);
memset(p, 0, 0x000003ff);

p = guest_flat_to_host(kvm, 0x000f0000);
memset(p, 0, 0x0000ffff);

p = guest_flat_to_host(kvm, 0x000c0000);
memset(p, 0, 0x00007fff);

p = guest_flat_to_host(kvm, 0x000f0000);
memcpy(p, bios_rom, bios_rom_size);

其中变量bios_rom需要提前编译,本次环境使用x86,因此需要编译x86/bios/entry.S获取bios_rom以及bios_rom_size,通过Makefile之后同时获取编译完成后bios-rom.h中BIOS_OFFSET的各种值

 

0x02 e820设置

在BIOS探测万内存信息后,会将内存信息保存在BIOS的数据区。当bootloader或者OS通过软中断的方式向BIOS询问内存信息时,BIOS中的中断处理函数将从BIOS的数据去复制内存信息到调用者制定的位置。因此e820需要准备好内存段的信息。

e820		= guest_flat_to_host(kvm, E820_MAP_START);
	mem_map		= e820->map;

	mem_map[i++]	= (struct e820entry) {
		.addr		= REAL_MODE_IVT_BEGIN,
		.size		= EBDA_START - REAL_MODE_IVT_BEGIN,
		.type		= E820_RAM,
	};
	mem_map[i++]	= (struct e820entry) {
		.addr		= EBDA_START,
		.size		= VGA_RAM_BEGIN - EBDA_START,
		.type		= E820_RESERVED,
	};
	mem_map[i++]	= (struct e820entry) {
		.addr		= MB_BIOS_BEGIN,
		.size		= MB_BIOS_END - MB_BIOS_BEGIN,
		.type		= E820_RESERVED,
	};

    // 扩展内存区
	if (kvm->ram_size < KVM_32BIT_GAP_START) {
		mem_map[i++]	= (struct e820entry) {
			.addr		= BZ_KERNEL_START,
			.size		= kvm->ram_size - BZ_KERNEL_START,
			.type		= E820_RAM,
		};
	} else {
		mem_map[i++]	= (struct e820entry) {
			.addr		= BZ_KERNEL_START,
			.size		= KVM_32BIT_GAP_START - BZ_KERNEL_START,
			.type		= E820_RAM,
		};
		mem_map[i++]	= (struct e820entry) {
			.addr		= KVM_32BIT_MAX_MEM_SIZE,
			.size		= kvm->ram_size - KVM_32BIT_MAX_MEM_SIZE,
			.type		= E820_RAM,
		};
	}

if判断内存的大小是否比32位能够处理的内存更大,如果更大则需要创建5个内存段,否则只需要创建四个内存段。第1个内存段用于储存实模式的IVT(中断向量表);第2个内存段是传统的地段内存;第3个是BIOS,包括主板BIOS和显卡BIOS;最后面的即为扩展内存区,主要可用的内存都在这个段。扩展内存区的size减去的BZ_KERNEL_START即为起始位置,值为0x100000UL也就是1MB。

 

0x03 准备中断0x15的处理函数以及设置IVT

在对vga输出VESA初始化之后进行中断向量表的设置。当CPU执行指令int 0x15后,将进入中断处理流程,CPU将从IVT表中的第0x15项取出中断处理函数的地址,然后跳转到中断处理函数执行。这个中断处理函数由BIOS实现,存储在BIOS ROM中。在x86架构的32位系统中,系统启动后,主板的BIOS ROM将被映射到内存地址空间0x000F0000 ~ 0x000FFFFF处。也就是,中断发生后,最终是跳转到地址空间0x000F0000 ~ 0x000FFFFF中的中断0x15对应的处理函数处运行。因此也需要在Guest的0x000F0000 ~ 0x000FFFFF这段内存中准备好0x15号中断的中断处理函数,包括

  • 在BIOS ROM中准备中断处理函数的实现
  • 设置IVT中0x15号表项的地指向0x15号中断的处理函数

 

unsigned long address = MB_BIOS_BEGIN;

...

address = BIOS_IRQ_PA_ADDR(bios_intfake);
intr_desc = (struct real_intr_desc) {
	.segment	= REAL_SEGMENT(MB_BIOS_BEGIN),
	.offset		= address - MB_BIOS_BEGIN,
};
// 初始化存储在KVM的结构体
interrupt_table__setup(&kvm->arch.interrupt_table, &intr_desc);

for (i = 0; i < ARRAY_SIZE(bios_irq_handlers); i++)
	setup_irq_handler(kvm, &bios_irq_handlers[i]);

// 将获取的中断地址存储在KVM结构体内
/* we almost done */
p = guest_flat_to_host(kvm, 0); // 代码[1]
interrupt_table__copy(&kvm->arch.interrupt_table, p, REAL_INTR_SIZE);

变量初始值MB_BIOS_BEGIN,是主板BIOS ROM映射在Guest内存地址空间中的起始位置。宏BIOS_IRQ_PA_ADDR即为BIOS ROm中确定0x15号中断的处理函数的起始存储位置,BIOS_OFFSET即为entry.S编译后给出的地址。具体代码如下

#define BIOS_IRQ_PA_ADDR(name)	(MB_BIOS_BEGIN + BIOS_OFFSET__##name)
#define BIOS_IRQ_FUNC(name)	((char *)&bios_rom[BIOS_OFFSET__##name])
#define BIOS_IRQ_SIZE(name)	(BIOS_ENTRY_SIZE(BIOS_OFFSET__##name))

 

static void setup_irq_handler(struct kvm *kvm, struct irq_handler *handler) {
    // 创建临时的IVT表项
	struct real_intr_desc intr_desc;
	void *p;

	p = guest_flat_to_host(kvm, handler->address);
	memcpy(p, handler->handler, handler->size);
    
    // 组织这个IVT表项
	intr_desc = (struct real_intr_desc) {
		.segment	= REAL_SEGMENT(MB_BIOS_BEGIN),
		.offset		= handler->address - MB_BIOS_BEGIN,
	};

	DIE_IF((handler->address - MB_BIOS_BEGIN) > 0xffffUL);
    // 记录到KVM数据结构中
	interrupt_table__set(&kvm->arch.interrupt_table, &intr_desc, handler->irq);
}

setup_irq_handler函数将0x15号中断的处理函数bios_int15的实现复制到这里,即BIOS ROM区域,address初始是Guest视角的GPA,因此需要转化为HVA。除了复制中断处理函数实现到BIOS ROM外,函数setup_irq_handler还需要负责设置IVT表项。在组织好所有的表项之后调用函数interrupt_table__copykvm->interrupt_table中的IVT表项一次性地复制到IVT。因为IVT存储在物理内存0处,所以传给guest_flat_to_host的第二个参数为0 (代码[1])。

 

至此,KVM的BIOS所有设置完成

 

0XFF Reference

  1. Wiki real mode
  2. https://frankjkl.github.io/2019/03/12/Linux%E5%86%85%E6%A0%B8-%E5%BC%95%E5%AF%BC%E5%90%AF%E5%8A%A8/
  3. Inside the Linux Virtualization Principle and Implementation