Linux KGDB 内核调试完全指南:原理、架构与应用

1. 概述:什么是 KGDB?

KGDBK ernel G eneral D ebugger 的缩写。它是一个内置在 Linux 内核中的源码级调试器,允许开发者像调试应用程序一样调试 Linux 内核(以及内核模块)。

printkftracekprobes 等输出日志的工具不同,KGDB 提供了交互式调试体验。你可以:

  • 在任意位置设置断点
  • 单步执行内核代码
  • 查看和修改变量、寄存器、内存
  • 查看调用栈回溯

核心价值:KGDB 极大地简化了复杂内核问题(如死锁、内存损坏、难以捉摸的竞态条件)的分析过程,是内核开发者不可或缺的强大工具。


2. 核心原理与架构

2.1 基本原理

KGDB 的核心思想是将运行中的 Linux 内核变成一个 GDB 的远程调试目标(Remote Target)

  1. 客户端/服务器模型

    • 目标机 (Target / Server):运行被调试的内核。内核内部集成了 KGDB 的"桩"代码(stub),它充当一个 GDB 远程服务器。
    • 开发机 (Host / Client) :运行标准的 GDB ,并加载带有调试符号的 vmlinux 文件。GDB 通过某种媒介(串口、以太网)与目标机的 KGDB stub 进行通信。
  2. 通信协议 :两者使用 GDB 远程串行协议 (RSP) 进行通信。这是一个基于数据包的简单协议,用于传输命令、内存内容、寄存器值等。

2.2 系统架构图

复制代码
+-----------------------------------+     +-----------------------------------+
|          开发机 (Host)            |     |          目标机 (Target)           |
|                                   |     |                                   |
|  +------------------------------+ |     |  +------------------------------+ |
|  |           GDB               | |     |  |        Linux Kernel          | |
|  |  (gdb) target remote /dev/ttyS0 |<----->|  +-----------------------+  | |
|  |  (gdb) break some_func      | |     |  |  |      KGDB Core         |  | |
|  |  (gdb) c                    | |     |  |  |  (处理断点、命令等)     |  | |
|  |                             | |     |  |  +-----------------------+  | |
|  +------------------------------+ |     |  |  |   I/O 驱动 (e.g., 串口) |  | |
|                                   |     |  |  +-----------------------+  | |
+-----------------------------------+     |  +------------------------------+ |
                                          |                                   |
                                          +-----------------------------------+
  • 箭头 表示通过串口线以太网建立的 GDB RSP 连接。

2.3 工作流程

  1. 中断 :通过特定方式(例如串口发送 BREAK 信号、SysRq 组合键、或代码中的 breakpoint())使内核进入调试状态。
  2. 接管:内核暂停所有 CPU 的执行,并将控制权交给 KGDB stub。
  3. 通信:KGDB stub 通过指定的 I/O 通道通知开发机上的 GDB,并等待命令。
  4. 调试:开发人员使用 GDB 发送指令(读内存、设断点、继续执行等)。
  5. 执行:KGDB stub 执行这些指令,并将结果返回给 GDB。
  6. 恢复 :当开发者发出 continue 命令后,KGDB stub 恢复内核的执行。

3. 环境搭建与配置

本教程以最稳定可靠的串口连接 为例。以太网 (KGDB over Ethernet) 配置更复杂,且在某些网络状态下可能导致调试连接中断。

3.1 硬件准备

  1. 两台计算机:开发机(Host)和目标机(Target)。
  2. 串口线:一条 null-modem 串口线(或两个 USB 转串口线 + 串口对接线)。
  3. 连接:将两台机器的串口通过串口线连接起来。

3.2 目标机内核配置

编译目标机内核时,必须启用以下选项:

bash 复制代码
# 进入内核源码目录
cd /path/to/linux-source

# 使用 menuconfig 进行配置
make menuconfig

必需的配置选项

复制代码
Kernel hacking  --->
    [*] KGDB: kernel debugger  --->
        <*>   KGDB: use kgdb over the serial console  # 最关键,启用串口调试
        [*]   KGDB: internal test suite               # 可选,测试用

    [*] Compile the kernel with debug info            # 必须,生成调试符号

