【99%环境搭建系列】xv6-riscv内核调试教程

本文最后更新于:2023年12月17日 下午

前言

最近在上《操作系统高级课程》,做的实验是MIT6.828的课程实验,在一个简易操作系统内核XV6上进行调试开发。后续我也会写一些介绍操作系统的文章。

关于XV6,这是MIT开发的一个教学目的的操作系统。XV6是在x86处理器上(x即指x86)用ANSI标准C重新实现的Unix第六版(Unix V6,通常直接被称为V6)。并且MIT6.828这门关于操作系统的课程也是广受好评的,也推荐大家去学习。

这里再贴出一份相关的资源清单(不要再说找不到学习资源了而不学了~):


回归主题,这次文章的来由就是实现虚拟机上实现gdb调试xv6。一开始因为只下载了内核的代码,所以只能进行编译运行,使用自带的gdb出现各种问题,后来发现是没有安装相应的编译工具。但是实际安装起来却花费了我好大一番功夫,所以特此记录,帮助后来人减少弯路。

报错信息1

报错信息2

再提一下我的机器环境:

  • 宿主机操作系统:Win10
  • 虚拟机:Ubuntu20.04

追记

后来经过指点,发现根本不需要下面riscv-gnu-toolchain 安装这一步骤,可以跳过那部分(其实那部分操作相当于是自己手动进行编译了,但实际上只需要用到编译好的工具程序即可)。

话不多说,直接上教程。

  1. 首先可以根据官网教程安装,也就是执行下面这条命令,安装相关工具:
1
sudo apt-get install git build-essential gdb-multiarch qemu-system-misc gcc-riscv64-linux-gnu binutils-riscv64-linux-gnu 
  1. 然后再克隆实验代码
1
git clone git://g.csail.mit.edu/xv6-labs-2020
  1. 切换分支,并进行编译
1
2
git checkout util
make qemu

如果出现类似下面的界面,则表明安装成功

xv6内核运行截图

  1. 按ctrl + a + x 退出内核运行,在启动make qemu-gdb
1
make qemu-gdb

make qemu-gdb运行结果

  1. 然后打开另一个中断窗口,直接执行gdb的话,会出现下面的提示

提示

  1. 按照提示,在~/.gdbinit文件中加入 set auto-load safe-path /
  2. 然后执行gdb-multiarch,看到出现类似下面的界面,就表示成功了,接下里就是愉快的调试时间了,可以参考下文的GDB调试内容。

执行成功

riscv-gnu-toolchain 安装

根据官网上的教程安装,发现虚拟机连不上外网(这里还尝试一番功夫,没能成功),所以看到这篇文章,riscv-gnu-toolchain工具链下载安装_roockiet的博客[1]

使用gitee上的镜像进行下载,需要执行的命令大体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 安装编译需要的一些依赖
sudo apt-get install autoconf automake autotools-dev curl libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev

# 克隆工具仓库
git clone https://gitee.com/mirrors/riscv-gnu-toolchain.git

# 进入目录
cd riscv-gnu-toolchain

# 手动克隆其他仓库,下载比较耗时,耐心等待吧
git clone https://gitee.com/mirrors/riscv-binutils-gdb.git riscv-binutils
git clone https://gitee.com/mirrors/riscv-dejagnu.git riscv-dejagnu
git clone https://gitee.com/mirrors/riscv-gcc.git riscv-gcc
git clone https://gitee.com/mirrors/riscv-binutils-gdb.git riscv-gdb
git clone https://gitee.com/mirrors/riscv-glibc.git riscv-glibc
git clone https://gitee.com/mirrors/riscv-newlib.git riscv-newlib

# 这个qemu目录,后面好像没有用到,可以把这个目录给去了,如果担心,那就也一并下载吧
git clone https://gitee.com/mirrors/qemu.git qemu

# 所有仓库下载完成,开始编译
./configure --prefix=/usr/local #(选择安装目录,请确保对应的目录有足够大的容量,算上下载的仓库大约20G)
sudo make
# 这样编译大约需要 1 ~ 2 小时

注意这个riscv-gnu-toolchain所有的仓库都克隆下来后,并且直接执行make安装的话,耗时较久,估计有1~2小时,最后大约有18G(我是安装在了/opt/bin目录下),所以一定要确保对应分区的容量足够,不然就会因为容量不够而写失败(都是血淋淋的教训啊,看到下图根目录的挂载点了吗,之前就是因为安装在根目录对应的分区下,导致容量空间直接满了。。)。

查看磁盘分区使用情况

如果为了节省空间和时间,可以只尝试下面的配置方式,只安装riscv64-unknown-elf-gcc,参考这篇文章,riscv各种版本gcc工具链编译与安装_weiqi7777的博客-CSDN博客_riscv编译器[2]

因为后面调试时只用到了riscv64-unknown-elf-gdb,所以我猜测只需要安装对应gcc工具即可,不过我自已没有尝试过可不可行,所以还是交由各位看官自行决定。

1
2
../configure --prefix=/opt/riscv64 --with-arch=rv64imc --with-abi=lp64
make

GDB调试

  1. 首先在一个窗口上执行:
1
make qemu-gdb
  1. 然后打开另一个窗口,执行:
1
riscv64-unknown-elf-gdb kernel/kernel
  1. 然后可以看到进入了gdb界面,接着远程连接上一个窗口的gdb,执行下面的命令:

