3. 调试工具链概览

调试器是嵌入式开发的「眼睛」。没有它,你只能靠 printf 和 LED 闪烁猜问题。OpenOCD 就是那个把调试器和 GDB 连接起来的桥梁。这一章,我带你把它装好、配通、跑起来。

3.1 OpenOCD 是什么?

OpenOCD(Open On-Chip Debugger)是一个开源软件。它负责两件事:

  • 向下:通过 JTAG/SWD 协议和调试器硬件(如 J-Link、ST-Link、CMSIS-DAP)通信
  • 向上:通过 GDB 远程协议和 GDB 通信

说白了,它就是个协议转换器。把 GDB 的调试命令翻译成 JTAG/SWD 时序信号,送到芯片内部。

核心流程:

GDB(调试器前端) ↔ TCP:3333 ↔ OpenOCD(协议转换) ↔ 调试器硬件 ↔ JTAG/SWD 总线 ↔ 目标芯片

3.2 安装 OpenOCD

安装方式取决于你的操作系统。我个人习惯用源码编译,因为能拿到最新版本和特定补丁。

Linux(Ubuntu/Debian)

# 安装依赖
sudo apt-get install build-essential libtool autoconf automake pkg-config
sudo apt-get install libhidapi-dev libusb-1.0-0-dev

# 下载源码(以 0.12.0 为例)
wget https://github.com/openocd-org/openocd/archive/refs/tags/v0.12.0.tar.gz
tar xzf v0.12.0.tar.gz
cd openocd-0.12.0

# 配置、编译、安装
./bootstrap
./configure --enable-stlink --enable-jlink --enable-cmsis-dap
make -j$(nproc)
sudo make install

小提示: 如果你只用某一种调试器,比如 ST-Link,可以只加 --enable-stlink,减少编译时间。我一般把常用的都打开,省得后面换调试器时重新编译。

Windows

Windows 下推荐用预编译包。去 OpenOCD 官方 Release 页面 下载 .exe 安装包。安装后把 bin 目录加到系统 PATH 里。

macOS

brew install openocd

安装完成后,验证一下:

openocd --version
# 输出类似:Open On-Chip Debugger 0.12.0

3.3 配置文件编写

OpenOCD 的配置文件是它的灵魂。没有配置文件,它不知道连什么芯片、用什么接口、怎么操作。配置文件通常分为三部分:

  • 接口配置:指定调试器硬件(J-Link、ST-Link 等)
  • 传输配置:指定协议(JTAG 或 SWD)
  • 目标配置:指定芯片型号(STM32F4、nRF52840 等)

3.3.1 接口配置

OpenOCD 自带了很多调试器的接口配置文件,放在 openocd/scripts/interface/ 目录下。你可以直接引用:

# 使用 ST-Link 调试器
source [find interface/stlink.cfg]

# 使用 J-Link 调试器
source [find interface/jlink.cfg]

# 使用 CMSIS-DAP 调试器
source [find interface/cmsis-dap.cfg]

如果你用的是非标准调试器,或者需要自定义引脚,可以自己写接口配置:

# 自定义 FTDI 接口配置
interface ftdi
ftdi_vid_pid 0x0403 0x6010
ftdi_layout_init 0x0018 0x001b
ftdi_layout_signal nTRST -data 0x0010
ftdi_layout_signal nSRST -data 0x0020

注意: 自定义接口配置时,一定要确认调试器的 VID/PID 和引脚映射。我曾经因为 VID/PID 写错,折腾了半天才发现是配置文件的问题。

3.3.2 传输配置

选择 JTAG 还是 SWD?

特性 JTAG SWD
引脚数 5(TCK、TMS、TDI、TDO、nTRST) 2(SWCLK、SWDIO)
速度 较慢(受限于 TAP 链长度) 较快(单线双向)
调试能力 支持多核、边界扫描 仅支持调试
适用场景 FPGA、多核 SoC ARM Cortex-M 系列

配置方式很简单:

# 使用 SWD 协议
transport select swd

# 使用 JTAG 协议
transport select jtag

我个人建议:ARM Cortex-M 系列用 SWD,省引脚、速度快。Cortex-A 系列或 FPGA 用 JTAG,因为需要边界扫描或多核调试。

3.3.3 目标配置

目标配置指定芯片型号和调试策略。OpenOCD 自带了很多芯片的配置文件,放在 openocd/scripts/target/ 目录下。

# STM32F407 配置
source [find target/stm32f4x.cfg]

# nRF52840 配置
source [find target/nrf52.cfg]

# 自定义芯片配置
set CHIPNAME mychip
set ENDIAN little
set CPUTAPID 0x4ba00477

jtag newtap $CHIPNAME cpu -irlen 4 -expected-id $CPUTAPID
target create $CHIPNAME.cpu cortex_m -endian $ENDIAN -chain-position $CHIPNAME.cpu

小提示: 如果你找不到芯片的配置文件,可以先用 openocd -f interface/xxx.cfg -c "adapter speed 1000" -c "init" -c "scan_chain" 扫描 JTAG 链,获取 TAP ID,然后自己写目标配置。

3.3.4 完整配置文件示例

下面是一个完整的配置文件,用于调试 STM32F407 开发板:

# 文件名:stm32f407.cfg

