kernel/debug 内核调试机制与原理详解

kernel/debug 内核调试机制与原理详解

源码路径rk3588/kernel-6.1/kernel/debug/
内核版本:Linux 6.1(RK3588 平台)
平台:RK3588(4×Cortex-A76 + 4×Cortex-A55 big.LITTLE,ARM64)

该目录实现 Linux 内核 KGDB(Kernel GDB) 调试子系统的核心代码。KGDB 允许通过串口或网络,使用主机端 GDB 远程调试运行中的内核;可选的 KDB 前端提供内核内置命令行调试器。二者共享 debug_core.c 提供的异常处理、断点管理、SMP 协调等基础设施。

说明:架构相关代码(断点指令、寄存器读写、异常向量)位于 arch/arm64/kernel/kgdb.c 等目录;串口 I/O 模块位于 drivers/tty/serial/kgdboc.c 等;本目录是调试框架的通用核心


目录


一、源码目录结构

1.1 编译依赖(Makefile)

1
2
obj-$(CONFIG_KGDB) += debug_core.o gdbstub.o
obj-$(CONFIG_KGDB_KDB) += kdb/
配置项 说明
CONFIG_KGDB 总开关,依赖 HAVE_ARCH_KGDBDEBUG_KERNEL
CONFIG_KGDB_SERIAL_CONSOLE 串口共享 kgdb(kgdboc)
CONFIG_KGDB_KDB 启用 KDB 内置命令行前端
CONFIG_KDB_KEYBOARD PS/2 键盘作为 KDB 输入
CONFIG_KGDB_HONOUR_BLOCKLIST 禁止在不安全符号上设断点

1.2 编译单元

编译单元 源文件 规模 功能
调试核心 debug_core.c ~1,239 行 异常入口、SMP roundup、断点、I/O 注册
GDB Stub gdbstub.c ~1,156 行 GDB 远程串行协议实现
KDB 主循环 kdb/kdb_main.c ~2,937 行 命令解析、注册、主 REPL 循环
KDB I/O kdb/kdb_io.c ~882 行 控制台输出、命令行输入
KDB 断点 kdb/kdb_bp.c ~591 行 KDB 断点/观察点管理
KDB 支持 kdb/kdb_support.c ~556 行 内存读写、符号解析、参数解析
KDB 栈回溯 kdb/kdb_bt.c ~221 行 bt/btp/bta/btc 命令
KDB 键盘 kdb/kdb_keyboard.c ~262 行 PS/2 键盘输入(可选)
KDB/KGDB 桥接 kdb/kdb_debugger.c ~176 行 kdb_stub、模式切换
内部头文件 debug_core.h ~87 行 核心与前端私有接口
KDB 私有头 kdb/kdb_private.h ~245 行 KDB 内部类型与 API
初始命令 kdb/kdb_cmds ~32 行 启动时执行的 defcmd 宏

1.3 相关代码分布(本目录外)

位置 功能
arch/arm64/kernel/kgdb.c ARM64 断点异常、寄存器、单步
include/linux/kgdb.h 公共 API、arch_kgdb_ops
drivers/tty/serial/kgdboc.c 串口 I/O 驱动(kgdboc=)
drivers/misc/kgdbts.c KGDB 内部测试套件
kernel/trace/trace_kdb.c ftrace 与 KDB 集成

二、整体架构

2.1 分层模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌─────────────────────────────────────────────────────────────┐
│ 主机端调试器 │
│ GDB (target remote) / kdb 命令行 │
└──────────────────────────┬──────────────────────────────────┘
│ 串口 / 网络
┌──────────────────────────▼──────────────────────────────────┐
│ gdbstub.c / kdb/kdb_main.c │
│ GDB Remote Protocol │ KDB 命令 REPL │
└──────────────────────────┬──────────────────────────────────┘

┌──────────────────────────▼──────────────────────────────────┐
│ debug_core.c (核心) │
│ kgdb_handle_exception │ kgdb_cpu_enter │ 断点 │ SMP roundup │
└──────────────────────────┬──────────────────────────────────┘

┌──────────────────────────▼──────────────────────────────────┐
│ arch/arm64/kernel/kgdb.c │
│ 断点指令 │ pt_regs 读写 │ 单步 │ kgdb_brk_fn 异常处理 │
└─────────────────────────────────────────────────────────────┘

2.2 双前端设计

模式 默认 入口 特点
KDB dbg_kdb_mode = 1 kdb_stub() 内核内置 shell,无需主机 GDB
GDB Stub 切换后 gdb_serial_stub() 标准 GDB 远程协议,功能完整

