You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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位模式一致性检查失败的情况包括:

  1. 在分页启用时尝试启用或禁用IA-32e模式;
  2. IA-32e模式已启用时,在启用PAE前尝试启用分页;
  3. IA-32e模式激活时尝试禁用PAE;
  4. 尝试激活IA-32e模式时当前CS的L位已设置;
  5. 尝试激活IA-32e模式时TR包含16位TSS。

前4个条件均已满足,我不理解第5点,且未设置任何TSS段。请问为何该指令会触发通用保护故障?


解决方案

核心问题出在**未初始化任务寄存器(TR)**上。虽然你没主动设置TSS,但处理器复位后TR默认指向一个无效的16位TSS描述符(TR的复位值对应16位模式的空TSS),这正好触发了Intel手册里的第5条检查:尝试激活IA-32e模式时TR包含16位TSS。

具体修复步骤

  1. 在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         // 基址高
  1. 在加载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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 07:36:28