3. GDB 基础命令与 JTAG 操作:load、continue、break、step、info registers、monitor halt/reset

好,咱们进入实战环节。这一章我带你过一遍 GDB 配合 JTAG 最常用的几个命令。说实话,很多驱动工程师调试死锁时,翻来覆去用的就是这些。你把这几个命令练熟了,80% 的现场都能应付。

核心思路:GDB 是大脑,JTAG 是手脚。GDB 发命令,JTAG 执行硬件级别的暂停、读取、写入。

3.1 建立连接:load 与目标板握手

先说说 load。这个命令的作用是把你的 ELF 或者二进制镜像烧写到目标板的 RAM 或 Flash 里。我刚开始用的时候,总以为 load 只是把文件传过去,其实它背后做了三件事:擦除、写入、校验。

(gdb) target remote localhost:2331
(gdb) load /home/project/my_driver.elf
Loading section .text, size 0x2a4c lma 0x80000000
Loading section .data, size 0x1a0 lma 0x80002a4c
Start address 0x80000000, load size 0x2bec
Transfer rate: 112 KB/sec, 12 bytes/write.

看到这个输出,说明烧写成功了。注意那个 Start address,它就是复位向量或者入口地址。我个人习惯在 load 之后立刻执行一次 monitor reset,确保 CPU 从干净状态开始跑。

小技巧:如果目标板是 Flash 启动,load 之后可能需要断电重启。RAM 运行的话,直接 continue 就行。

3.2 运行与暂停:continue 与 monitor halt/reset

continue(简写 c)就是让 CPU 跑起来。但死锁排查的关键在于——你得知道什么时候让它停。

monitor halt 是 JTAG 的硬暂停。它不依赖软件断点,直接通过调试接口把 CPU 冻住。我在项目中遇到过一种情况:系统已经死锁,软件断点根本触发不了,这时候 monitor halt 是唯一的救命稻草。

(gdb) monitor halt
target halted due to debug-request, current mode: Supervisor
xPSR: 0x01000000 pc: 0x80001a3c
(gdb) info registers
r0             0x00000001   1
r1             0x2000ff00   536936704
pc             0x80001a3c   0x80001a3c <spin_lock+8>

看到 pc 停在 spin_lock+8,我心里就有数了——八成是自旋锁没释放。你再看看 monitor reset,这个命令会复位 CPU,但不会复位调试逻辑。我一般用它来重现问题:复位后立刻 halt,然后单步跟踪初始化流程。

命令 作用 适用场景
monitor halt 硬件暂停 CPU 死锁、死循环、无法响应断点
monitor reset 硬件复位 CPU 重现问题、重启调试会话
continue 恢复执行 正常调试流程
注意:monitor reset 不会复位外设寄存器。如果你需要完全干净的硬件状态,得用 monitor reset halt 组合。

3.3 设置断点:break 的三种用法

break(简写 b)是软件断点。它会在指定地址插入一条 BKPT 指令,CPU 执行到那里就会陷入调试异常。我常用的有三种形式:

  1. 按函数名设断点:b spin_lock —— 最简单,适合入口函数。
  2. 按行号设断点:b driver.c:128 —— 精确到某一行。
  3. 按地址设断点:b *0x80001a3c —— 适合没有符号表的情况。
(gdb) b spin_lock
Breakpoint 1 at 0x80001a30: file kernel/locking/spinlock.c, line 178.
(gdb) b driver.c:128
Breakpoint 2 at 0x80002b4c: file driver.c, line 128.
(gdb) info breakpoints
Num     Type           Disp Enb Address    What
1       breakpoint     keep y   0x80001a30 spin_lock
2       breakpoint     keep y   0x80002b4c driver.c:128

我曾经踩过一个坑:在中断处理函数里设断点,结果断点触发后系统直接跑飞。后来才发现,有些 RTOS 的中断上下文不允许长时间暂停。解决办法是用 monitor halt 代替软件断点,或者把断点设在中断入口处,单步跳过关键部分。

