OpenOCD

技术栈
工具链
openocd调试jtagswdgdb烧录

概览

OpenOCD 技术栈概览

OpenOCD(Open On-Chip Debugger)是开源片上调试与烧录工具,由 Dominic Rath 于 2005 年发起。它作为 GDB 与硬件调试器之间的桥梁,支持 JTAG/SWD 协议,覆盖 ARM Cortex-M/A/R、RISC-V、MIPS 等数百种芯片。

解决什么问题

  • 调试器碎片化:统一 GDB Server 接口,同一个 OpenOCD 驱动 J-Link、ST-Link、CMSIS-DAP 等多种调试探针
  • 开源替代:无需昂贵的商业调试器许可证,完全免费
  • 自动化烧录:支持命令行批量烧录,适合产线固件下载
  • 边界扫描测试:JTAG BSDL 边界扫描,硬件调试利器

关键特性

  • GDB Remote Serial Protocol:标准 GDB Server,支持断点、单步、内存/寄存器读写
  • 多探针适配:ST-Link/v2/v3、J-Link、FTDI MPSSE、CMSIS-DAP、ULINK 等
  • Flash 编程:支持 SPI NOR/NAND Flash、内部 Flash 的擦写验证
  • TCL 脚本化:配置文件和自动化流程用 TCL 编写
  • RTOS 感知:支持 FreeRTOS、ThreadX 等线程列表查看

安装

OpenOCD 安装指南

1. 环境准备

项目 要求
操作系统 Windows 10+ / macOS 11+ / Linux (x86_64, ARM64)
依赖 libusb-1.0、libftdi1(Linux/macOS 编译时)
调试器硬件 ST-Link / J-Link / CMSIS-DAP / FTDI 适配器等
配套工具 arm-none-eabi-gdb(调试用)、telnet(OpenOCD 控制台)

2. 安装步骤

Linux(包管理器 — 最简单)

# Ubuntu / Debian
sudo apt update
sudo apt install openocd

# Fedora
sudo dnf install openocd

# Arch
sudo pacman -S openocd

# 验证
openocd --version

macOS

# Homebrew
brew install openocd

# 查看支持的目标
openocd --search  # 输出 scripts 路径

Windows

  1. 下载预编译二进制:https://github.com/openocd-org/openocd/releases
  2. 解压到 C:\openocd\
  3. 添加 C:\openocd\bin 到系统 PATH
  4. 安装驱动(ST-Link 用 Zadig 替换为 WinUSB)

从源码编译(需定制时)

git clone https://github.com/openocd-org/openocd.git
cd openocd
./bootstrap
./configure --enable-stlink --enable-jlink --enable-cmsis-dap
make -j$(nproc)
sudo make install

udev 规则(Linux 必需)

# 复制默认 udev 规则
sudo cp contrib/60-openocd.rules /etc/udev/rules.d/
sudo udevadm control --reload-rules
sudo udevadm trigger

3. 常见安装问题

问题 解决方案
Error: libusb_open() failed Linux 需 udev 规则 + 用户加入 plugdev;Windows 需 Zadig 替换驱动
找不到目标配置文件 指定 scripts 路径:openocd -s /usr/share/openocd/scripts -f board/st_nucleo_f4.cfg
ST-Link 固件太旧 用 STM32CubeProgrammer 或 stlink 工具升级固件
多调试器冲突 hla_serialadapter serial 指定序列号连接
权限 (macOS) SIP 可能阻止驱动,尝试 sudo 运行或关闭 SIP

示例

OpenOCD 进阶:Flash 编程与批量烧录脚本

目标

掌握 OpenOCD 的 Flash 分区操作、选项字节编程、多设备并行烧录和自动化脚本。

一、Flash 分区擦写

读取 Flash 内容

# read_flash.tcl
set FLASH_START 0x08000000
set FLASH_SIZE  0x00100000   ; # 1MB (STM32F407)

halt
flash read_bank 0 flash_dump.bin 0 $FLASH_SIZE
puts "Flash 已导出到 flash_dump.bin ($FLASH_SIZE bytes)"
resume
exit
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg -f read_flash.tcl

解锁/锁定 Flash

# unlock.tcl
halt
stm32f4x unlock 0        ; # 解锁 bank 0
puts "Flash 解锁成功"
resume
exit

选项字节编程(读写保护)

# 设置读保护 Level 1
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg \
    -c "init" \
    -c "halt" \
    -c "stm32f4x lock 0" \
    -c "reset run" \
    -c "exit"

# 查看选项字节
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg \
    -c "init" \
    -c "halt" \
    -c "stm32f4x options_read 0" \
    -c "exit"

二、批量烧录自动化脚本

#!/bin/bash
# batch_flash.sh — 批量烧录多板

INTERFACE="interface/stlink.cfg"
TARGET="target/stm32f4x.cfg"
FIRMWARE="build/firmware.hex"
SERIAL_FILE="devices.txt"        # 每行一个 ST-Link 序列号
LOG_DIR="logs/$(date +%Y%m%d_%H%M%S)"

