1. 调试基础与环境搭建

调试,说白了就是给嵌入式系统看病。没有趁手的工具,你连症状都摸不清。这一章我们先把地基打牢——GDB 基础命令、交叉编译链、OpenOCD/JLink 驱动,最后用 VSCode + Cortex-Debug 把它们串起来。

本章核心目标: 搭建一套可复用的 ARM Cortex-M 调试环境,能单步、断点、查看变量、分析堆栈。

1.1 GDB 基础命令回顾

GDB 是调试界的瑞士军刀。我见过不少工程师只会用 next 和 continue,遇到复杂 bug 就抓瞎。其实掌握十几个命令就够了。

命令 简写 作用
break b 设置断点(行号/函数/条件)
continue c 继续运行到下一个断点
next n 单步跳过(不进入函数)
step s 单步进入函数内部
print p 打印变量或表达式值
backtrace bt 查看函数调用栈
info registers i r 查看所有寄存器
display disp 每次停下时自动打印某个变量
我的习惯: 调试 RTOS 任务栈溢出时,先用 bt 看调用链,再用 info registers sp 看栈指针位置。十有八九能定位到问题。

举个例子,假设你有一个 uart_send() 函数卡死了:

(gdb) b uart_send
(gdb) c
(gdb) bt
#0  uart_send (data=0x55) at uart.c:42
#1  0x08000234 in main () at main.c:28
(gdb) p/x data
$1 = 0x55
(gdb) info registers r0
r0: 0x55

嗯,这里要注意:p/x 是以十六进制打印,看外设寄存器时特别有用。

1.2 交叉编译工具链配置

嵌入式调试和桌面调试最大的区别——你得用交叉编译链。你的 PC 是 x86,目标芯片是 ARM,编译器必须能生成 ARM 指令。

我个人推荐 ARM GNU Toolchain(gcc-arm-none-eabi)。为什么?开源、稳定、社区活跃。我在项目中踩过不少坑,最典型的就是版本不匹配——GDB 版本太老,不认识新芯片的调试特性。

组件 说明 版本建议
arm-none-eabi-gcc C/C++ 编译器 ≥ 10.3.1
arm-none-eabi-gdb 调试器 ≥ 10.2
arm-none-eabi-objcopy 生成 .hex/.bin 随工具链自带
Makefile / CMakeLists.txt 构建脚本 按项目需求
我曾经踩过的坑: 用 Ubuntu 自带的 apt 安装 gcc-arm-none-eabi,版本是 6.x,结果调试 STM32H7 时 GDB 无法识别 Cortex-M7 的 FPU 寄存器。后来手动从 ARM 官网下载 10.3 版本才解决。建议直接去 developer.arm.com 下载。

安装后验证一下:

$ arm-none-eabi-gcc --version
arm-none-eabi-gcc (GNU Arm Embedded Toolchain 10.3-2021.10) 10.3.1 20210824
$ arm-none-eabi-gdb --version
GNU gdb (GNU Arm Embedded Toolchain 10.3-2021.10) 10.2.90.20210621

看到版本号,说明工具链就绪了。

1.3 OpenOCD 与 JLink 驱动安装

调试器是 PC 和芯片之间的翻译官。OpenOCD 开源免费,JLink 商业但稳定。我两个都用,看项目需求切换。

OpenOCD

OpenOCD 支持 ST-Link、CMSIS-DAP、FTDI 等调试器。安装方式:

# Ubuntu / Debian
sudo apt install openocd

# macOS (Homebrew)
brew install openocd

# Windows
# 从 https://openocd.org 下载预编译包

启动命令示例(以 STM32F4 + ST-Link 为例):

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

你会看到类似输出:

Info : Listening on port 3333 for gdb connections

端口 3333 就是 GDB 要连接的调试通道。

JLink

JLink 驱动从 Segger 官网下载。安装后有两个关键工具:

  • JLinkGDBServer:启动 GDB 服务器
  • JLinkExe:命令行调试工具

启动 GDB 服务器:

JLinkGDBServer -device STM32F407VG -if SWD -speed 4000
我的经验: SWD 模式只需要两根线(SWDIO + SWCLK),比 JTAG 省引脚。大部分 Cortex-M 项目用 SWD 就够了。速度我一般设 4000 kHz,再高容易不稳定。

1.4 VSCode + Cortex-Debug 插件集成

命令行调试虽然强大,但效率不如 IDE。VSCode 配上 Cortex-Debug 插件,能让你在代码里直接设断点、看变量、查寄存器。

安装步骤:

  1. 安装 VSCode
  2. 安装 Cortex-Debug 插件(作者:Marcin Borowczak)
  3. 安装 Arm Assembly 插件(语法高亮)

关键配置在 .vscode/launch.json:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Cortex Debug",
      "type": "cortex-debug",
      "request": "launch",
      "servertype": "openocd",
      "cwd": "${workspaceRoot}",
      "executable": "./build/firmware.elf",
      "device": "STM32F407VG",
      "configFiles": [
        "interface/stlink.cfg",
        "target/stm32f4x.cfg"
      ],
      "svdFile": "./STM32F407.svd"
    }
  ]
}
svdFile 的作用: 外设寄存器描述文件。有了它,调试时可以直接看到寄存器名字和位域,不用翻手册。我每次建新项目第一件事就是找对应的 .svd 文件。

配置好之后,按 F5 启动调试。你会看到:

  • 代码左侧出现断点红点
  • 变量窗口实时更新
  • 寄存器窗口显示外设状态
  • 调用栈窗口显示函数调用链

下面这张图展示了整个调试环境的架构关系:

嵌入式调试环境架构图 嵌入式调试环境架构 开发机(x86/ARM64) VSCode + Cortex-Debug 插件 arm-none-eabi-gdb 远程调试协议(端口 3333) 调试服务器 OpenOCD 或 JLinkGDBServer OpenOCD ST-Link / CMSIS-DAP / FTDI JLinkGDBServer Segger J-Link / J-Trace 目标板(ARM Cortex-M) SWD / JTAG 接口 · 固件 firmware.elf 说明:GDB 通过远程协议连接调试服务器,服务器通过 SWD/JTAG 控制目标芯片 VSCode + Cortex-Debug 封装了 GDB 命令,提供图形化调试体验
注意: 如果 VSCode 连不上调试服务器,先检查端口。用 netstat -an | grep 3333 确认 OpenOCD 是否在监听。我遇到过好几次防火墙把端口挡了。

调试环境搭好之后,你可以试试:在 main 函数第一行设断点,按 F5 启动。如果能停在断点,说明整个链路通了。如果不行,按这个顺序排查:

  1. 硬件连接:SWD 线序对不对?GND 接了没?
  2. 驱动:OpenOCD 或 JLink 能识别到芯片吗?
  3. 配置:launch.json 里的 device 和芯片型号一致吗?
  4. 固件:.elf 文件编译时带 -g 选项了吗?
一个小技巧: 第一次调试时,先用 monitor reset halt 让芯片停在复位向量表。这样能确认调试器确实控制了芯片,而不是在跑飞的状态下乱停。