Linux内核开发环境-使用GDB调试内核代码

建议点击这里查看个人主页上的最新原文

点击这里在哔哩哔哩bilibili在线观看配套的教学视频

点击这里在哔哩哔哩bilibili在线观看配套的加餐视频(就是一些补充)

点击跳转到内核课程所有目录

下面介绍Linux内核编译环境和测试环境的搭建过程,当然我也为各位朋友准备好了已经安装好的虚拟机镜像,只需下载运行即可。

点击这里从百度网盘下载对应平台的虚拟机镜像x86_64(也就是你平时用来安装windows系统的电脑,或者2020年前的苹果电脑)选择ubuntu-x64_64.ziparm64(2020年末之后的苹果电脑)选择ubuntu-aarch64.zip。虚拟机运行后,登录界面的密码是1

我刚开始是做用户态开发的,习惯了利用gdb调试来理解那些写得不好的用户态代码,尤其是公司内部一些不开源的比狗屎还难看的用户态代码(当然其中也包括我自己写的狗屎一样的代码)。

转方向做了Linux内核开发后,也尝试用qemu+gdb来调试内核代码。

要特别说明的是,内核的大部分代码是很优美的,并不需要太依赖qemu+gdb这一调试手段,更建议通过阅读代码来理解。但某些写得不好的内核模块如果利用qemu+gdb将能使我们更快的熟悉代码。

这里只介绍x86_64下的qemu+gdb调试,其他cpu架构以此类推,只需要做些小改动。

如果是其他cpu架构,要安装:

sudo apt install gdb-multiarch -y

编译选项和补丁

首先确保修改以下配置:

CONFIG_DEBUG_SECTION_MISMATCH=y # 防止内联
CONFIG_DEBUG_INFO=y # 调试信息
CONFIG_DEBUG_KERNEL=y # 调试信息
CONFIG_GDB_SCRIPTS=y # gdb python
DEBUG_INFO_REDUCED=n # 关闭
CONFIG_FRAME_POINTER=y # Makefile 中选择GCC编译选项
CONFIG_RANDOMIZE_BASE = n # 关闭地址随机化

可以使用我常用的x86_64的内核配置文件

gcc的编译选项O1优化等级不需要修改就可以编译通过。O0优化等级无法编译(尝试CONFIG_JUMP_LABEL=n还是不行),要修改汇编代码,有兴趣的朋友可以和我一直尝试。Og优化等级经过修改可以编译通过,x86_64合入目录courses/kernel/src/x86_64对应版本的补丁。建议使用Og优化等级编译,既能满足gdb调试需求,也能尽量少的修改代码。

QEMU命令选项

qemu启动虚拟机时,要添加以下几个选项:

-append "nokaslr ..." # 防止地址随机化,编译内核时关闭配置 CONFIG_RANDOMIZE_BASE
-S # 挂起 gdbserver
-gdb tcp::5555 # 端口5555, 使用 -s 选项表示用默认的端口1234
-s # 相当于 -gdb tcp::1234 默认端口1234,不建议用,最好指定端口

完整的启动命令查看制作好的Ubuntu虚拟机镜像(从百度网盘中下载的)中的${HOME}/qemu-kernel/start.sh脚本。

GDB命令

启动GDB:

gdb build/vmlinux

如果是其他架构:

gdb --tui build/vmlinux # --tui: Use a terminal user interface.
(gdb) set architecture aarch64

进入GDB界面后:

(gdb) target remote:5555 # 对应qemu命令中的-gdb tcp::5555
(gdb) b func_name # 普通断点
(gdb) hb func_name # 硬件断点,有些函数普通断点不会停下, 如: nfs4_atomic_open,降低优化等级后没这个问题

gdb命令的用法和用户态程序的调试大同小异。

GDB辅助调试功能

使用内核提供的GDB辅助调试功能可以更方便的调试内核(如打印断点处的进程名和进程id等)。

内核最新版本(2024.04)使用以下命令开启GDB辅助调试功能,注意最新版本编译出的脚本无法调试4.19和5.10的代码:

echo "set auto-load safe-path /" > ~/.gdbinit # 设置自动加载共享库文件的安全路径
echo "source ${HOME}/.gdb-linux/vmlinux-gdb.py" >> ~/.gdbinit
make O=build scripts_gdb # 在内核仓库目录下执行
rm -rf ${HOME}/.gdb-linux/
mkdir ${HOME}/.gdb-linux/
cp build/scripts/gdb/* ${HOME}/.gdb-linux/ -rf # 在内核仓库目录下执行
cp scripts/gdb/vmlinux-gdb.py ${HOME}/.gdb-linux/ # 在内核仓库目录下执行
sed -i '/sys.path.insert/s/^/# /' ${HOME}/.gdb-linux/vmlinux-gdb.py # 将sys.path.insert所在的行注释掉
sed -i '/sys.path.insert/a\sys.path.insert(0, "'${HOME}'/.gdb-linux")' ${HOME}/.gdb-linux/vmlinux-gdb.py # 插入 sys.path.insert(0, "${HOME}/.gdb-linux")

内核5.10使用以下命令开启GDB辅助调试功能,也可以调试内核4.19代码,但无法调试内核最新的代码:

echo "set auto-load safe-path /" > ~/.gdbinit # 设置自动加载共享库文件的安全路径
echo "source ${HOME}/.gdb-linux-5.10/vmlinux-gdb.py" >> ~/.gdbinit
make O=build scripts_gdb # 在5.10内核仓库目录下执行
rm -rf ${HOME}/.gdb-linux-5.10/
mkdir ${HOME}/.gdb-linux-5.10/
cp build/scripts/gdb/* ${HOME}/.gdb-linux-5.10/ -rf # 在5.10内核仓库目录下执行
cp scripts/gdb/vmlinux-gdb.py ${HOME}/.gdb-linux-5.10/ # 在5.10内核仓库目录下执行
sed -i '/sys.path.insert/s/^/# /' ${HOME}/.gdb-linux-5.10/vmlinux-gdb.py # 将sys.path.insert所在的行注释掉
sed -i '/sys.path.insert/a\sys.path.insert(0, "'${HOME}'/.gdb-linux-5.10")' ${HOME}/.gdb-linux-5.10/vmlinux-gdb.py # 插入 sys.path.insert(0, "${HOME}/.gdb-linux-5.10")

重新启动GDB就可以使用GDB辅助调试功能:

(gdb) apropos lx # 查看有哪些命令
(gdb) p $lx_current().pid # 打印断点所在进程的进程id
(gdb) p $lx_current().comm # 打印断点所在进程的进程名

GDB打印结构体偏移

结构体定义有时候加了很多宏判断,再考虑到内存对齐之类的因素,通过看代码很难确定结构体中某一个成员的偏移大小,使用gdb来打印就很直观。

如结构体struct cifsFileInfo:

struct cifsFileInfo {
    struct list_head tlist;
    ...
    struct tcon_link *tlink;
    ...
    char *symlink_target;
};

想要确定tlink的偏移,可以使用以下命令:

gdb ./cifs.ko # ko文件或vmlinux
(gdb) p &((struct cifsFileInfo *)0)->tlink

(struct cifsFileInfo *)0: 这是将整数值 0 强制类型转换为指向 struct cifsFileInfo 类型的指针。这实际上是创建一个指向虚拟内存地址 0 的指针,该地址通常是无效的。这是一个计算偏移量的技巧,因为偏移量的计算不依赖于结构体的实际实例。

(0)->tlink: 指向虚拟内存地址 0 的指针的成员tlink

&(0)->tlink: tlink的地址,也就是偏移量。

ko模块代码调试

使用gdb vmlinux启动gdb后,如果调用到ko模块里的代码,这时候就不能直接对ko模块的代码进行打断点之类的操作,因为找不到对应的符号。

这时就要把符号加入进来。首先,查看被调试的qemu虚拟机中的各个段地址:

cd /sys/module/ext4/sections/ # ext4 为模块名
cat .text .data .bss # 输出各个段地址

在gdb窗口中加载ko文件:

add-symbol-file <ko文件位置> <text段地址> -s .data <data段地址> -s .bss <bss段地址>

这时就能开心的对ko模块中的代码进行打断点之类的操作了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值