两者可在运行时通过 kgdb 命令或 GDB 3 转义包互相切换。

2.3 关键全局状态

1
2
3
4
5
6
// debug_core.c
struct debuggerinfo_struct kgdb_info[NR_CPUS]; // 每 CPU 调试上下文
atomic_t kgdb_active; // 当前 master CPU
int kgdb_connected; // 主机 GDB 是否已连接
struct kgdb_io *dbg_io_ops; // 当前 I/O 驱动
int dbg_kdb_mode = 1; // 1=KDB, 0=GDB stub

三、核心数据结构

3.1 struct kgdb_state — 异常上下文

1
2
3
4
5
6
7
8
9
10
11
12
// debug_core.h
struct kgdb_state {
int ex_vector; // 异常向量号
int signo; // 信号号(SIGTRAP 等)
int err_code; // 错误码
int cpu; // 触发 CPU
int pass_exception; // 是否将异常传回内核
unsigned long threadid; // 当前线程 ID
long kgdb_usethreadid;
struct pt_regs *linux_regs; // CPU 寄存器
atomic_t *send_ready; // NMI 回调同步
};

3.2 struct debuggerinfo_struct — 每 CPU 状态

1
2
3
4
5
6
7
8
9
struct debuggerinfo_struct {
void *debuggerinfo; // 架构相关调试信息(通常 = pt_regs)
struct task_struct *task; // 触发时的 current
int exception_state; // DCPU_WANT_MASTER 等标志
int ret_state; // 返回状态
int irq_depth; // 进入时硬中断深度
int enter_kgdb; // 递归进入计数
bool rounding_up; // 是否正在 roundup
};

CPU 状态标志:

标志 含义
DCPU_WANT_MASTER 等待成为 master 调试 CPU
DCPU_NEXT_MASTER 切换 master CPU
DCPU_IS_SLAVE 从 CPU 已进入等待
DCPU_WANT_BT 从 CPU 需打印栈回溯

3.3 struct kgdb_bkpt — 软件断点

1
2
3
4
5
6
// include/linux/kgdb.h
struct kgdb_bkpt {
unsigned long bpt_addr; // 断点地址
char saved_instr[BREAK_INSTR_SIZE]; // 原始指令
int state; // BP_SET / BP_REMOVED / BP_UNDEFINED
};

最多 KGDB_MAX_BREAKPOINTS 个(通常 1000),由 GDB 或 KDB 管理。

3.4 struct kgdb_io — I/O 驱动接口

1
2
3
4
5
6
7
8
9
10
struct kgdb_io {
const char *name;
int (*read_char)(void);
void (*write_char)(u8);
void (*flush)(void);
int (*init)(void);
void (*pre_exception)(void);
void (*post_exception)(void);
int is_early; // 是否可用于 early debug
};

通过 kgdb_register_io_module() 注册,典型实现为 kgdboc(串口)。


四、异常进入与 SMP 协调

4.1 主入口

1
2
3
4
5
6
7
// debug_core.c
int kgdb_handle_exception(int evector, int signo, int ecode, struct pt_regs *regs)
{
// 1. 填充 kgdb_state
// 2. 检查重入(kgdb_reenter_check)
// 3. 调用 kgdb_cpu_enter() 进入调试主循环
}

ARM64 上由 kgdb_brk_fn() 在断点异常时调用:

1
2
3
4
5
6
// arch/arm64/kernel/kgdb.c
static int kgdb_brk_fn(struct pt_regs *regs, unsigned long esr)
{
kgdb_handle_exception(1, SIGTRAP, 0, regs);
return 0;
}

4.2 kgdb_cpu_enter 流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
kgdb_cpu_enter()
├── 获取 master 锁 (dbg_master_lock)
├── kgdb_roundup_cpus() // SMP: 通知其他 CPU 停止
├── 等待所有 CPU 进入 kgdb 等待态
├── dbg_deactivate_sw_breakpoints() // 临时移除 SW 断点
├── 关闭 ftrace (tracing_off)
└── 主循环:
if (dbg_kdb_mode)
kdb_stub(ks) // KDB 命令行
else
gdb_serial_stub(ks) // GDB 协议
→ 根据返回值切换模式或退出
├── dbg_activate_sw_breakpoints() // 恢复断点
├── 释放 slave 锁,等待从 CPU 退出
└── 恢复 tracing,返回正常执行

4.3 SMP Roundup

