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

开发x86_64操作系统时切换至用户态触发GPF错误的解决咨询

x86_64内核切换至Ring3用户态触发GPF错误排查

问题背景

开发基于x86架构的64位操作系统,已实现包含Null、内核代码段、内核数据段、用户代码段、用户数据段、TSS的GDT。编写switch_to_user_mode函数尝试切换至低半内存的Ring3用户空间,但执行iret指令时触发通用保护错误(GPF,中断13),栈内容显示压入的切换参数正确,但切换未完成。

相关代码

switch_to_user_mode函数

__attribute__((naked, noreturn))
void switch_to_user_mode(uint64_t stack_addr, uint64_t code_addr)
{
    asm volatile(
        "cli\n"                 // 禁用中断
        "mov $0x23, %%ax  \n"   // 加载用户态数据段选择子(Ring3)
        "mov %%ax, %%ds   \n"
        "mov %%ax, %%es   \n"
        "mov %%ax, %%fs   \n"
        "mov %%ax, %%gs   \n"
        "push $0x23       \n"   // 压入用户态SS选择子
        "push %[stack]    \n"   // 压入用户栈指针
        "pushf            \n"   // 压入RFLAGS
        "pop %%rax        \n"   // 将RFLAGS加载到RAX
        "or $0x200, %%rax \n"   // 设置IF位,切换后自动启用中断
        "push %%rax       \n"   // 压入更新后的RFLAGS
        "push $0x1B       \n"   // 压入用户态CS选择子
        "push %[entry]    \n"   // 压入用户程序入口地址        
        "iret             \n"   // 中断返回至用户态
        :
        : [stack] "r" (stack_addr), [entry] "r" (code_addr)
        : "rax"
    );
}

用户态初始化与调用代码

uint64_t create_user_function() {
    void *user_code = (void *) uheap_alloc(0x1000);

    // 无限循环机器码
    uint8_t user_program[] = {
        0xeb, 0xfe  // jmp $
    };

    printf("user_code or user_programe is null\n");
    memcpy(user_code, (void*)&user_program, sizeof(user_program));

    return (uint64_t)user_code; // 返回用户态可访问的函数指针
}


void init_user_mode(){
    uint64_t code_addr = (uint64_t) create_user_function();
    page_t *code_page = get_page(code_addr, 0, (pml4_t *)get_cr3_addr());
    code_page->rw = 0;      // 设为只读
    code_page->nx = 0;      // 设为可执行
    code_page->user = 1;    // 设为用户可访问

    uint64_t stack_base_addr = ((uint64_t) uheap_alloc(STACK_SIZE));
    for(uint64_t addr = stack_base_addr; addr < stack_base_addr + STACK_SIZE; addr += 0x1000){
        page_t *_page = get_page(addr, 0,  (pml4_t *)get_cr3_addr());
        _page->rw = 1;      // 设为可读写
        _page->nx = 1;      // 设为不可执行
        _page->user = 1;    // 设为用户可访问
    }
    uint64_t stack_top_addr = stack_base_addr + STACK_SIZE;   // 设置栈顶
    
    flush_tlb_all();
    
    printf("Starting Switching to the user mode: code addr.- %x, stack addr.- %x\n", code_addr, stack_top_addr);
    switch_to_user_mode(stack_top_addr, code_addr);
}

错误输出

