Skip to content

门闩住在 MSR 里 ​

铺垫都齐了,咱们去拔门闩。长模式的使能位不住在 CR0、CR4 这些老地方,它住在一个咱们没打过交道的处所:MSR,它的中文名叫型号特定寄存器。您可以把它想成一柜子编了号的抽屉,每个抽屉里装的都是一组开关,操作系统就是拿编号寻址的。读写的约定是一对指令:咱们在 ECX 里放抽屉编号,rdmsr 把那个抽屉里 64 位的值读进 EDX:EAX,高的一半住的是 EDX,低的一半住的是 EAX,而 wrmsr 再原路写回去。这对指令只在特权级 0 的户口下才肯干活,而咱们从上一卷起,就是 0 级的户口了。

装门闩的抽屉就是编号 0xC0000080 的 EFER。抽屉里跟咱们有关的住户就一位:LME,它占的是第 8 位、值 0x100,管的正是长模式的使能。但它不是独居的,而隔着三个门牌,住的正是 SVME:第 12 位、值 0x1000,AMD 硬件虚拟化的开关,跟咱们眼下的活八竿子打不着。而两位就住在同一个抽屉里、门牌只差一个零,这正是咱们马上要讲的旧事的病根。咱们把这一卷的写法摆在 boot/lm/lm.hpp 里:

C++
inline constexpr unsigned int kMsrEfer = 0xC0000080;
inline constexpr unsigned int kEferLme = 0x100;
inline constexpr unsigned int kEferSvme = 0x1000;

inline constexpr unsigned int kCr4Pae = 0x20;
inline constexpr unsigned long kCr0Pg = 0x80000000;

两位邻居并排地住着,各带各的名牌,写的就是 Lme 和 Svme,名字里就写着自己的地界。断言跟在后面又把了一道关,static_assert(kEferLme != kEferSvme);,要求的就是两个值互不相等,给手滑的人再留一道闸。您再留意的话,会发现这几个常量保的是裸 unsigned,没穿 BitMask 的词汇:它们是要被喂进纯汇编当立即数的,而词汇管的是 C++ 里的位,汇编串里的位,就交给常量自己的名字管。而两套方言,也就这样各守各的地界了。

为什么说并排和名牌是防线?因为当年的第一遍里,这里是没有防线的。现在咱们就把当年那一晚真走一遍吧。

序列写完了:建表、装 CR3、开 PAE、拨 LME、再装一回 GDT、开 PG、远跳,咱们一样都没有省,笔者按下运行。上一行还是 32 位世界的那句 alive,而下一瞬,QEMU 的窗口就没了。这就是三重故障的现场。而这样的链条咱们上一卷就见过:CPU 抛了异常,咱们没有中断表,所以第一次没人接,再抛的时候接的人还是没有,等抛到第三次、机器就放弃治疗直接复位了。而咱们的 run 命令带着 -no-reboot,所以 QEMU 连装模作样的重启都不给,直接就退场了。咱们什么都看不到:没有报错、没有输出、也没有遗言,三重故障就是这样的脾气。

那怎么查?咱们跟着当年的推理链一步一步走。第一步咱们验桥:在 GDB 里 x/8gx 0x3000,咱们看到 PD 的四颗大页项全都老老实实地在场,连字节的值都对得上,而往上两级的指针项也是对的——桥铺得没毛病。第二步咱们验 PAE:CR4 读回来的值是 0x20,而该亮的 bit 5 也确实亮了,这一步也是对的。第三步是三颗寄存器的联读,这一眼就定了案:cr0 是 0x11,PE 亮着而 PG 没亮,这就是当年 GDB 里读到的现场,机器就死在置 PG 前后的那一段。cr4 的读数还是 0x20,也是对的。而 efer 是 0x1000:抽屉里只有 SVME 亮着,而 LME 根本就没被拨过。病根找到了:当年的常量把邻居的门牌抄成了自家的,.set EFER_LME, 0x1000,就差了一个零,拨亮的是虚拟化的开关,而长模式的门闩从头到尾都没动过。

这样的推理链,值得咱们单独看一眼:每排除一样嫌疑,剩下的嫌疑就少一样。桥验过是对的,页表的嫌疑就排掉了。PAE 验过也是对的,又排掉了一样。剩下的嫌疑只有 EFER,而它的读数,把缺的那个位指得明明白白。咱们再说崩点本身。置 PG 的指令,并不整体验收长模式的资格——不过也不能说它什么都不查:像 LME 拨了而 PAE 没开的组合,置 PG 换来的是当场的通用保护异常(#GP)。可当年这个案子缺的是 LME,不在被硬查的组合里。而 PG 一亮,机器就按 32 位 PAE 分页的路数,去走咱们这套按四级表形状立的数据,走是走不通的,死成了三重故障、到不了远跳——而死在哪一拍,当年的笔记也只记到执行完或紧接着的样子,咱们就只说到这里。长模式其实是一台状态机,而不只是页表够不够好的事:在 PG 之前,PAE、LME、合法的页表三样缺一不可,而缺了哪一样,推门的那一拍都是同一种死法。咱们把这三样叫作推门前的资格,是给这套死法起的模型名。而那一晚之后,笔者对位常量的敬畏进了骨头。咱们在位词汇一节里立的法,它的根子就埋在这儿。

旧事还有一个安静的尾声:咱们拨上 LME 的时候,机器是不动的,它只替咱们把“请求长模式”记了下来。真正盖章的时刻在 PG 亮起的那一拍:激活完成,CPU 自己就把 EFER 里的另一位 LMA、也就是第 10 位,点亮成长模式激活的回执。等验收的环节,咱们拿 GDB 去读 EFER,读数是 0x500:低处的 0x100,是咱们写的请求,而高处的 0x400,是 CPU 盖的章。请求与回执就这样同在一个寄存器里了。

r05_long_mode-2-gfc5569b · fc5569b · 2026-10-01