推荐配置

复制代码
# 启用更多调试功能
Kernel hacking  --->
    [*] Magic SysRq key                               # 非常重要!用于触发调试
    [*] Kernel debugging
    [*]   Compile the kernel with frame pointers      # 使栈回溯更准确

保存配置后,编译并安装新内核到目标机。

bash 复制代码
make -j$(nproc) && make modules_install && make install

3.3 目标机内核启动参数

修改目标机的引导加载程序(GRUB)。编辑 /etc/default/grub,在 GRUB_CMDLINE_LINUX 行添加以下参数:

bash 复制代码
# 告诉内核在启动时等待 GDB 连接(通常用于调试早期启动代码)
# kgdbwait 会等待直到 GDB 连接上来
kgdbwait kgdboc=ttyS0,115200

# 或者,更常见的做法是:内核正常启动,之后通过 SysRq 手动激活 KGDB
kgdboc=ttyS0,115200
  • kgdbock gdb o ver c onsole,指定调试用的控制台。这里指定第一个串口 (ttyS0),波特率为 115200。
  • kgdbwait:让内核在启动初期、在 kgdboc 指定的端口初始化后立即暂停,并等待 GDB 的连接。这对于调试内核启动代码非常有用。

更新 GRUB 并重启目标机:

bash 复制代码
sudo update-grub
sudo reboot

4. 实战应用:调试内核

假设目标机已正常启动(使用了不带 kgdbwait 的参数)。

4.1 步骤一:在目标机上触发调试状态

内核运行后,我们需要一种方式让它停下来等待调试器。最常用的方法是 Magic SysRq 组合键。

  1. 确保 SysRq 功能已开启:

    bash 复制代码
    # 在目标机上执行
    echo 1 > /proc/sys/kernel/sysrq
  2. 触发 KGDB:

    • 方法 A:通过键盘 (如果目标机有键盘)

      依次按下:Alt + SysRq(或 PrtSc) + g

    • 方法 B:通过 /proc 接口(更通用)

      bash 复制代码
      # 在目标机上执行
      echo g > /proc/sysrq-trigger

执行后,目标机的整个系统会完全冻结,并在串口上等待 GDB 的连接。

4.2 步骤二:在开发机上启动 GDB

  1. 启动 GDB 并加载符号文件

    bash 复制代码
    # 使用目标机编译出的带调试符号的内核文件 vmlinux
    gdb /path/to/linux-source/vmlinux
  2. 连接到目标机

    在 GDB 提示符下,连接到目标机的串口(请确保开发机对应用户有读写串口的权限,通常需要将用户加入 dialout 组)。

    bash 复制代码
    (gdb) set serial baud 115200    # 设置波特率,必须与 kgdboc 参数一致
    (gdb) target remote /dev/ttyS0  # 连接到本地串口设备

    如果成功,GDB 会打印出停止时所在的CPU和函数,例如:

    复制代码
    Remote debugging using /dev/ttyS0
    kgdb_breakpoint () at kernel/debug/debug_core.c:1083
    1083            wmb(); /* Sync point after breakpoint */
    (gdb)

    恭喜!你现在已经完全控制了目标机的内核

4.3 步骤三:开始调试

现在你可以使用所有熟悉的 GDB 命令了。

  • 设置断点

    bash 复制代码
    (gdb) break sys_open      # 在 sys_open 系统调用处中断
    (gdb) break module.c:200  # 在 module.c 的第 200 行中断
    (gdb) break *0xc0123456   # 在特定虚拟地址处中断
  • 继续执行

    bash 复制代码
    (gdb) continue            # 继续执行,直到下一个断点或异常
  • 查看信息

    bash 复制代码
    (gdb) info registers      # 查看所有寄存器
    (gdb) info threads        # 查看所有CPU(线程)状态
                              # '*' 号表示当前停止的CPU
    (gdb) backtrace           # 查看当前调用栈
    (gdb) print variable_name # 打印变量的值
    (gdb) list                # 显示当前位置的源代码
  • 单步执行

    bash 复制代码
    (gdb) nexti               # 汇编指令级别单步(跳过函数调用)
    (gdb) stepi               # 汇编指令级别单步(进入函数调用)
  • 观察点 (Watchpoint):当某个变量或内存地址被读写时中断,非常强大。

    bash 复制代码
    (gdb) watch global_var    # 当 global_var 被写入时中断
    (gdb) rwatch global_var   # 当 global_var 被读取时中断
    (gdb) awatch global_var   # 被读或写时都中断
  • 恢复运行:当你完成调试,想让目标机恢复正常:

    bash 复制代码
    (gdb) continue

    目标机将继续正常运行。


