最近在写一个玩具操作系统,在编写过程中,经常需要进行代码调试。平常我们在Windows或者Linux下编写应用程序时,可以使用像VS,GDB等等这些调试工具进行调试,但是现在要调试的不是应用程序,而是操作系统本身。那要怎么调试操作系统的代码呢?笔者就以自己编写玩具操作系统时的经历,介绍一下如何调试操作系统的代码。

在开发操作系统的过程中,一般是先在模拟器或者虚拟机中运行调试,没什么大问题了,再在实体机上运行。常见的模拟器如Bochs、Qemu等,常见的虚拟机如VMWare、VirtualBox、Virtual PC等。

本文就以Windows下的Bochs介绍一下如何使用Bochs模拟器来调试操作系统代码,文中有使用MinGW环境下的命令。

一、Bochs内置调试器

Bochs本身内置的调试器,是汇编级的调试器,对于调试像boot或者loader这样的16位汇编代码时,还是非常有用的,Bochs的调试命令还是非常丰富的,而且与GDB的调试命令也比较相似。下面列一些常用的调试命令: 命令功能示例示例说明b打断点b 0x7c00在0x7c00处设置断点c继续执行c继续执行n单步执行,不会进入函数调用n执行一条语句,如果遇到CALL指令,不会进入函数s单步执行,并且会进入函数调用s执行一条语句,如果遇到CALL指令,则进入函数x显示指定线性地址开始的内存信息x /32bc 0x7c00以字符的形式显示线性地址0x7c00开始的32个字节xp显示指定物理地址开始的内存信息x /32bc 0x7c00以字符的形式显示物理地址0x7c00开始的32个字节r显示各通用寄存器的值r显示各通用寄存器的值sreg显示各段寄存器的值sreg显示各段寄存器的值blist显示所有断点信息blist显示所有断点信息del删除断点del 1删除1号断点,几号断点是由blist列出help显示帮助helphelp没有参数则显示命令概览,如果想要查看具体命令的帮助信息,则后面跟上命令即可,比如help info则是查看info命令的帮助信息q退出q退出调试控制台ldsym装载调试符号ldsym global “loader.sym”从装载loader.sym文件中装载调试符号,并且作为全局符号

更多的调试命令可以通过help查看。

1.直接运行

在Windows下编写好bxrc文件,则可以在直接双击运行。下图是笔者写的Loader执行完成,准备进入内核的输出画面:

2.调试

在bxrc文件右键弹出菜单中有一个debugger命令,执行它即可进入下面的开始对话框界面,默认是使用的bxrc文件中的配置,如果想要临时修改配置,也可以双击“Edit Options”列表框中的分类进行编辑,完成后执行“start”即开始调试。

此时会暂停在f000:fff0处,如下图所示:

这里就是Bochs的内置调试控制台了,可以在此输入各种调试命令。

下图是笔者调试刚进入Loader的情况:

可以看到,全部是汇编代码,没有任何符号信息,调试起来是相当的费劲,如果不对照源码,根本就难以定位源码。笔者在刚开始写Loader时就是在没有任何调试信息的情况下来调试Loader的,非常费时费力。后面随着代码越来越多(笔者是使用大量的C语言来写Loader的),这种调试方式更是费时费力,其实Bochs是可以加载调试符号的,虽然相对GDB的调试信息,比较简陋,但是还是要方便很多。

Bochs的符号文件格式为“%x %s”,即前面是地址,后面是符号,比如:

100003182 memcpy

表示地址0x00003182为memcpy的入口地址。

使用如下的命令生成调试符号文件:

1nm loader.elf | grep -i ' T ' | awk '{ print $1" "$3 }' > loader.sym

可以在CMakeLists.txt中使用:

1nm ${CMAKE_BINARY_DIR}/loader/loader.elf | grep -i ' T ' | awk '{ print $$1" "$$3 }' > ${CMAKE_CURRENT_SOURCE_DIR}/loader.sym

生成符号后,就可以使用ldsym来装载调试符号了,如下所示:

可以从图中看到已经有符号信息显示了,调试起来就方便多了。

如果每次调试时都手动输入ldsym来装载调试信息还是比较繁琐,可以直接配置在bxrc文件中,添加下面一行配置即可:

1debug_symbols: file="loader.sym", offset=0x8000

本文链接地址: https://blog.csdn.net/witton/article/details/126414601?spm=1001.2014.3001.5501

二、Bochs+GDB调试

由于Bochs内置的调试器是汇编级的,在操作系统进入内核后,将会有大量的C代码,如果还是使用汇编级的调试还是相当麻烦的,这就需要有源码级的调试了,GDB将成为首选源码级调试器。如果还是Bochs模拟器的话,可以使用GDB连接到Bochs来进行源码级调试。

Windows下的Bochs默认安装文件是不支持GDB调试的,需要自己重新编译Bochs源码,编译时需要添加参数--enable-gdb-stub,下面是笔者在MinGW下编译Bochs的参数:

1../bochs-2.7/configure --enable-all-optimizations --enable-long-phy-address --enable-alignment-check --enable-pci --enable-cdrom --enable-gameport --enable-large-ramfile --enable-show-ips --with-all-libs --enable-usb --enable-usb-ohci --enable-usb-ehci --enable-usb-xhci --enable-logging --enable-fpu --enable-3dnow --enable-busmouse --enable-iodebug --enable-sb16 --enable-ne2000 --enable-clgd54xx --enable-voodoo --enable-es1370 --enable-e1000 --with-rfb --enable-x86-debugger --enable-debugger-gui --enable-gdb-stub --with-win32

然后在bxrc文件中,添加下面一行配置:

1gdbstub: enabled=1, port=1234, text_base=0, data_base=0, bss_base=0

输入命令(注意一定要使用自己编译的Bochs,不能像前面那样在右键菜单中使用Debugger了):

1./bochs -f bochsrc.bxrc -q

即可看到Bochs控制台中输出:

1Waiting for gdb connection on port 1234

这是在等待GDB的连接,这里的连接端口为1234

在MinGW控制台输入如下命令启动交叉编译的GDB:

1x86_64-elf-gdb -ex "file build/kernel/kernel" -ex "target remote :1234" -ex "b kmain"
  • -ex “file build/kernel/kernel” 让GDB自动装载build/kernel/kernel文件中的调试信息
  • -ex “target remote :1234” 远程连接本机的1234端口
  • -ex “b kmain” 在kmain函数入口设置断点

这样就可以开启GDB的源码级调试了,如下图所示:

使用命令行方式手动输入GDB命令来调试还是麻烦了点,可以直接使用Visual Studio来可视化调试,目前新版本的Visual Studio已经支持使用GDB来调试GCC生成的C/C++程序了,可以参见笔者前面的博文: Visual Studio 2022使用MinGW来编译调试C/C++程序

下图为笔者使用VS 2022调试Kernel的情况,可以看到借助VS IDE的强大功能,调试起来是非常方便,与调试VC的普通应用程序体验高度一致:

由于Bochs是完全使用软件的方式来模拟,使用它调试运行内核,是相当慢的,后面笔者将介绍使用Qemu来运行调试操作系统,Qemu的运行效率比Bochs快很多,参见后文:使 用QEMU+GDB调试操作系统代码

希望此文能帮助到喜欢研究、编写操作系统的读者,更希望有朝一日看到国人能够写出完全自主的操作系统并流行起来!哈哈!