在学习 XV6 代码的过程中,我们需要不断地用 GDB 调试器观察代码的行为,因此需要掌握一些 GDB 常用命令。
用 GDB 调试 QEMU 时可以将调试目标分为两种:
bootloader
(启动扇区) 开始调试虚拟机启动过程,即 XV6 代码。我们这里需要调试的是 QEMU 生成的 XV6 代码,而不是 QEMU 仿真器本身的代码。
首先要安装 GDB 调试工具。
sudo apt-get install gdb
XV6 已经帮我们写好了 GDB 启动脚本,我们需要在用户根目录下添加一个加 .gdbinit
的文件,内容如下
set auto-load safe-path /
打开一个终端,进入 XV6 目录下执行 make qemu-nox-gdb
启动调试模式,实际上执行了调试服务器 gdbserver
的角色,等待 GDB 客户端的接入。终端信息如下
sed "s/localhost:1234/localhost:25500/" < .gdbinit.tmpl > .gdbinit
*** Now run 'gdb'.
qemu-system-i386 -nographic -drive file=fs.img,index=1,media=disk,format=raw -drive file=xv6.img,index=0,media=disk,format=raw -smp 2 -m 512 -S -gdb tcp::25500
可以看出调试服务器 gdbserver
在 tcp
端口 25500 上监听,因此我们打开另一个终端,进入 XV6 目录运行 gdb kernel -silent
会自动连接,内容如下:
Reading symbols from kernel...done.
+ target remote localhost:25500
The target architecture is assumed to be i8086
[f000:fff0] 0xffff0: ljmp $0x3630,$0xf000e05b
0x0000fff0 in ?? ()
+ symbol-file kernel
(gdb)
此时使用 c
命令,将开始内核的执行,并在 gdbserver
端看到系统启动并执行 shell
,输出如下内容:
xv6...
cpu1: starting 1
cpu0: starting 0
sb: size 1000 nblocks 941 ninodes 200 nlog 30 logstart 2 inodestart 32 bmap start 58
init: starting sh
$
后面我们将使用 QEMU + GDB 的调试方式来观察 XV6 的运行。
我们重新进入调试模式,在 GDB 客户端上用 b main
将断点设置在内核 main()
入口,然后用 c
命令运行到该处。接着用多个 n
命令,逐个执行直到出现 startothers()
为止,然后用 info threads
查看线程信息。关键信息如下
(gdb) n
=> 0x80102f12 <main+98>: add $0xc,%esp
34 startothers(); // start other processors
(gdb) info threads
Id Target Id Frame
* 1 Thread 1 (CPU#0 [running]) main () at main.c:34
2 Thread 2 (CPU#1 [halted ]) 0x000fd412 in ?? ()
上述表明 cpu#1
对应的线程 2 还是处于停机 halted
状态。再执行 n
命令,此时对应的 thread 2
已经退出 halted
状态并表内 running
状态。
如果此时执行 thread 2
命令,我们将调试器连接到线程 2 从而控制 CPU#1
的运行。此时再用 info threads
命令查看,会发现线程 2 的前面会标注有 *
号。
补充
thread apply 1 2 command # 让线程 1 和线程 2 执行 GDB 命令 command
thread apply all command # 让所有被调试线程执行 GDB 命令 command