qemu + gdb + busybox 内核调试流程
0. 准备
软件版本信息
- OS: Ubuntu 20.10 / CentOS 8.2.2004
- gcc: gcc-10.2.0-13(ubuntu 原生)/ gcc-8.3.1-5 (centos 原生)
- qemu: qemu-5.0.0(ubuntu 源码编译安装)/ qemu-kvm-2.12.0-99 (centos 原生)
- gdb: gdb-9.2-0(ubuntu 原生)/ gdb-8.2-11 (centos 原生)
- busybox: busybox-1.33.0(源码编译安装)
- kernel: linux-4.19.157(编译)/ 5.8.0-26-generic(ubuntu 原生)/ 4.18.0-193(centos 原生)
源码获取地址
- qemu: qemu-5.0.0.tar.xz
- busybox: busybox-1.33.0.tar.bz2
- linux: linux-4.19.157.tar.xz
1. 安装 qemu
我在 ubuntu 20.10 上使用了编译安装方式,在 centos 8.2.2004 上使用了直接安装方式,具体如下:
方式一:源码编译安装
(1)获取 qemu 源码 qemu-5.0.0.tar.xz 。
(2)编译安装,命令如下:
apt install bison flex pkg-config libglib2.0-dev libpixman-1-dev -y
xz -d qemu-5.0.0.tar.xz
tar xvf qemu-5.0.0.tar
cd qemu-5.0.0
./configure
make -j4
make install -j4
./configure
过程中可能遇到的报错及解决方法如下:
ERROR: pkg-config binary 'pkg-config' not found # 安装 pkg-config
ERROR: glib-2.48 gthread-2.0 is required to compile QEMU # 安装 libglib2.0-dev
ERROR: pixman >= 0.21.8 not present.
Please install the pixman devel package. # 安装 libpixman-1-dev
(3)成功安装后,系统中会生成各种 qemu- 开头的命令,如 qemu-system-x86_64
。
方式二:直接安装
centos 和 ubuntu 都提供了qemu 包,可以直接安装。centos 8.2 上命令如下:
yum install qemu-kvm-core -y
# 安装后执行如下命令
ln -sf /usr/libexec/qemu-kvm /usr/bin/qemu-system-x86_64
这里简单介绍下配置本地 yum 源的方法,当然也可以直接使用网络源:
(1)下载 centos 8.2 iso 到本地,然后通过 vmware 光驱加载或者下载到虚拟机内部
(2)执行如下命令挂载:
# 挂载目录随意,这里在 /mnt 下面创建一个 repo 目录
mkdir -p /mnt/repo
# 如果 iso 通过 vmware 光驱添加,则执行:
mount /dev/sr0 /mnt/repo
# 如果 iso 直接下载到虚拟机内部,则执行:
mount CentOS-8.2.2004-x86_64-dvd1.iso /mnt/repo
# 配置 yum 源
cd /etc/yum.repos.d
mkdir bak
mv *.repo bak
touch local.repo # 内容见下方
yum clean all
yum makecache
local.repo 文件内容如下:
[base]
name=base
baseurl=file:///mnt/repo/BaseOS
enabled=1
gpgcheck=0
[stream]
name=stream
baseurl=file:///mnt/repo/AppStream
enabled=1
gpgcheck=0
2. 安装 gdb
方式一:源码编译安装
网上很多 qemu + gdb 调试内核的帖子中都提到系统自带 gdb 使用时会有如下报错:
Remote 'g' packet reply is too long: 000000000000000020000000000000004000000000000000001006000000000000f009000000000028aece81ffffffff981fc081ffffffff901fc081ffffffff0030c1010000000000000000000000000000000000000000f0b926020000000020f1d281ffffffffb01fc081ffffffff00e0e681ffffffff0010e781ffffffff02fbd281ffffffff9600000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007f0300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000801f0000
然后需要修改 gdb 源码重新编译,可参考 《QEMU+GDB调试Linux内核总结(全)》。
方式二:直接安装
我在 ubuntu 20.10 和 centos 8.2 中直接使用原生 gdb 都没有遇到这个问题,所以没有重新编译 gdb。
# ubuntu 20.10 命令如下:
apt install gdb -y
# centos 8.2 命令如下:
yum install gdb -y
3. 通过 busybox 制作 initrd
我之前一篇文章 《Linux 启动流程 – BIOS/UEFI & bootloader & kernel & initramfs & systemd》 有介绍 initrd 的作用。initrd 是一个 mini 根文件系统,提供一些必要的工具和驱动,主要起引导作用,帮助 OS 顺利过渡到真实的根文件系统。这里制作的 initrd 不仅起引导作用,同时也是最终的根文件系统,和硬盘分区上的根文件系统不同,它是一个内存文件系统。
下载 busybox 源码 busybox-1.33.0.tar.bz2。编译安装,命令如下:
创建编译目录
命令如下:
apt install libncurses-dev -y
tar jxvf busybox-1.33.0.tar.bz2
mkdir -p busybox-build
cd busybox-1.33.0
make O=../busybox-build defconfig
如果没有安装 libncurses-dev
,make menuconfig
会报如下错误信息:
make -C /home/busybox/busybox-1.33.0 O=/home/busybox/busybox-build menuconfig
HOSTCC scripts/basic/fixdep
HOSTCC scripts/basic/split-include
HOSTCC scripts/basic/docproc
GEN /home/busybox/busybox-build/Makefile
HOSTCC scripts/kconfig/conf.o
HOSTCC scripts/kconfig/kxgettext.o
HOSTCC scripts/kconfig/mconf.o
HOSTCC scripts/kconfig/zconf.tab.o
HOSTLD scripts/kconfig/mconf
HOSTCC scripts/kconfig/lxdialog/checklist.o
<command-line>: fatal error: curses.h: No such file or directory
compilation terminated.
make[4]: *** [scripts/Makefile.host:120: scripts/kconfig/lxdialog/checklist.o] Error 1
make[3]: *** [/home/busybox/busybox-1.33.0/scripts/kconfig/Makefile:14: menuconfig] Error 2
make[2]: *** [/home/busybox/busybox-1.33.0/Makefile:444: menuconfig] Error 2
make[1]: *** [Makefile:112: menuconfig] Error 2
make: *** [Makefile:19: menuconfig] Error 2
生成编译 .config 文件
命令如下:
cd ../busybox-build
make menuconfig
这里需要设置静态编译,编译出的二进制可以独立运行,不依赖其他库。“Setting”(回车进入子菜单) => “Build static binary (no shared libs)” (tab 键选择)。选中后,选择界面下方 Exit 退出,最后会提示保存,选择 Yes,保存 .config。具体过程如下图:
编译安装生成 initrd
命令如下:
# make 和 make install 执行成功后会在 busybox-build 目录下生成 _install 目录
make
make install
# 在 busybox-build 同级目录下创建 rootfs 目录
mkdir rootfs
cd rootfs
cp -ar ../busybox-build/_install/* .
# 创建软连接 init 指向 bin/busybox,内核启动到最后会执行 init 进程
ln -sf bin/busybox init
mkdir -p {sys,proc,dev,etc/init.d}
# 启动脚本,相当于 rc.local
touch etc/init.d/rcS # 内容见下方
chmod 755 etc/init.d/rcS
touch etc/fstab # 内容见下方
# 这里的 pigz 可以多线程压缩,需要安装 pigz,或者使用 gzip 替代。
find . -print0 | cpio --null -ov --format=newc | pigz -9 > ../initrd-busybox.img
rcS 文件内容如下,来自 tinycorelinux/Core-scripts:
#!/bin/sh
# RC Script for Tiny Core Linux
# (c) Robert Shingledecker 2004-2012
# Mount /proc.
[ -f /proc/cmdline ] || /bin/mount /proc
# Remount rootfs rw.
/bin/mount -o remount,rw /
# Mount system devices from /etc/fstab.
/bin/mount -a
clear
fstab 文件内容如下:
sysfs /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0
proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0
udev /dev devtmpfs rw,nosuid,noexec,relatime,mode=755 0 0
上述命令执行成功后,会在 busybox-build 同级目录下生成 initrd-busybox.img
,即 initrd。上述 initrd 是在 ubuntu 环境上制作的,也可以直接在 centos 使用。
4. 获取内核 vmlinuz 和 vmlinux
方式一:内核源码编译
(1)下载内核源码 linux-4.19.157.tar.xz。
(2)编译安装,命令如下:
apt install libelf-dev libssl-dev binutils-dev -y
xz -d linux-4.19.157.tar.xz
tar xvf linux-4.19.157.tar
cd linux-4.19.157
# 生成编译选项文件 .config,和 busybox 流程类似
make menuconfig
# 编译成功后,源码根目录下会生成带调试信息的 vmlinux 文件,
# 内核文件在 arch/x86/boot 目录下,文件名为 bzImage,即 vmlinuz。
make -j4
方式二:直接从 ubuntu 或 centos 发布件中获取
centos 8.2 获取方式如下:
- vmlinuz: 位于系统 boot 目录下,文件名为
vmlinuz-4.18.0-193.el8.x86_64
。 - vmlinux: kernel-debuginfo-4.18.0-193.el8.x86_64.rpm
可以通过rpm2cpio kernel-debuginfo-4.18.0-193.el8.x86_64.rpm | cpio -divm
命令解压 rpm 包,vmlinux 位于./usr/lib/debug/lib/modules/4.18.0-193.el8.x86_64/vmlinux
。 - source: kernel-debuginfo-common-x86_64-4.18.0-193.el8.x86_64.rpm
可以通过rpm2cpio kernel-debuginfo-common-x86_64-4.18.0-193.el8.x86_64.rpm | cpio -divm
或者直接安装,gdb 中通过dir
命令指定源码路径即可。
ubuntu 20.10 获取方式如下:
- vmlinuz: 位于系统 boot 目录下,文件名为
vmlinuz-5.8.0-26-generic
- vmlinux: linux-image-unsigned-5.8.0-26-generic-dbgsym_5.8.0-26.27_amd64.ddeb
可以通过dpkg -X linux-image-unsigned-5.8.0-26-generic-dbgsym_5.8.0-26.27_amd64.ddeb
解压安装包或者直接安装,vmlinux 路径为./usr/lib/debug/boot/vmlinux-5.8.0-26-generic
。 - source: groovy-src-2.iso 没找到单个源码包,只找到源码包 iso 镜像,镜像中有包名 linux_5.8.0-25.26.diff.gz,版本号不匹配。
5. 调试内核
启动 qemu
命令如下:
qemu-system-x86_64 -kernel /home/kernel/linux-4.19.157/arch/x86/boot/bzImage -initrd /home/busybox/initrd-busybox.img -append "console=ttyS0 nokaslr" -nographic -S -s
-kernel
指定内核镜像文件。-initrd
指定 initrd 文件。-append
指定内核启动参数。console=ttyS0
输出重定向到串口。nokaslr
关闭内核地址随机化,不添加该参数,内核地址随机化,使得 gdb 从 vmlinux 计算得到的断点位置并不是实际位置,最终会导致断点失效,参考 《开启内核地址随机化KASLR后, qemu 调试 kernel 不能设置断点》。
-nographic
重定向串口 I/O 到 console,该参数和console=ttyS0
内核启动参数共同作用,使得可以直接在 ssh 连接界面看到内核打印的日志以及登录 shell。-S
cpu 启动时暂停。-s
等同于-gdb tcp::1234
,启动 gdbserver 并监听 1234 端口。
上述命令执行后,界面会停住,等待 gdb 客户端连接,如下:
启动 gdb 调试内核
(1)重新起一个 ssh 连接,在已编译完成的内核源码路径下执行命令 gdb ./vmlinux
:
(2)gdb 中执行命令 target remote :1234
,回车后显示如下:
(3)设置断点 b start_kernel
,然后 continue
。
(4)qemu 端回显如下:
(5)gdb 继续执行 continue
,qemu 端显示如下,最终进入 shell,等待执行命令:
至此,qemu + gdb + busybox 整个内核调试流程介绍完毕。