1. 调试环境搭建:Linux内核编译与模块加载、QEMU虚拟机调试环境、JTAG/SWD硬件调试器配置

调试环境搭建,是驱动开发的第一道坎。说实话,我见过太多人卡在这一步——环境没配好,后面所有分析都像在黑暗中摸索。今天我把这些年踩过的坑、积累的经验,一次性说清楚。

1.1 Linux内核编译与模块加载

内核编译这事儿,说白了就是给你的驱动准备一个「舞台」。舞台搭不好,演员(驱动)再优秀也白搭。

1.1.1 获取内核源码

我个人习惯用主线内核,因为文档全、社区活跃。你可以从 kernel.org 下载,或者直接用发行版的内核源码包:

# Ubuntu/Debian
sudo apt-get install linux-source
cd /usr/src
tar -xjf linux-source-*.tar.bz2

# 或者直接克隆主线
git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
cd linux
我的习惯: 我会在 /home/<user>/workspace/kernel 下维护一个本地仓库,方便随时切换分支。别放在 /usr/src 下,普通用户权限不够,每次编译都要 sudo,很烦。

1.1.2 配置与编译

配置内核,我建议从当前系统的配置开始,这样能保证硬件兼容性:

# 使用当前系统配置
cp /boot/config-$(uname -r) .config
make olddefconfig

# 开启调试选项(这一步很关键)
make menuconfig

在 menuconfig 里,必须打开以下选项:

选项 路径 说明
CONFIG_DEBUG_KERNEL Kernel hacking → Kernel debugging 内核调试总开关
CONFIG_DEBUG_INFO Kernel hacking → Compile-time checks → Debug info 生成调试符号(必须!)
CONFIG_KGDB Kernel hacking → KGDB 内核调试器支持
CONFIG_KPROBES General setup → Kprobes 动态插桩,调试利器
CONFIG_DYNAMIC_DEBUG Kernel hacking → Dynamic debugging 动态控制打印
我曾经踩过的坑: 第一次编译内核时,我忘了开 CONFIG_DEBUG_INFO,结果 gdb 里全是 ??,根本看不到源码和变量名。白白浪费了三个小时。记住,调试符号是驱动调试的命根子。

编译命令很简单:

make -j$(nproc)   # 并行编译,快很多
sudo make modules_install
sudo make install

重启后选择新内核。验证一下:

uname -r
cat /proc/config.gz | gunzip | grep DEBUG_INFO

1.1.3 模块加载与卸载

驱动开发中,模块加载是家常便饭。我常用的命令:

# 加载模块
sudo insmod my_driver.ko

# 查看模块信息
lsmod | grep my_driver
modinfo my_driver.ko

# 卸载模块
sudo rmmod my_driver

# 查看内核日志
dmesg | tail -20

调试小技巧: 我习惯在 insmod 之前先执行 dmesg -c 清空日志,这样加载后只看新输出,不会被旧日志干扰。

1.2 QEMU虚拟机调试环境

QEMU 是我最推荐的调试环境。为什么?因为你在宿主机上就能完成所有工作,不用反复烧写、重启真机。我早期做嵌入式驱动时,每次烧写要等 3 分钟,一天下来光等就浪费两小时。用 QEMU 后,效率翻倍。

1.2.1 安装 QEMU

sudo apt-get install qemu-system-arm qemu-system-x86 qemu-utils

我个人常用 ARM 和 x86 两种架构。ARM 用来模拟嵌入式设备,x86 用来调试 PCIe、ACPI 等桌面/服务器驱动。

1.2.2 创建根文件系统

你需要一个最小的根文件系统。我推荐用 Buildroot 或直接下载现成的:

# 下载最小根文件系统
wget https://people.debian.org/~aurel32/qemu/armhf/debian_wheezy_armhf_standard.qcow2

# 或者用 Buildroot 自己构建
git clone https://git.buildroot.net/buildroot
cd buildroot
make qemu_arm_versatile_defconfig
make

1.2.3 启动 QEMU 并连接 GDB

这是核心操作。启动 QEMU 时加上 -s 和 -S 参数:

qemu-system-arm \
  -M versatilepb \
  -kernel vmlinuz \
  -initrd initrd.img \
  -append "root=/dev/ram console=ttyAMA0" \
  -nographic \
  -s -S

参数说明:

  • -s:在 1234 端口开启 GDB 服务(等价于 -gdb tcp::1234)
  • -S:启动时暂停 CPU,等待 GDB 连接

然后在另一个终端启动 GDB:

gdb-multiarch vmlinux
(gdb) target remote :1234
(gdb) continue
我的经验: 我习惯在 GDB 里先设置 set architecture arm,然后 lx-symbols 加载模块符号。这样调试模块时,断点可以直接打在函数名上,不用算偏移。

