工具链测试在之前的博文中已经详细说明,不在此重复。 首先需要克隆实验的github repo
git clone https://pdos.csail.mit.edu/6.828/2018/jos.git lab可以运行make run用评分程序测试结果,查看输出文件resp-lab*.txt来查看failed case。
这部分主要是为了熟悉x86汇编语言和PC启动过程。虽然无需撰写任何代码,但是回答问题对后续理解十分重要。
Exercise 1要求熟悉x86汇编。需要注意的是NASM汇编器使用了Intel syntax 而GNU则使用AT&T syntax。这使得两者的汇编文件至少表面上看起来很不一样。Exercise 1推荐阅读Brennan’s Guide to Inline Assembly。这篇文章给我们将要使用的AT&T语法一个很好又很简要的描述。 这篇文章主要讲了内联汇编的故事。内联汇编可以嵌入在C语言写的代码中。内联汇编和C中的内联函数很像,将汇编语言写的函数以内联方式嵌入到函数调用处。这里面提到了一个叫Clobbered register的东西。这个东西是指在内联代码中操作的一些寄存器。寄存器内容可能被修改了(而之后也没有进行还原操作)。这导致程序很可能产生一些难以预料的情况。因为此时GCC并不知道你已经将寄存器内容修改了。这点尤其是在编译器对代码进行了一些优化的情况下而导致问题。于是需要在扩展内联汇编中显式地指定Clobbered list来避免这个问题。 如果对文章有什么不清楚的,可以参考GCC内联汇编基础。
我们将使用QEMU模拟器。QEMU可以开放接口给GDB远程连接。 首先需要build boot loader and kernel, 运行以下命令:
cd lab make如果出现提示信息,需要根据提示信息修conf/env.mk中的gcc或qemu安装路径信息,需要注意的是安装路径指向可执行文件;如果出现提示undefined reference to `__udivdi3则需要安装gcc-multilib包。 obj/kern/kernel.img文件作为我们模拟PC的虚拟硬盘,包括了boot loader (obj/boot/boot) 和kernel (obj/kernel)。然后运行:
make qemu #or make qemu-nox without GUI在启动之后可以看到弹出了QEMU的窗口。可以输入kerninfo 命令。下面将会解释其内容并说明PC是如何启动的。
要明白PC是如何启动的,先要了解PC的物理地址空间。最早的PC——Intel 8088只能寻址1MB的地址空间。其中最低的640KB空间叫做Low Memory 是早期PC能使用的所有RAM。它的物理地址为0x00000000 到0x000A0000。 从0x000A0000到0x000FFFFF的384KB空间由硬件保留,为了特殊目的比如作为显示的缓存等。其中最重要的是从0x000F0000到0x000FFFFF的64KB空间,其中存储着BIOS程序。BIOS程序提供开机自检功能,导入并将控制移交给操作系统。 Intel 80286和80386分别提供了对16MB和4GB物理地址空间的寻址支持,但为了后向兼容,保留了原来的地址空间安排。但是由于BIOS从只读存储(ROM)转移到了可更新的闪存芯片上,于是在内存地址中留下了空洞。现在地址空间的最高部分通常由BIOS保留给32位的PCI设备使用。 由于现代PC支持超过32位的物理地址空间,需要把原来给PCI设备映射用的地址空间继续保留下来,这形成了第二个地址空洞。而由于JOS只需要256MB的物理地址空间,我们可以暂时假设所有PC都只有32位物理地址空间。
这部分将用QEMU和GDB一起探究(Investigate)IA-32兼容的计算机是如何启动的。 首先打开两个控制台,进入刚才make过的文件夹,在其中一个控制台中运行命令:
make qemu-gdbQEMU启动后将在执行第一条指令之前停下,等待GDB接入。 在另一个控制台运行命令”
make gdb以接入QEMU。接入之后gdb输出:
[f000:fff0] 0xffff0: ljmp $0xf000,$0xe05b这是GDB对第一条指令的反汇编结果。它说明了以下几个问题:
IBM PC从物理地址0x000ffff0开始执行第一条指令。这是ROM BIOS中非常顶部的位置。PC上电之后,RAM中还没有任何程序。CPU直接进入实模式并设置CS和IP寄存器分别为0xf000和0xfff0,并从该位置开始执行程序。它对应着被硬编码在ROM中的BIOS程序的物理地址。地址的计算方式为段加便宜的方式,即addr = 16 * CS + IP第一条指令是一条jmp指令,跳转到CS为0xf000,IP为0xe05b的位置。 BIOS程序建立起中断描述符表(IDT)(中断描述符表的每一个表项为对应的中断指明了中断处理程序的入口地址),并且对许多外设进行了初始化。接下来它寻找可以启动的设备,比如硬盘,U盘等,加载boot loader并将控制转移给它。Exercise 2 要求用GDB si单步调试命令运行几条bios指令,猜测他们的用途。 这里大约有三十条指令,以下简单说明指令的用途:
[f000:fff0] 0xffff0: ljmp $0xf000,$0xe05b第一条指令跳转到fe05b位置开始执行程序。因为我们在最开始启动的位置距离BIOS的顶端只有16 Byte,做不了太多事情。
[f000:e05b] 0xfe05b: cmpl $0x0,%cs:0x6ac8 [f000:e062] 0xfe062: jne 0xfd2e1其中cmpl指令决定了这是一次reboot还是resume。如果不相等说明这是一次resume,跳转到resume程序的入口地址。具体位置和BIOS有关,感兴趣的可以查看stackoverflow上相关问题。
[f000:e066] 0xfe066: xor %dx,%dx [f000:e068] 0xfe068: mov %dx,%ss [f000:e06a] 0xfe06a: mov $0x7000,%esp [f000:e070] 0xfe070: mov $0xf34c2,