PAYATHON 2026

启用 x86-64 long mode 时,QEMU 为何重置?

数据小许

结论

先检查 PD 中两个 2 MiB 页面目录项的物理基址。启用分页后,当前代码地址 0x8000 和栈地址 0x90000 都必须映射到实际所在的物理内存。你贴出的页表将虚拟地址 0x00000000–0x001FFFFF 映射到物理地址 0x00100000–0x002FFFFF,所以 0x8000 会被翻译为 0x108000,而不是 bootloader 实际加载的位置 0x8000。CPU 随后可能从错误的位置取指并引发异常;如果异常处理也失败,QEMU 通常会因 triple fault 重置。

先修正页表映射

在 CR4 中启用 PAE 后,使用 2 MiB 页时,PD 项的 PS 位(0x80)应置位,基址也必须按 2 MiB 对齐。要让第二阶段继续在原地址执行,并使 0x90000 处的栈可用,至少要把低 2 MiB identity-map 到同一段物理内存:

align 4096
PD:
    dq 0x00000000 | 0x83    ; VA 0–2 MiB -> PA 0–2 MiB

当前的 0x00100000 + 0x83 表示虚拟地址 0–2 MiB 映射到物理地址 1–3 MiB;0x00300000 + 0x83 表示虚拟地址 2–4 MiB 映射到物理地址 3–5 MiB。如果还需要映射下一段连续的 2 MiB,通常应使用物理基址 0x00200000:

    dq 0x00000000 | 0x83
    dq 0x00200000 | 0x83

页表自身、当前指令、GDT,以及切换后使用的栈,都应位于已映射区域。PML4、PDPT、PD 已通过 align 4096 对齐;还要确认这些表所在的物理地址可访问,且各级表项指向正确的物理地址。

检查切换顺序和跳转

代码中已经包含切换所需的主要步骤:先设置 CR4.PAE,再把 PML4 的物理地址写入 CR3,然后在 IA32_EFER 中设置 LME,最后设置 CR0.PG。启用分页后,紧接着的远跳转必须能正常取指,并使用 long-mode code segment。

gdt64 的代码段描述符 0x00209A0000000000 设置了 L 位,远跳转选择子 0x08 指向该描述符。修正页表后如果仍然重置,请逐项确认 CR3 指向 PML4 的物理地址,PML4、PDPT 和 PD 的 Present 位都已设置,且 far jump 的目标地址已映射。也要确认所用的 CPU 模型支持 long mode。

可以在调试器中检查设置 CR0.PG 前后的寄存器,并在重置前查看 QEMU 的异常信息。如果分页后出现取指页错误或其他异常,这些信息通常有助于区分页表映射错误与描述符、跳转目标等问题。

另外检查 boot sector 填充表达式

第一阶段代码末尾写的是:

times 510 - ($ - $) db 0
dw 0xAA55

$ - $ 恒为零,因此这行会固定填充 510 字节,而不是把当前内容补齐到第 510 字节。标准写法应使用 $$ 表示当前节的起始位置:

times 510 - ($ - $$) db 0
dw 0xAA55

否则,生成文件中的 0xAA55 不一定位于 boot sector 要求的偏移处。如果实际运行的第一阶段与贴出的代码完全一致,这处也需要修正。它和分页后的地址映射是两个独立的检查点。

备注:内容仅供参考。