Linux内核调试环境的搭建 QEMU+GDB
最近在折腾Linux内核,但是折腾调试环境以及修改内核的编译选项就折腾了我一晚上的时间。
我使用的调试环境包括:
- QEMU(用于软件模拟系统执行环境,方便对硬件各寄存器等状态进行调试,为了方便起见,这里使用x86_64环境)
- GDB
以下是我的折腾步骤:
Step 0. 安装必要工具
需要确保你已经有以下软件包(对于Debian/Ubuntu):
- build-essential
- gdb
- qemu-system-x86
- rsync(用于带权限的文件复制)
如果你采用其他发行版,可以参考对应发行版的相关Wiki,以及使用软件包管理器的搜索功能。
Step 1. 准备内核
Substep 1.1. 下载内核
可以从https://www.kernel.org/网站上下载,也可以直接采用git的方式将Linux内核代码仓库clone到本地。
# 方法一
https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.11.3.tar.xz
tar -xvf linux-5.11.3.tar.xz
# 方法二
git clone git@github.com:torvalds/linux.git
# 之后根据方法的不同进入对应的文件夹
cd linux
Substep 1.2. 配置编译选项
内核的编译我一开始入了个坑,如果直接采用make menuconfig
,出现的默认配置会打开非常多不必要的选项,结果编译出来的内核高达700M(x86_64架构下),这显然是不可接受的。
所以我们首先将内核的编译选项设置到该架构的默认设置,这样会关闭很多不必要的设置。
make defconfig
但是如果此时直接make,生成出来的vmlinux这个elf文件并没有调试信息,同时也由于没有PVH ELF Note而不能被QEMU直接启动。因此,我们对编译选项进行修改。
这里我推荐使用的nconfig
,相较于defconfig
,它的操作更加方便。
make nconfig
然后我们会看到一个如图所示的界面。
在这里,我们重点需要修改2个选项:
- 开启编译调试信息
进入Kernel hacking
->Compile-time checks and compiler options
首先打开Compile the kernel with debug info
(选择后按空格即可)
同时推荐打开Provide GDB scripts for kernel debugging
然后不断按键盘←
键回到首页。
- 打开PVH Note,以便QEMU可以直接运行
进入Processor type and features
,打开Linux guest support
然后进入Linux guest support
,打开Support for running PVH guests
之后按F6
保存,F9
退出。
Substep 1.3. 编译内核
这里我们直接使用make即可,其中可以用-j参数指定编译所使用的线程数,推荐让使用的线程数=CPU所拥有的线程数。
然后经过大约几分钟的等待,内核就编译完成了。
make -j 16 # 16根据CPU实际情况修改
Substep 1.4. 测试内核
qemu-system-x86_64 -kernel vmlinux -nographic -append "console=ttyS0"
此时应该可以看到以下输出,说明内核载入到VFS的时候因为不存在root文件系统而无法继续,但是大体上已经可以工作了。
这里我们可以按键盘Ctrl
+A
,然后松开这两个键,再按X
,退出QEMU。
Step 2. 准备root文件系统
Substep 2.1. 编译Busybox
我们刚才编译的Linux仅仅只是内核本身,包含了驱动等等的各种模块,但是它并不包含一个基本的与用户交互的Shell等种种用户使用所需的组件,因此我们需要将这些部分放在root文件系统中,方便内核进行使用。
而Busybox可以提供一个轻量级的Shell以及相关工具,因此我们在此选用Busybox。
我们先到Busybox官网https://busybox.net/ ,点击Download Source
,找到最新版本进行下载。
然后依次进行默认设置以及对编译选项进行设置。
wget https://busybox.net/downloads/busybox-1.33.0.tar.bz2
tar -xvf busybox-1.33.0.tar.bz2
cd busybox-1.33.0
make defconfig
make menuconfig
由于我们缺少一些Busybox所需要的Library,所以需要修改Busybox编译选项进行静态编译。进入Settings
,在底下找到Build Options
,然后选择Build static binary (no shared libs)
,按Y
打开,然后退出,并保存。
进行编译
make install
然后,我们所需的根文件系统就被创建在了busybox目录中的_install
文件夹中。
Substep 2.2. 准备虚拟磁盘
此时我们可以考虑两种方式:
- 使用initramfs,通过创建initrd镜像来进行使用,许多Linux发行版也使用这种方式来在挂载磁盘之前加载所需要的驱动模块(比如有的可能是btrfs over lvm over luks,)以及在磁盘无法挂载的时候提供必要的急救服务。
- 直接挂载磁盘(较为方便)
这里考虑到要对内核模块进行调试,需要经常对文件进行修改,因此采用直接挂载磁盘镜像的方式。
首先创建虚拟磁盘并挂载:
cd ..
qemu-img create -f raw disk.img 1G
sudo mkfs.ext4 disk.img
mkdir rootfs
sudo mount disk.img rootfs
使用rsync将busybox的_install
目录下所有文件复制过来,然后再创建一些基本的文件。
sudo rsync -ar busybox-1.33.0/_install/* rootfs
cd rootfs
sudo chown root:root * -R
sudo mkdir -p proc sys dev etc/init.d
sudo mknod -m 622 dev/console c 5 1
sudo mknod -m 666 dev/null c 1 3
sudo mknod -m 666 dev/zero c 1 5
sudo mknod -m 666 dev/ptmx c 5 2
sudo mknod -m 666 dev/tty c 5 0
sudo mknod -m 444 dev/random c 1 8
sudo mknod -m 444 dev/urandom c 1 9
sudo chown root:tty dev/{console,ptmx,tty}
sudo sh -c 'echo "#!/bin/sh\nmount -t proc none /proc\nmount -t sysfs none /sys\nln -s /dev/null /dev/tty2\nln -s /dev/null /dev/tty3\nln -s /dev/null /dev/tty4\nexit 0" > etc/init.d/rcS'
sudo chmod a+x etc/init.d/rcS
现在,就可以卸载虚拟磁盘,启动虚拟机了。
cd ..
sudo umount rootfs
qemu-system-x86_64 -kernel linux/vmlinux -nographic -append "console=ttyS0 root=/dev/sda rw" -drive file=disk.img,format=raw,id=hd0
效果如图:
Step 3. 内核调试
内核调试也非常简单,我们给QEMU的运行参数添加-S
与-s
,分别是不自动启动,以及开启gdb并监听TCP 1234端口。
在一个终端执行以下命令:
qemu-system-x86_64 -kernel linux/vmlinux -nographic -append "console=ttyS0 root=/dev/sda rw" -drive file=disk.img,format=raw,id=hd0 -S -s
在另一个终端执行以下命令:
cd linux
gdb vmlinux # 如果看到了.gdbinit的相关提示,按照提示操作添加白名单即可
target remote :1234
b kernel_execve # 添加自己所需要的断点,这里举个例子
layout src
c
效果如图: