4. KGDB串口调试实战:使用串口线连接目标机与开发机、启动KGDB、GDB连接内核、设置断点与单步调试

KGDB(Kernel GNU Debugger)是Linux内核源码级调试的核心工具,允许开发者通过串口或网络连接,使用标准GDB命令调试运行中的内核。本章聚焦于最稳定、最常用的串口KGDB调试方案,从硬件连接、内核配置、启动KGDB,到GDB远程连接、设置断点与单步执行,完整覆盖一次典型的内核调试会话。

适用场景:KGDB串口调试特别适合驱动开发、内核崩溃分析、调度器行为验证、以及无法使用QEMU模拟的硬件平台。串口调试不依赖网络栈,在系统早期启动阶段或网络驱动故障时依然可用。

4.1 硬件连接与串口参数

KGDB串口调试需要两台机器:目标机(Target)运行待调试的内核,开发机(Host)运行GDB。两者通过Null Modem串口线(交叉线)直连。

组件 说明 典型配置
目标机 运行被调试内核的物理机或开发板 x86_64 PC / ARM64开发板
开发机 运行GDB的调试主机 Linux工作站 / 笔记本
串口线 Null Modem(2-3交叉,5-5直连) DB9母头对母头交叉线
串口参数 波特率、数据位、停止位、校验 115200 8N1(无校验)
常见陷阱:许多笔记本没有原生串口,需要使用USB转串口适配器(如FT232、CP2102)。务必确认适配器在Linux下被识别为 /dev/ttyUSB0 或 /dev/ttyACM0,且目标机BIOS中串口未被禁用。
KGDB串口调试硬件连接拓扑 目标机 (Target) 运行被调试内核 /dev/ttyS0 开发机 (Host) 运行GDB /dev/ttyUSB0 Null Modem 交叉线 115200 8N1 ⚠ 连接检查 在开发机执行: screen /dev/ttyUSB0 115200 → 目标机应回显字符

4.2 内核配置与编译

目标机内核必须开启KGDB相关配置选项。以下为最小配置集(通过 make menuconfig 或直接修改 .config):

# KGDB核心支持
CONFIG_KGDB=y
CONFIG_KGDB_SERIAL_CONSOLE=y

# 串口作为KGDB I/O后端
CONFIG_KGDB_LOW_LEVEL_TRAP=y

# 调试符号(必须)
CONFIG_DEBUG_INFO=y
CONFIG_DEBUG_INFO_DWARF4=y

# 可选:允许在KGDB中修改内存
CONFIG_KGDB_KDB=y

# 关闭串口控制台抢占(避免冲突)
# CONFIG_SERIAL_8250_CONSOLE is not set
关键提示:如果目标机同时使用串口作为控制台(console),需要在内核启动参数中明确区分KGDB使用的串口与控制台串口,否则两者会冲突导致无法输入。推荐将KGDB绑定到 ttyS1,控制台保留在 ttyS0。

4.3 启动KGDB:内核启动参数

在目标机的bootloader(GRUB、U-Boot等)中,向内核传递以下启动参数:

kgdboc=ttyS0,115200 kgdbwait
参数 含义 示例值
kgdboc KGDB Over Console,指定串口和波特率 ttyS0,115200
kgdbwait 内核启动后立即进入KGDB等待GDB连接 (无参数值)
kgdbcon 允许KGDB消息输出到控制台(调试用) (可选)

GRUB配置示例(/etc/default/grub):

GRUB_CMDLINE_LINUX="console=tty0 kgdboc=ttyS0,115200 kgdbwait nokaslr"
安全提示:生产环境务必添加 nokaslr 关闭内核地址空间布局随机化,否则GDB无法正确解析符号地址。调试完成后应移除该参数。
KGDB启动流程 ① Bootloader加载 传递kgdboc参数 ② 内核初始化 KGDB注册串口I/O ③ kgdbwait触发 内核暂停等待 ④ GDB远程连接 (gdb) target remote 💡 调试入口 目标机启动后串口输出 "Entering kdb..." 即表示等待GDB连接

4.4 GDB连接内核

在开发机上启动GDB,加载带有调试符号的内核镜像(vmlinux),然后通过串口远程连接目标机:

# 开发机终端
cd /path/to/kernel/source
gdb vmlinux

(gdb) set serial baud 115200
(gdb) target remote /dev/ttyUSB0

