linux内核与驱动调试-使用GDB+Qemu调试Linux 内核与驱动
使用GDB+Qemu调试Linux 内核与驱动
时间:20220510,版本:V0.1
作者:robotech_erx
调试内核或者驱动除了双机调试外,还可以使用Qemu来调试。Qemu实现了GDB stub,支持对linux的内核调试。
环境:
主机:ubuntu 20.04 LTS 桌面版
Qemu:QEMU emulator version 4.2.1
1编译带调试信息的内核
内核也可以不用编译,可以直接从发行版里拷贝/下载,ubuntu等这些发行版也有专门的符号服务器。也可以使用kdress等工具从system.map里恢复一个带符号的可调试的vmlinux。
这里还是编译一个:
$ sudo apt update $ sudo apt install build-essential flex bison libssl-dev libelf-dev libncurses-dev dwarves -y
(1)指定硬件体系架构
$ export ARCH=x86 #如果是其他平台,还需要安装交叉编译器
(2)配置board config,此处配置为 x86_64_defconfig
$ make x86_64_defconfig
(3)配置内核
# make menuconfig
需要开启配置有:
Kernel hacking--->
Compile-time checks and compiler options --->
[*] Compile the kernel with debug info
[*] Provide GDB scripts for kernel debugging
这里是使用Qemu实现的GDB stub来调试,所以不需要打开KGDB 。
KASLR可以在qemu启动参数里控制,也不需要专门配置。
编译:
$ make -j$(nproc) #vmlinux 、 bzImage都会编译
编译好的bzImage位于:arch/x86/boot/bzImage (x86_64目录下是这个的符号链接),Vmlinux在源码根目录。GDB scripts(vmlinux-gdb.py)也是源码根目录,这个脚本提供了一些辅助内核调试的脚本,例如辅助加载调试符号,具体参考相应的文档。
(4)其他一些有用的make选项
清理文件:
make clean: 删除大部分生成的文件但是保留配置文件和足够的构建外部模块的构建支持
make mrproper: 删除所有生成的文件,配置和各种各样备份的文件
make distclean: 相当于执行一次make mrproper,然后再删除编辑器备份和补丁文件
删除文件的范围比较:make clean < make mrproper < make distclean
make oldconfig:使用已有的.config,但会询问新增的配置项
make vmlinux #未经压缩的内核
make modules #编译驱动模块
#编译设备树
make bzImage #压缩过的内核
2制作根文件 or initramfs
如果只是简单的调试一些驱动,只准备一个initramfs就足够了。内核中initramfs的配置是默认开启的:
General setup ---> [*] Initial RAM filesystem and RAM disk (initramfs/initrd) support
在内核启动阶段,initramfs取代了ramdisk,尽管还都是使用 -initrd参数。如果要使用ramdisk,还要打开相应的驱动支持Device Drivers --->Block devices --->RAM block device support。具体请搜索相关文章。
制作完整一些的根文件系统,可以使用buildroot或者busybox,嵌入式的根文件系统,但是足够用了。需要注意的是,如果没有特别的去配置,busybox 编译的文件系统默认是uClibc,使用glibc记得单独配置一下。
如果是跨平台,还可以使用buildroot制作交叉编译工具链。
3主机和qemu虚拟机的文件传输
按照qemu的文档安装好。
由于qemu里运行的是一个嵌入式的系统,工具匮乏,效率也低。需要调试的文件最好是在host上编译好,然后上传到debuggee虚拟机里。
传输文件文件的方式包括:
挂载根文件系统的镜像到一个目录中,然后拷贝到目录中;
网络传输,需要配置桥接模式,然后使用tftp之类的协议传输;
NFS挂载文件系统,需要内核的支持;
等等。
(1)使用挂载的方式拷贝文件
这里用挂载的方式,首先关闭虚拟机,然后执行回环挂载(回环挂载就是把一个文件挂载到一个目录上,而不是把一个真实的设备挂到目录上,linux上的回环差不多都一个意思):
$ sudo mount -o loop rootfs.ext2 ./tmp
拷贝完成后,
$ sudo umount ./tmp
然后重启虚拟机。
每次需要重启,使用起来稍微麻烦一点。
也可以单独挂载一个新的硬盘设备。
4使用GDB+qemu调试
在qemu运行命令中,加入-S和-s参数:
-s: 在1234端口接受GDB调试
-S:冻结CPU直到远程GDB输入相应命令
$ qemu-system-x86_64 -M pc -kernel ./linux-4.9.304/arch/x86/boot/bzImage -drive file=rootfs.ext2,format=raw -append root=/dev/sda rootfstype=ext2 console=tty1 console=ttyS0 nokaslr -serial stdio -net nic -net user -S -s
启动GDB并读取vmlinux中的符号信息
$ gdb ./linux-4.9.304/vmlinux # 如果不是x86,用gdb-multiarch ... (gdb) target remote localhost:1234 (gdb) b start_kernel #在入口函数start_kernel上下断点 (gdb) c (gdb) layout src #如果是安装了gef,就不要layout了,乱码~~ ...
Linux 的内核入口函数是位于 init/main.c 中的 start_kernel ,在这里完成各种内核数据结构的初始化。但是这已经是 bootstrap 很后面的过程了。真正的第一行代码在 arch/x86/boot/header.S。
5调试可加载驱动模块
为了调试内核模块,还需要加载驱动模块符号文件。加载可以使用gdb命令:
add-symbol-file {filename} {addr}
要手动查找ko文件加载到的地址,首先在系统里面获取驱动的加载基地址:
# cat /proc/modules | grep drv drv 2252 0 - Live 0xffffffffa0000000 (O)
然后在 gdb 里面加载:
gef➤ add-symbol-file ./kernel_rop/drv.ko 0xffffffffa0000000 #add symbol table from file ./kernel_rop/drv.ko at .text_addr = 0xffffffffa0000000 Reading symbols from ./kernel_rop-master/drv.ko...
(驱动文件里什么时候保存有符号)
此时就可以直接对驱动的函数下断点了:
b device_ioctl
然后c继续运行,随后user space的驱动调用程序,就可以断下来了。加载符号可以使用linux提供的helper命令lx-symbols。
参考:
https://blog.csdn.net/weixin_44465434/article/details/121194613
https://blog.csdn.net/luteresa/article/details/119188792
https://blog.csdn.net/eydwyz/article/details/114019532
https://www.hex-rays.com/wp-content/static/tutorials/xnu_debugger_primer/xnu_debugger_primer.html
https://xz.aliyun.com/t/2306
https://docs.kernel.org/dev-tools/gdb-kernel-debugging.html
https://consen.github.io/2018/01/17/debug-linux-kernel-with-qemu-and-gdb/