GDB+QEMU调试内核模块(实践篇)
调试内核模块的方法有很多。最常用的,可能也是上手难度最低的就是使用prinfk打印出相关的调试信息,但是总给人的感觉不够geeek,所以这里描述一种能够使用gdb+qemu来调试内核的方法。
启动虚拟机
/data/bin/qemu-system-x86_64 -m 6144 -M accel=kvm -cpu host -smp 4 -nographic \
-kernel /root/code/linux-4.18.2/arch/x86/boot/bzImage \
-device virtio-blk-pci,scsi=off,bus=pci.0,addr=0x6,drive=drive-virtio-disk1,id=virtio-disk0,disable-modern=off,disable-legacy=on \
-drive format=raw,if=none,id=drive-virtio-disk1,cache=directsync,file=/data/rootfs.img,aio=native \
-netdev tap,id=tap0,ifname=virbr0-nic,vhost=no,script=no \
-device virtio-net-pci,netdev=tap0 \
-append "root=/dev/vda rw console=ttyS0 nokaslr" -enable-kvm -S -gdb tcp::8889
当进入到虚拟机之后,启动相关的网卡,可以方便的向虚拟机中拷贝相关需要的文件,方便调试。
内核模块实例
使用以下链接 中内核模块,作为调试示例。
如果调试的目标虚拟机是Ubuntu16.04,使用如下分支:
git clone -b build-for-ubuntu16046 https://github.com/caisan/simplefs
如果是centos7 作为调试的目标虚拟机主机,使用如下分支:
git clone -b build-for-centos7 https://github.com/caisan/simplefs
为了能在Guest中编译simplefs文件系统,需要在Guest中编译下Linux-kernel 4.18.2,但是不是完全编译,只需要编译所需要的kernel headers,module依赖。
进入到Guest中:
cd linux-4.18.2
make menuconfig
make modules - j4
make modules_install -j4
上述编译结束后,到Guest 的路径下:/lib/modules/4.18.2
下确认下,不出意外用ls -al
可以看到build
和source
都链接到/root/linux-4.18.2
,如下:
然后再进入到simplefs, 执行编译此simplefs模块:
make
随后加载该simplefs.ko模块。注意如果是在centos7上加载此模块,需要先加载jbd2,即就是:modprobe jbd2
调试的虚拟机我选的Ubuntu 16.04.6,内核版本是
如下:
insmod ./simplefs.ko
cat /sys/module/simplefs/sections/.text
0xffffffffc098f000
cat /sys/module/simplefs/sections/.data
0xffffffffc0992000
cat /sys/module/simplefs/sections/.bss
0xffffffffc0992580
以上通过.text
, .data
, .bss
定位到了模块的加载地址,可以使用这三个信息开始我们的调试。
在Host上调试内核。
以上将基于QEMU+kvm中的Guest安装好了,现在在Host上进行调试工作。这里说在Host上其实不太准确,调试的目标是Guest kernel上的模块,Host上执行相当于是GDB远程调试,whatever,意会这个意思就可以了。但是请务必将Host上调试的kernel代码和Guest上的版本保持一致。
以我为例,我Host上kernel代码路径是在/root/code/linux-4.18.2
因此可以在~/.gdbinit
中添加如下内容:
add-auto-load-safe-path /root/code/linux-4.18.2/scripts/gdb/vmlinux-gdb.py
target remote:8889
(这里要强调的是,Host上的kernel同样是经过编译过后的)
进入到已经编译好的kernel源码目录:
gdb ./vmlinux
此时进入到了gdb中,由于我们调试的simplefs.ko模块,因此还要在gdb中加载这个模块,注意:这里的加载和使用insmod加载有区别,两者都要做。
将Guest中网络设置好之后,可以将在Guest中编译好的simplefs.ko模块拷贝出来,下面.text``.data``.bss
相关的地址和在Guest中保持一致。
(gdb) add-symbol-file ./simplefs.ko 0xffffffffc098f000 -s .data 0xffffffffc0992000 -s .bss 0xffffffffc0992580
加载好之后就可以调试了。
触发调试。
这里以调试mount的操作为例,在Guest中执行常规的挂载mount 操作,如下:
dd bs=4096 count=100 if=/dev/zero of=image
./mkfs-simplefs image
mount -o loop -t simplefs image /root/simplefs/mount/
或者采用以下方式设置挂载:
fallocate -l 1G disk.img
losetup -f /root/disk.img
取消挂载:
losetup -D
然后在Host的gdb中设置断点:
(gdb) b simplefs_fill_super
(gdb) c
等待触发到breakpoint。调试命令和gdb使用相同。