调试环境搭建:内核调试工具链安装、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
我的小习惯: 把工具链路径写到 ~/.bashrc 里,省得每次开终端都要 export 一遍。另外,我建议用 7.5 版本,太新的版本有时跟老内核不兼容,我在项目里吃过这个亏。

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 知识体系总览

下面这张图,是我自己总结的调试环境搭建流程。你看一眼,心里就有谱了:

内核驱动调试环境搭建流程 交叉编译工具链 内核编译依赖 QEMU模拟器 编译带调试符号的内核 (vmlinux + debug info) QEMU模拟启动 qemu-system-aarch64 -s -S 真板子KGDB启动 kgdboc=ttyAMA0,115200 kgdbwait GDB 连接调试:target remote :1234 / /dev/ttyUSB0

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