MIT 6.S081 xv6 调试指北

本文详细介绍了如何使用qemu模拟RISC-V架构并进行xv6内核的调试。首先通过makeqemu启动qemu模拟器,然后利用gdb的远程调试功能与qemu建立连接。在gdb中设置断点,通过target remote localhost:25000指令连接到qemu,进而实现对xv6内核的调试。文章以exec函数为例,展示了如何单步执行、查看源代码、设置断点以及查看变量等调试步骤,帮助读者掌握调试技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

MIT 6.S081 xv6 调试指北

前言: xv6 调试折磨了我整整一上午,在询问老师 + 谷歌之后,稍有心得,记录之

运行 qemu 模拟器

在 64 位(x86) 的电脑上模拟 RISC-V 架构的 CPU。在计组实验中,通过硬件电路设计 CPU,而 qemu 通过 C 语言来模拟 CPU 的取指、译码和执行操作。

xv6-labs-2020 目录下输入 make qemu 并回车,随后出现单独窗口。qemu 的虚拟 BIOS 将从 xv6.img 文件中含有的虚拟硬盘映像加载引导文件,随后启动 xv6 内核程序。

接下来可以在 shell 界面执行指令。

[root@localhost xv6-labs-2020]# make qemu
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

xv6 kernel is booting

hart 2 starting
hart 1 starting
init: starting sh
$ ls   
.              1 1 1024
..             1 1 1024
README         2 2 2059
xargstest.sh   2 3 93
cat            2 4 24208
	......
primes         2 23 26584
find           2 24 26008
console        3 25 0
$ cat README
xv6 is a re-implementation of Dennis Ritchie's and Ken Thompson's Unix
Version 6 (v6).  xv6 loosely follows the structure and style of v6,
	......
You will need a RISC-V "newlib" tool chain from
https://github.com/riscv/riscv-gnu-toolchain, and qemu compiled for
riscv64-softmmu. Once they are installed, and in your shell
search path, you can run "make qemu".
$ 

qemu 下远程调试 xv6

可以通过 gdb 程序的远程调试功能和 qemu 的远程gdb调试接口来对程序进行调试。

在内核开发中经常遇到远程调试技术。在远程调试中,调试器(gdb) 在主机 A 上,而被调试的程序在主机 B 上运行。通常情况下主机 A 具有完善的开发环境的主机中运行,而被调试的程序可能直接在硬件设备上运行,且不具备完整的环境来正常的运行调试器。因此在主机 B 上仅运行一个简单的 debug 程序(stub),仅负责设置断点、启动、停止等基本操作,而在主机 A 上由 gdb 执行更为复杂的操作。

在本例中调试器 gdb 在 open-euler 虚拟机下运行,而待调试的 xv6 程序在 qemu 模拟的 cpu 上运行,两者通过 localhost 端口进行通信。

同时,也可以使用虚拟机 Bochs 来执行 xv6 程序,虽然程序速度会变慢,但该虚拟机具有内置的调试器(功能不如 gdb 完善)而不需要远程调试功能。

1、开启 gdb 状态下的 qemu

执行下列指令可以在 qemu 下运行 xv6 程序,同时支持远程调试

[root@localhost xv6-labs-2020]# make 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::25000

此时虚拟机启动,并在执行第一条指令之前停止运行,等待远程 gdb 的连接,可以发现,监听的端口为 25000

2、开启 gdb

接着使 gdb 连接到 qemu 的 stub。打开新的终端窗口,切换至相同的 xv6 目录,输入

[root@localhost cs]# cd xv6-labs-2020/
[root@localhost xv6-labs-2020]# riscv64-unknown-elf-gdb kernel/kernel
GNU gdb (GDB) 10.1
Copyright (C) 2020 Free Software Foundation, Inc.
	.................................
	... 乱七八糟的输出信息,此处省略 ...
	.................................