target remote localhost:26000 (这里的26000填上一个窗口的端口号)

  1. 连接成功之后,显示如下:

远程连接程序

  1. 然后把自己编译好的用户程序加载进来,也就是user目录下的文件file user/_find

加载要调试的编译文件

设置断点并执行

  1. 然后设置好断点,就可以按下 c 让程序运行到断点处,再返回上个窗口,执行对应命令,就可以看到程序暂停住了,返回gdb调试程序窗口,也能看到当前暂停的地方就是刚刚设置的断点处。

远程内核开始运行

gdb查看当前断点

  1. 之后就是痛苦地愉快地调试bug啦~有关gdb调试技巧,还可以参考我之前写的这篇文章,GDB调试学习

踩坑记录

这次安装过程中遇到两个坑,一个是在克隆仓库的时候,虚拟机无法访问外网,一个是安装riscv编译工具时,分区的容量不够了,这里就简单地做个记录。

虚拟机网络配置

一开始的需求是实现虚拟机共享主机的VPN,也能够访问外网,参考了这篇文章,Ubuntu虚拟机共享主机VPN(适用于NAT或桥接)[3]

尝试之后,报错代理服务器错误,正常的国内网页无法访问了。因为这个时候我用的是NAT模式,所以就想要尝试用桥接模式再试一下。

参考这里的视频教程 VMware网络选择和ubuntu 20_04 网络配置教程[4]

配置静态IP的方式,我之前也提到过【99%环境搭建系列】云计算管理平台Devstack安装,后来虽然国内网络能够正常访问了,但是外网还是不行。

后来尝试很久还是没能成功,最后也就作罢了。既然外网访问不了,那就找国内镜像吧,幸运的是这条路走通了。


最后没有实现需求,我想可能还是自己对网络的了解还是太粗浅了,日后一定要再恶补一下。不过借此机会,也了解学习了下VPN、桥接模式、NAT模式的概念,这里做个简单总结吧。

VPN:Vitrual Private Network,简单来说就是通信双方之间的数据进行了加密。更多内容参考 VPN概念,技术原理和误区_哔哩哔哩_bilibili[5]

桥接模式:虚拟机使用宿主机的真实网卡,宿主机和虚拟机在相同的公网IP段下。

NAT模式:虚拟机和宿主机使用同一个虚拟网卡,从而实现在同一个局域网网段下,比如192.168.17.0 / 24。

ubuntu添加磁盘分区

还有一个坑就是在编译安装的时候,磁盘分区容量不够了。所以这里在演示一下如何给linux虚拟机添加磁盘分区。

  1. 首先在VMware -> 虚拟机选项 -> 管理,选择扩展,添加想要增加的分区容量。(注意需要关闭虚拟机进行设置)
  2. 此时是还无法使用新增容量的,需要在虚拟机上添加分区,并挂载某一目录。那么首先登陆虚拟机,添加分区。
1
2
3
4
5
6
7
8
# 列出所有设备,找到 type 类型为 disk 的设备名称
lsblk

# fdisk 适合MBR分区
fdisk /dev/sda # 这里的设备名称就是上面找到的设备名称

# 然后按 n , 新增分区,选择一个区号,一路回车,最后 w 写入
# 更详细的教程可以参考 https://www.cnblogs.com/rootshaw/p/12895022.html
  1. 添加好分区之后,就是格式化 sudo mkfs.ext4 /dev/sda3 (设备名换成刚刚新增的分区)

  2. 然后就是挂载目录sudo mount /dev/sda3 /opt/bin(选择自己需要挂载的目录)

  3. 使用df -h 可以看是否挂载成功。挂载成功之后就可以使用该分区了。

  4. 上面的挂载,每次重启后都需要手动重新设置,所以这里再实现开机自动挂载,其实就是往配置文件/etc/fstab 里写配置,格式如下:

1
2
分区/设备名     要挂载的目录     文件系统类型     defaults        0       0
/dev/sda3 /opt/bin ext4 defaults 0 0

总结

至此,折腾了我快一星期,总计耗时约10小时的调试环境安装之旅终于落下了帷幕。此时此刻我的心情是复杂的,一方面是为自己的无能而流泪(一个简单的调试环境搭建弄了这么久),兜兜转转这么久,也还只是完成了调试前的准备工作,查bug还在后头呢。另一方面也是一种释然,至少我没有半途而废,虽然过程坎坷,但我终究还是来到了终点。或许一开始我可以尝试用print输出来调试,但是我知道这种调试方式终究走不远,因为真正出问题的时候,是没有办法通过实时输出来进行调试的。所以这次使用gdb来调试内核,是一个千载难逢的机会,我不该错过。


缓慢的、持久的、有纪律的努力,最终会导致令人难以置信的结果。

每当我无法忍受日常生活时,我就提醒自己,没有什么比每天坚持做下去更重要了。虽然我很难看到未来会怎样,但我知道持久性具有强大的威力,就像一句古老格言说的“继续去做”(keep doing)。

——《大海教给我的》

备注/参考


【99%环境搭建系列】xv6-riscv内核调试教程
https://2017zhangyuxuan.github.io/2022/03/19/2022-03/2022-03-19 环境搭建系列-xv6内核调试教程/
作者
Zhang Yuxuan
发布于
2022年3月19日
许可协议