Linux驱动模块双机调试详细步骤
下面是调试Linux驱动模块的全面详细步骤,涵盖从环境准备到实际调试的完整流程:
一、环境准备
1. 硬件准备
- 两台计算机:
- 目标机:运行被调试的Linux系统及驱动模块
- 主机:运行调试器(GDB),可以是Linux、Windows(WSL)或macOS
- 连接方式:
- 串口线:推荐使用USB转串口适配器
- 网络连接:确保两机在同一网络
2. 目标机内核配置
确保目标机内核已启用以下配置:
CONFIG_DEBUG_INFO=y # 保留调试信息
CONFIG_GDB_SCRIPTS=y # 支持GDB脚本
CONFIG_KGDB=y # 内核GDB支持
CONFIG_KGDB_SERIAL_CONSOLE=y # 串口KGDB支持
CONFIG_KGDB_KDB=y # KDB支持(可选但推荐)
CONFIG_KALLSYMS=y # 内核符号支持
CONFIG_KALLSYMS_ALL=y # 导出所有符号
CONFIG_FRAME_POINTER=y # 启用帧指针(改善堆栈跟踪)
CONFIG_RANDOMIZE_BASE=n # 禁用内核地址随机化
如果无法重新编译内核,可以查看当前配置:
cat /boot/config-$(uname -r) | grep KGDB
cat /proc/config.gz | zcat | grep KGDB # 部分系统
二、驱动模块准备
1. 修改驱动源码(可选)
添加调试辅助代码:
// 在关键点添加调试断点
#include <linux/kgdb.h>
...
void my_critical_function(void) {kgdb_breakpoint(); // 主动触发断点...
}// 添加调试打印
#define DEBUG
#include <linux/kernel.h>
...
pr_debug("Debug value: %d\n", value);
2. 编译带调试信息的模块
修改Makefile:
# 添加调试标志
ccflags-y += -g -DDEBUG -O1
编译模块:
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
3. 保存调试符号
复制未压缩的模块到安全位置:
cp mydriver.ko /path/to/debug/symbols/
三、配置目标机
1. 启动KGDB
方法1:内核启动参数(需重启):
# 在GRUB引导参数或/etc/default/grub中添加
kgdboc=ttyS0,115200 kgdbwait
方法2:运行时启用(无需重启):
# 加载kgdb模块(如果编译为模块)
modprobe kgdboc# 配置KGDB使用串口
echo ttyS0,115200 > /sys/module/kgdboc/parameters/kgdboc# 或配置KGDB使用网络(需内核支持)
echo udp://192.168.1.100:5551 > /sys/module/kgdboc/parameters/kgdboc
2. 加载驱动模块
# 先插入模块
insmod /path/to/mydriver.ko# 获取模块加载地址和各段地址
cat /sys/module/mydriver/sections/.text
cat /sys/module/mydriver/sections/.data
cat /sys/module/mydriver/sections/.bss
记录下这些地址,例如:
- .text: 0xffffffffc0567000
- .data: 0xffffffffc0569000
- .bss: 0xffffffffc056a000
3. 触发KGDB断点
方法1:使用sysrq:
# 通过sysrq触发KGDB
echo g > /proc/sysrq-trigger
方法2:在代码中主动触发:
kgdb_breakpoint(); // 当执行到此处时会暂停
此时目标机应显示:
KGDB: Entering KGDB
四、主机端调试设置
1. 启动GDB
在主机上(Linux环境或Windows的WSL中):
# 使用与目标机内核版本匹配的GDB
gdb# 或者指定内核符号文件
gdb /path/to/vmlinux
2. 配置GDB会话
# 设置串口连接(Linux主机)
target remote /dev/ttyUSB0# 或网络连接
target remote 192.168.1.200:5551# Windows主机使用COM端口
target remote com1
3. 加载驱动模块符号
使用前面记录的地址:
add-symbol-file /path/to/mydriver.ko 0xffffffffc0567000 \-s .data 0xffffffffc0569000 \-s .bss 0xffffffffc056a000
五、驱动调试流程
1. 设置断点
# 在驱动初始化函数设置断点
break mydriver_init# 在特定源码行设置断点
break mydriver.c:123# 在文件操作函数设置断点
break mydriver_read
break mydriver_write
break mydriver_ioctl
2. 开始调试
# 继续执行直到遇到断点
continue# 或简写为
c
3. 在断点处进行调试
# 查看变量值
print my_variable# 查看数据结构
print *device_struct# 单步执行
next # 不进入函数
step # 进入函数# 查看调用栈
backtrace# 查看寄存器
info registers# 切换栈帧
frame 2# 查看源码
list# 设置变量值(谨慎使用)
set my_variable = 123
4. 监控特定内存
# 监视内存地址变化
watch *0xffffffffc0569100# 监视变量变化
watch my_global_variable
5. 触发驱动行为
在另一个终端连接到目标机,执行会触发驱动的操作:
# 例如读写设备
echo "test" > /dev/mydevice
cat /dev/mydevice# 或使用ioctl
./my_test_app
6. 结束调试
# 解除连接但保持目标机运行
detach# 完全退出GDB
quit
六、高级调试技巧
1. 动态重新加载模块
如需修改代码并重新调试:
# 目标机上卸载模块
rmmod mydriver# 重新编译并加载
insmod mydriver.ko# 获取新地址并在GDB中更新
cat /sys/module/mydriver/sections/.text
在GDB中:
# 移除旧符号
remove-symbol-file# 添加新符号
add-symbol-file /path/to/mydriver.ko 新地址
2. 自动化调试脚本
创建.gdbinit
文件:
# 连接设置
target remote /dev/ttyUSB0# 加载符号
add-symbol-file /path/to/mydriver.ko 0xffffffffc0567000# 设置常用断点
break mydriver_init
break mydriver_ioctl# 自定义命令
define reload_moduleremove-symbol-fileadd-symbol-file /path/to/mydriver.ko $arg0break mydriver_init
end
3. 调试通过内核访问的用户数据
# 检查用户空间数据(copy_from_user目标)
x/10x 0xabcd1234# 查看字符串
x/s 0xabcd1234
4. 断点条件设置
# 只在特定条件触发断点
break mydriver_ioctl if cmd == 0x8001
这个详细指南涵盖了Linux驱动模块双机调试的完整过程,从环境准备到实际调试操作。通过这些步骤,您应该能够有效地调试Linux内核驱动模块。对于复杂的驱动问题,这种方法能提供比简单的printk日志更详细的运行时信息。