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

16位实模式转32位保护模式失败:Bootloader加载GDT后停滞排查

实模式转保护模式Bootloader崩溃排查方案

针对你遇到的“加载GDT后无输出”问题,以下是实模式转保护模式的常见坑和排查步骤:

1. 检查GDT伪描述符格式

lgdt指令加载的是6字节的伪描述符(2字节长度 + 4字节基地址),而非GDT表本身,这是新手最容易踩的坑:

  • 错误示例:直接把GDT表地址传给lgdt,会导致CPU读取错误数据直接崩溃。
  • 正确定义示例:
    gdt_descriptor:
        dw gdt_end - gdt_start - 1  ; 注意:长度必须是GDT总字节数-1
        dd gdt_start                ; GDT的线性基地址
    
  • 务必验证长度计算:如果GDT总字节数是N,长度字段必须是N-1,否则CPU会越界访问GDT触发异常。

2. 验证GDT段描述符正确性

段描述符的权限位、基地址、限长错误会直接导致模式切换失败:

  • 空段必须全0,不能有任何非零值。
  • 32位代码段/数据段的标准格式:
    gdt_start:
        dd 0x00000000  ; 空段描述符
        dd 0x00000000
    
    gdt_code:          ; 32位代码段
        dw 0xFFFF       ; 段限长0-15位
        dw 0x0000       ; 基地址0-15位
        db 0x00         ; 基地址16-23位
        db 0x9A         ; 权限位:存在(1)、特权级0、代码段、可执行、可读
        db 0xCF         ; 限长16-19位 + 标志:4KB粒度、32位段
        db 0x00         ; 基地址24-31位
    
    gdt_data:          ; 32位数据段
        dw 0xFFFF
        dw 0x0000
        db 0x00
        db 0x92         ; 权限位:存在(1)、特权级0、数据段、可写
        db 0xCF
        db 0x00
    gdt_end:
    
  • 权限位错误是重灾区:比如代码段的0x9A写成0x92,会导致远跳时CPU认为是不可执行段,直接抛出异常。

3. 检查CR0设置后的远跳转

设置CR0的PE位后,必须立即执行远跳转清空指令预取队列,否则CPU会继续执行实模式预取的指令,导致崩溃:

mov eax, cr0
    or eax, 0x1     ; 置位PE(保护模式启用位)
    mov cr0, eax
    ; 远跳转到32位代码段,段选择子=GDT中代码段索引*8
    jmp 0x08:protected_mode_start  ; 0x08是代码段选择子(空段索引0,代码段索引1,1*8=0x08)
  • 段选择子计算:每个段描述符占8字节,索引从0开始,TI位(第2位)设0表示使用GDT,RPL位(0-1位)设0表示特权级0。

4. 分步调试定位故障点

在关键步骤前后添加实模式打印,确认故障发生位置:

  • 在lgdt [gdt_descriptor]后添加打印:
    mov si, msg_gdt_loaded
      call print_string  ; 调用你已有的实模式字符串打印函数
    
    • 如果这一步没输出,说明lgdt指令本身触发了异常,大概率是伪描述符格式错误。
  • 在设置CR0前添加打印,确认能走到这一步,再逐步缩小排查范围。

5. 确认A20线真的启用成功

即使你认为已经打开A20,也要验证:如果A20未真正启用,访问1MB以上内存会回绕,导致GDT或代码被覆盖:

mov ax, 0xFFFF
    mov es, ax
    mov al, byte [es:0x0000]  ; 读取0xFFFF0地址
    mov ah, byte [0x100000]   ; 读取0x100000地址
    cmp al, ah
    je a20_not_enabled        ; 两者相等说明A20未打开

6. 保护模式下栈的有效性

进入保护模式后,需确保数据段覆盖栈的地址范围,最好重新设置栈指针:

[bits 32]
protected_mode_start:
    mov ax, 0x10  ; 数据段选择子(GDT中第三个条目,索引2,2*8=0x10)
    mov ds, ax
    mov ss, ax
    mov esp, 0x90000  ; 将栈顶设到0x90000,远离Bootloader和内核加载区域

7. QEMU调试工具使用

用QEMU+GDB精准定位问题:

  • 启动QEMU调试模式:
    qemu-system-x86_64 -drive format=raw,file=disk.img,index=0,if=floppy -s -S
    
  • 打开GDB连接:
    gdb
    target remote localhost:1234
    
  • 设置断点、单步执行:
    break *0x7cXX  # XX是你代码在Bootloader中的偏移地址
    si             # 单步执行
    info registers # 查看寄存器状态
    info gdt       # 检查GDT加载情况
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 00:55:58