我们在内核调试的时候,会有很多方法,比如printk, ftrace, kprobe, ebpf和gdb等。比起其他的方法,gdb可以单步运行代码,实时获取变量信息等优势。但劣势也比较明显,比如效率较低,比较适合代码较少,操作简便,场景单一的情况。
编译内核
本实验采用x86的内核,需要增加打开CONFIG_BLK_DEV_RAM选项,并将CONFIG_BLK_DEV_RAM_SIZE设置为65535,以便于使用qemu启动,方法如下:
make menuconfig
进入Device drivers --> Block devices
选中RAM block device support
构建根文件系统
1)下载busybox
- wget http://www.busybox.net/downloads/busybox-1.35.0.tar.bz2 --no-check-certificate
- tar xvjf busybox-1.35.0.tar.bz2
2)编译busybox
- make menuconfig
-
在Settings中选中“Build static binary”
-
make
3)制作rootfs
- dd if=/dev/zero of=rootfs.img bs=1M count=20
- mkfs.ext4 rootfs.img
- mkdir fs
- sudo mount -t ext4 -o loop rootfs.img ./fs
- sudo make install CONFIG_PREFIX=./fs
- cd fs/
- sudo mkdir proc dev etc home mnt
- sudo cp -r ../examples/bootfloppy/etc/* etc/
- sudo mkdir /sys
- 修改etc/fstab为如下内容:
proc /proc proc defaults 0 0
tmpfs /tmp tmpfs defaults 0 0
sysfs /sys sysfs defaults 0 0
tmpfs /dev tmpfs defaults 0 0
debugfs /sys/kernel/debug debugfs defaults 0 0
到此,我们得到了我们新制作的根文件系统rootfs.img
安装gdb工具
我使用的是优麒麟系统,所以键入命令“sudo apt-get install gdb gdb-multiarch”
使用qemu启动内核,我的qemu启动脚本为
#!/bin/bash
qemu-system-x86_64 \
-cpu kvm64 \
-smp 2 \
-m 1024M \
-kernel ./linux-objtrace-jeff-v15/arch/x86_64/boot/bzImage \
-qmp unix:/tmp/mem/qemu/qmp,server,nowait \
-gdb unix:/tmp/mem/qemu/gdb,server,nowait \
-nographic \
-append "root=/dev/ram0 rw rootfstype=ext4 console=ttyS0 init=./linuxrc nokaslr" \
-initrd ./rootfs_x86_64.img
使用gdb调试内核
1)启动gdb
sudo gdb-multiarch --tui vmlinux (其中vmlinux为编译好的内核elf文件)
2) 连接内核
target remote /tmp/mem/qemu/gdb
3) 设置断点
b __kprobe_trace_func
本节我们使用一个比较典型的gdb场景,调试kprobe代码,因为kprobe本身属于ftrace的一部分,所以不能使用ftrace来调试kprobe,gdb比较合适。
4) continue (或直接运行c)
让内核继续运行起来。
5) 在内核中输入如下命令,进入断点
cd /sys/kernel/debug/tracing/
echo 1 > events/kprobes/p_vfs_open_0/enable
cat trace
6) 常用调试命令
单步命令:n
进入调用函数内部:s
查看当前函数参数:i args
查看局部变量:i locals
查看单独变量: p variable
查看结构中每一项的值: p *pointer
查看结构定义:ptype struct (ptype /o code还可以查看结构中每一项地址的offset)
查看指定区域的内存内容x/32 addr(读取addr后32个64bit数据)
本例中函数store_trace_args将字符串”trace”放到了entry中,我们查看了entry后面的内存内容,找到了字符串”trace”的ascii,即“74,72,61,63,65”。
查看调用栈:bt
反汇编函数:disas function_name
查看反汇编指令:x/3i $pc
单步调试汇编:ni
查看寄存器:info register rdi (i register rdi)
基本就是这些,虽然不像eclipse, visual studio那样可以使用鼠标操作,但熟练之后效率也不会低的。