多核系统中,一个 CPU 触发调试后需冻结其他 CPU,避免并发修改内核状态:

  • Master CPU 获取 dbg_master_lock,设置 kgdb_active
  • 通过 IPI/NMI 调用 kgdb_nmicallback() 让从 CPU 进入 spin 等待
  • 从 CPU 设置 DCPU_IS_SLAVE,在 cpu_loop 中等待
  • 调试结束后释放锁,从 CPU 恢复

可通过内核参数 nokgdbroundup 禁用 roundup(调试用,有风险)。

4.4 递归保护

  • exception_level — 防止调试器内部再次触发异常时死锁
  • kgdb_info[cpu].enter_kgdb — 每 CPU 进入计数
  • kgdb_reenter_check() — 检测双重异常

五、GDB Stub 远程协议

5.1 协议格式

GDB 远程串行协议使用 $<data>#<checksum> 帧格式:

1
2
3
// gdbstub.c
static void get_packet(char *buffer); // 接收并校验
static void put_packet(char *buffer); // 发送并附加校验

5.2 主要命令处理

命令 功能
? 查询停止原因(信号号)
g / G 读/写全部寄存器
p / P 读/写单个寄存器
m / M 读/写内存
c 继续执行
s 单步执行
Z / z 设置/删除断点
H 设置当前线程
T 查询线程是否 alive
q 各类查询(supported、offsets、Xfer 等)
D / k 分离 / 杀死
R 重启
3 转义回 KDB 模式

5.3 线程与多任务

GDB stub 支持按线程调试:

  • Hg<tid> — 设置当前线程
  • Hc<tid> — 设置继续执行的线程
  • T<tid> — 检查线程状态
  • 配合 /proc 或内核 task 列表遍历所有线程

5.4 架构相关处理

kgdb_arch_handle_exception()(ARM64)处理 c/s 包中的地址参数,设置 PC 并启用/禁用硬件单步。


六、KDB 内置调试器

6.1 命令注册机制

1
2
3
4
5
6
7
8
9
10
11
// kdb_main.c
static struct kdb_cmd kdb_cmds[] = {
{ .name = "md", .func = kdb_md, ... }, // 内存显示
{ .name = "mm", .func = kdb_mm, ... }, // 内存修改
{ .name = "go", .func = kdb_go, ... }, // 继续执行
{ .name = "bt", .func = kdb_bt, ... }, // 栈回溯
{ .name = "ps", .func = kdb_ps, ... }, // 进程列表
{ .name = "bp", .func = kdb_bp, ... }, // 断点
{ .name = "kgdb", .func = kdb_kgdb, ... }, // 切换到 GDB
// ...
};

命令通过 kdb_register() 加入 kdb_cmds_head 链表;kdb_parse() 解析用户输入并 dispatch。

6.2 常用 KDB 命令

命令 功能
md / mdr / mdp / mds 显示内存(raw/phys/proc/symbol)
mm 修改内存
rd / rm 读/写 MSR(架构相关)
go 继续执行(可选地址)
bt 当前栈回溯
btp <pid> 指定进程栈回溯
bta 所有进程栈回溯
btc 每 CPU 一个进程栈回溯
ps 进程列表
pid 切换当前进程上下文
bp / bc / ba 断点 set/clear/list
ss 单步
cpu 切换 CPU
dmesg 显示内核 log 缓冲
summary 系统摘要
lsmod 已加载模块
reboot 重启
kill 发送信号给进程
set / env 环境变量(RADIX、PROMPT 等)
help / ? 帮助
kgdb 切换到 GDB stub 模式
defcmd / endefcmd 定义命令宏
sr 触发 SysRq

6.3 权限控制

kdb_cmd_enabled(模块参数 cmd_enable)按位控制命令权限:

权限
0x0002 任意内存读、符号查找
0x0004 任意内存写
0x0008 寄存器读
0x0010 寄存器写
0x0020 被动检查(bt、ps、lsmod)
0x0040 流程控制(断点、单步)
0x0080 信号进程
0x0100 重启

6.4 启动宏(kdb_cmds)

kdb/kdb_cmds 在 KDB 初始化时执行,预定义诊断宏:

1
2
3
defcmd dumpcommon ...   # summary + cpu + ps + dmesg + bt
defcmd dumpall ... # dumpcommon + bta
defcmd dumpcpu ... # dumpcommon + btc

七、断点管理

7.1 软件断点(KGDB 核心)

1
2
3
4
5
6
7
// debug_core.c
kgdb_arch_set_breakpoint(bpt)
→ 保存原始指令到 saved_instr
→ 写入架构断点指令(ARM64: BRK)