1.2.4 调试内核模块

在 QEMU 里加载模块后,GDB 里执行:

(gdb) lx-symbols /path/to/my_driver.ko
(gdb) break my_driver_init
(gdb) continue

当模块初始化时,GDB 会自动停在断点处。你可以单步、查看变量、甚至修改内存。

注意: QEMU 模拟的硬件和真实硬件有差异。有些寄存器行为、中断时序可能不同。所以 QEMU 调试通过后,一定要在真机上验证一遍。我曾经在 QEMU 上跑得好好的驱动,上真机就崩了——原因是 QEMU 没模拟某个硬件 bug。

1.3 JTAG/SWD硬件调试器配置

当 QEMU 不够用,或者你需要调试真实硬件时,JTAG/SWD 调试器就派上用场了。这东西说白了就是一根「探针」,直接插到芯片的调试接口上,让你能控制 CPU、读写内存、设置断点。

1.3.1 常见调试器对比

调试器 接口 速度 价格 适用场景
J-Link JTAG/SWD 最高 50 MHz 中高 ARM Cortex 系列,稳定可靠
ST-Link SWD 最高 4 MHz 低 STM32 系列,性价比高
OpenOCD + FT2232 JTAG/SWD 取决于硬件 低 开源方案,灵活但配置复杂
Lauterbach JTAG/SWD 最高 100 MHz 高 工业级,功能最全

我个人最常用 J-Link,因为它兼容性好、速度快,而且 GDB 服务器集成得很完善。

1.3.2 硬件连接

SWD 只需要 4 根线:

  • SWDIO:数据线
  • SWCLK:时钟线
  • GND:地线
  • VREF:参考电压(可选,用于电平匹配)

JTAG 需要 5 根:

  • TMS:模式选择
  • TCK:时钟
  • TDI:数据输入
  • TDO:数据输出
  • nTRST:复位(可选)
我的建议: 如果芯片同时支持 JTAG 和 SWD,优先用 SWD。它只需要 2 根信号线,布线简单,而且速度不差。我在一个项目里因为 JTAG 引脚被复用了,不得不切到 SWD,结果发现调试体验几乎没差别。

1.3.3 软件配置

以 J-Link 为例,配置 GDB 服务器:

# 启动 J-Link GDB 服务器
JLinkGDBServer -device STM32F407VG -if SWD -speed 4000 -port 2331

# 在另一个终端启动 GDB
arm-none-eabi-gdb vmlinux
(gdb) target remote :2331
(gdb) monitor reset
(gdb) continue

用 OpenOCD 的话,配置文件大概长这样:

# openocd.cfg
source [find interface/stlink.cfg]
source [find target/stm32f4x.cfg]

adapter speed 4000
reset_config srst_only

启动命令:

openocd -f openocd.cfg
# 然后在另一个终端
telnet localhost 4444
> reset halt
> flash write_image erase my_driver.bin 0x08000000
> reset run
我曾经踩过的坑: 第一次用 OpenOCD 时,我忘了设置 adapter speed,结果默认速度太低,调试器响应慢得像蜗牛。后来我把速度从 100 kHz 调到 4 MHz,整个世界都流畅了。但注意,速度不是越高越好——线太长、干扰大的时候,高速会导致通信错误。我一般从 1 MHz 开始试,稳定后再往上加。

1.4 本章知识体系

下面这张图,把三种调试环境的关系和适用场景梳理清楚了:

调试环境搭建知识体系 Linux内核编译与模块加载 QEMU虚拟机调试环境 JTAG/SWD硬件调试器 获取源码 → 配置 → 编译 开启调试选项(DEBUG_INFO等) insmod / rmmod / dmesg 安装QEMU + 创建根文件系统 -s -S 参数启动,暂停等待GDB target remote + lx-symbols 选择调试器(J-Link/ST-Link等) 硬件连接(SWD 4线 / JTAG 5线) GDB服务器 + OpenOCD配置 适用场景对比 • 内核编译:所有驱动开发的基础,必须掌握 • QEMU:适合逻辑调试、早期开发,效率高但硬件模拟不完整 • JTAG/SWD:真机调试,适合时序、中断、外设等硬件相关问题

三种环境各有侧重。我的建议是:先用 QEMU 把驱动逻辑调通,再用 JTAG/SWD 在真机上验证。内核编译是贯穿始终的基础,不管用哪种环境,都得先把内核配好、编好。

调试环境搭建好了,后面的事情就顺了。记住,磨刀不误砍柴工——花半天时间把环境配扎实,后面调试时能省下几天时间。