[Info] Interrupt Based System Call initialized!
GDT Entry 0x0: Base=0x0 Limit=0x0 Access=0x0 Flags=0x0
GDT Entry 0x8: Base=0x0 Limit=0xFFFF Access=0x9A Flags=0xA
GDT Entry 0x10: Base=0x0 Limit=0xFFFF Access=0x93 Flags=0xA
GDT Entry 0x1B: Base=0x0 Limit=0xFFFF Access=0xFA Flags=0xA
GDT Entry 0x23: Base=0x0 Limit=0xFFFF Access=0xF2 Flags=0xA
GDT Entry 0x28: Base=0x8059A060 Limit=0x67 Access=0x8B Flags=0x0
Current stack address: 0xFFFF80007FF41FC0
Current rip address: 0xFFFFFFFF8000EF0A
Current rflags address: 0x286
Starting Switching to the user mode: code addr.- 0x1000, stack addr.- 0x7000
General protection fault (pushes an error code)
recieved interrupt: 13
Error Code: 0x0
CS: 0x8, RIP : 0xFFFFFFFF8000EA61
Stack (rsp = 0xFFFF80007FF41F70) Contents(First 26) :
  [0xFFFF80007FF41F70] = 0x1000
  [0xFFFF80007FF41F78] = 0x1B
  [0xFFFF80007FF41F80] = 0x292
  [0xFFFF80007FF41F88] = 0x7000
  [0xFFFF80007FF41F90] = 0x23
  [0xFFFF80007FF41F98] = 0xFFFFFFFF8000EBCD
  [0xFFFF80007FF41FA0] = 0x7FF28030
  [0xFFFF80007FF41FA8] = 0x7000
  [0xFFFF80007FF41FB0] = 0x3000
  [0xFFFF80007FF41FB8] = 0x7FF28008
  [0xFFFF80007FF41FC0] = 0x1000
  [0xFFFF80007FF41FC8] = 0x7000
  [0xFFFF80007FF41FD0] = 0xFFFF80007FF41FF0
  [0xFFFF80007FF41FD8] = 0xFFFFFFFF800080A1
  [0xFFFF80007FF41FE0] = 0x0
  [0xFFFF80007FF41FE8] = 0xFEBD5000
  [0xFFFF80007FF41FF0] = 0x0
  [0xFFFF80007FF41FF8] = 0x0
  [0xFFFF80007FF42000] = 0x800000015CD00037
  [0xFFFF80007FF42008] = 0xFFFF
  [0xFFFF80007FF42010] = 0x0
  [0xFFFF80007FF42018] = 0x0
  [0xFFFF80007FF42020] = 0x0
  [0xFFFF80007FF42028] = 0x0
  [0xFFFF80007FF42030] = 0x0
  [0xFFFF80007FF42038] = 0x0
System Halted!

排查与解决方法

1. 检查TSS配置与加载

x86_64下从Ring0切换至Ring3时,CPU依赖TSS保存内核栈信息(RSP0),若TSS未正确配置会触发GPF:

  • 确认已通过ltr指令加载TSS选择器(GDT中TSS条目为0x28,需执行ltr 0x28)。
  • 确保TSS的RSP0字段被设置为当前内核栈指针(调用switch_to_user_mode前,将%rsp的值写入TSS的RSP0区域)。

2. 修正RFLAGS压入指令

64位模式下,pushf指令实际压入的是32位EFLAGS并零扩展为64位,虽然不影响IF位,但建议使用pushfq明确压入完整的64位RFLAGS,避免潜在的栈帧对齐或寄存器位问题:
将pushf替换为pushfq,对应的pop %%rax无需修改,因为pop %%rax会读取64位值。

3. 检查页表全层级的User位设置

当前代码仅设置了页表项(PT级)的user=1,但上级页表(PML4、PDPT、PD)的user位也必须设为1,否则CPU会判定该内存区域为内核态专属,访问时触发GPF:

  • 修改get_page相关逻辑,确保用户态内存对应的所有层级页表项(从PML4到PT)的user位都被置1。

4. 验证用户栈的映射完整性

用户栈顶地址为0x7000,需确保栈的所有页(从stack_base_addr到stack_top_addr)都被正确分配并映射物理内存,且权限设置正确(rw=1、nx=1、user=1)。可添加日志输出确认uheap_alloc返回的地址是否有效,以及所有页表项是否正确修改。

5. 确认GDT加载正确性

虽然GDT条目输出显示段描述符配置正确,但需确保已通过lgdt指令正确加载GDT,且GDT的基址和限长设置无误。

内容的提问来源于stack exchange,提问作者baponkar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 09:39:51