kgdb_arch_remove_breakpoint(bpt)
→ 恢复原始指令

进入调试器时 deactivate 所有 SW 断点(避免调试代码自身触发),退出时 activate 恢复。

7.2 KDB 断点(kdb_bp.c)

KDB 独立维护最多 KDB_MAXBPT(16)个断点:

类型 说明
BP_HARDWARE_BREAKPOINT 指令断点
BP_WRITE_WATCHPOINT 数据写观察点
BP_ACCESS_WATCHPOINT 数据读写观察点

支持延迟断点(bp_delay)和单步恢复机制(SSBPT 标志)。

7.3 Blocklist 保护

启用 CONFIG_KGDB_HONOUR_BLOCKLIST 时,禁止在 kprobe blocklist 中的符号(如异常处理路径上的函数)设置断点,防止递归陷阱。


八、I/O 与触发方式

8.1 串口 I/O(kgdboc)

1
2
3
4
# 内核命令行
kgdboc=ttyFIQ0,115200 # RK3588 常用 FIQ 调试串口
# 或
kgdboc=ttyS2,115200

I/O 驱动通过 kgdb_register_io_module() 注册到 dbg_io_ops

8.2 触发进入调试器

方式 说明
SysRq-g echo g > /proc/sysrq-trigger 或 Magic SysRq 键 + g
kgdbwait 内核启动参数,启动后等待 GDB 连接
断点/Oops 内核 panic/oops 时自动进入(若已注册)
KDB 键盘 PS/2 键(需 CONFIG_KDB_KEYBOARD)
1
2
3
4
5
6
// debug_core.c
static void sysrq_handle_dbg(int key)
{
kgdb_breakpoint(); // 触发断点异常
}
register_sysrq_key('g', &sysrq_dbg_op);

8.3 内核参数

参数 功能
kgdbwait 启动时等待 GDB 连接
kgdboc=<tty>,<baud> 指定 kgdb 串口
nokgdbroundup 禁用 SMP CPU roundup
kdb.cmd_enable=<hex> KDB 命令权限掩码
kgdbreboot=<n> panic 时行为

九、KDB 与 GDB 模式切换

9.1 KDB → GDB

在 KDB 提示符输入:

1
kdb> kgdb

kdb_stub() 返回 DBG_PASS_EVENT,主循环切换 dbg_kdb_mode = 0,下次迭代进入 gdb_serial_stub()

9.2 GDB → KDB

GDB 发送转义包 '3',或 KDB I/O 检测到非 GDB 格式输入时切换。

9.3 主循环切换逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// debug_core.c — cpu_master_loop
while (1) {
if (dbg_kdb_mode) {
error = kdb_stub(ks);
} else {
error = gdb_serial_stub(ks);
}
if (error == DBG_PASS_EVENT)
dbg_kdb_mode = !dbg_kdb_mode; // 切换模式
else if (error == DBG_SWITCH_CPU_EVENT)
goto cpu_loop; // 切换 CPU
else
break; // 退出调试
}

十、完整调试会话时序

SysRq-g 触发 → GDB 连接 为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
[用户 / 按键]
echo g > /proc/sysrq-trigger
→ sysrq_handle_dbg()
→ kgdb_breakpoint()
→ ARM64 BRK 异常
→ kgdb_brk_fn(regs)
→ kgdb_handle_exception(SIGTRAP, regs)

[debug_core — SMP 协调]
kgdb_cpu_enter()
→ master CPU 获取 dbg_master_lock
→ kgdb_roundup_cpus() — IPI 冻结其他 7 核
→ dbg_deactivate_sw_breakpoints()
→ tracing_off()

[KDB 模式 — 默认]
kdb_stub(ks)
→ kdb_common_init_state()
→ 显示 kdb> 提示符,等待输入

[用户输入 kgdb 切换]
kdb> kgdb
→ 返回 DBG_PASS_EVENT
→ dbg_kdb_mode = 0

[GDB Stub 模式]
gdb_serial_stub(ks)
→ put_packet("S05") // 通知 GDB: SIGTRAP
→ get_packet() 循环处理 m/M/g/G/c/s/Z/z ...

[主机 GDB]
(gdb) target remote /dev/ttyUSB0
(gdb) break some_kernel_func
(gdb) continue

[继续执行后再次触发]
→ 再次进入 kgdb_handle_exception
→ GDB 收到停止通知,可 inspect 寄存器/内存

[退出调试]
GDB 发送 'D' (detach) 或 'c' (continue)
→ kgdb_cpu_enter 主循环 break
→ dbg_activate_sw_breakpoints()
→ 释放 slave 锁,从 CPU 恢复
→ 系统正常运行

