ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Ubuntu下用OpenOCD与GDB调试STM32的实战指南

Ubuntu下用OpenOCD与GDB调试STM32的实战指南 最近在Ubuntu下调一块STM32F103折腾OpenOCD加GDB这套调试环境前后花了两天时间中间遇到不少坑。用习惯了IDE一键调试的人冷不丁转到命令行调试确实会有点懵但当你真正搞清楚OpenOCD和GDB各自在干什么之后会发现这套组合其实比IDE更通透排查问题也更直接。这篇文章把我从安装、源码编译到联调启动的整个过程记录下来适合刚接触嵌入式调试、或者想在Ubuntu下搭一套不依赖IDE的调试环境的朋友参考。先说结论OpenOCD负责连接调试器并访问芯片GDB负责接收你的调试指令、处理断点和查看变量两者通过GDB远程调试协议沟通。搞清楚这个分工后面遇到任何报错都能顺着链路去排查。1. 先搞懂OpenOCD和GDB谁干什么后面才不会踩坑很多人在命令行调试时一脸懵是因为IDE把底层细节都藏起来了。你在Keil里点一下Debug背后其实是IDE调用了一堆工具去连接芯片、下载程序、切换状态。OpenOCD加GDB这套方案只是把IDE帮你完成的事情拆开给你看但这两个工具的分工差异非常大。1.1 OpenOCD的角色连接调试器的“翻译官”OpenOCD的全称是Open On-Chip Debugger一个开源项目。它做的事情非常聚焦就是和位于PC与MCU之间的调试适配器打交道常见的有ST-Link、J-Link、CMSIS-DAP、FT2232等OpenOCD都有对应的驱动支持。它把从GDB那边过来的命令转换成JTAG或者SWD协议时序再经过调试适配器最终作用到芯片的调试接口模块上。反过来芯片返回的状态、寄存器的值也由OpenOCD收集整理后回传给GDB。我习惯把OpenOCD理解成一个翻译官。GDB就像说中文的指挥官芯片是只会执行特定指令的外国人。指挥官问一个问题翻译官翻译给对方再把对方的回答一字不差带回来。中间如果翻译官出了问题比如协议不对、接线不良、配置错误那指挥官再怎么下命令也没用。1.2 GDB的角色真正的人机交互窗口GDB是GNU项目的调试器它本身并不关心你的芯片具体是什么型号也不关心调试适配器是哪个牌子。GDB关心的是程序运行状态当前执行到哪一行、某个变量现在是什么值、内存里某个地址存放了什么。它通过TCP端口连接到OpenOCD提供的GDB Server服务然后把用户输入的命令发送过去比如break、next、printOpenOCD再去操作硬件。这里有一个关键点嵌入式调试使用的GDB和普通Ubuntu里自带的不一样。普通gdb是给x86本地程序用的而ARM交叉编译环境用的是arm-none-eabi-gdb这条命令包含在ARM的GNU工具链里能解析ARM架构的ELF文件也认识ARM的指令集。如果你想用系统的gdb去load一个STM32的elf文件十有八九会直接报错。1.3 这套组合的核心优势在哪为什么要绕开IDE非要在命令行里组合这两个工具我实际体验下来有几个非常现实的原因第一不受IDE绑定。Keil和IAR是商业软件License费用不低而且只能在Windows上跑。STM32CubeIDE虽然免费但每次启动都特别慢内存占用也高。OpenOCD加GDB完全开源Linux、Windows、macOS全平台都能跑在服务器上没显示器也能调试。第二可脚本化、可自动化。命令行工具的最大好处是能把操作写成脚本。比如我经常需要给一块板子批量烧录固件用IDE一个个点按钮效率太低而用OpenOCD的flash write_image命令配合脚本流水线一下就建立起来了。GDB也有批处理模式可以在不交互的情况下执行一串调试命令这在CI环境里特别有用。第三调试器选择灵活。用IDE调试一般只能用IDE默认支持的调试器。OpenOCD不一样只要配置文件里声明了adapter它就能让ST-Link、J-Link、CMSIS-DAP等不同调试器无缝切换。我在公司用ST-Link回家用DAP-Link配置文件改一行就行工作流程完全不变。2. Ubuntu下安装与编译OpenOCD的完整过程Ubuntu下安装OpenOCD有两种方式一种是直接用apt安装另一种是从源码编译。很多人图省事只执行sudo apt install openocd但实际调试中会遇到调试器支持不全、芯片支持不够新等一堆问题所以更建议自己编译一次彻底搞清楚整个过程。2.1 先准备好基础编译依赖不管是apt安装还是编译安装第一步都是把系统更新一下并安装一些基础工具。我这里用的是Ubuntu 22.04 LTS其他版本步骤基本通用。sudo apt update sudo apt upgrade -y编译OpenOCD需要一系列依赖库最主要的是libusb因为调试器基本都是通过USB接口和PC通信的。另外还需要autoconf、automake、libtool这些用于生成配置脚本的工具以及texinfo用来生成文档。sudo apt install -y build-essential git autoconf automake libtool pkg-config libusb-1.0-0-dev texinfo其中libusb-1.0-0-dev特别关键没有它后面配置OpenOCD时会报找不到libusb的错误。build-essential提供了gcc和make这些编译工具如果之前没装过第一次编译OpenOCD的时候很容易漏掉这一步。2.2 两种安装方式的取舍我先说结论建议直接源码编译。使用apt安装OpenOCD好处是安装速度快命令一条搞定而且会自动处理好系统依赖。但坏处也很明显就是OpenOCD的版本通常很旧。Ubuntu 22.04的软件源里OpenOCD版本还停留在0.11.0而目前github上已经更新到0.12.0甚至更新的开发版很多新出的MCU支持补丁都打在主分支上旧版本根本跑不起来对应的配置。相比之下源码编译的好处有三个版本最新、编译参数可控、方便后续做二次开发。比如说我想让OpenOCD支持额外的调试器或者额外的芯片可以在configure阶段通过--enable参数显式开启对应的驱动模块。这一步是apt安装做不到的灵活度。对比项apt安装源码编译安装速度快一条命令慢需要编译几分钟OpenOCD版本较旧Ubuntu源里通常滞后可以拉最新主分支支持模块编译好的固定版本configure时自由裁剪适合场景快速搭环境、验证基本功能长期开发、新芯片调试、二次开发2.3 源码编译OpenOCD的具体步骤从GitHub拉取OpenOCD的源码然后执行bootstrap脚本生成configure文件。git clone https://github.com/openocd-org/openocd.git cd openocd ./bootstrapbootstrap过程会调用autoconf、automake、libtool这些工具生成configure脚本。这一步如果报缺工具回去检查一下2.1里的依赖是不是装全了。接下来是配置编译参数。这里要根据你手上的调试器来调整--enable选项。比如用ST-Link就开stlink用J-Link就开jlink用CMSIS-DAP就开cmsis-dap。不建议全开除非你要交叉测试不同调试器否则多余模块会增加编译时间还会引入一些不稳定的驱动代码。./configure --enable-stlink --enable-jlink --enable-cmsis-dap执行完configure之后用make开始编译。这里我习惯用-j参数指定多核并行编译比如四核的机器用make -j4八核的机器用make -j8能明显加快编译速度。make -j4 sudo make install编译过程如果不出意外几分钟就能结束。安装完成后验证一下版本号确认安装成功。openocd -v看到输出里有Open On-Chip Debugger 0.12.0之类的字样说明编译安装已经完成。如果提示找不到openocd命令多半是/usr/local/bin没进PATH把这一行加到~/.bashrc里再source一下就能解决。2.4 安装GDB交叉调试工具OpenOCD编译安装好了接下来还要准备GDB。前面说过嵌入式ARM开发要用arm-none-eabi-gdb。安装方式有两种一种是直接用apt安装方便快捷。sudo apt install -y gdb-arm-none-eabi另一种是从ARM官网下载GNU Arm Embedded Toolchain。官网提供的工具链是自带gdb的而且版本比较新如果你是做新架构的芯片调试比如Cortex-M33或者Cortex-A系列建议直接下载官方工具链。我用apt装完后执行arm-none-eabi-gdb --version输出显示的是GNU gdb (GNU Tools for ARM Embedded Processors)之类的版本信息说明GDB已经就绪。3. 启动OpenOCD的GDB Server并确认连接OpenOCD安装好之后真正开始上手时第一个门槛是理解配置文件。很多人在这一步卡住明明软件都装好了执行openocd命令却各种报错其实问题大多出在配置文件的选取不对。3.1 配置文件到底在写什么OpenOCD的配置文件通常包含了三大类信息。第一类是接口配置也就是你用的是哪个调试器它的驱动方式是什么。第二类是目标配置也就是你要调试的是哪个芯片它的内核是什么、Flash大小多少、有没有特殊的调试特性。第三类是板级配置也就是把接口和目标组合起来再加上一些针对特定开发板的初始化命令。OpenOCD安装完成后自带的脚本目录在/usr/local/share/openocd/scripts下里面按interface、target、board等子目录归档了各种配置文件。你可以先用ls命令去翻一翻这个目录找到对应自己开发板的配置。ls /usr/local/share/openocd/scripts/interface/ ls /usr/local/share/openocd/scripts/target/ ls /usr/local/share/openocd/scripts/board/3.2 启动GDB Server的几条常用命令最基础的启动方式是指定interface和target两类配置文件。以最常见的STM32F103加ST-Link组合为例命令是这样openocd -f interface/stlink.cfg -f target/stm32f1x.cfginterface/stlink.cfg告诉OpenOCD使用ST-Link调试器target/stm32f1x.cfg告诉OpenOCD目标芯片是STM32F1系列。OpenOCD会先初始化ST-Link再探测SWD总线上的芯片最后启动一个GDB Server默认监听在3333端口。如果一切正常终端里会看到类似下面的输出Info : Listening on port 3333 for gdb connections Info : target device found. Info : Listening on port 3333 for gdb connections看到Listening on port 3333恭喜你OpenOCD已经把调试通道建好了下一步就轮到GDB上场。因为板级配置文件更方便你也可以直接指定board文件比如openocd -f board/stm32f103c8t6.cfgboard配置文件内部一般会通过source命令引用interface和target配置省得自己敲两条-f。不过对常用开发板来说能找到对应的board文件更好如果找不到就用interface加target组合的方式反而更明确。3.3 排查OpenOCD启动失败的步骤启动OpenOCD时最常见的失败原因有三个。第一个是找不到调试器也就是OpenOCD报找不到ST-Link设备。这个时候用lsusb看一眼系统的USB设备列表正常情况下能看到STMicroelectronics ST-LINK设备如果没有检查一下USB线是否连接正常、有没有插到转接Hub上某些Hub供电不足会导致调试器识别不到。第二个是权限问题OpenOCD报libusb_open() failed。这通常是当前用户没有访问USB设备的权限。最简单的处理方式是给调试器添加udev规则或者直接用sudo运行openocd。我自己的做法是把用户加入plugdev组再配置一个udev规则这样以后就不用每次sudo了。第三个是配置文件和芯片不匹配。OpenOCD启动时会尝试读取芯片的IDCODE如果我做了简单的启动尝试就是执行成功但加载目标时失败很多情况下和配置文件中芯片型号的IDCODE对不上有关。可以尝试用-d参数启动OpenOCD把日志级别调到debug查看它实际识别到的IDCODE是多少然后去target配置里核对。openocd -d -f interface/stlink.cfg -f target/stm32f1x.cfg4. GDB连接与调试实战OpenOCD的GDB Server启动之后调试的主战场就转移到了GDB这一侧。很多人把OpenOCD启动成功当成调试成功的标志其实真正的调试才刚刚开始。4.1 连接GDB到OpenOCD服务器首先需要准备一个带调试信息的最终固件。平时用IDE编译生成的hex和bin文件是不包含调试符号信息的GDB需要的是elf文件或者axf文件里面既包含机器码又包含源码路径、变量名、行号这些调试信息。我用STM32CubeMX生成工程然后用arm-none-eabi-gcc编译编译选项里要加-g不加的话GDB里看不到源码和变量名。准备工作完成后用下面的命令启动GDBarm-none-eabi-gdb build/firmware.elf启动后会进入GDB的交互界面输入以下命令连接OpenOCDtarget extended-remote :3333这条命令告诉GDB调试目标不是本机进程而是一个远程目标地址是localhost的3333端口。实际执行后GDB会通过OpenOCD与芯片通信如果芯片状态正常GDB不会报错但此时芯片可能还在运行或者处于未知状态。为了获得一个干净的初始状态一般会执行两条monitor命令monitor reset haltmonitor前缀的意思是这不是GDB自身的命令而是把命令直接透传给OpenOCD去执行。reset halt让芯片复位并且立即暂停相当于把芯片抬到起跑线上待命。然后把固件下载到芯片的Flash里loadload命令会把当前打开的elf文件写入芯片Flash并自动处理地址映射。写入完成后就可以开始正式调试了。我把这一串初始化命令整理成速查表方便大家对照命令作用target extended-remote :3333连接OpenOCD的GDB Servermonitor reset halt复位芯片并暂停monitor reset init复位并初始化芯片时钟等外设load下载固件到Flashmonitor flash erase_address 0x08000000 0x1000擦除指定Flash区域disconnect断开GDB连接4.2 常用调试命令的全过程讲解芯片暂停之后最常用的操作是下断点然后全速运行。断点这个词来自CPU的硬件调试单元在STM32这类Cortex-M芯片上Flash断点一般是靠硬件断点实现的数量有限F103的DWT模块最多支持4个硬件断点。所以断点别下太多下多了会出现资源不够的提示。break main continuebreak main是在main函数入口下断点continue是继续执行碰到断点后芯片会暂停GDB会显示当前停在哪个文件哪一行。整个过程和IDE里按F5、F6非常像只是换成了命令。单步执行也有几个命令next执行下一行遇到函数调用不会进入函数内部step执行下一行遇到函数调用会进到函数内部finish会一直执行到当前函数返回。平时调试时next用得多step用于深入函数内部排查。查看变量的值用print命令。比如有个变量叫count直接print count就能看到它的值。如果想实时观察变量的变化可以用display count之后每次暂停都会自动显示。查看寄存器状态用info registers这条命令会打印所有内核寄存器的当前值。看特定外设寄存器地址里的内容可以用x命令比如想查看0x40010800这个地址开头的四个32位数据就是x/4wx 0x40010800w表示按32位显示x表示十六进制。4.3 用GDB脚本化调试来提高效率GDB支持自动加载脚本把每次调试都要重复敲的初始化命令写进去可以省掉很多机械操作。在项目根目录创建一个.gdbinit文件内容类似下面这样target extended-remote :3333 monitor reset halt load break main continue然后在启动GDB的时候指定脚本位置arm-none-eabi-gdb -x .gdbinit build/firmware.elf启动后GDB会自动执行脚本里的命令直接停在main断点上。这个方式对需要频繁刷固件、验证启动过程的场景特别有用。我后来甚至还写过一个自动跑一轮冒烟测试的GDB脚本让板子上电后自动加载固件、设断点、跑测试用例全程不用人盯。5. 常见问题与排查技巧实录命令行调试不像IDE那么友好报错了就一个干巴巴的错误字符串但好在错误信息一般都比较明确顺着链路去查大多数问题都能自己定位。5.1 提示cant perform jtag flash, because openocd server is not running!这个报错经常出现在用脚本或者某些IDE调用OpenOCD烧录的场合。字面意思是无法执行JTAG烧录因为OpenOCD服务器没有运行。我用DAP-Link烧录时踩过一次这个坑。那次迷迷糊糊没注意直接在烧录脚本里写了openocd相关的命令但OpenOCD服务器进程之前没启动或者烧录前就退出了。排查的步骤其实很简单先确认OpenOCD进程是否还活着用ps命令看一眼。ps -ef | grep openocd如果进程不存在就重新启动OpenOCD。如果进程在再看它输出日志里有没有报错有时候OpenOCD虽然启动着但和目标芯片的连接已经断了比如SWD线松了这种状态下也没法执行烧录需要重新插拔调试器或者重启OpenOCD。还有一种情况是端口被占用。OpenOCD默认监听3333端口如果之前已经启动了一个OpenOCD实例第二个实例会报端口被占用但其实第一个实例可能和目标板状态不对。这种情况下直接把所有OpenOCD进程杀掉再重来往往是最快的。5.2 GDB连接报错Remote communication error执行target extended-remote :3333时如果报Remote communication error通常意味着OpenOCD服务没有启动或者端口写错了。排查方法跟上面类似先确认OpenOCD进程是否存在再检查它监听的端口。用netstat命令看3333端口是否在监听netstat -tlnp | grep 3333如果OpenOCD运行正常但GDB还是连接不上可以用telnet做一次端口探测telnet localhost 3333能连上说明端口OK连不上就去找OpenOCD的问题。如果OpenOCD输出一堆Error相关的日志尤其提到target not found或者SWD Communication Failure那就是芯片没接好或者调试器驱动不对而不是GDB的问题。5.3 OpenOCD启动时报libusb_open() failed这个错误本质上是USB设备访问权限不够。Ubuntu默认情况下普通用户无法直接用libusb控制USB设备需要设置udev规则。一种省事的做法是用sudo运行openocd但这样治标不治本。更推荐的方式是安装OpenOCD的udev规则。OpenOCD源码目录里自带了一个contrib/60-openocd.rules文件把它复制到系统的udev规则目录下sudo cp contrib/60-openocd.rules /etc/udev/rules.d/ sudo udevadm control --reload-rules sudo udevadm trigger然后重新插拔一下调试器再免密执行openocd基本就正常了。5.4 断点不生效或者程序跑飞了用GDB调试STM32时偶尔会遇到断点设置了但继续执行后完全没有作用程序直接跑飞。这个往往和芯片的调试端口初始化时序有关。Cortex-M芯片刚上电时如果调试器连接时机太晚或者芯片已经在运行某些Flash断点功能可能没法正确生效。最稳妥的做法是上电前就让调试器进入连接状态使用monitor reset halt把芯片拉停再下断点。如果程序在跑飞状态下载入断点可以先reset halt让MCU停下来再重新设置断点。另外也要注意如果固件代码运行在RAM里Flash断点没法用需要在目标配置里确认芯片的类型和内存映射是否正确。5.5 Ubuntu系统升级后OpenOCD突然不好用了有些朋友可能会遇到系统更新之后OpenOCD连接调试器失败、或者lsusb识别不到设备了。这种情况多数和libusb版本变动有关。如果是源码编译的OpenOCD系统升级后动态库版本可能发生变化建议重新编译一次OpenOCD确保和系统当前的libusb版本匹配。如果不想重新编译也可以检查一下当前OpenOCD链接的libusb路径用ldd openocd命令查看。如果发现链接到旧版本的libusb而系统里已经装了新版本那重新编译一次是最省心的。6. 说说实操中总结的几点经验整套环境跑通之后回头再看其实OpenOCD加GDB的调试方式并不复杂真正难的是对底层机制的把握。我自己的体会是用命令行调试的过程就是在逼你去理解芯片的调试接口、Flash编程时序、内存映射这些平时被IDE藏起来的概念。给刚开始接触的朋友几个建议不要急着一上来就去折腾复杂的脚本和自动化的东西先把OpenOCD启动时那一行行日志读懂把GDB的常用命令练熟能在命令行中断点、单步、查看变量之后再考虑进一步提高效率。我现在写调试脚本已经比较顺手了但每一行命令背后是什么原理仍然会反复去对照OpenOCD的文档和芯片的数据手册。另外还要提醒一句调试器连接盒的线序真的很容易出问题尤其是SWD模式下SWDIO、SWCLK、GND这三根线必须接对否则OpenOCD会反复报信号错误。遇到这类情况先别怀疑软件先用万用表确认接线通断很多时候问题反而出在最小的地方。
返回列表