# 成功连接后GDB提示符变为:
Remote debugging using /dev/ttyUSB0
kgdb_breakpoint () at kernel/debug/debug_core.c:1078
1078            wmb(); /* Sync point before entering the breakpoint */
(gdb)
连接验证:执行 (gdb) info registers 查看寄存器状态,如果返回正常值(如RIP指向内核代码段),说明KGDB通信正常。如果返回 Remote 'g' packet reply is too long,通常是串口波特率或数据位不匹配。

4.5 设置断点与单步调试

连接成功后,即可使用标准GDB命令进行内核调试。以下为典型调试会话:

# 在函数入口设置断点
(gdb) b do_fork
Breakpoint 1 at 0xffffffff8108a3c0: file kernel/fork.c, line 2100.

# 在特定行设置断点
(gdb) b kernel/fork.c:2150
Breakpoint 2 at 0xffffffff8108a5a0: file kernel/fork.c, line 2150.

# 条件断点:仅当pid大于1000时触发
(gdb) cond 1 current->pid > 1000

# 继续执行
(gdb) c
Continuing.

# 命中断点后单步
Breakpoint 1, do_fork (clone_flags=0x1200011, stack_start=0x0, 
    stack_size=0x0, parent_tidptr=0x0, child_tidptr=0x0) at kernel/fork.c:2100
2100    {
(gdb) step
2101            struct task_struct *p;
(gdb) step
2103            trace_task_newtask(current, clone_flags);
(gdb) next
2105            if (clone_flags & CLONE_THREAD)
(gdb) print current->pid
$1 = 1234

# 查看调用栈
(gdb) bt
#0  do_fork (clone_flags=0x1200011, ...) at kernel/fork.c:2105
#1  0xffffffff8108a9e0 in kernel_thread (fn=0xffffffff81a0b000, ...) at kernel/fork.c:2550
#2  0xffffffff8100b200 in rest_init () at init/main.c:450

# 修改变量(危险操作)
(gdb) set var current->pid = 9999

# 继续运行
(gdb) c
GDB命令 KGDB调试用途 注意事项
b / break 设置软件断点(int3指令) 不可在中断上下文设置
step 单步进入函数内部 可能进入汇编代码
next 单步跳过函数调用 不会进入子函数
finish 执行到当前函数返回 用于快速退出函数
print 打印内核变量/表达式 注意类型转换
set var 修改内核内存 可能导致系统崩溃
断点限制:KGDB软件断点依赖int3指令,不能在以下位置设置:中断处理函数(IRQ)、NMI上下文、自旋锁持有期间、以及 __init 函数(执行完后被释放)。在这些位置设置断点会导致系统死锁或崩溃。
KGDB调试会话状态机 运行态 内核正常执行 断点命中 int3异常触发 GDB交互 单步/打印/修改变量 断点触发 GDB接管 (gdb) continue → 恢复执行 💡 调试循环 运行 → 断点命中 → GDB交互 → continue → 运行(循环往复)

4.6 断开与恢复

调试完成后,需要正确断开KGDB连接,使目标机恢复正常运行:

# 在GDB中断开连接
(gdb) detach

# 退出GDB
(gdb) quit

# 如果目标机卡在kgdbwait状态,需要在目标机串口终端输入:
# (通过串口终端如screen/minicom连接目标机)
$ gdb
# 或者直接重启目标机
恢复技巧:如果调试过程中目标机死锁,可以通过目标机的硬件复位按钮重启。如果KGDB配置了 kgdbcon,可以在GDB中使用 (gdb) monitor 命令发送KDB命令(如 bt、ps)进行紧急诊断。

4.7 常见问题与排查

现象 可能原因 解决方案
GDB连接超时 串口线错误 / 波特率不匹配 检查线序;确认双方波特率一致
Remote packet reply too long GDB与KGDB协议版本不兼容 升级GDB至7.0+;或使用 set remote protocol
断点无法命中 KASLR导致符号地址偏移 添加 nokaslr 启动参数
单步后系统死机 在中断上下文单步 避免在IRQ/NMI中单步;使用 finish 快速退出
串口无输出 BIOS禁用串口 / 驱动未加载 检查BIOS设置;确认 CONFIG_SERIAL_8250 已编译
调试最佳实践:
  • 始终在开发机上保留一份与目标机完全一致的内核源码树和编译产物(vmlinux、.ko文件)。
  • 使用 .gdbinit 文件自动设置串口参数和加载符号,避免每次手动输入。
  • 对于模块调试,使用 add-symbol-file 命令加载模块符号表。
  • 在调试前通过 make scripts_gdb 生成GDB辅助脚本(如lx-dmesg、lx-cpus等),大幅提升效率。