4. KGDB 双机调试:原理、配置与实战

KGDB 是 Linux 内核提供的远程调试框架,允许开发者通过 GDB 在另一台主机上调试目标内核。本章从 KGDB 的底层原理出发,逐步讲解串口与网络两种连接方式,并演示断点设置、单步执行等核心操作。

适用场景:KGDB 最适合调试内核启动早期代码、中断处理路径、驱动 probe 函数以及无法通过 printk 定位的竞态问题。双机调试避免了目标机崩溃时丢失调试会话的风险。

4.1 KGDB 工作原理

KGDB 在内核中嵌入了一个轻量级调试代理(stub),该代理通过串口或网络与远程 GDB 通信。当内核触发断点或异常时,KGDB 暂停所有 CPU 的执行,进入等待命令的循环,直到 GDB 发送继续、单步或内存读写指令。

KGDB 双机调试架构 开发机 (Host) GDB + vmlinux 符号表 + 源码 目标机 (Target) KGDB Stub 被调试内核 串口 / 以太网 KGDB 协议 (RSP) GDB 发送:断点设置 / 单步 / 内存读写 KGDB 回复:寄存器值 / 内存内容 / 异常原因

4.2 串口与网络配置

KGDB 支持两种物理传输层:串口(UART)和网络(UDP)。串口配置简单、延迟低,适合早期调试;网络配置灵活、带宽高,适合后期功能调试。

4.2.1 串口配置

串口调试需要一根交叉串口线(Null Modem)连接两台机器的串口。目标机内核启动参数需指定 KGDB 使用的串口设备及波特率。

# 目标机内核启动参数(GRUB 或 U-Boot)
kgdboc=ttyS0,115200 kgdbwait
参数说明典型值
kgdbocKGDB 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 函数
KGDB 调试模块加载流程 目标机 insmod sys_init_module do_init_module 模块 init 函数 返回 / 异常 B1 B2

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 代理。