想到自己搞了这么久内核居然还不会单步调试,心里感到特别惭愧和无能。做个笔记记录一下如何使用QEMU和GDB来单步调试内核。本来想先研究下用VirtualBox加串口来调试的,奈何发现自己的键盘没有SysRq键,不想重新映射键盘,也有点担心最后的效果可能真的没有QEMU效果好,所以暂时先弄QEMU好了。为什么VirtualBox调试的时候需要SysRq键呢,因为我用宿主机的GDB通过串口连VirtualBox里的虚拟机时,居然无法通过GDB的Ctrl+C
中断正在执行的虚拟机里的Linux,只能在虚拟机里通过往/proc/sysrq-trigger
里写g
命令来暂停操作系统的运行,可是这么做根本没有意义,因为我用GDB单步调试内核的目标场景就是系统出现故障无法响应的时候,通过GDB看下当前堆栈信息,找出是哪里出了错,这个目标场景下根本就没有shell可用。这个g
命令本来是可用Alt+SysRq+g
组合键实现的,可是我的键盘把SysRq键映射成了PrintScreen键,于是这个组合键就暂时用不了,需要自己手动去修改键盘映射。内核文档里有写SysRq键被映射为PrintScreen键之后怎么改回来的说明,可是我暂时没弄懂这个改键原理,就先暂时搁置算了。
Table of Contents
概述
想要达成使用QEMU调试内核代码的目标,需要做到以下几点:
- 准备rootfs
只有一个内核的话没啥可调的(至少对本菜鸟是这样),所以我们希望QEMU运行的最好是一个日常使用的Linux发行版一样的东西,有帐号管理,有各种命令行工具,我们在这个系统里面重现出Bug的场景,然后通过宿主机的GDB连上这个系统的内核,开始诊断问题。这个准备rootfs的工作就相当于定制我们自己的发行版了: - 制作一个虚拟硬盘;
- 把我们想要的软件都装进去;
- 内核的话,看情况我们有三种选择:使用各种官方发行版里面自带的内核;安装自己编译或者从其他地方搞过来的内核到虚拟硬盘里;不安装内核,在QEMU的启动选项里指定内核文件的位置; 如果有精力的话,可以去看Linux From Scratch这本书,对制作Linux发行版将会有更深刻和全面的理解。
- 编译kernel
- 启动QEMU
- 使用GDB连接正在运行的内核
动手实验
只有通过动手实验才能记忆的更深刻,才有机会发现各种意想不到的问题。下面记录一下我的实验过程,基本是按照后面列的别人的博客来走的,加上一点点自己的疑问和思考。
准备rootfs
1.准备一个虚拟硬盘
qemu-img create debian-stretch-image.img 4g
mkfs.ext2 debian-stretch-image.img
根据自己预估的要往rootfs里装的软件大小,可以将硬盘的大小设为适当的容量;文件系统也可以用其他的,比如ext4。
2.挂载虚拟硬盘
# 创建挂载点
mkdir mp
sudo mount -o loop debian-stretch-image.img mp
loop
选项的说明看mount的manual吧。
3.往虚拟硬盘里装必要的东西
装一个基本的Debian系统
sudo debootstrap --arch amd64 jessie mp
如果选的是jessie的话,那么后面启动的时候是直接root帐号登录的,如果选的是stretch的话,那么需要先在rootfs里创建用户帐号和设置root密码才行。
创建账户和设置root密码:
sudo chroot mp
passwd root
# 设置root密码
useradd -s '/bin/bash' -m -G adm,sudo myusername
passwd myusername
# 设置myusername账户密码
exit
在执行debootstrap一步后,我们的虚拟硬盘里面就已经有一个比较完整的系统debian系统了,我们甚至可以在里面使用apt命令装软件。方法就是在chroot之前把dns解析配置拷贝一份进去,然后在chroot进去执行apt命令安装:
sudo cp -b /etc/resolv.conf temp/etc/resolv.conf
sudo chroot mp
apt update
# 爱装啥装啥
exit
你要是想在里面装一个内核而不是使用自己另外编译好的内核的话,那么就在chroot之后安装一下linux-image-xxx
之类的包就可以了。
装一个Ubuntu的系统
安装Ubuntu Base的包:
wget http://cdimage.ubuntu.com/ubuntu-base/releases/16.04/release/ubuntu-base-16.04-core-amd64.tar.gz
tar xf ubuntu-base-16.04-core-amd64.tar.gz -C mp/
剩下的步骤和装debian的就完全一样了。
直接装一个完整的发行版
一个疑问
我看这篇博客在chroot之前重新挂载了/sys
,/proc
,/dev
等目录,可是像这篇博客又没有做这个操作,我自己实验的时候也没有这么做,并不清楚这个步骤的必要性。
mount --rbind /sys /mnt/sys
mount --rbind /proc /mnt/proc
mount --rbind /dev /mnt/dev
chroot <your-mount-point>
4.卸载虚拟硬盘
sudo umount mp
编译kernel
我不是不会编译内核,可是人家的总结确实很简练:
git clone --depth=1 git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
cd linux
make x86_64_defconfig
make kvmconfig
make -j 8
需要注意的是,我们为了顺利达成GDB的调试目的,那么在make x86_64_defconfig
这步之后,去改下这个生成的.config
文件,里面添加或者修改一句CONFIG_DEBUG_INFO=y
,再接着执行后面的编译步骤。
编译完成之后,给QEMU运行的是bzImage
文件,给GDB调试的是vmlinux
文件。
启动QEMU
终于可以运行自己的kernel了:
sudo qemu-system-x86_64 -kernel <path-to-bzImage> -hda debian-stretch-image.img -append "root=/dev/sda"
- QEMU选项里加上
-s
选项,以提供串口通信,这样才能用GDB调试这个正在运行的kernel;
嫌用串口麻烦的话,直接连QEMU监听的1234的TCP端口就可以了,只要开启了-s
选项,QEMU就默认提供TCP通信; - append选项后面跟的字符串是提供给内核作为启动参数的;为了防止内核KASLR给GDB带来的调试困难,在内核的启动选项里加上
nokaslr
选项; - 为了避免QEMU对虚拟硬盘格式的warning,把
-hda debian-stretch-image.img
换成-drive file=debian-stretch-image.img,index=0,media=disk,format=raw
;
所以,最终版本的QEMU运行命令是:
sudo qemu-system-x86_64 -kernel linux/arch/x86_64/boot/bzImage -drive file=debian-stretch-image.img,index=0,media=disk,format=raw -append "root=/dev/sda nokaslr" -s
其实还有很多可以调的选项,比如开启kvm的--enable-kvm
,但这些算是QEMU的进阶知识,下次再总结吧。
使用GDB连接正在运行的内核
gdb vmlinux
target remote localhost:1234
然后就可以像调试应用程序一样调试这个内核了。为了GDB调试的时候用的爽,可以考虑像linux-kernel-labs一样,下载个GDB的配置文件:wget -P ~ git.io/.gdbinit
。
资料
- Debian QEMU教程,讲述了如何用qemu启动平常下载的发行版iso文件;
- 一篇博客,讲述了如何用qemu启动自己编译的内核;
- 一篇类似的博客,只不过是以arch为例子;
- 使用GDB调试QEMU启动的内核;
旧版本Ubuntu镜像下载地址
这个链接还真是不太好找,就像官方不想让你知道只想让你使用最新版一样。
近期评论