4. GDB 基础命令:启动、加载与核心操作

GDB(GNU Debugger)是 Linux 内核调试的基石工具。本章将系统讲解 GDB 的启动方式、如何加载内核符号文件 vmlinux、目标架构设置,以及最常用的断点与执行控制命令。掌握这些基础操作,是后续深入内核调试的前提。

4.1 启动 GDB 的三种方式

根据调试场景不同,GDB 的启动方式分为本地调试、远程调试和附加进程三种。下表对比了它们的适用场景与命令格式:

启动方式 命令格式 典型场景
本地直接启动 gdb vmlinux 分析内核崩溃转储文件(vmcore)
远程调试(KGDB) gdb vmlinux -ex "target remote /dev/ttyS0" 双机内核调试,目标机通过串口连接
附加到 QEMU gdb vmlinux -ex "target remote :1234" QEMU/KVM 虚拟机内核调试
提示: 推荐使用 gdb-multiarch 替代原生 gdb,以支持跨架构调试(如 x86 主机调试 ARM 内核)。

4.2 加载 vmlinux 与设置架构

vmlinux 是编译内核后生成的未压缩 ELF 文件,包含完整的符号表和调试信息。GDB 通过它才能将地址解析为函数名、行号和变量。

4.2.1 加载 vmlinux

# 方式一:启动时加载
gdb /usr/src/linux/vmlinux

# 方式二:在 GDB 内部加载
(gdb) file /usr/src/linux/vmlinux

# 方式三:使用符号文件(分离调试信息时)
(gdb) symbol-file /path/to/vmlinux.debug

4.2.2 设置目标架构

当调试非本地架构的内核(如 ARM64、RISC-V)时,必须显式设置架构,否则 GDB 无法正确解析指令和寄存器。

# 设置 ARM64 架构
(gdb) set architecture aarch64

# 设置 x86_64 架构(默认)
(gdb) set architecture i386:x86-64

# 查看当前架构
(gdb) show architecture
注意: 架构设置必须在加载 vmlinux 之后、连接远程目标之前完成。错误的架构设置会导致反汇编结果完全不可读。

4.3 核心断点命令:break

break(简写 b)是 GDB 中使用频率最高的命令。内核调试中,断点可以设置在函数入口、行号、条件表达式或地址上。

断点类型 命令示例 说明
函数断点 b do_fork 在 do_fork 函数入口暂停
行号断点 b kernel/fork.c:123 在源文件指定行暂停
条件断点 b do_fork if pid == 0 满足条件时才触发
地址断点 b *0xffffffff81000000 在虚拟地址处暂停
硬件断点 hbreak do_fork 使用硬件调试寄存器,适合只读内存区域

4.4 执行控制命令:run / continue / next / step

设置断点后,需要通过执行控制命令来驱动调试过程。下表总结了四个核心命令的差异:

命令 简写 行为 内核调试典型用法
run r 启动程序(或重新启动) 在本地调试 vmcore 时无效;远程调试时启动目标内核
continue c 继续执行直到下一个断点 从当前断点恢复内核执行
next n 单步执行,不进入函数内部 跳过内核函数调用,只观察当前函数流程
step s 单步执行,进入函数内部 跟踪进入子函数(如 schedule、do_exit)
实战要点:
  • 内核调试中 run 通常不直接使用,因为内核已经在运行;continue 才是恢复执行的主要命令。
  • next 和 step 在汇编级别分别对应 nexti(ni)和 stepi(si),后者在无源码时更常用。
  • 在中断上下文或自旋锁持有期间使用 step 可能导致系统死锁,需谨慎。

4.5 断点与执行控制流程

下图展示了 GDB 内核调试中从启动到断点触发的完整流程:

GDB 内核调试断点与执行控制流程 启动 GDB 加载 vmlinux 设置目标架构 设置断点 (break) 连接远程目标 continue 恢复执行 断点命中,暂停 断点命中后的操作 • next (n):单步跳过函数 • step (s):单步进入函数 • continue (c):继续执行 断点管理命令 • info breakpoints:列出所有断点 • delete N:删除编号 N 的断点 • disable N:临时禁用断点

4.6 常用辅助命令

除了核心的断点和执行控制命令,以下辅助命令在日常内核调试中同样不可或缺:

  • list(l):显示当前断点附近的源代码,默认 10 行。
  • info registers(ir):查看所有 CPU 寄存器的当前值。
  • backtrace(bt):打印当前调用栈,定位内核执行路径。
  • print(p):打印变量或表达式的值,如 p current->pid。
  • disassemble(disas):反汇编当前函数或指定地址。
  • thread:切换当前调试的 CPU 核心(SMP 系统)。
效率技巧: 在 GDB 中按 Tab 键可自动补全命令和符号名;按 Ctrl+C 可中断正在执行的内核并返回 GDB 提示符(需 KGDB 支持)。

4.7 实战示例:调试 do_fork 流程

以下是一个完整的调试会话,演示从启动到单步跟踪 do_fork 的过程:

# 1. 启动 GDB 并加载内核符号
$ gdb /usr/src/linux/vmlinux

# 2. 设置架构(ARM64 示例)
(gdb) set architecture aarch64

# 3. 连接 QEMU 远程目标
(gdb) target remote :1234

# 4. 在 do_fork 函数入口设置断点
(gdb) b do_fork

# 5. 恢复内核执行,等待断点触发
(gdb) c

# 6. 断点命中后,查看调用栈
(gdb) bt
#0  do_fork (clone_flags=..., stack_start=..., ...) at kernel/fork.c:2020
#1  kernel_thread (fn=..., arg=..., flags=...) at kernel/fork.c:2550
#2  ...

# 7. 单步进入 do_fork 内部
(gdb) s
__do_fork (clone_flags=..., ...) at kernel/fork.c:2100

# 8. 打印当前进程 PID
(gdb) p current->pid
$1 = 1

# 9. 继续执行到下一个断点
(gdb) c
关键总结:
  • break 是内核调试的起点,支持函数名、行号、地址和条件四种形式。
  • continue 是恢复内核执行的主要命令,step 用于深入函数内部,next 用于跳过函数调用。
  • 加载正确的 vmlinux 和设置匹配的架构是 GDB 正确工作的前提。
  • 结合 backtrace 和 print 命令,可以在断点处快速获取内核执行上下文。