iT邦幫忙

2025 iThome 鐵人賽

DAY 4
0
自我挑戰組

30 天 hypervisor 入門系列 第 4

Day 04 讓 Guest 輸出 HELLO

  • 分享至 

  • xImage
  •  

昨天翻了 Intel 手冊,知道哪些指令可觸發 VM-exit。
今天來做個做小實驗,觀察 Guest 執行到敏感指令時,KVM 如何把控制權交回 host。KVM 文檔

Guest code 範例

static const uint8_t guest_code[] = {
    0xBA, 0xE9, 0x00,       // mov dx, 0x00E9
    0xB0, 'H', 0xEE,        // mov al,'H'; out dx,al
    0xB0, 'E', 0xEE,
    0xB0, 'L', 0xEE,
    0xB0, 'L', 0xEE,
    0xB0, 'O', 0xEE,
    0xF4,                   // hlt
};

這段程式的作用是將字符 HELLO,透過 OUT 依序送到 0xE9 I/O port,最後透過 HLT 讓 CPU 休眠。
因此只要在 host 這看到終端打印 HELLO,並退出,就代表 VM-exit 的觀測成功。

HOST 如何觀測 VM-exit?

在 host 端,透過 ioctl(vcpufd, KVM_RUN, 0) 讓 Guest 跑起來。
每當 Guest 執行敏感指令(這裡是 OUT 與 HLT),處理器就會觸發 VM-exit,KVM 會把原因填進 struct kvm_run。
讀取 exit_reason 即可判斷 VM-exit 的來源,再做相應處理即可。

switch (run->exit_reason) {
case KVM_EXIT_IO:
    printf("[host] KVM_EXIT_IO: %c\n", *((char *)run + run->io.data_offset));
    break;
case KVM_EXIT_HLT:
    puts("[host] guest halted");
    break;
}

以下為執行結果。
https://ithelp.ithome.com.tw/upload/images/20250918/20178814ojZZ2Y8OHN.png

如何配置 vCPU?

在 kvm 中,配置 vCPU 主要靠兩組結構體。
struct kvm_regs(通用暫存器)、struct kvm_sregs(特殊暫存器)
只要通過 ioctl 向 kvm 配置這兩組暫存器,並配置記憶體,即可讓 Guest 從我們只定的入口執行。
流程大概像這樣:

  1. 打開 KVM 並建立 VM
    int kvm = open("/dev/kvm", O_RDWR | O_CLOEXEC);
    int vmfd = ioctl(kvm, KVM_CREATE_VM, 0);
  1. 配置 Guest 記憶體並載入程式碼
#define GUEST_PHY_START  0U
#define GUEST_LOAD_GPA   0x1000U
#define GUEST_MEM_SIZE   (2 * 1024 * 1024)
void *guest_mem = mmap(NULL, GUEST_MEM_SIZE, PROT_READ | PROT_WRITE,
                       MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
uint8_t *guest_ptr = (uint8_t *)guest_mem + (GUEST_LOAD_GPA - GUEST_PHY_START);
memcpy(guest_ptr, guest_code, sizeof(guest_code));
struct kvm_userspace_memory_region region = {
    .slot = 0,
    .flags = 0,
    .guest_phys_addr = GUEST_PHY_START,
    .memory_size = GUEST_MEM_SIZE,
    .userspace_addr = (uint64_t)guest_mem,
};
ioctl(vmfd, KVM_SET_USER_MEMORY_REGION, &region);

這裡的 memcpy 就像 loader,把程式碼搬運到 Guest RAM。
GUEST_LOAD_GPA 選擇 0x1000,是因為傳統 real mode 低位址是中斷向量表(其實在 KVM 裡可放 0x0)。

3.建立 vCPU

int vcpufd = ioctl(vmfd, KVM_CREATE_VCPU, 0);
int vcpu_mmap_size = ioctl(kvm, KVM_GET_VCPU_MMAP_SIZE, 0);
struct kvm_run *run = mmap(NULL, vcpu_mmap_size,
                           PROT_READ | PROT_WRITE, MAP_SHARED,
                           vcpufd, 0);

vcpufd 是 vCPU 的控制 fd,struct kvm_run 是 kernel 與 userspace 共享的 exit 狀態。
4.配置暫存器

#define REALMODE_SEG(sel) (struct kvm_segment){ \
    .base     = (uint32_t)(sel) << 4, \
    .limit    = 0xFFFF, \
    .selector = (sel), \
    .type     = 0x3, \
    .present  = 1, \
    .s        = 1, \
}

struct kvm_sregs sregs;
ioctl(vcpufd, KVM_GET_SREGS, &sregs);

sregs.cr0 = 0x10U;
sregs.cr2 = 0;
sregs.cr3 = 0;
sregs.cr4 = 0;

SET_SREG(sregs.cs, 0);
SET_SREG(sregs.ds, 0);
SET_SREG(sregs.es, 0);
SET_SREG(sregs.fs, 0);
SET_SREG(sregs.gs, 0);
SET_SREG(sregs.ss, 0);

ioctl(vcpufd, KVM_SET_SREGS, &sregs);
struct kvm_regs regs = {0};
regs.rflags = 0x00000002u;
regs.rip    = GUEST_LOAD_GPA;
ioctl(vcpufd, KVM_SET_REGS, &regs); 

到這裡,就完成了 vCPU 的基礎配置,接著執行 ioctl(vcpufd, KVM_RUN, 0) 就可以讓 Guest 跑起來,並在 host 端觀察到 OUT 和 HLT 帶來的 VM-exit。


上一篇
Day 03 從 Intel 手冊看 VM-Exit
下一篇
Day 05 設計簡易的 VM-exit 處理表
系列文
30 天 hypervisor 入門11
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言