KVM环境下启动代码执行wrmsr指令触发通用保护故障排查
启用EFER.LM位触发通用保护故障的问题排查
问题背景
我正尝试使用KVM将处理器启动至长模式,已基于LWN示例搭建了小型KVM虚拟机监控程序。当前执行的启动流程如下:
- 通过KVM将初始eip设置为0x1000,从该地址启动代码而非复位状态;
- 加载GDT,长跳转至32位跳板代码;
- 在跳板代码中,启用cr4中的PAE、PSE位,将页目录地址加载至cr3,随后设置EFER寄存器的LM位时触发通用保护故障(已通过配置IDT并触发陷阱处理程序验证)。
相关代码
__reset: // 0x1000 cli mov $gdtr, %eax lgdt (%eax) movl %cr0, %eax orl $1, %eax movl %eax, %cr0 jmp $0x08, $trampoline trampoline: movl $STACK_START, %esp movl %esp, %ebp // Set up the protected-mode data segment registers movl $0x10, %eax // Our data segment selector movl %eax, %ds // -> DS: Data Segment movl %eax, %es // -> ES: Extra Segment movl %eax, %ss // -> SS: Stack Segment movl $0, %eax // Zero segments not ready for use movl %eax, %fs // -> FS movl %eax, %gs // -> GS call fill_idt movl $idt_descriptor, %eax lidtl (%eax) // 9.8.5 Initializing IA-32e Mode, pg 3153 intel-manual // step-1 disable cr0.paging // it is disabled already right now // step-2 enable PAE. movl %cr4, %eax orl $(CR4_PAE | CR4_PSE), %eax movl %eax, %cr4 // step-3 set cr2 to level-4 or level-5 // set cr3 to a pointer to pml4 movl $boot_p4, %eax movl %eax, %cr3 // step-4 efer.lme = 1 // enable long mode and the NX bit movl $MSR_EFER, %ecx rdmsr orl $(EFER_LM | EFER_NX | EFER_SCE), %eax wrmsr // <------ IT FAULTS HERE. GENERAL PROTECTION FAULT .p2align 4 NULL_DESC: .quad 0x0000000000000000 CODE_DESC: .word 0xFFFF // limit low .word 0 // base low .byte 0 // base middle .byte 0b10011010 // access .byte 0b11001111 // granularity .byte 0 // base high DATA_DESC: .word 0xFFFF // data descriptor .word 0 // limit low .byte 0 // base low .byte 0b10010010 // access .byte 0b11001111 // granularity .byte 0 // base high gdtr: .word gdtr - NULL_DESC - 1 .long NULL_DESC
执行wrmsr前的寄存器状态
rip = 0x000000000000189e // hlt instruction added just before wrmsr rax = 0x0000000000000901 rbx = 0x00000000000020e0 rcx = 0x00000000c0000080 rdx = 0x0000000000000000 rsp = 0x000000000000b000 rbp = 0x000000000000b000 rdi = 0x000000000000a000 rsi = 0x0000000000000000 rflags = 0x0000000000000002 efer = 0x0000000000000000 cr0 = 0x0000000060000011 cr2 = 0x0000000000000000 cr3 = 0x0000000000003000 cr4 = 0x0000000000000030
疑问点
根据Intel手册“9.8.5 Initializing IA-32e Mode”章节,64位模式一致性检查失败的情况包括:
- 在分页启用时尝试启用或禁用IA-32e模式;
- IA-32e模式已启用时,在启用PAE前尝试启用分页;
- IA-32e模式激活时尝试禁用PAE;
- 尝试激活IA-32e模式时当前CS的L位已设置;
- 尝试激活IA-32e模式时TR包含16位TSS。
前4个条件均已满足,我不理解第5点,且未设置任何TSS段。请问为何该指令会触发通用保护故障?
解决方案
核心问题出在**未初始化任务寄存器(TR)**上。虽然你没主动设置TSS,但处理器复位后TR默认指向一个无效的16位TSS描述符(TR的复位值对应16位模式的空TSS),这正好触发了Intel手册里的第5条检查:尝试激活IA-32e模式时TR包含16位TSS。
具体修复步骤
- 在GDT中添加一个32位TSS描述符(不需要实际的TSS结构,仅需保证描述符格式为32位有效类型即可):
TSS_DESC: .word 0x0000 // 限制低(暂时填0,无需实际TSS内容) .word 0x0000 // 基址低 .byte 0x00 // 基址中 .byte 0b10001001 // 访问位:P=1(存在)、DPL=0、类型=32位TSS(0x9) .byte 0b00000000 // 粒度位(无需扩展,填0) .byte 0x00 // 基址高
- 在加载GDT并进入保护模式后,使用
ltr指令加载该TSS描述符的选择子(假设它是GDT中第4个条目,选择子为0x18):
// 在lgdt之后、跳转trampoline之前或者trampoline开头添加 movw $0x18, %ax ltr %ax
额外验证点
- 确认
boot_p4(PML4表)地址按4KB对齐(寄存器中cr3=0x3000,低12位为0,符合要求); - 确认CR0的PG位(第31位)为0(cr0=0x60000011,PG位是0,分页未启用,符合初始化要求)。
完成TR初始化后,再执行wrmsr设置EFER.LM位就不会触发通用保护故障了。
内容的提问来源于stack exchange,提问作者zephyr0110
相关产品推荐
相关产品推荐