3.4 单步跟踪:step 与 next 的区别

step(简写 s)会进入函数内部,next(简写 n)会跳过函数。死锁排查时,我更喜欢用 step,因为死锁往往藏在某个函数调用的深处。

(gdb) step
spin_lock (lock=0x2000ff00) at kernel/locking/spinlock.c:178
178     {
(gdb) step
179             arch_spin_lock(&lock->rlock);
(gdb) step
arch_spin_lock (lock=0x2000ff00) at arch/arm/include/asm/spinlock.h:55
55      {
(gdb) info registers pc
pc             0x80001a4c   0x80001a4c <arch_spin_lock+16>

你看,三步就追到了架构相关的自旋锁实现。如果这时候 pc 卡在某个循环里不动,基本可以断定是锁竞争导致的死锁。

经验之谈:单步时如果发现 pc 在同一个地址来回跳,先别慌。可能是编译器优化后的循环展开。这时候用 info registers 看看循环变量,比瞎猜强。

3.5 查看现场:info registers 与关键寄存器解读

info registers(简写 i r)会 dump 出所有通用寄存器和状态寄存器。死锁现场,我最关注三个东西:

  • PC(程序计数器):当前执行到哪了?是不是在锁函数里?
  • LR(链接寄存器):谁调了当前函数?回溯调用栈。
  • SP(栈指针):栈有没有溢出?有没有被踩?
(gdb) info registers
pc             0x80001a3c   0x80001a3c <spin_lock+8>
lr             0x80002b80   0x80002b80 <my_driver_write+24>
sp             0x2000fe80   0x2000fe80
r0             0x00000001   1
r1             0x2000ff00   536936704
xPSR           0x01000000   16777216

看到 lr 是 my_driver_write+24,说明是驱动写函数调用了 spin_lock。再结合 r0 是 1,r1 是锁的地址,现场就很清楚了。

注意:有些调试器在 halt 后不会自动更新寄存器缓存。如果你发现 info registers 显示的值不对劲,先执行一次 monitor halt 强制刷新。

3.6 知识体系:命令之间的协作关系

下面这张图,是我自己总结的 GDB+JTAG 调试流程。你照着这个顺序走,基本不会漏掉关键信息。

GDB+JTAG 调试命令协作流程图 GDB + JTAG 调试命令协作流程 1. 连接目标 target remote / load 2. 暂停 / 复位 monitor halt / reset 3. 设置断点 break / info breakpoints 4. 运行 / 单步 continue / step / next 5. 查看现场 info registers / backtrace 关键原则 • 先 halt 再设断点 • 死锁时优先 halt • 寄存器值以 halt 后为准 常见误区 • 忘记 halt 直接设断点 • 单步时忽略 LR 寄存器 • 复位后不 halt 直接跑

嗯,这张图把整个调试流程串起来了。你从连接目标开始,到暂停、设断点、运行、查看现场,每一步都有对应的命令。死锁排查时,我通常会在第 2 步和第 5 步之间反复循环——halt 一次,看看寄存器,再 continue,再 halt,直到抓到锁竞争的瞬间。

3.7 实战避坑:我曾经犯过的错

最后分享几个我踩过的坑,你遇到了能少走弯路:

  • 忘记 halt 直接设断点:CPU 还在跑,你设的断点可能被覆盖。先 monitor halt 再设断点,稳一点。
  • 单步时忽略 LR:有一次我单步追了半天,发现 PC 在一个循环里打转,其实 LR 早就指向了错误返回地址。后来养成习惯,每步都看一眼 LR。
  • 复位后不 halt 直接跑:复位后 CPU 会从入口开始执行,如果你不 halt,可能几毫秒就跑到死锁点之外了。复位后立刻 halt,再设断点,再 continue。
一句话总结:load 烧程序,halt 抓现场,break 设陷阱,step 追线索,info registers 看证据。这五个动作配合好,死锁无处可逃。