验证与调试现场
照咱们的老路数,收工前咱们要回一趟 host。ctest 的清单里,boot 的出品添到了第三件:test_gdt。它的身板跟前两件是一个模子——编译期能查的早就升了格,住进了 gdt.hpp 那排 static_assert 里,测试文件里留下的只有三个哨兵。
哨兵一把 access 字节重新当位段读了一遍。这一回干活的,是 bit_ops 家的 HighNibble 和 LowNibble,一个取的是字节的高四位,另一个取的是低四位。这对小函数咱们早就写好了,一直没轮上正经的活计,到这儿才等来了头一位客户——access 字节的含义,正好就在半个字节的边界上分的家,高四位里合住的是 P 位、DPL 和 S 位,低四位整个是 type 的地盘:
TEST("gdt: flat descriptors decode bit by bit") {
auto const kCode = kTemplate.code;
ASSERT_TRUE(HighNibble(kCode.access) == 0x9); // P=1,DPL=00,S=1
ASSERT_TRUE(LowNibble(kCode.access) == 0xA); // type=代码/可读
ASSERT_TRUE(LowNibble(kTemplate.data.access) == 0x2);
ASSERT_TRUE((kCode.flags_limit_high >> 4) == 0xC);
ASSERT_TRUE((kCode.flags_limit_high & 0xF) == 0xF);
}您认出来了,这就是咱们逐位讲 0x9A 的那部分,只不过换成了机器可读的版本:人眼一位一位认过的东西,机器再一位一位地确认一遍,两边的答案得一致。
哨兵二管的是扁平语义:两个段的 base 低位都是零,合成出来的限长恰好等于 0xFFFFFFFF,从零铺满了整个 4GB。哨兵三查的是尺寸。咱们把它放在 LP64 的 host 世界里,又闸了一道:描述符 8 字节、整表 24 字节、GDTR 6 字节。这层的意思跟 VESA 那会儿是一样的,host 的 64 位 ABI,对齐的口味跟 16 位世界不一样,咱们看着它们在这边也原样站住了,packed 就真的一字节都没漂。
编译期、host、QEMU 的三道都过了,咱们才算验完。QEMU 那一道用的还是老命令,面板的尾部:
[stage2] leaving real mode
[pm] 32-bit world alive这两行咱们开头就预告过,现在它们真的肩并肩躺在终端里了。头一行是 16 位世界打的最后一行字,打完它就调进了切换序列,后一行是 32 位世界的第一句话。隔在它们中间的,就是 cli、lgdt、一个 bit 加一句远跳的组合。看到了第二行,链路就是通的。
要是您还想再较真一步,咱们可以请 GDB 来取证。您给 run 的 QEMU 命令手工添上 -s -S 两位参数再启动,咱们在另一头开 GDB、连上 target remote :1234,断点下到的是 PmEntry,放行了以后再看 info registers。这一眼要看的是两样东西:CS 的值是 0x08,正是咱们的代码段选择子,实打实的证据,而寄存器窗里写的是 eip,不再是 ip 了。寄存器的名字,是跟着 CPU 的状态走的:16 位的世界里它叫 ip,过了远跳它就叫 eip。当年笔者调试的时候,对着 GDB 报的一句 Invalid register ip 发过懵:明明前一秒还打得开的名字,怎么就失效了。现在咱们知道了,报错的句子本身就是个路标,说的就是:您已经不在原来的世界里了。
调试的这段路上,还有两桩旧事值得咱们记着,因为它们的脾气到现在也没改。
头一桩:错表不炸在 lgdt 的头上。lgdt 指令干的就是往 GDTR 里搬六个字节,内容的合法性查不查?它一个字都不查的——合法性要等到头一回真正用某个段选择子做访问时才查。所以表里哪一位要是写错了,咱们要隔上几条指令才等得到爆炸,炸的位置跟作案现场也就隔了一段距离。当年排查这类的问题,最费劲的就是走这段回头路:段访问头一回出事的位置好找,病根却埋在 GDT 的表内容里,咱们得从炸点一路往回查到 lgdt 和表内容才见分晓。这一遍不一样了:整张表都是从工厂里出来的,想动 base 也得走工厂的路。改了工厂,kTemplate 跟着就变了,断言读的恰恰是它,编译期当场就把错值拦下了。这桩旧事到咱们这一遍,算是提前了结。
咱们要讲的另一桩旧事,说的是往调试器里喂哪个文件,这是有讲究的。当年笔者在 GDB 里看 pm_entry,它报出来的地址是 0x66,一个明显不是代码的数,查了半天,才发现是把裸二进制的 bin 当带符号的 ELF 喂进去了,GDB 把它当数据解释了一通,符号全都错了位。两个文件各管各的差事:ELF 里带着符号和段的信息,是给调试器认门牌用的。bin 是从 ELF 剥出来的裸字节,启动加载走的才是它,谁也替不了谁。
末了咱们报一下尺寸:stage2 现在约 4.6KB,12 个扇区装的是 6144 字节,余量还有一千四五百字节的样子。新添的东西——24 字节的表、6 字节的 GDTR、切换序列、32 位世界那头的一小段——全都在预算里头,构建闸没什么可说的。