利用QEMU+GDB搭建Linux内核调试环境
Posted LinuxDriverDev
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了利用QEMU+GDB搭建Linux内核调试环境相关的知识,希望对你有一定的参考价值。
前言
对用户态进程,利用gdb调试代码是很方便的手段。而对于内核态的问题,可以利用crash等工具基于coredump文件进行调试。
其实我们也可以利用一些手段对Linux内核代码进行gdb调试,qemu就是一种。
qemu是一款完全软件模拟(Binary translation)的虚拟化软件,在虚拟化的实现中性能相对较差。但利用它在测试环境中gdb调试Linux内核代码,是熟悉Linux内核代码的一个好方法。
本文实验环境:
- ubuntu 20.04
- busybox-1.32.1
- Linux kernel 4.9.3
- QEMU
- GDB 10.1
编译内核源码
git clone git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
tar -xvzf linux-4.9.301.tar.gz
cd linux-4.9.301
make menuconfig
在内核编译选项中,开启如下"Compile the kernel with debug info"
Kernel hacking --->
Compile-time checks and compiler options --->
[ ] Compile the kernel with debug info
示意图如下,利用键盘选中debug选项,然后敲"Y"勾选:
以上配置完成后会在当前目录生成 .config
文件,我们可以使用 grep
进行验证:
grep CONFIG_DEBUG_INFO .config
CONFIG_DEBUG_INFO=y
编译内核
make bzImage -j4
编译完成后,会在当前目录下生成vmlinux,这个在 gdb 的时候需要加载,用于读取 symbol 符号信息,包含了所有调试信息,所以比较大。
压缩后的镜像文件为bzImage, 在arch/x86/boot/目录下。
➜ linux-4.9.301 ls -hl vmlinux
-rwxrwxr-x 1 ubuntu ubuntu 578M Apr 15 08:14 vmlinux
➜ linux-4.9.301 ls -hl ./arch/x86_64/boot/bzImage
lrwxrwxrwx 1 ubuntu ubuntu 22 Apr 15 08:15 ./arch/x86_64/boot/bzImage -> ../../x86/boot/bzImage
➜ linux-4.9.301 ls -hl ./arch/x86/boot/bzImage
-rw-rw-r-- 1 ubuntu ubuntu 9.3M Apr 15 08:15 ./arch/x86/boot/bzImage
几种linux内核文件的区别:
vmlinux 编译出来的最原始的内核文件,未压缩。
zImage 是vmlinux经过gzip压缩后的文件。
bzImage bz表示“big zImage”,不是用bzip2压缩的。两者的不同之处在于,zImage解压缩内核到低端内存(第一个640K)。
bzImage解压缩内核到高端内 存(1M以上)。如果内核比较小,那么采用zImage或bzImage都行,如果比较大应该用bzImage。
uImage U-boot专用的映像文件,它是在zImage之前加上一个长度为0x40的tag。
vmlinuz 是bzImage/zImage文件的拷贝或指向bzImage/zImage的链接。
initrd 是“initial ramdisk”的简写。一般被用来临时的引导硬件到实际内核vmlinuz能够接管并继续引导的状态。
编译busybox
Linux系统启动阶段,boot loader加载完内核文件vmlinuz后,内核紧接着需要挂载磁盘根文件系统,但如果此时内核没有相应驱动,无法识别磁盘,就需要先加载驱动。
而驱动又位于/lib/modules
,得挂载根文件系统才能读取,这就陷入了一个两难境地,系统无法顺利启动。
于是有了initramfs根文件系统,其中包含必要的设备驱动和工具,bootloader加载initramfs到内存中,内核会将其挂载到根目录/
,然后运行/init
脚本,挂载真正的磁盘根文件系统。
这里借助BusyBox构建极简initramfs,提供基本的用户态可执行程序。
可以从busybox官网地址下载最新版本,或者直接使用wget下载我使用的版本。
wget https://busybox.net/downloads/busybox-1.32.1.tar.bz2
$ tar -xvf busybox-1.32.1.tar.bz2
$ cd busybox-1.32.1/
$ make menuconfig
在编译busybox之前,我们需要对其进行设置,执行make menuconfig,如下
这里一定要选择静态编译,编译好的可执行文件busybox
不依赖动态链接库,可以独立运行,方便构建initramfs。
之后选择Exit退出,到这里我们就可以编译busybox了,执行下面的命令
make -j 8
# 安装完成后生成的相关文件会在 _install 目录下
make && make install
构建initramfs根文件系统
[root@localhost temp]# ls
busybox-1.29.0 busybox-1.29.0.tar.bz2
[root@localhost temp]# mkdir initramfs
[root@localhost temp]# cd initramfs
[root@localhost initramfs]# cp ../busybox-1.32.1/_install/* -rf ./
[root@localhost initramfs]# mkdir dev proc sys
[root@localhost initramfs]# sudo cp -a /dev/null,console,tty,tty1,tty2,tty3,tty4 dev/
[root@localhost initramfs]# rm -f linuxrc
[root@localhost initramfs]# vim init
[root@localhost initramfs]# chmod a+x init
[root@localhost initramfs]# ls
bin dev init proc sbin sys usr
其中init的内容如下
echo "==DBG== INIT SCRIPT"
mount -t proc none /proc
mount -t sysfs none /sys
echo -e "==DBG== Boot took $(cut -d -f1 /proc/uptime) seconds"
exec /sbin/init
打包initramfs
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs.cpio.gz
[root@localhost initramfs]# ls ../
busybox-1.29.0 busybox-1.29.0.tar.bz2 initramfs initramfs.cpio.gz
安装QEMU
apt install qemu qemu-utils qemu-kvm virt-manager libvirt-daemon-system libvirt-clients bridge-utils
安装GDB
wget https://ftp.gnu.org/gnu/gdb/gdb-10.1.tar.gz
tar -xzvf gdb-10.1.tar.gz
cd gdb-10.1
./configure
# 必需要安装这两个库
sudo apt-get install texinfo
sudo apt-get install build-essential
make -j 8
sudo make install
QEMU启动调试内核
➜ linux-4.9.301 qemu-system-x86_64 -kernel ./arch/x86/boot/bzImage -initrd ../initramfs.cpio.gz -append "nokaslr console=ttyS0" -s -S -nographic
-
-kernel ./arch/x86/boot/bzImage
:指定启用的内核镜像; -
-initrd ../initramfs.cpio.gz
:指定启动的内存文件系统; -
-append "nokaslr console=ttyS0"
:附加参数,其中nokaslr
参数必须添加进来,防止内核起始地址随机化,这样会导致 gdb 断点不能命中; -
-s
:监听在 gdb 1234 端口; -
-S
:表示启动后就挂起,等待 gdb 连接; -
-nographic
:不启动图形界面,调试信息输出到终端与参数console=ttyS0
组合使用;
在另一个窗口中,输入gdb,即可开启调试。
(gdb) target remote localhost:1234
Remote debugging using localhost:1234
warning: Can not parse XML target description; XML support was disabled at compile time
Remote g packet reply is too long (expected 560 bytes, got 608 bytes): 0000000000000000000000000000000000000000000000006306000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f0ff0000000000000200000000f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007f0300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000801f0000
(gdb) Remote debugging using localhost:1234
Undefined command: "Remote". Try "help".
(gdb) warning: Can not parse XML target description; XML support was disabled at compile timeQuit
但是,在启动GDP调试时报错了,在查阅了诸多资料后,很多博客都给出了修复方法:源码重新安装gdb,并修改gdb/remote.c文件的一段代码。但是我尝试了,发现行不通。
出现该问题的原因是:编译 的是64 位模式的内核代码,但是运行是在 32 位保护模式下。64 位代码将无法在该环境中正常运行。
终于在stackflow上找到了修复方法:具体可以参考下面两篇文章。
https://stackoverflow.com/questions/48620622/how-to-solve-qemu-gdb-debug-error-remote-g-packet-reply-is-too-long
https://wiki.osdev.org/QEMU_and_GDB_in_long_mode
文章中给出了三种修复方法,我这里只列出了一种,即修改GDB源码,重新编译安装。
--- gdb/remote.c 2016-04-14 11:13:49.962628700 +0300
+++ gdb/remote.c 2016-04-14 11:15:38.257783400 +0300
@@ -7181,8 +7181,28 @@
buf_len = strlen (rs->buf);
/* Further sanity checks, with knowledge of the architecture. */
+// HACKFIX for changing architectures for qemu. Its ugly. Dont use, unless you have to.
+ // Just a tiny modification of the patch of Matias Vara (http://forum.osdev.org/viewtopic.php?f=13&p=177644)
if (buf_len > 2 * rsa->sizeof_g_packet)
- error (_("Remote g packet reply is too long: %s"), rs->buf);
+
+ warning (_("Assuming long-mode change. [Remote g packet reply is too long: %s]"), rs->buf);
+ rsa->sizeof_g_packet = buf_len ;
+
+ for (i = 0; i < gdbarch_num_regs (gdbarch); i++)
+
+ if (rsa->regs[i].pnum == -1)
+ continue;
+
+ if (rsa->regs[i].offset >= rsa->sizeof_g_packet)
+ rsa->regs[i].in_g_packet = 0;
+ else
+ rsa->regs[i].in_g_packet = 1;
+
+
+ // HACKFIX: Make sure at least the lower half of EIP is set correctly, so the proper
+ // breakpoint is recognized (and triggered).
+ rsa->regs[8].offset = 16*8;
+
/* Save the size of the packet sent to us by the target. It is used
as a heuristic when determining the max size of packets that the
cd gdb-10.1
./configure
make -j 8
使用 GDB + Qemu 调试 Linux 内核