4. KGDB 双机调试:原理、配置与实战
KGDB 是 Linux 内核提供的远程调试框架,允许开发者通过 GDB 在另一台主机上调试目标内核。本章从 KGDB 的底层原理出发,逐步讲解串口与网络两种连接方式,并演示断点设置、单步执行等核心操作。
适用场景:KGDB 最适合调试内核启动早期代码、中断处理路径、驱动 probe 函数以及无法通过 printk 定位的竞态问题。双机调试避免了目标机崩溃时丢失调试会话的风险。
4.1 KGDB 工作原理
KGDB 在内核中嵌入了一个轻量级调试代理(stub),该代理通过串口或网络与远程 GDB 通信。当内核触发断点或异常时,KGDB 暂停所有 CPU 的执行,进入等待命令的循环,直到 GDB 发送继续、单步或内存读写指令。
4.2 串口与网络配置
KGDB 支持两种物理传输层:串口(UART)和网络(UDP)。串口配置简单、延迟低,适合早期调试;网络配置灵活、带宽高,适合后期功能调试。
4.2.1 串口配置
串口调试需要一根交叉串口线(Null Modem)连接两台机器的串口。目标机内核启动参数需指定 KGDB 使用的串口设备及波特率。
# 目标机内核启动参数(GRUB 或 U-Boot)
kgdboc=ttyS0,115200 kgdbwait
| 参数 | 说明 | 典型值 |
|---|---|---|
kgdboc | KGDB Over Console,指定串口设备和波特率 | ttyS0,115200 |
kgdbwait | 内核启动后立即进入 KGDB 等待状态 | 无参数值 |
kgdbcon | 允许 KGDB 使用控制台作为调试通道 | 可选 |
注意:串口波特率必须与开发机终端设置一致。115200 是常用值,但长距离或高噪声环境可降速至 57600 或 38400。串口线长度建议不超过 5 米。
4.2.2 网络配置
网络模式使用 UDP 协议,通过 kgdboe 参数指定网卡和端口。网络模式支持多 CPU 调试,且不受串口线长度限制。
# 目标机内核启动参数(网络模式)
kgdboe=eth0@192.168.1.100:5555,@192.168.1.200:5555 kgdbwait
| 参数段 | 含义 | 示例 |
|---|---|---|
eth0@192.168.1.100:5555 | 目标机网卡、IP 和监听端口 | eth0 网卡,端口 5555 |
@192.168.1.200:5555 | 开发机 IP 和端口(可选,用于过滤) | 仅接受来自该地址的连接 |
4.3 GDB 连接内核
目标机启动并进入 KGDB 等待后,开发机启动 GDB 并加载带符号表的 vmlinux 镜像,然后通过 target remote 命令连接。
# 开发机操作
gdb vmlinux
(gdb) target remote /dev/ttyS0 # 串口连接
(gdb) target remote udp:192.168.1.100:5555 # 网络连接
(gdb) kgdb-ok # 验证连接状态
连接成功后,GDB 会显示目标机当前 CPU 的寄存器状态和暂停位置。此时可以执行所有标准 GDB 调试命令。
连接检查清单:
- 目标机内核是否编译了
CONFIG_KGDB、CONFIG_KGDB_SERIAL_CONSOLE或CONFIG_KGDB_ETHERNET? - 开发机 GDB 版本是否 ≥ 7.0?低版本对 KGDB 支持不完整。
- 串口/网络物理连接是否正常?可用 minicom 或 netcat 先测试。
- 目标机是否已暂停在
kgdbwait?可通过串口终端观察输出。
4.4 设置断点与单步调试内核代码
KGDB 支持硬件断点(利用 CPU 调试寄存器)和软件断点(插入 int3 指令)。内核代码调试与用户态 GDB 用法基本一致,但需注意内核上下文特殊性。
4.4.1 断点设置
(gdb) break do_fork # 软件断点,函数入口
(gdb) break schedule+0x20 # 偏移地址断点
(gdb) hbreak __switch_to # 硬件断点,不修改代码段
(gdb) watch jiffies # 数据变化监视
| 断点类型 | GDB 命令 | 适用场景 |
|---|---|---|
| 软件断点 | break | 可执行代码段,修改指令为 int3 |
| 硬件断点 | hbreak | 只读代码段或 flash 执行环境 |
| 监视点 | watch | 跟踪全局变量或寄存器变化 |
4.4.2 单步调试
(gdb) stepi # 单步执行一条指令
(gdb) nexti # 单步跳过函数调用
(gdb) continue # 恢复执行直到下一个断点
(gdb) finish # 执行到当前函数返回
内核调试注意事项:
- 在中断上下文或自旋锁持有期间单步可能导致死锁,建议在进程上下文调试。
- 使用
lx-symbols命令(需要gdbscripts支持)加载内核模块符号。 - 调试模块代码时,先
add-symbol-file加载模块符号表。 - 单步
schedule()函数时,GDB 可能因进程切换而丢失上下文,建议使用sched-keep参数。
4.5 实战:调试内核模块加载流程
以下演示通过 KGDB 跟踪 init_module 系统调用的完整流程,定位模块初始化失败原因。
# 步骤 1:目标机启动并等待 KGDB
# 内核参数添加 kgdboc=ttyS0,115200 kgdbwait
# 步骤 2:开发机连接
gdb vmlinux
(gdb) target remote /dev/ttyS0
(gdb) break sys_init_module
(gdb) continue
# 步骤 3:目标机触发断点(执行 insmod)
# GDB 停在 sys_init_module 入口
(gdb) print (char *)__argv[1] # 查看模块路径
(gdb) stepi # 单步跟踪参数校验
(gdb) break do_init_module
(gdb) continue
# 步骤 4:进入模块初始化函数
(gdb) print module->name # 打印模块名
(gdb) stepi # 单步执行模块 init 函数
4.6 常见问题与排查
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| GDB 连接超时 | 串口参数不匹配或网线不通 | 检查波特率、IP 地址、防火墙规则 |
| 断点无法命中 | 符号表与运行内核版本不一致 | 重新编译 vmlinux 并确认 CONFIG_KGDB 已开启 |
| 单步后系统死锁 | 在中断上下文或 spinlock 中单步 | 使用 break 跳过临界区,或改用硬件断点 |
| 模块符号无法解析 | 未加载模块符号表 | 使用 lx-symbols 或 add-symbol-file |
| KGDB 不响应 | 内核 panic 或 NMI 看门狗触发 | 添加 nmi_watchdog=0 内核参数 |
安全提示:KGDB 在生产环境应禁用。调试完成后务必移除
kgdboc/kgdboe 启动参数,或通过 echo 0 > /sys/module/kgdboc/parameters/enable 关闭 KGDB 代理。