mkdir -p "$LOG_DIR"

flash_device() {
    local serial=$1
    local log="$LOG_DIR/${serial}.log"

    echo "────────────────────────────"
    echo "烧录设备: $serial"
    echo "────────────────────────────"

    openocd \
        -f "$INTERFACE" \
        -c "hla_serial $serial" \
        -f "$TARGET" \
        -c "init" \
        -c "halt" \
        -c "flash write_image erase $FIRMWARE" \
        -c "verify_image $FIRMWARE" \
        -c "reset run" \
        -c "exit" \
        &;> "$log"

    if [ $? -eq 0 ]; then
        echo "✅ $serial — 成功"
    else
        echo "❌ $serial — 失败!查看日志: $log"
        return 1
    fi
}

# 遍历设备
TOTAL=0
SUCCESS=0

while IFS= read -r serial || [ -n "$serial" ]; do
    [ -z "$serial" ] &;& continue
    TOTAL=$((TOTAL + 1))
    if flash_device "$serial"; then
        SUCCESS=$((SUCCESS + 1))
    fi
done <; "$SERIAL_FILE"

echo ""
echo "===================================="
echo "烧录完成: $SUCCESS / $TOTAL"
echo "日志目录: $LOG_DIR"
echo "===================================="

三、结合 CMake 构建后自动烧录

# CMakeLists.txt 末尾添加烧录目标
add_custom_target(flash
    COMMAND openocd
        -f ${OPENOCD_SCRIPTS}/interface/stlink.cfg
        -f ${OPENOCD_SCRIPTS}/target/stm32f4x.cfg
        -c "program $<TARGET_FILE:${PROJECT_NAME}> verify reset exit"
    DEPENDS ${PROJECT_NAME}
    COMMENT "Flashing firmware..."
)

# 使用
# cmake --build . --target flash

四、多核调试(CM4 + CM0 异构系统)

# STM32H745 (CM7 + CM4 双核)
openocd -f interface/stlink.cfg -f target/stm32h7x_dual_bank.cfg

# 新开 GDB 终端
arm-none-eabi-gdb cm7_firmware.elf
(gdb) target extended-remote localhost:3333    # 连接 CM7

# 另一个 GDB 终端
arm-none-eabi-gdb cm4_firmware.elf
(gdb) target extended-remote localhost:3334    # 连接 CM4

五、实用 TCL 函数库

# utils.tcl — 可复用的 OpenOCD 辅助函数
proc wait_halt {timeout_ms} {
    set start [clock milliseconds]
    while {[expr {[clock milliseconds] - $start}] < $timeout_ms} {
        if {[capture "halted"] == ""} {
            sleep 10
        } else {
            return 1
        }
    }
    error "等待暂停超时"
}

proc dump_regs {} {
    set regs {r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 sp lr pc xPSR}
    foreach r $regs {
        puts [format "%-6s: 0x%08X" $r [reg $r]]
    }
}

proc flash_erase_all {} {
    flash erase_sector 0 0 last
    puts "Flash 已全部擦除"
}

在启动时加载:

openocd -f interface/stlink.cfg -f target/stm32f4x.cfg -f utils.tcl

关键点

  • verify_image 烧录后自动校验,确保数据完整性
  • hla_serial 用于多 ST-Link 并行烧录时区分设备
  • 选项字节操作需谨慎:错误的写保护设置可能导致芯片"变砖"
  • TCL 脚本支持条件、循环,可实现复杂自动化流程

OpenOCD:命令行烧录与 GDB 调试 STM32

目标

使用 OpenOCD + arm-none-eabi-gdb 通过 ST-Link 对 STM32F4 进行命令行烧录和源码级调试。

硬件

  • STM32 Nucleo-F407ZG 开发板(内置 ST-Link/v2-1)
  • 或任意 STM32 + ST-Link 连接

一、烧录固件

1. 启动 OpenOCD Server

openocd -f interface/stlink.cfg -f target/stm32f4x.cfg

成功输出:

Info : STLINK V2J37M26 (API v2) VID:PID 0483:374B
Info : Target voltage: 3.249107
Info : stm32f4x.cpu: hardware has 6 breakpoints, 4 watchpoints
Info : Listening on port 3333 for gdb connections
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections

2. 通过 Telnet 烧录

新开终端:

telnet localhost 4444

# OpenOCD 命令提示符下:
>; halt                    # 暂停 CPU
>; flash write_image erase /path/to/firmware.elf
>; reset run               # 复位并运行
>; exit

3. 一行命令烧录(无需 Telnet)

openocd -f interface/stlink.cfg \
        -f target/stm32f4x.cfg \
        -c "program firmware.elf verify reset exit"
参数 含义
program 烧录命令
verify 烧录后校验
reset 烧录后复位
exit 完成后退出 OpenOCD

二、GDB 源码调试

1. 启动 OpenOCD(后台)

openocd -f interface/stlink.cfg -f target/stm32f4x.cfg &;

2. 启动 GDB 连接

arm-none-eabi-gdb firmware.elf