5. 高级用法与技巧

  1. 调试内核模块

    • 模块的代码和符号在加载前不存在。

    • 需要在 GDB 中手动添加符号文件

      bash 复制代码
      (gdb) add-symbol-file /path/to/module.ko 0xffffffffc0123000

      0xffffffffc0123000 是模块的 .text 段加载地址。可以通过目标机的 /sys/module/<module_name>/sections/.text 文件获取。

  2. 调试启动阶段

    使用 kgdbwait 启动参数。内核会在初始化完串口后立即停止,让你可以从 start_kernel 等非常早的函数开始调试。

  3. 调试死锁和宕机 (Panic)

    KGDB 可以在内核发生 Panic 时自动触发。确保编译时启用了:

    复制代码
    Kernel hacking --->
        [*] KGDB: kernel debugger --->
            [*] KGDB: allow debugging with traps in notifiers   # 建议启用
            (0) panic timeout after N seconds waiting for KGDB  # 设置为0则一直等待

    当内核发生 Panic 时,它会自动等待 GDB 连接,无需手动触发 SysRq。

  4. 多核 (SMP) 调试
    info threads 可以列出所有 CPU 核心。你可以切换当前上下文到任何核心进行检查:

    bash 复制代码
    (gdb) thread 2  # 切换到 CPU 1 的上下文 (GDB 线程编号从 1 开始,对应 CPU0)
    (gdb) bt        # 现在查看的是 CPU1 的调用栈

6. 常见问题与故障排除 (FAQ)

  • Q: 连接时出现 Packet too long 或 CRC 错误。
    A: 通常是波特率不匹配或硬件问题。确保双方波特率一致,串口线连接可靠。

  • Q: 目标机触发 SysRq-g 后,GDB 无法连接,目标机无响应。
    A: 检查 kgdboc 参数是否正确指定了串口设备(如 ttyS0 还是 ttyUSB0?)。检查串口线连接。

  • Q: GDB 可以连接,但设置断点后无效,continue 后目标机直接跑飞。
    A: 最大的可能是 GDB 加载的 vmlinux 文件与目标机运行的内核版本不匹配 。请确保是完全相同的源码编译出来的。

  • Q: print 变量显示 <optimized out>
    A: 编译器优化(如 -O2)会将变量放入寄存器或直接优化掉。这是正常现象。可以尝试查看汇编代码 (disassemble) 来推断变量值,或者尝试编译一个 -O0 的内核用于深度调试(但可能引入新问题)。

  • Q: 如何在不中断的情况下查看回溯?
    A: 在目标机上,可以使用 echo l > /proc/sysrq-trigger 来让内核打印所有 CPU 的当前回溯到控制台,而不会完全停止等待 GDB。这在生产环境排查问题时非常有用。


7. 总结

KGDB 是 Linux 内核开发者武器库中最强大的工具之一。它通过将内核转变为 GDB 远程目标,提供了无与伦比的交互式调试能力。

  • 核心 :理解其客户端-服务器架构 和基于串口/网络的通信方式。
  • 关键 :正确配置和编译内核 ,并传递正确的启动参数
  • 实践 :熟练使用 SysRq 触发调试,并掌握 GDB 常用命令
  • 进阶 :学会调试模块启动代码分析宕机

虽然初始设置稍显复杂,但一旦掌握,KGDB 将为你分析和解决最棘手的内核问题打开一扇新的大门。