System Learning Daily 6
一个和GDT有关的Bug和相关Debug工具的使用。
Segment Descriptor中的B/D位
这是一个不小心导致的Bug。
在Intel的Intel Architecture Software Developer's Manual, Volume 3中写到:
D/B (default operation size/default stack pointer size and/or upper bound) flag :
Performs different functions depending on whether the segment descriptor is an executable code segment, an expand-down data segment, or a stack segment. (This flag should always be set to 1 for 32-bit code and data segments and to 0 for 16-bit code and data segments.)
进入保护模式之后,我们必然要使用32bit的代码段和数据段。但是我忘记将其置于1了。
于是就出现了一种奇怪的现象。
- IF flag set,程序在跳转到32bit代码段一瞬间就triple fault
- IF flag clear,程序可以运行,跳转指令似乎还是可以运行的,但是有关数据的指令,无法修改数据。这就造成一种怪象。例如下面这段代码:
1 | void osloader_main(void) |
在调试过程中,里面的这些counter变量都不会改变,一直仅仅保持一个数值。在调试器中,程序还是在一直运行着的。相当诡异了。
看下图。

键入si,print counter

counter的数值根本没变化,这很让人迷惑。
工具
虚拟机
我是用的虚拟机有两个,一个是QEMU,一个是Bochs。
具体优缺点我不太好分析,对我来说,QEMU我使用起来更加的熟悉。然而Bochs有个非常好的地方在于其自带的调试器,当然Bochs也可以使用GDB进行调试,不过自带的调试器和GDB只能选择一个。
列出来我自己的QEMU的启动参数吧。
1 | qemu-system-i386 -m 128M -hda build/img.bin -S \ |
Bochs的参数非常简单,就不在此列出了。
调试器
显然,我用的是GDB了。值得一提的是,我是用了一个叫做GEF的GDB插件来优化GDB的使用体验。GEF非常适合这种可能要随时检查寄存器和栈的应用场景。
不得不说,GDB还是很强大的,使用Python脚本对其进行拓展更是让其异常强大。具体参考我之前的System Learning Daily 3
工具链
就是GCC那一套了,汇编器使用nasm。
这种开发选择GCC总是没问题的。具体是根据OSDev上面的Configure构建的得到的。
gcc
需要注意的就是使用一些编译选项了,我用的是
1 | CC_FLAGS = -std=gnu11 -ffreestanding -m32 -masm=intel -Wall -Wextra -Wno-address-of-packed-member -g -O0 |
ld
有些地方确实需要会写链接脚本(Linker Script),至少在Kernel的编译中,需要使用链接脚本。
objdump
用来生成flat binary文件和Debug用的调试符号文件。
其他
主要是一些命令行工具。
dd
构建镜像的时候使用,直接通过dd来构建一个raw的文件,里面放入MBR,OSLoader,Kernel等。
截取makefile的一个小片段
1 | dd if=/dev/zero of=${IMG_BIN} bs=1024 count=1024 conv=notrunc |
xxd
用来显示一个二进制文件,这是我发现读硬盘Bug的关键。配合diff使用,最终发现了硬盘读不全的问题。
diff
1 | xxd dump.bin > build/dump.bin.txt |
当我们需要注意检查QEMU的内存中的内容是否与镜像一致时。
我们首先在QEMU的Monitor窗口中输入
1 | memsave 0x7c00 0x2000 dump.bin |
保存从0x7c00开始的内存,长度为0x2000,到dump.bin文件中。
再看上面那个 diff的脚本,可以直接比较镜像内容与内存中的内容的差别。