# GDB 命令提示符下:
(gdb) target extended-remote localhost:3333
(gdb) monitor reset halt          # OpenOCD 命令:复位并暂停
(gdb) load                        # 下载固件到 Flash
(gdb) break main                  # 断点在 main 函数
(gdb) continue                    # 运行到断点
(gdb) step                        # 单步执行
(gdb) print variable_name         # 查看变量
(gdb) info registers              # 查看寄存器
(gdb) x/10xw 0x20000000           # 查看内存 (10 words)
(gdb) monitor reset run           # 复位运行
(gdb) quit

3. 一键调试脚本

#!/bin/bash
# debug.sh — 启动 OpenOCD + GDB
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg &;>/dev/null &
OOCD_PID=$!
sleep 1

arm-none-eabi-gdb -ex "target extended-remote localhost:3333" \
                  -ex "monitor reset halt" \
                  -ex "load" \
                  -ex "break main" \
                  -ex "continue" \
                  "$@"

kill $OOCD_PID 2>;/dev/null

常见 GDB 命令速查

命令 作用
monitor reset halt 复位 MCU 并暂停
monitor flash write_image erase file.bin 0x08000000 烧录 .bin
break function_name 函数断点
break *0x08001234 地址断点
watch variable 变量修改时中断
backtrace 调用栈
layout src 源码窗口
Ctrl+X A 退出 TUI 模式

关键点

  • localhost:3333 = GDB Server,:4444 = Telnet 控制台
  • ST-Link/v2 最大 SWD 频率约 4MHz,可用 -c "adapter speed 4000" 设置
  • 如果 load 失败,先 monitor reset init 再试

教程

OpenOCD 调试实战手册

本章目标

深入掌握 OpenOCD 的调试技巧:硬件断点、条件断点、RTOS 感知调试、Flash 操作。


1. OpenOCD 架构理解

GDB Client ──TCP:3333──> OpenOCD Server ──USB──> 调试探针 ──SWD/JTAG──> MCU
                            │
                    Telnet :4444 (控制台)

OpenOCD 的核心角色是 协议转换器:将 GDB Remote Serial Protocol 翻译为调试探针的底层命令。


2. 硬件断点 vs 软件断点

类型 实现 数量限制 适用
硬件断点 调试寄存器 (FPB) 4-8 个 Flash/ROM 中的代码
软件断点 替换指令为 BKPT 不限 RAM 中的代码
# 硬件断点(默认 Flash 中使用)
(gdb) hbreak main
(gdb) hbreak *0x08000420

# 软件断点
(gdb) break function_name

# 条件断点(硬件断点支持条件)
(gdb) break main.c:42 if count > 100

3. 内存与寄存器查看

# 查看寄存器
(gdb) info registers
(gdb) print/x $sp
(gdb) print/x $pc

# 内存 dump
(gdb) x/16xw 0x20000000      # 16 个 32-bit words(SRAM 起始)
(gdb) x/32xb 0x08000000      # 32 字节(Flash 起始 = 向量表)

# 内存写入
(gdb) set {int}0x20000010 = 0x12345678

# 对比 Flash 内容与固件
(gdb) monitor flash read_bank 0 /tmp/flash.bin 0 0x10000

4. RTOS 感知调试

# FreeRTOS 线程列表
(gdb) info threads

# 切换线程
(gdb) thread 3
(gdb) backtrace

# OpenOCD 需要加载 FreeRTOS 支持
openocd -f interface/stlink.cfg \
        -f target/stm32f4x.cfg \
        -c "rtos FreeRTOS"

5. 复杂调试场景

硬故障(HardFault)分析

# 发生 HardFault 后
(gdb) monitor reset halt
# 查看故障寄存器
(gdb) x/1xw 0xE000ED2C   # CFSR (Configurable Fault Status Register)
(gdb) x/1xw 0xE000ED28   # HFSR (HardFault Status Register)
(gdb) x/1xw 0xE000ED34   # MMFAR / BFAR

# 从栈帧恢复调用现场
(gdb) x/8xw $sp          # 查看压栈的寄存器
# R0, R1, R2, R3, R12, LR, PC, xPSR

外设寄存器实时监控

# Telnet 4444 控制台
>; mdw 0x40021000 16     # 读 RCC 寄存器
>; mdw 0x40020000 16     # 读 GPIOA 寄存器
>; mww 0x40020014 0xFFFF # 设置 GPIOA ODR

6. 与 IDE 集成

// VSCode launch.json (Cortex-Debug 插件)
{
    "type": "cortex-debug",
    "request": "launch",
    "servertype": "openocd",
    "cwd": "${workspaceRoot}",
    "executable": "./build/firmware.elf",
    "configFiles": [
        "interface/stlink.cfg",
        "target/stm32f4x.cfg"
    ],
    "svdFile": "./STM32F407.svd"  // 外设寄存器视图
}

思考题

  1. 为什么 Flash 中的代码只能用硬件断点?
  2. HardFault 发生后如何从栈帧定位出错的代码行?
  3. RTOS 感知调试依赖什么机制获取线程列表?