# 接口配置:使用 ST-Link
source [find interface/stlink.cfg]

# 传输协议:SWD
transport select swd

# 适配器速度:4 MHz
adapter speed 4000

# 目标配置:STM32F4x 系列
source [find target/stm32f4x.cfg]

# 复位配置
reset_config srst_only srst_nogate

# 初始化脚本
$_TARGETNAME configure -event gdb-attach {
    echo "GDB attached, halting target..."
    halt
}

$_TARGETNAME configure -event gdb-detach {
    echo "GDB detached, resuming target..."
    resume
}

3.4 启动 OpenOCD

配置文件写好后,启动 OpenOCD:

openocd -f stm32f407.cfg

如果一切正常,你会看到类似这样的输出:

Open On-Chip Debugger 0.12.0
Info : ST-LINK V2J29S7 (API v2) VID:PID 0483:3748
Info : Target voltage: 3.3V
Info : clock speed 4000 kHz
Info : stm32f4x.cpu: hardware has 6 breakpoints, 4 watchpoints
Info : Listening on port 3333 for gdb connections

看到 Listening on port 3333,说明 OpenOCD 已经准备好接受 GDB 连接了。

常见问题:

  • 找不到调试器: 检查 USB 驱动,Linux 下可能需要 udev 规则
  • 连接超时: 降低 adapter speed,比如从 4000 降到 1000
  • 芯片 ID 不匹配: 检查目标配置中的 CPUTAPID 是否正确

3.5 与 GDB 远程联调

OpenOCD 启动后,GDB 就可以通过 TCP 连接上来了。这是整个调试链路的最后一步。

3.5.1 启动 GDB 并连接

# 启动 GDB,加载目标文件
arm-none-eabi-gdb my_firmware.elf

# 在 GDB 中连接 OpenOCD
(gdb) target remote localhost:3333

# 如果连接成功,GDB 会显示:
# Remote debugging using localhost:3333
# 0x08000000 in Reset_Handler ()

3.5.2 常用调试命令

命令 说明 示例
load 下载程序到目标芯片 (gdb) load
continue 继续执行 (gdb) c
break 设置断点 (gdb) b main
step 单步进入 (gdb) s
next 单步跳过 (gdb) n
info registers 查看寄存器 (gdb) info reg
monitor reset 复位目标芯片 (gdb) mon reset
monitor halt 暂停目标芯片 (gdb) mon halt

3.5.3 完整调试流程

# 1. 启动 OpenOCD(新终端)
openocd -f stm32f407.cfg

# 2. 启动 GDB(另一个终端)
arm-none-eabi-gdb my_firmware.elf

# 3. 连接 OpenOCD
(gdb) target remote localhost:3333

# 4. 下载程序
(gdb) load
Loading section .text, size 0x1234 lma 0x08000000
Loading section .data, size 0x100 lma 0x08001234
Start address 0x08000000, load size 4916

# 5. 设置断点
(gdb) b main
Breakpoint 1 at 0x08000100: file main.c, line 42.

# 6. 运行
(gdb) c
Continuing.

# 7. 断点命中后,查看变量
(gdb) p my_var
$1 = 42

# 8. 单步调试
(gdb) n
(gdb) s

# 9. 退出
(gdb) quit

小提示: 我习惯在 GDB 里用 .gdbinit 文件自动执行一些初始化命令,比如自动连接、加载符号表。这样每次启动 GDB 就不用重复输入了。

3.6 知识体系图

下面这张图展示了 OpenOCD 在整个调试工具链中的位置和核心流程:

OpenOCD 调试链路架构图 OpenOCD 调试链路架构 GDB 调试器前端 TCP:3333 OpenOCD 协议转换 / 调试代理 配置文件:接口 + 传输 + 目标 USB 调试器硬件 ST-Link / J-Link / CMSIS-DAP JTAG / SWD 目标芯片 STM32 / nRF52 / 其他 ARM 配置三要素 ① 接口配置 ② 传输配置 ③ 目标配置 调试能力 断点 / 单步 / 读写 Flash 下载 寄存器 / 内存查看

3.7 避坑指南

调试过程中,我遇到过不少坑。挑几个典型的说说:

  • SWD 连接不稳定: 我曾经在 STM32F103 上遇到 SWD 连接时断时续。后来发现是 SWCLK 和 SWDIO 引脚上拉电阻没加。加上 10kΩ 上拉后,问题解决。
  • Flash 下载失败: 检查芯片的读保护(RDP)级别。如果 RDP 级别是 1,需要先解除保护才能下载。
  • GDB 连接被拒绝: 确认 OpenOCD 是否在运行,端口 3333 是否被占用。可以用 netstat -an | grep 3333 检查。
  • 断点不生效: 检查芯片是否支持硬件断点。Cortex-M0 只有 4 个硬件断点,用完了就不生效了。

重要提醒: 调试时,目标芯片的供电必须稳定。我曾经用 USB 供电调试大电流模块,结果电压波动导致调试器频繁断开。后来换成独立电源,问题消失。

3.8 小结

这一章我们走通了 OpenOCD 的完整链路:安装、配置、启动、联调。配置文件是核心,接口、传输、目标三要素缺一不可。GDB 远程联调是最终目的,掌握了这个流程,你就能用命令行完成大部分调试工作。