Skip to content

调试档案 002 · 实模式到保护模式切换的几个命门

document/notes/002/1.mddocument/notes/002/2.md 提炼,配套 002 · 进入保护模式。实模式 → 保护模式这一跳,要在几条指令内同时切换寻址模型、译码宽度、CS 来源,几乎每个环节都能独立制造一次三重故障。下面是三个最常要命的。

案例一:lgdt 之后直接崩

  • 症状:lgdt gdt_ptr 这条执行完(或执行中),机器三重故障重启,根本走不到置 PE 那步。
  • 原因:lgdt 在实模式下按 DS << 4 + 偏移 计算操作数地址。如果 DS 还是 BIOS/Stage2 留下的脏值,DS<<4 会在 gdt_ptr 的链接偏移上再叠一段,lgdt 从错误内存读 GDTR,base/limit 全是垃圾,后续一访问段就 #GP → 三重故障。
  • 定位:GDB 在 lgdt 前后查看 GDTR(info registers 或相应的 monitor 命令),limit 应为 23(3 项 × 8 − 1),base 应落在 0x81xx 附近(stage2 链接在 0x8000);base 一眼不对就是 DS 问题。
  • 修复:lgdt 前先 movw $0,%ax; movw %ax,%dsDS 清零,让实模式寻址退化成 偏移 = 绝对地址。Stage2 同时要确保链接在 . = 0x8000(=载入地址),这样 gdt_ptr 的偏移就是它真实的线性地址。
  • 防复发:lgdt 前置 DS=0 写进 PM 切换的固定开场;链接脚本里 Stage2 的 . 必须等于载入地址,改其一必须确认另一个。记牢:lgdt 只搬运 6 个字节、不校验 GDT 内容,错会延迟到用选择子时才爆。

案例二:置了 CR0.PE 就原地重启

  • 症状:movl %eax,%cr0 把 PE 位置 1 之后,程序没走几条就三重故障重启,根本到不了 pm_entry
  • 原因:置 CR0.PE 那一刻,CPU "名义上"进了保护模式,但 CS 还是实模式遗留的、译码还是 16 位。必须有一条远跳(或远调用)带着新选择子强制重载 CS,保护模式才算"生效"。漏了这条,后面 .code32 编码的 32 位指令被当 16 位解码,译码错位 → 非法指令 → 三重故障。
  • 定位:在置 PE 的 movl %eax,%cr0 之后、下一条设断点单步,看 rip/eip 走向——如果没走几步就乱跳或复位,基本就是没刷新 CS。也可以看 QEMU -d int 的中断日志,会看到 #UD/#GP。
  • 修复:置 PE 后紧接着(中间别插别的要紧指令)ljmp $0x08, $pm_entry。远跳用 GAS 在 .code16 下自动生成的 16 位编码,不要手拼 ea <off16> <seg16> 机器码——手拼最容易拼错。
  • 防复发:把"置 PE"和"远跳"视为一个不可分割的动作对。任何 PM 切换代码,看到 CR0.PE=1 却没有紧随的远跳,先当作 bug。

案例三:GDB 报 Invalid register / 反汇编一堆 (bad)

  • 症状:GDB 报 Invalid register ip,或者反汇编窗口里出现一串 (bad) + rex 之类的乱码;寄存器值(比如 SP 显示成 0x000a)明显不合理。
  • 原因:译码宽度和 GDB 视角对不上。两种常见来源:(1) .code16/.code32 放错了位置——它们是汇编器指令不是 CPU 指令,只决定机器码编成 16 位还是 32 位;真正决定 CPU 译码的是 CS 指向段的 D 位。把 lgdt 之类错放到 .code32 后面,CPU 实际还在 16 位译码却拿到 32 位编码,错位崩溃。(2) 给 GDB 喂的是 stage2.bin(裸二进制,无符号、无段信息)而不是 stage2(ELF)。
  • 定位:检查 .code16/.code32 是否分布在远跳两侧(前 16、后 32);info registers 看寄存器名,实模式是 ip/sp,PM 是 eip/esp——跨过远跳这个名字会变,既是症状也是判断"是否真进 PM"的旁证。
  • 修复:对齐 .code16/.code32 位置;GDB 一律 file build/boot/stage2 用 ELF,bin 只给启动加载用,两者不可互相替代。
  • 防复发:.code16/.code32 的切换点永远锁在远跳那条指令上;调试 bootloader 永远先 file <ELF>

一句话总结

PM 切换的三个命门是寻址基址(DS=0)、CS 刷新(远跳)、译码宽度(.code16/.code32 + 段的 D 位)——三者在置 CR0.PE 前后必须一致地翻过来,任何一处停在旧状态,表现都是三重故障。把这三条和"全程 cli(无 IDT)"绑成一套固定动作,这一跳就稳了。

035_multi_terminal-40-g5d72b8b · 5d72b8b · 2026-06-26