自己动手从零写桌面操作系统GrapeOS系列教程——12.QEMU+GDB调试

学习操作系统原理最好的方法是自己写一个简单的操作系统。


写程序不免需要调试,写不同的程序调试方式也不同。如果做应用软件开发,相应的程序调试方式是建立在有操作系统支持的基础上的。而我们现在是要开发操作系统,如何调试操作系统的程序呢?如果操作系统程序直接跑在真机上或虚拟机上(比如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

posted @ 2023-03-13 21:45  成宇佳  阅读(1572)  评论(4编辑  收藏  举报