十一、RK3588 平台配置与使用

11.1 内核配置

1
2
3
4
5
6
7
8
9
zcat /proc/config.gz | grep -E 'KGDB|KDB|DEBUG'

# 典型调试内核配置
# CONFIG_DEBUG_KERNEL=y
# CONFIG_HAVE_ARCH_KGDB=y
# CONFIG_KGDB=y
# CONFIG_KGDB_SERIAL_CONSOLE=y
# CONFIG_KGDB_KDB=y # 可选
# CONFIG_FRAME_POINTER=y # 建议,改善栈回溯

ARM64 RK3588 在 arch/arm64/Kconfig 中选择 HAVE_ARCH_KGDB,具体实现在 arch/arm64/kernel/kgdb.c

11.2 设备树 / 命令行示例

1
2
3
# U-Boot 或 bootargs 追加
console=ttyFIQ0,115200 earlycon=uart8250,mmio32,0xfeb50000 \
kgdboc=ttyFIQ0,115200 kgdbwait
  • ttyFIQ0:Rockchip 平台常用 FIQ 调试串口
  • kgdbwait:内核初始化完成后暂停,等待 GDB 连接

11.3 主机端 GDB 连接

1
2
3
4
5
6
7
8
# 假设串口为 /dev/ttyUSB0
aarch64-linux-gnu-gdb vmlinux
(gdb) set serial baud 115200
(gdb) target remote /dev/ttyUSB0
(gdb) bt # 栈回溯
(gdb) info registers # 寄存器
(gdb) x/10i $pc # 反汇编
(gdb) continue

11.4 无 GDB 时使用 KDB

1
2
3
4
5
6
7
8
9
# 触发 SysRq-g 进入 KDB
echo g > /proc/sysrq-trigger

# KDB 提示符下
kdb> bt # 栈回溯
kdb> ps # 进程列表
kdb> md 0xffffffc008000000 # 读内存
kdb> dmesg 100 # 最近 100 条 log
kdb> go # 继续

11.5 Android / 嵌入式注意事项

要点 说明
串口访问 需物理 UART 或 USB 转串口,Android 量产机通常无暴露接口
性能影响 kgdbwait 会阻塞启动,仅用于 bring-up 调试
SMP RK3588 8 核,roundup 需等待所有 CPU,超时约 1 秒
与 ftrace 交互 进入 KGDB 时自动 tracing_off(),退出后恢复
Lockdown 启用 Kernel Lockdown 时,GDB 写内存会被拒绝,自动回退 KDB

11.6 与 ftrace/KDB 集成

启用 CONFIG_KGDB_KDB 且 ftrace 支持时,kernel/trace/trace_kdb.c 允许在 KDB 中使用 ftrace 相关命令,便于运行时追踪与调试结合。


十二、总结

kernel/debug 是 Linux 内核源码级调试的基础设施:

  1. debug_core.c — 统一异常入口、SMP 多核协调、断点管理、I/O 抽象
  2. gdbstub.c — 实现 GDB 远程串行协议,支持主机 GDB 全功能调试
  3. kdb/ — 可选内置命令行调试器,适合无 GDB 环境或 early boot 诊断
  4. 双模式切换 — KDB 与 GDB stub 共享核心,可按场景灵活切换
  5. 架构分离 — ARM64 相关代码在 arch 层,本目录保持架构无关

在 RK3588 平台 bring-up、驱动调试、内核 panic 分析场景中,KGDB/KDB 是与串口日志、ftrace、crash dump 互补的重要工具。生产环境通常不启用;开发和深度调试内核时,配合 kgdboc + kgdbwait + 主机 GDB 是标准工作流。


附录:源文件完整清单

文件 行数 分类
kdb/kdb_main.c 2,937 KDB 主循环与命令
debug_core.c 1,239 KGDB 核心
gdbstub.c 1,156 GDB 远程协议
kdb/kdb_io.c 882 KDB I/O
kdb/kdb_bp.c 591 KDB 断点
kdb/kdb_support.c 556 KDB 辅助函数
kdb/kdb_keyboard.c 262 键盘输入
kdb/kdb_bt.c 221 栈回溯
kdb/kdb_debugger.c 176 KDB/KGDB 桥接
debug_core.h 87 内部头文件
kdb/kdb_private.h 245 KDB 私有头文件
kdb/kdb_cmds 32 启动宏定义

文章互动

阅读 --

留言

0 条留言

正在加载留言…