Reading symbols from kernel/kernel...
warning: File "/home/cs/xv6-labs-2020/.gdbinit" auto-loading has been declined by your `auto-load safe-path' set to "$debugdir:$datadir/auto-load".
To enable execution of this file add
        add-auto-load-safe-path /home/cs/xv6-labs-2020/.gdbinit
line to your configuration file "/root/.gdbinit".
To completely disable this security protection add
        set auto-load safe-path /
line to your configuration file "/root/.gdbinit".
For more information about this security protection see the
"Auto-loading safe path" section in the GDB manual.  E.g., run from the shell:
        info "(gdb)Auto-loading safe path"
(gdb) 

注意此时虽然执行了 gdb xxx,但是并没有开始调试程序。因为此时 gdb 为待执行的程序提供的是用户模式下的 Linux 进程,而内核程序 kernel 希望在原生的 x86 硬件环境中以特权模式运行。

虽然内核程序不在此时的 gdb 环境下运行,但 gdb 仍需要读入此时内核的 ELF 程序映像,使得 gdb 可以提取到调试过程中所需信息,比如 C 函数的地址、符号、以及源码的行号。以上过程发生在屏幕显示

Reading symbols from kernel/kernel...

的过程中。

3、使用 gdb 远程连接 qemu

此时输入如下指令

(gdb) target remote localhost:25000
Remote debugging using localhost:25000
0x0000000000001000 in ?? ()
(gdb) 

与 qemu 进行连接,端口号(localhost)需要与之前的监听端口相同。此时返回 xv6 当前地址。

4、开始调试

此时我们在 exec 函数部分设置断点,接着运行虚拟机,直到虚拟机运行至断点处停止

(gdb) b exec
Breakpoint 1 at 0x80004b0c: file kernel/exec.c, line 14.
(gdb) c
Continuing.
[Switching to Thread 1.2]

Thread 2 hit Breakpoint 1, exec (path=path@entry=0x3fffffdf00 "/init", 
    argv=argv@entry=0x3fffffde00) at kernel/exec.c:14
14      {
(gdb) 

于此同时,第一次开启的终端 qemu-gdb 界面显示

xv6 kernel is booting

hart 2 starting
hart 1 starting

此时 xv6 内核进行初始化,并开始载入并执行它的第一个用户模式进程,/init 程序,故执行到 exec 处,触发中断。

若此时接着运行,输入 c 并回车

(gdb) c
Continuing.
[Switching to Thread 1.1]

Thread 1 hit Breakpoint 1, exec (path=path@entry=0x3fffffbf00 "sh", 
    argv=argv@entry=0x3fffffbe00) at kernel/exec.c:14
14      {
(gdb) 

在第一次开启的中断 qemu-gdb 界面显示

init: starting sh

在此次运行中,xv6 内核继续执行,直至使用 exec 调用 sh,现在如果继续输入 c 并回车,会发现 gdb 调试界面持续显示 continuing,因为此时 shell 程序已经启动,在等待用户输入交互指令。

(gdb) cContinuing.

此时,如果在 qemu 界面输入 cat README,则在 gdb 界面再次中断

(gdb) cContinuing.Thread 1 hit Breakpoint 1, exec (path=path@entry=0x3fffff9f00 "cat",     argv=argv@entry=0x3fffff9e00) at kernel/exec.c:1414      {(gdb) 

说明此时 shell 调用 exec 函数执行 cat 指令,执行到 exec 时产生中断。如果在 gdb 界面输入如下指令

(gdb) p argv[0]$1 = 0x87f49000 "cat"(gdb) p argv[1]$2 = 0x87f48000 "README"(gdb) p argv[2]$3 = 0x0(gdb) 

可以发现 gdb 的 p 指令打印出了 exec 函数入口中的 argv[] 参数值,符合我们在 qemu 中的输入指令。

使用 gdb 进行快乐调试

我们已经理解了使用 gdb 远程连接 qemu 进行调试的基本过程,现在就可以愉快的使用 gdb 指令进行调试了。接着上面的运行程序,我们可以

  • 查看当前位置的源代码

    (gdb) list910      static int loadseg(pde_t *pgdir, uint64 addr, struct inode *ip, uint offset, uint sz);1112      int13      exec(char *path, char **argv)14      {15        char *s, *last;16        int i, off;17        uint64 argc, sz = 0, sp, ustack[MAXARG+1], stackbase;18        struct elfhdr elf;(gdb) 
    
  • 单步调试

    (gdb) n22        struct proc *p = myproc();(gdb) n24        begin_op();(gdb) n26        if((ip = namei(path)) == 0){(gdb) n30        ilock(ip);(gdb) 
    
  • 设置断点,并跳转至断点

    (gdb) break 42Breakpoint 2 at 0x80004bea: file kernel/exec.c, line 42.(gdb) cContinuing.[Switching to Thread 1.3]Thread 3 hit Breakpoint 2, exec (path=path@entry=0x3fffff9f00 "cat",     argv=argv@entry=0x3fffff9e00) at kernel/exec.c:4242        for(i=0, off=elf.phoff; i<elf.phnum; i++, off+=sizeof(ph)){(gdb) 
    
  • 结束当前函数,返回函数调用点

    (gdb) finishRun till exit from #0  exec (path=path@entry=0x3fffff9f00 "cat",     argv=argv@entry=0x3fffff9e00) at kernel/exec.c:42
    

调试指令有很多,不在详述,但基本方法与汇编实验调试过程相似,熟练掌握指令后就可以快乐 debug 了。

具体程序代码调试(太长不看版)

假设我们需要调试程序 find

首先打开终端 1,输入

make qemu-gdb

打开终端 2,输入(千万注意前边需要加横线 _

riscv64-unknown-elf-gdb _find

打开调试界面,并载入 _find 调试信息。随后连接远程主机

(gdb) target remote localhost:xxxx

main 函数处打上断点

(gdb) b main

运行

(gdb) c

这时终端 1 显示出 $ 号,代表 xv6 的 shell 开始运行,在 $ 之后输入 find path filename,并回车

$ find . a

此时终端 1 开始执行 find 程序,进入 find 程序后,遇到在 main 处打上的断点,故在终端 2 上显示出断点信息

Thread 1 hit Breakpoint 1, main (argc=3, argv=0x2fb0) at user/find.c:6666      int main(int argc, char *argv[]) {

接着就可以使用 n 指令一步一步调试程序了。

在这里插入图片描述

总结

qemu-gdb 用于开启虚拟机,在虚拟机中对程序进行交互,执行输入输出;而 riscv64-unknown-elf-gdb 用于调试程序,设置断点,查看变量。因此需要同时在两个不同终端开启 qemu-gdb 和 gdb,前者用于查看程序输出信息,或给予程序必要的输入,后者用于调试。

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值