本文将在Ubuntu22.04下搭建一个QEMU的运行环境,并编译Linux内核源码,随后通过QEMU来调试Linux内核,用以辅助Linux内核的学习。
事实上,我在Ubuntu22.04和Ubuntu24.04下都试过了,流程上是一样的,但是Ubuntu24.04因为内核版本太新(至少对于在写这篇文章的时间点来说),踩了一些细节上的坑,最终调试效果并不好,所以我还是建议选用Ubuntu22.04。
我采用的是从源码编译再安装的方式,这个方法具备一般性。
首先去官网下载QEMU的源码,我使用的是10.1.2版本的,你可以根据需要选择合适的版本:
https://www.qemu.org/download/
随后查看官方的编译环境配置手册,根据具体系统来进行具体配置:
https://www.qemu.org/docs/master/devel/build-environment.html
我使用的是Ubuntu,根据官方手册内容,可以使用命令:
shellsudo apt update && sudo apt build-dep qemu
然后进入QEMU源码根目录,创建编译目录并开始编译:
shellmkdir build
cd build
../configure
# 这里我会开启并行编译加快编译速度
# 不需要并行编译可以直接make
make -j`nproc`
编译完成后可以安装到系统目录:
shellsudo make install
随后可以随便选择一个架构来看看编译安装的情况如何,正常情况下以下命令会输出一些帮助信息:
shellqemu-system-x86_64 -h
先安装一个库:
shellsudo apt install -y libelf-dev
随后下载Linux内核源码,选择你需要的版本:
https://cdn.kernel.org/pub/linux/kernel/
我选择的版本是6.8.12,使用如下命令下载并解压,再进入内核源码根目录:
shellwget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.8.12.tar.xz tar -xf linux-6.8.12.tar.xz cd linux-6.8.12
Ubuntu22.04的内核大版本也是6.8,而Ubuntu24.04的内核大版本是6.14,这一点你可以通过命令uname -r来确定。这可能会对内核的编译带来影响,因为
GCC编译器也是跟着系统版本来的,至少对于通过apt安装的是这样。
然后使用默认配置:
shellmake defconfig
如果你需要调整内核的配置,可以换用以下命令:
shellmake menuconfig
然后就可以开始编译内核了:
shellmake -j`nproc` vmlinux bzImage
编译成功后,得到两个关键的文件:
# 位于内核源码根目录 vmlinux # 位于./arch/x86/boot bzImage
构建根文件系统的方式还挺多的,我们也不需要太复杂的根文件系统,所以我打算采用Busybox+手动构建,类似的选择还有Buildroot和Yocto,但这两个配置上复杂一些,不过好处就是不用手动创建一些文件。
构建根文件系统的方式不唯一,你可以根据你的知识积累做这一步,用其他的方式构建可能需要你去更改内核配置(使用命令
make menuconfig),打开initramfs的支持,在我这默认是打开了的:General setup ---> [*]Initial RAM filesystem and RAM disk(initramfs/initrd)
去官网下载Busybox:
https://busybox.net/downloads/
我选择的版本是1.37.0。
随后解压并进入其根目录:
shelltar -xf busybox-1.37.0.tar.bz2 cd busybox-1.37.0
需要调整Busybox的配置:
shellmake menuconfig
随后会打开一个界面,如下图所示:

我们选择以下选项:
Settings ---> [*] Build static binary (no shared libs)

配置完成保存退出即可,最简单的方式是疯狂按Esc键,直到弹出询问你是否保存的界面,选择保存即可。

然后开始编译根文件系统:
shellmake
这里有个踩坑提示,在Ubuntu24.04下会编译失败,Ubuntu22.04是没有这个问题的,主要还是因为Ubuntu24.04用的内核版本太新了(可以通过命令uname -r来查看)。

原因如下:
https://github.com/gramineproject/gramine/issues/1909
解决方法可以查看以下链接:
https://lists.busybox.net/pipermail/busybox-cvs/2024-January/041752.html
我的做法是在根目录下编辑配置文件,将CONFIG_TC这个选项关掉:
shellvi .config

然后清理一下之前的编译产物并重新编译即可:
shellmake clean && make
编译完成之后,打包一下:
shellmake install
打包路径为当前目录下的_install文件夹
shellcd _install ls

该目录将成为我们制作的根文件系统,从当前该目录所罗列的文件可以看出,少了些东西,我们现在需要手动构建。
shellmkdir proc
mkdir sys
# 创建并编辑文件init,复制下面给出的内容即可
vi init
chmod +x init
init的内容#!/bin/sh mkdir /tmp mount -t proc none /proc mount -t sysfs none /sys mount -t debugfs none /sys/kernel/debug mount -t tmpfs none /tmp mdev -s setsid /bin/cttyhack setuidgid 1000 /bin/sh
接下来打包根文件系统到HOME目录:
shellfind . | cpio -o --format=newc > ~/rootfs.img
确定我们准备好了以下三个文件:

打开两个终端,一个终端运行QEMU,另一个终端运行GDB
shell# 终端A
qemu-system-x86_64 -kernel ./bzImage -initrd ./rootfs.img -append "nokaslr console=ttyS0" -s -S -nographic
# 终端B
gdb ./vmlinux
(gdb) target remote :1234
(gdb) b start_kernel
(gdb) c
我们给内核的start_kernel函数下了断点,并开始执行内核,现在会在内核跑到start_kernel函数时暂停:

这样大体上就成功了,可以开始用GDB调试Linux内核源码了,可以给GDB输入命令c让它继续加载进入系统。
本文作者:Test
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!