正常
调试档案 003 · 进长模式的顺序与标志位陷阱
从
document/notes/003/1.md提炼,配套 003 · 跨进长模式。从 32 位保护模式进 64 位长模式,几乎所有故障都归结到两类:进入序列的顺序错了,或某个页表/段描述符的标志位错了。下面是最典型的几个。
案例一:CR0.PG 一置位就三重故障
- 症状:
CR0 |= PG那条指令执行完(或紧接着),机器三重故障重启,根本到不了远跳。 - 原因:分页一开,所有地址翻译都走我们搭的页表;页表要是有问题,CPU 连下一条指令都翻译不出来。典型是:(1) 某层表没清零,残留字节被当成有效项去查;(2)
PD的大页项漏了Large(PS)位,CPU 以为还要往下查 PT,查到不存在的 PT → 缺页;(3) 中间层指针写错地址,指向的不是下一张表。 - 定位:置
CR0.PG那条设断点,用 GDB 看三张表——x/1gx 0x1000看PML4[0]应是0x2003;x/1gx 0x2000看PDPT[0]应是0x3003;x/4gx 0x3000看PD[0..3]应是0x83/0x200083/0x400083/0x600083这种带 PS 位的 2MB 页。 - 修复:
setup_page_tables里三张表先rep stosl清零;中间层项|0x03、大页项|0x83(务必带0x80的 Large 位)。 - 防复发:搭页表时先清零再填,是铁律;大页项的 PS 位用常量
PAGE_FLAGS(present|writable|large)拼,别手写裸数字。
案例二:置 EFER.LME 或开 PG 时 #GP
- 症状:执行到
wrmsr或CR0.PG置位时触发 #GP,而非页表缺页那种三重故障。 - 原因:进入长模式的顺序是 Intel 定死的:
CR4.PAE→EFER.LME→CR0.PG,缺一不可、不可乱序。常见错误:忘了开CR4.PAE就置 LME;或先开CR0.PG再置 LME(等于在还没"请求"长模式时分页就开了)。CPU 对这条序列的检查是硬件级的,违反就 #GP。 - 定位:看
enter_long_mode里几条movl %cr*/rdmsr的先后,和 SDM §9.8.1.1 的序列逐条对。 - 修复:严格按
CR3→CR4.PAE→EFER.LME→lgdt→CR0.PG→远跳的顺序;改控制寄存器一律orl保留其它位。 - 防复发:把这串序列当模板,顺序记死;
EFER.LME设了不生效、要等CR0.PG那拍才激活——理解了这点就不会乱排。
案例三:远跳进 long_mode_entry 后又崩
- 症状:远跳看似成功(到了 64 位入口),但执行几条就崩,或 GDB 反汇编全乱。
- 原因:64 位代码段描述符的 L/D 位错了。L=1(长模式代码段)时 D 必须为 0;
0x00AF9A000000FFFF的 flags 高 nibble 是0xA(1010:G=1,D=0,L=1)。要是写成0xC(1100:D=1,L=0),它就退化成普通 32 位段,CPU 不认它是长模式,用 64 位译码去跑 32 位段(或反之),译码错位崩掉。 - 定位:GDB
x/8bx &gdt_code64看字节,第 7 个字节(byte[6])应是0xAF(不是0xCF/0x8F)。 - 修复:确认 64 位代码描述符是
0x00AF9A000000FFFF(flags=0xA,L=1,D=0);远跳用0x18选子。 - 防复发:32 位段、64 位段、数据段的 byte[6] 区分清楚——
0xCF(32 位 code/data)、0xAF(64 位 code,L=1)、0x8F(64 位 data)。别混用。
案例四:链接报"64 位重定位"错误
- 症状:链接 Stage2 时报
relocation truncated或 unsupported 64-bit relocation 一类错误,根本产不出stage2.bin。 - 原因:Stage2 按 32 位 ELF(
elf_i386)链接,但gdt64_ptr里用了.quad gdt想表达 64 位 base,触发 32 位 ELF 不支持的 64 位重定位。这是纯工具链问题,和 CPU 无关。 - 定位:看
gdt64_ptr的定义,是不是用了.quad。 - 修复:用
.long gdt+.long 0两段拼出 64 位 base——GDT 在低地址,高 32 位是 0。 - 防复发:32 位 ELF 里凡是"需要 64 位地址"的地方,都用
.long+.long拼,不用.quad。
一句话总结
进长模式的故障,十有八九是顺序(PAE→LME→PG)或标志位(大页 PS 位、64 位段的 L/D 位)。把"页表先清零再填 + 大页带 PS 位"、"CR 改位用 orl 保留其它位"、"64 位代码段 flags=0xA"这三条钉死,这一跳就稳了。