调试环境搭建:内核调试工具链安装、QEMU模拟环境配置、GDB+KGDB调试环境搭建
说实话,做内核驱动开发这么多年,我踩过最大的坑就是——环境没搭好就急着写代码。你想想看,驱动写完了,一加载就死机,连个日志都抓不到,那才叫欲哭无泪。所以这一章,咱们先把吃饭的家伙备齐了。
1.1 内核调试工具链安装
调试内核,说白了就是跟硬件和操作系统底层打交道。工具链不对,后面全是白搭。
1.1.1 交叉编译工具链
我习惯用 Linaro 的 GCC 工具链,稳定,社区支持也好。咱们以 ARM64 架构为例:
# 下载 Linaro aarch64 工具链
wget https://releases.linaro.org/components/toolchain/binaries/latest-7/aarch64-linux-gnu/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar.xz
# 解压到 /opt 目录
sudo tar -xvf gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar.xz -C /opt/
# 添加环境变量
export PATH=$PATH:/opt/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin
1.1.2 内核编译依赖
编译内核还需要一些基础库。嗯,这里要注意,不同发行版包名可能不一样:
# Ubuntu/Debian
sudo apt-get install build-essential libncurses-dev bison flex libssl-dev libelf-dev
# CentOS/RHEL
sudo yum groupinstall "Development Tools"
sudo yum install ncurses-devel bison flex elfutils-libelf-devel openssl-devel
1.2 QEMU模拟环境配置
为什么用 QEMU?说白了,就是省钱省事。你不可能每次调试都拿块开发板插拔吧?QEMU 模拟一个 ARM 环境,内核跑起来跟真板子差不多。
1.2.1 安装 QEMU
# 安装 QEMU 系统模拟器
sudo apt-get install qemu-system-arm qemu-system-aarch64
# 验证安装
qemu-system-aarch64 --version
1.2.2 编译可调试的内核
这里有个关键点——必须打开调试选项。我曾经有一次忘了开,折腾了两天才发现是内核没带调试符号。
# 配置内核
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig
# 打开调试选项
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- menuconfig
在 menuconfig 中,务必勾选以下选项:
| 选项路径 | 选项名 | 说明 |
|---|---|---|
| Kernel hacking | Compile-time checks and compiler options | → 勾选 "Compile the kernel with debug info" |
| Kernel hacking | KGDB: kernel debugger | → 勾选 "KGDB: kernel debugger" |
| Kernel hacking | KGDB: kernel debugger | → 勾选 "KGDB: use kgdb over serial" |
INSTALL_MOD_STRIP=1,只给内核本身保留符号,模块可以 strip 掉。
1.2.3 制作根文件系统
QEMU 启动内核需要根文件系统。我一般用 Buildroot 快速生成:
# 下载 Buildroot
wget https://buildroot.org/downloads/buildroot-2021.02.tar.gz
tar -xvf buildroot-2021.02.tar.gz
cd buildroot-2021.02
# 配置 ARM64 目标
make qemu_aarch64_virt_defconfig
make
# 生成的根文件系统在 output/images/rootfs.ext4
1.3 GDB+KGDB调试环境搭建
这是咱们调试驱动的核心武器。GDB 负责前端交互,KGDB 是内核里的调试后端。两者通过串口通信。
1.3.1 启动 QEMU 并等待调试器连接
启动 QEMU 时,要加上 -s 和 -S 参数:
qemu-system-aarch64 \
-machine virt \
-cpu cortex-a57 \
-kernel arch/arm64/boot/Image \
-drive file=rootfs.ext4,if=none,format=raw,id=hd0 \
-device virtio-blk-device,drive=hd0 \
-append "console=ttyAMA0 root=/dev/vda nokaslr" \
-nographic \
-s -S
-s 表示监听 TCP 1234 端口,-S 表示启动时暂停 CPU,等 GDB 连上来再跑。
1.3.2 使用 GDB 连接内核
# 启动 GDB,加载内核符号
aarch64-linux-gnu-gdb vmlinux
# 在 GDB 中连接 QEMU
(gdb) target remote :1234
# 设置断点,比如在驱动入口函数
(gdb) break my_driver_init
# 让内核继续运行
(gdb) continue
核心要点: 调试时一定要加 nokaslr 内核参数。KASLR 会让内核地址随机化,GDB 断点根本打不上去。我刚开始不知道这个,断点设了跟没设一样,排查了半天才发现是 KASLR 搞的鬼。
1.3.3 KGDB 的另一种用法
如果不用 QEMU,在真板子上调试,就得用 KGDB 了。原理一样,只是通信走物理串口:
# 在内核启动参数中添加
kgdboc=ttyAMA0,115200 kgdbwait
# 然后在主机上
aarch64-linux-gnu-gdb vmlinux
(gdb) set serial baud 115200
(gdb) target remote /dev/ttyUSB0
1.4 知识体系总览
下面这张图,是我自己总结的调试环境搭建流程。你看一眼,心里就有谱了:
1.5 验证环境是否正常
环境搭好了,怎么知道能不能用?我一般写个最简单的测试驱动:
// test_drv.c
#include <linux/module.h>
#include <linux/kernel.h>
static int __init test_init(void)
{
printk(KERN_INFO "Hello from debug driver!\n");
return 0;
}
static void __exit test_exit(void)
{
printk(KERN_INFO "Goodbye from debug driver!\n");
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
编译后加载,如果能用 GDB 在 test_init 函数上下断点并停住,说明环境没问题。
-g 选项。还有一次,QEMU 启动参数里忘了加 -s,GDB 连不上。这些小细节,真能让人抓狂。
嗯,环境搭好了,后面调试驱动就顺手多了。记住一句话:调试环境花一天时间搭好,后面能省你一周的排查时间。
公众号:蓝海资料掘金营,微信deep3321