XV6

在学习 XV6 代码的过程中,我们需要不断地用 GDB 调试器观察代码的行为,因此需要掌握一些 GDB 常用命令。

用 GDB 调试 QEMU 时可以将调试目标分为两种:

  1. 用 GDB 调试由 QEMU 生成的虚拟机,即远程调试虚拟机系统内核,可以从虚拟机 bootloader(启动扇区) 开始调试虚拟机启动过程,即 XV6 代码。
  2. 调试 QEMU 仿真器的代码而不是虚拟机要运行的代码。

我们这里需要调试的是 QEMU 生成的 XV6 代码,而不是 QEMU 仿真器本身的代码。

1. 准备工作

首先要安装 GDB 调试工具。

sudo apt-get install gdb

XV6 已经帮我们写好了 GDB 启动脚本,我们需要在用户根目录下添加一个加 .gdbinit 的文件,内容如下

set auto-load safe-path /

2. 进入调试

打开一个终端,进入 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

可以看出调试服务器 gdbservertcp 端口 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 的运行。

3. 多核调试

我们重新进入调试模式,在 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