1. 概述:什么是 KGDB?
KGDB 是 K ernel G eneral D ebugger 的缩写。它是一个内置在 Linux 内核中的源码级调试器,允许开发者像调试应用程序一样调试 Linux 内核(以及内核模块)。
与 printk
、ftrace
或 kprobes
等输出日志的工具不同,KGDB 提供了交互式调试体验。你可以:
- 在任意位置设置断点
- 单步执行内核代码
- 查看和修改变量、寄存器、内存
- 查看调用栈回溯
核心价值:KGDB 极大地简化了复杂内核问题(如死锁、内存损坏、难以捉摸的竞态条件)的分析过程,是内核开发者不可或缺的强大工具。
2. 核心原理与架构
2.1 基本原理
KGDB 的核心思想是将运行中的 Linux 内核变成一个 GDB 的远程调试目标(Remote Target)。
-
客户端/服务器模型:
- 目标机 (Target / Server):运行被调试的内核。内核内部集成了 KGDB 的"桩"代码(stub),它充当一个 GDB 远程服务器。
- 开发机 (Host / Client) :运行标准的 GDB ,并加载带有调试符号的
vmlinux
文件。GDB 通过某种媒介(串口、以太网)与目标机的 KGDB stub 进行通信。
-
通信协议 :两者使用 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 工作流程
- 中断 :通过特定方式(例如串口发送
BREAK
信号、SysRq 组合键、或代码中的breakpoint()
)使内核进入调试状态。 - 接管:内核暂停所有 CPU 的执行,并将控制权交给 KGDB stub。
- 通信:KGDB stub 通过指定的 I/O 通道通知开发机上的 GDB,并等待命令。
- 调试:开发人员使用 GDB 发送指令(读内存、设断点、继续执行等)。
- 执行:KGDB stub 执行这些指令,并将结果返回给 GDB。
- 恢复 :当开发者发出
continue
命令后,KGDB stub 恢复内核的执行。
3. 环境搭建与配置
本教程以最稳定可靠的串口连接 为例。以太网 (KGDB over Ethernet) 配置更复杂,且在某些网络状态下可能导致调试连接中断。
3.1 硬件准备
- 两台计算机:开发机(Host)和目标机(Target)。
- 串口线:一条 null-modem 串口线(或两个 USB 转串口线 + 串口对接线)。
- 连接:将两台机器的串口通过串口线连接起来。
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
kgdboc
:k gdb o ver c onsole,指定调试用的控制台。这里指定第一个串口 (ttyS0
),波特率为 115200。kgdbwait
:让内核在启动初期、在kgdboc
指定的端口初始化后立即暂停,并等待 GDB 的连接。这对于调试内核启动代码非常有用。
更新 GRUB 并重启目标机:
bash
sudo update-grub
sudo reboot
4. 实战应用:调试内核
假设目标机已正常启动(使用了不带 kgdbwait
的参数)。
4.1 步骤一:在目标机上触发调试状态
内核运行后,我们需要一种方式让它停下来等待调试器。最常用的方法是 Magic SysRq 组合键。
-
确保 SysRq 功能已开启:
bash# 在目标机上执行 echo 1 > /proc/sys/kernel/sysrq
-
触发 KGDB:
-
方法 A:通过键盘 (如果目标机有键盘)
依次按下:
Alt
+SysRq
(或PrtSc
) +g
-
方法 B:通过 /proc 接口(更通用)
bash# 在目标机上执行 echo g > /proc/sysrq-trigger
-
执行后,目标机的整个系统会完全冻结,并在串口上等待 GDB 的连接。
4.2 步骤二:在开发机上启动 GDB
-
启动 GDB 并加载符号文件:
bash# 使用目标机编译出的带调试符号的内核文件 vmlinux gdb /path/to/linux-source/vmlinux
-
连接到目标机 :
在 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. 高级用法与技巧
-
调试内核模块:
-
模块的代码和符号在加载前不存在。
-
需要在 GDB 中手动添加符号文件 :
bash(gdb) add-symbol-file /path/to/module.ko 0xffffffffc0123000
0xffffffffc0123000
是模块的.text
段加载地址。可以通过目标机的/sys/module/<module_name>/sections/.text
文件获取。
-
-
调试启动阶段 :
使用
kgdbwait
启动参数。内核会在初始化完串口后立即停止,让你可以从start_kernel
等非常早的函数开始调试。 -
调试死锁和宕机 (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。
-
多核 (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 将为你分析和解决最棘手的内核问题打开一扇新的大门。