学习操作系统原理最好的方法是自己写一个简单的操作系统。
写程序不免需要调试,写不同的程序调试方式也不同。如果做应用软件开发,相应的程序调试方式是建立在有操作系统支持的基础上的。而我们现在是要开发操作系统,如何调试操作系统的程序呢?如果操作系统程序直接跑在真机上或虚拟机上(比如VirtualBox)是很难调试的,所以我们在开发阶段操作系统程序主要在虚拟机QEMU上跑,因为QEMU支持调试。当然很多事情都是有利也有弊的,QEMU虽然支持调试,但它的运行效率比VitrualBox要低,所以我们最终的GrapeOS程序是跑在VirtalBox上的。QEMU需要结合GDB才能实现调试,下面我们一起来学习一下。
一、QEMU调试模式
在Windows的cmd命令行中输入如下一行命令:
qemu-system-i386 d:\GrapeOS\VMShare\GrapeOS.img -S -s
上面这行命令比之前多了两个参数,“-S”表示让CPU在将要执行第一条指令前暂停,“-s”表示让QEMU打开自带的GDB服务端功能,且网络端口号是1234。截图如下:
执行上面的命令后,会弹出QEMU的窗口:
从上图中可以看到QEMU窗口中间显示一行文字“Guest has not initialized the display(yet).”,此时QEMU已进入调试模式。当QEMU进入调试模式后,就在等待GDB客户端来连接它。当GDB客户端连接上QEMU的GDB服务端就可以调试了。就像我们用PowerShell连接到CentOS就可以在PowerShell中操纵CentOS一样,此时PowerShell是客户端,CentOS是服务端。下面我们来介绍GDB客户端。
二、GDB调试
GDB分为服务端和客户端,单说GDB,一般是指GDB客户端。GDB是Linux中的一个调试软件,所以我们准备在CentOS中使用它。首先我们通过PowerShell登录CentOS。
1.安装GDB
首次使用GDB可能需要安装一下:
yum install gdb
2.启动GDB
敲命令gdb
就运行了,如下图:
3.GDB连接到QEMU
在GDB中输入如下命令连接QEMU:
target remote 192.168.10.102:1234
上面这行命令中的IP地址“192.168.10.102”是我的Windows的IP地址,你需要替换成你的Windows的IP地址。截图如下:
如上截图所示,我们已经通过GDB连接到QEMU了。图中倒数第二行的十六进制数“0x0000fff0”表示CPU将要执行的指令地址。还记得前面介绍的实模式下1M内存的布局吗?这个地址在BIOS中,是CPU执行的第一条指令所在的地址。
4.设置断点
设置断点是调试必备的一个功能,比如我们在0x7c00处设置个断点:
b *0x7c00
这样就设置好了一个断点。可以用同样的方式设置多个断点。
5.继续运行
这个命令简单,只有一个字母“c”,然后回车即可让CPU继续运行,当遇到断点时会自动暂停。截图如下:
6.查看寄存器
查看所有寄存器的命令是i r
,截图如下:
从上图中可以看到此时CPU中很多寄存器的值,有朋友可能会有个疑问,以前学的寄存器是“ax、bx、cx……”这些,上面截图中怎么是“eax、ebx、ecx……”呢?原因是当年8086CPU的寄存器都是16位的,也就是“ax、bx、cx……”这些,很多讲x86汇编语言的资料都只讲了8086下的情况。而我们现在启动的是32位x86模拟器“qemu-system-i386”,所有通用寄存器多了一个字母“e”表示扩展,从16位扩展成了32位。这些32位通用寄存器中的低16位就是原来的16位寄存器,比如eax的低16位还是ax,ah和al仍然表示ax的高8位和低8位,其它寄存器也一样。这就是兼容,能让旧程序在新CPU上运行。之前的16位寄存器中只有段寄存器没有扩展,还是16位的,而且还增加了2个,分别是fs和gs。增加的这2个段寄存器作用和es基本一样,之所以增加是怕在复杂的程序中出现段寄存器不够用的情况。当数据比较多的时候GDB一般只输出一部分,此时如果按回车键还会显示出其它一些寄存器,但我们用不上,按“q”键退出继续输出即可。
下面来看一下如何查看单个寄存器,比如我们要查看寄存器ax的值,输入命令p $ax
,如果想以十六进制显示可以输入命令p /x $ax
,截图如下:
7.查看内存
命令格式:x /nfu addr
n表示数量
f表示格式:x(hex), d(decimal), c(char)等。
u表示显示单位:b(byte), h(halfword), w(word), g(giant, 8 bytes)。
下面我们分别演示查看0x7c00开始的8个单字节、8个双字节、8个四字节、8个八字节的内存值。截图如下:
虽然目前看到的数据都是0,但我们以后写上程序就不一样了。
8.反汇编
有时候需要将机器码反汇编成汇编代码方便查看,下面我们以反汇编0x7c00开始的10个字节为例:
disas 0x7c00,+10
截图如下:
上面截图中显示的汇编代码是GDB默认的AT&T语法,我们可以设置改成Intel语法:
set disassembly-flavor intel
截图如下:
需要说明一下的是这里的反汇编结果是错的。因为它是按照32位模式反汇编的,而我们现在还处在16位实模式中,所以这个反汇编功能只能等后面我们进入32位模式才有用。至于反汇编16位代码我们会在后续教程中介绍其它方法。
9.执行下一条指令
在调试的时候有时需要一条指令一条指令的单步执行,单步执行的命令是si
。
从上面的截图可以看到,每输入一个si回车,就会执行一条命令。每个si
命令下面一行中的十六进制数表示下一条指令的地址,可以看到地址在不断增加,说明的确在执行指令。如果想知道每一步都执行了什么指令,可以用下面这个命令来反汇编下一条要执行的指令:
set disassemble-next-line on
从上面截图中可以看到每一步的指令,但这个反汇编结果也是错的,原因和上面的一样。
顺便介绍个小技巧,如果不输入命令直接回车会重复上一个GDB命令,就像上图中最后两步,什么命令都没有直接回车就表示重复执行si
这个GDB命令。
10.退出GDB
本讲最后介绍的指令是退出GDB,非常简单,输入q
,然后再输入y
即可。 截图如下:
退出GDB后就又回到进入GDB前的Linux命令行环境中了。
三、退出GDB后的问题
如果大家按照上面顺序做实验,退出GDB后,CPU占用率会比较高,和上讲中的情况一样,直接关闭QEMU窗口即可。这个问题我们在下一讲中解决。
本讲视频版地址:https://www.bilibili.com/video/BV18G4y1P7CU/
本教程代码和资料:https://gitee.com/jackchengyujia/grapeos-course
GrapeOS操作系统QQ群:643474045