- 作者: 陈孝松
- 主页: chenxiaosong.com
- 哔哩哔哩: 陈孝松
- 课程: chenxiaosong.com/courses
- 博客: chenxiaosong.com/blog
- 贡献: chenxiaosong.com/contributions
- 邮箱: [email protected]
- QQ交流群: 544216206, 点击查看群介绍
点击这里在哔哩哔哩bilibili在线观看配套的加餐视频(就是一些补充)。
下面介绍Linux内核编译环境和测试环境的搭建过程,当然我也为各位朋友准备好了已经安装好的虚拟机镜像,只需下载运行即可。
点击这里从百度网盘下载对应平台的虚拟机镜像,x86_64
(也就是你平时用来安装windows系统的电脑,或者2020年前的苹果电脑)选择ubuntu-x64_64.zip
,arm64
(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模块中的代码进行打断点之类的操作了。
标签:Linux,5.10,gdb,GDB,内核,linux,HOME,调试 From: https://blog.csdn.net/chenxiaosongcsdn/article/details/142282728