本文是针对 深入学习操作系统!详细剖析 MIT 6.S081 课程 Lab 3 : page tables - 1 Speed up system calls - 掘金 (juejin.cn) 一文中,寻找是什么触发了 panic 的调试方法的补充。
系统配置
- 本地系统: windows 11 专业版 23H2
- 虚拟机: Ubuntu 20.04.6 LTS on wsl 2
完成 Lab 的过程使用 VSCode 连接到 wsl 。
GDB 常用命令
断点设置及操作
-
若要将断点打在当前源程序第 n 行:
(gdb) break n该命令可在在源程序第 n 行设置一个断点,
break可以缩写为b。 -
若要将断点打在源程序 main() 函数入口:
(gdb) break main该命令可在 main() 函数入口设置一个断点。
-
若要将断点打在指定文件的函数入口或者其他文件的特定行号:
(gdb) break path:func (gdb) break path:npath 为文件路径,例如 kernel/vm.c 。
-
想要查看已经设置的断点,可以使用
info命令:(gdb) info break该命令列出我们所有打的断点的信息,break 可以用 b 缩写替代。
-
除设置断点外,我们还能删除指定断点或者删除全部断点:
(gdb) delete (gdb) delete ndelete可以删除全部断点,delete n可以删除指定的断点号为 n 的断点,断点号可以通过info break查看 -
还可以使能和屏蔽断点:
(gdb) disable n (gdb) enable n
执行调试
执行调试的命令,与在 IDE 调试程序能使用的功能类似:
run类比于 IDE 的全速运行,遇到断点会停下,run可以用r缩写替代。next类比于 IDE 不进入函数的单步调试,next可以用n缩写替代。step类比于 IDE 进入函数的单步调试,step可以用s缩写替代。continue可以继续执行直到遇到下一个断点停下,没有断点直接结束,可以用c缩写替代。until n会直接跳转到指定行号程序,n 表示行号。finish会使运行在函数中的程序,直接执行完当前函数并打印函数返回信息。kill将直接结束当前进程,程序可以从头运行。quit能退出 gdb ,quit可以用q缩写替代。
打印信息
print x可以打印变量、地址、表达式值等, x 表示需要打印的东西,print可以用p替代。backtrace可以打印当前调用堆栈的信息,可以简写为bt。
调试方法 1 :基于命令行的调试
按照 6.S081 课程视频中的方法,使用命令行,对 xv6 进行调试。
在 xv6 文件夹下,运行
$ make clean
$ make CPUS=1 qemu-gdb
*** Now run 'gdb' in another window.
qemu-system-riscv64 -machine virt -bios none -kernel kernel/kernel -m 128M -smp 3 -nographic -drive file=fs.img,if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 -S -gdb tcp::26000
该命令使用 QEMU 模拟器运行 xv6 操作系统,同时通过 GDB 进行调试,且限制 CPU 数目为 1。命令行要求我们在另一个窗口中运行 GDB ,由于我们运行的 qemu 基于 risc-v 架构,因此应使用 gdb-multiarch 命令,同时,需要 GDB 读取 kernel/kernel 中的符号,因此,我们应再打开另外一个终端窗口,运行以下命令:
$ gdb-multiarch kernel/kernel
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04.1) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from kernel/kernel...
此时,命令行转入 GDB 中,我们使用 target 命令连接到远程调试目标,从 make 命令给我们返回的信息看出, qemu-gdb 监听在端口 26000 上,因此,输入以下命令:
(gdb) target remote localhost:26000
Remote debugging using localhost:26000
0x0000000000001000 in ?? ()
此时, GDB 已成功与远程调试目标连接。
我们需要使用 GDB 提供的命令来设置断点并进行调试,以 深入学习操作系统!详细剖析 MIT 6.S081 课程 Lab 3 : page tables - 1 Speed up system calls - 掘金 (juejin.cn) 一文中,寻找触发 panic 的原因为例,这里再将程序输出的错误信息展示一下:
xv6 kernel is booting
hart 2 starting
hart 1 starting
panic: freewalk: leaf
可见是 freewalk() 函数输出了这一 panic ,我们在 panic("freewalk: leaf"); 语句上打上断点,准备进行调试。
(gdb) b kernel/vm.c:281
Breakpoint 1 at 0x800009ee: file kernel/vm.c, line 281.
使程序直接运行到 Breakpoint 1 :
(gdb) c
Continuing.
[Switching to Thread 1.2]
Thread 2 hit Breakpoint 1, freewalk (pagetable=pagetable@entry=0x87f73000) at kernel/vm.c:281
warning: Source file is more recent than executable.
281 panic("freewalk: leaf");
打印当前堆栈信息分析问题:
(gdb) bt
#0 freewalk (pagetable=pagetable@entry=0x87f73000) at kernel/vm.c:281
#1 0x00000000800009b4 in freewalk (pagetable=pagetable@entry=0x87f74000) at kernel/vm.c:276
#2 0x00000000800009b4 in freewalk (pagetable=pagetable@entry=0x87f75000) at kernel/vm.c:276
#3 0x0000000080000a32 in uvmfree (pagetable=pagetable@entry=0x87f75000, sz=sz@entry=4096)
at kernel/vm.c:294
#4 0x000000008000108c in proc_freepagetable (pagetable=0x87f75000, sz=sz@entry=4096) at kernel/proc.c:228
#5 0x00000000800043f6 in exec (path=path@entry=0x3fffffcf10 "/init", argv=argv@entry=0x3fffffce10)
at kernel/exec.c:117
#6 0x0000000080004f92 in sys_exec () at kernel/sysfile.c:444
#7 0x0000000080002096 in syscall () at kernel/syscall.c:154
#8 0x0000000080001d80 in usertrap () at kernel/trap.c:67
#9 0x0505050505050505 in ?? ()
可以看到,栈中最初执行的是 kernel/proc.c 中的 proc_freepagetable() 函数, proc_freepagetable() 函数调用了 kernel/vm.c 中的 uvmfree() 函数, uvmfree() 函数又调用了 freewalk() 函数, freewalk() 函数存在递归调用机制,在递归的过程中出现了问题。
分析 proc_freepagetable() 函数的代码我们可以发现,我们还需要释放我们之前建立 USYSCALL 页面的页表项和其所占用的物理内存,因此,我们应将这段代码修改为:
// kernel/proc.c
// ...
// Free a process's page table, and free the
// physical memory it refers to.
void
proc_freepagetable(pagetable_t pagetable, uint64 sz)
{
uvmunmap(pagetable, TRAMPOLINE, 1, 0);
uvmunmap(pagetable, TRAPFRAME, 1, 0);
uvmunmap(pagetable, USYSCALL, 1, 0);
uvmfree(pagetable, sz);
}
// ...
这就是本次实验中 Debug 的全部过程,随后会附上上文中提到的这几个函数的代码及更深入的分析。
此外,还可通过配置 VSCode ,直接在 VSCode 中调试 xv6 的代码,这一方法后续也会补充在本文中。