最近在写一个玩具操作系统,在编写过程中,经常需要进行代码调试。平常我们在Windows或者Linux下编写应用程序时,可以使用像VS,GDB等等这些调试工具进行调试,但是现在要调试的不是应用程序,而是操作系统本身。那要怎么调试操作系统的代码呢?笔者就以自己编写玩具操作系统时的经历,介绍一下如何调试操作系统的代码。
在开发操作系统的过程中,一般是先在模拟器或者虚拟机中运行调试,没什么大问题了,再在实体机上运行。常见的模拟器如Bochs、Qemu等,常见的虚拟机如VMWare、VirtualBox、Virtual PC等。
本文就以Windows下的Bochs介绍一下如何使用Bochs模拟器来调试操作系统代码,文中有使用MinGW环境下的命令。
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 | 显示帮助 | help | help没有参数则显示命令概览,如果想要查看具体命令的帮助信息,则后面跟上命令即可,比如help info则是查看info命令的帮助信息 |
q | 退出 | q | 退出调试控制台 |
ldsym | 装载调试符号 | ldsym global “loader.sym” | 从装载loader.sym文件中装载调试符号,并且作为全局符号 |
更多的调试命令可以通过help查看。
在Windows下编写好bxrc文件,则可以在直接双击运行。下图是笔者写的Loader执行完成,准备进入内核的输出画面:
在bxrc文件右键弹出菜单中有一个debugger命令,执行它即可进入下面的开始对话框界面,默认是使用的bxrc文件中的配置,如果想要临时修改配置,也可以双击“Edit Options”列表框中的分类进行编辑,完成后执行“start”即开始调试。
此时会暂停在f000:fff0处,如下图所示:
这里就是Bochs的内置调试控制台了,可以在此输入各种调试命令。
下图是笔者调试刚进入Loader的情况:
可以看到,全部是汇编代码,没有任何符号信息,调试起来是相当的费劲,如果不对照源码,根本就难以定位源码。笔者在刚开始写Loader时就是在没有任何调试信息的情况下来调试Loader的,非常费时费力。后面随着代码越来越多(笔者是使用大量的C语言来写Loader的),这种调试方式更是费时费力,其实Bochs是可以加载调试符号的,虽然相对GDB的调试信息,比较简陋,但是还是要方便很多。
Bochs的符号文件格式为“%x %s”,即前面是地址,后面是符号,比如:
00003182 memcpy
表示地址0x00003182为memcpy的入口地址。
使用如下的命令生成调试符号文件:
nm loader.elf | grep -i ' T ' | awk '{ print $1" "$3 }' > loader.sym
可以在CMakeLists.txt中使用:
nm ${CMAKE_BINARY_DIR}/loader/loader.elf | grep -i ' T ' | awk '{ print $$1" "$$3 }' > ${CMAKE_CURRENT_SOURCE_DIR}/loader.sym
生成符号后,就可以使用ldsym来装载调试符号了,如下所示:
可以从图中看到已经有符号信息显示了,调试起来就方便多了。
如果每次调试时都手动输入ldsym
来装载调试信息还是比较繁琐,可以直接配置在bxrc文件中,添加下面一行配置即可:
debug_symbols: file="loader.sym", offset=0x8000
本文链接地址:https://blog.csdn.net/witton/article/details/126414601?spm=1001.2014.3001.5501
由于Bochs内置的调试器是汇编级的,在操作系统进入内核后,将会有大量的C代码,如果还是使用汇编级的调试还是相当麻烦的,这就需要有源码级的调试了,GDB将成为首选源码级调试器。如果还是Bochs模拟器的话,可以使用GDB连接到Bochs来进行源码级调试。
Windows下的Bochs默认安装文件是不支持GDB调试的,需要自己重新编译Bochs源码,编译时需要添加参数--enable-gdb-stub
,下面是笔者在MinGW下编译Bochs的参数:
../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文件中,添加下面一行配置:
gdbstub: enabled=1, port=1234, text_base=0, data_base=0, bss_base=0
输入命令(注意一定要使用自己编译的Bochs,不能像前面那样在右键菜单中使用Debugger了):
./bochs -f bochsrc.bxrc -q
即可看到Bochs控制台中输出:
Waiting for gdb connection on port 1234
这是在等待GDB的连接,这里的连接端口为1234
在MinGW控制台输入如下命令启动交叉编译的GDB:
x86_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调试操作系统代码
希望此文能帮助到喜欢研究、编写操作系统的读者,更希望有朝一日看到国人能够写出完全自主的操作系统并流行起来!哈哈!