拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Cortex-M调试新境界:新版工具整合Debug与SWO Trace实战

最近有朋友问我为什么要把我常用的调试工具从老版本换到新版本。我说别的倒还好最值得提的一点是这次工具版本更新终于给 Arm Cortex-M 补上了完整的 Trace 和 Debug 支持。以前这套工具只能做擦除、烧录、校验这些基本操作想断点调试得另开 IDE想看实时日志得外接串口或者逻辑分析仪。现在新版本把 GDB Server、断点管理、变量监视、寄存器窗口、SWO Trace 全部整合进同一套命令行工作流一个工具就能从头跟到尾。对于做嵌入式开发、日常维护老项目、或者在 CI 环境里做自动化测试的人来说这个变化非常实用。这篇文章我不打算做成一份官方发布说明而是按我自己从一个纯烧录工具迁移到调试工具的使用经历来写。包含新版工具怎么装、怎么配置 STM32CubeMX 开启 Trace、怎么用 Keil 或命令行查看变量、碰到那些奇奇怪怪报错时怎么排查。不管你是刚入门的 Cortex-M 玩家还是已经写了几年固件的老手只要按着里面的思路走一遍应该都能把这套调试能力用起来。1. 这次工具版本更新核心变化是什么1.1 Debug 和 Trace 不是一件事这点要先分清很多人在聊 Cortex-M 开发时会把 Debug 和 Trace 混在一起但它们在工具链里的地位完全不同。Debug 指的是通过调试接口连接内核让 CPU 暂停、单步、设置断点、读写内存和寄存器。比如你在 Keil 里按 F5 开始调试按 F10 单步这就是 Debug。它的特点是会打断程序执行程序停在断点上一切实时性都不存在了。Trace 则是指 CPU 在全速运行的情况下通过内核里的跟踪单元把指令流、事件、数据访问记录下来输出到调试器一侧分析。典型代表是 ARM 的 ITMInstrumentation Trace Macrocell和 ETMEmbedded Trace Macrocell。你写一个 printf通过 ITM/SWO 输出到上位机终端程序本身完全不停顿这就是 Trace 的典型用法。这次工具版本更新本质上就是把 Debug 和 Trace 两条链路都补齐了。以前工具只露出 Debug 的一小部分比如连接目标、读取 ID Code、写 Flash现在它具备完整的 GDB 远程调试能力可以暂停、恢复、设断点、看变量同时又能接收 SWO/ITM 输出的跟踪数据。对用户来说最直观的体验是“之前用命令行做不了的事现在能做了”。1.2 为什么补 Trace 支持比补几个断点更重要只做 Debug 支持的工具说实话门槛不算高因为大部分工作可以由 GDB Server 和 OpenOCD 这类开源方案完成。真正难的是 Trace因为 Trace 需要处理 CoreSight 组件的初始化、时钟分频配置、多路 ITM 端口、同步传输格式以及不同 Cortex-M 版本之间的差异。工具厂商做 Debug 支持时只需要把调试寄存器和断点寄存器操作对就行而 Trace 需要理解 MCU 的内部跟踪架构。从应用场景上也能看出 Trace 的不可替代性。调试中断触发这类问题时如果只靠断点你会发现中断触发得非常快等你停下来时现场已经被破坏了。而用 Trace 记录事件不打断程序运行可以看到中断发生前最后几条指令、寄存器变化、函数调用顺序问题更容易定位。又比如做电机控制或电源管理程序不允许随便暂停暂停了 PWM 输出可能直接烧电路。这种场景下让 CPU 停下来显然不可行只能用 Trace 去观测。所以这次更新最大的意义不是“多了一个功能菜单”而是把原本割裂的两个工作模式合并进同一个工具链。你用同一个界面既能设断点又能看到全速运行时的日志和事件流排查问题时不需要来回切换工具。2. 环境准备硬件、探针和工具安装2.1 确认你的调试探针支持 SWO 和 Trace不是所有调试器都支持 Trace。普通的 ST-Link V2 支持 SWO 输出但速度一般J-Link 的 SWO 支持做得比较好还能配合自家的 RTT 使用DAPLink 有些版本带 SWO 接口有些则没有需要看具体固件。如果你想启用 ETM 指令跟踪那需要更高端探针普通 ST-Link 基本不用考虑。建议在做环境准备时先做三件事看探针引脚定义确认 SWO/TDO 引脚是否引出。很多开发板把 SWO 通过跳线帽连接默认不上电导致 Trace 数据完全收不到。查探针驱动版本。老版本驱动对 CoreSight 初始化支持不全新工具虽然做了软件兼容但驱动太老还是会出现连接失败。确认目标板供电与地线稳定。Trace 是高频采样接地不良会导致采样时序不稳定出现丢包和乱码。2.2 安装新版本工具并验证驱动这里以命令行工具为例。下载新版本压缩包后先解压到工作目录然后打开终端执行./tool --version看到版本号后再执行连接目标板./tool connect --if swd --speed 4000如果连接失败优先检查权限。Linux 下常见问题是 USB 设备没有 udev 规则导致无法打开调试器。可以加一个规则或者临时用 sudo 运行。Windows 下最常见是驱动冲突拔掉其他调试器再插一次。还需要确认你的芯片支持 SWO 引脚输出。Cortex-M0/M0 部分型号没有 SWO即使是新版本工具也没办法凭空生成 Trace 功能。这点在选型时就要注意免得买了板子后才发现硬件上不支持。3. Debug 功能实操从连接目标板到查看变量3.1 启动一个调试会话的基本流程新版本工具启动调试会话的逻辑和 Keil 大致相同只是交互从图形界面变成命令行或 GDB 命令。先把编译生成的 ELF 文件准备好文件里要包含调试符号也就是不要 strip 掉。然后启动 GDB Server./tool gdbserver --port 3333 --elf build/firmware.elf再开一个终端用 GDB 连接arm-none-eabi-gdb build/firmware.elf (gdb) target remote :3333 (gdb) monitor reset halt (gdb) load (gdb) continue这里有个容易被新人忽略的点monitor reset halt和monitor reset不一样。前者会把 CPU 复位后停在 Reset_Handler 处方便你从头开始调试后者则直接开始运行如果固件里设置有问题可能启动就跑了不利于定位启动阶段 bug。3.2 变量查看、断点和寄存器窗口的命令映射如果你用过 Keil 的 Debug 界面看变量是点“Watch”窗口。命令行下对应的操作是print、info locals、watch。例如(gdb) break main.c:42 (gdb) continue (gdb) print my_var (gdb) info locals (gdb) info registers r0 r1 r2调试时最常用的几个命令我列在下面方便对照操作Keil 界面命令行/GDB设置断点行号前点击break main.c:42或break function_name查看变量鼠标悬停或 Watch 窗口print var_name单步F10next单步进入F11step查看调用栈Call Stack 窗口backtrace查看寄存器Register 窗口info registers继续运行F5continue修改变量输入值到 Watchset var_name 123这个对应关系对从 IDE 迁移过来的人特别有用。你不需要把所有 GDB 命令都背下来先用这张表找到你熟悉的操作入口剩下命令用到时再查。3.3 Keil 用户迁移过来要留意的几个差异第一优化等级会导致变量不可见。Keil 默认工程有时会在 Debug 时自动把优化等级降下来但命令行工具不会帮你改编译选项。如果你用-O2编译变量可能被优化掉导致你在 GDB 里print报 “No symbol” 错误。建议 Debug 版本统一用-O0 -g3编译。第二不要直接加载 bin 文件调试要用 ELF。因为 bin 文件没有符号信息GDB 只能看到地址和汇编没办法显示变量名和函数名。第三如果以前在 Keil 的 debugger 选项卡里做了 FLASH 下载算法配置命令行工具不一定兼容。你需要依赖load命令自动完成 Flash 写操作或者单独用烧录命令先烧一遍再启动调试。两个流程不要混着来否则容易出现“校验不一致”的警告。4. Trace 功能实操SWO/ITM 完整配置指南4.1 理解 ITM、SWO、TRACEIO 之间的关系Trace 支持是这次版本更新的重头戏所以我单独用一大节来说。先理清楚几个名词ITMCortex-M 内核里的跟踪单元软件可以通过写 ITM 寄存器输出数据。SWOSingle Wire Output是 ITM 数据输出到调试器引脚的物理通道。很多板子上就是 SWD 接口旁边的一根独立引脚也叫 TDO 或 SWO。TRACEIO更宽的多引脚跟踪接口通常用于更高端的指令跟踪。平时我们用 SWO Trace一般就是把 ITM 的 stimulus port 0 映射到 SWO 引脚上。这个映射不需要用户手动设置Cortex-M 里的 CoreSight 组件会自动把 ITM 数据包通过 SWO 发出来。你真正需要配置的是 SWO 引脚的引脚复用、时钟分频以及调试器的波特率。这就像把 ITM 当做一个 UARTSWO 就是它的 TX 引脚。软件往 ITM 的端口写一个字节调试器在另一端用相同的波特率去接收就能看到数据。只不过这个“UART”不是标准串口引脚是专用的逻辑电平也要靠 CoreSight 内部的框去解析。4.2 在 STM32CubeMX 里把 Trace 功能配置出来很多人问 STM32CubeMX 如何配置 Trace 功能这里给一个通用步骤以 STM32F4 为例打开 STM32CubeMX选择你的芯片型号初始化工程。在System Core - SYS页面里将 Debug 选项从Disabled改为Serial Wire。这一步很关键默认是 Disabled调试引脚会被当作普通 GPIO导致 SWD 连接不稳定。如果需要使用 SWO 引脚在System Core - SYS里确认Trace Asynchronous选项是打开的不同版本写法可能略有不同但核心都是使能异步跟踪。检查 GPIO 配置确保 PA13/PA14/PA15/PB3 这些 SWD/JTAG 相关引脚没有被别的外设占用。如果冲突IDE 会提示但有时只是警告实际还是会拉低调试口信号。生成代码然后在main.c里初始化 Trace。有些 HAL 库版本需要调用ITM_SendChar来输出字符有些则要手动配置。CubeMX 的作用就是生成初始化代码省去你手写寄存器操作。但注意CubeMX 里 Serial Wire 只是解决了调试口连接问题Trace 数据能不能收还取决于 SWO 引脚有没有接到调试器、以及主频和分频配置是否一致。4.3 把 printf 重定向到 SWO 输出要让 Trace 直接输出 printf 内容最通用做法是重定向fputc。在 GCC 工具链下可以这样实现int _write(int file, char *ptr, int len) { for (int i 0; i len; i) { ITM_SendChar(*ptr); } return len; }如果用 Keil/ARMCC则重定向fputcint fputc(int ch, FILE *f) { ITM_SendChar(ch); return ch; }ITM_SendChar 的实现是轮询 ITM_STIM0 寄存器如果当前端口还没有准备好就一直等待。这会让 printf 有一定开销但对大部分调试场景来说完全能接受。如果追求更高吞吐可以改成直接写寄存器但不等待缺点是可能丢数据。做完重定向后程序里调用printf(hello trace\r\n)数据就从 ITM 端口 0 发出去了。这个过程中程序不会被暂停非常适合打印实时状态。4.4 工具端配置频率并查看输出配置好 MCU 侧后还要让工具知道 SWO 的波特率否则收到的是一堆乱码。SWO 输出频率依赖于内核时钟而不是系统主频。对于 STM32F4SWO 频率通常等于 HCLK 经过分频后的值。如果你不确定具体频率可以先读取调试器的 Trace 配置或者用工具自带的自动检测。命令行下一般是这样./tool trace init --pin swo --baud 2000000 ./tool trace read这里的 2000000 只是举例。实际应该根据主频去算。比如 STM32F407 跑 168 MHz软件配置 SWO 分频为 2则输出频率是 168 / (21) 56 MHz这显然太高普通调试器根本解不出来。正常配置会设置较大的分频把 SWO 频率降到几 MHz。调试器接收侧没有标准的“波特率”概念它更底层是按比特流解析的。用户能做的是调高或调低 SWO 分频器找到一个稳定值。我自己的经验是 SWO 频率在 1 MHz 到 4 MHz 之间比较稳太高容易受干扰和抖动。5. 常见问题与排查技巧实录5.1 调试器报 “VD is starting, please check vendor daemons status” 怎么回事这个报错我一开始也遇到过初次看到会以为和目标板有关其实不是。VD 是 vendor daemon 的缩写通常在某个商业调试器配合 FlexLM 授权服务时出现。工具启动时会去检查授权服务是否在线如果 license 服务没有启动或者环境变量设置不对就会提示这条信息。排查思路比较简单先确认调试器的授权服务是否启动看看对应进程在不在。如果在 Windows 上检查服务管理器里有没有相关服务。如果确认服务已经启动再去看 debug log它会告诉你 vendor daemon 监听在哪个端口、是哪一步握手失败。大多数情况下重装驱动或者手动启动服务就能解决和你的代码、芯片都没有关系。如果你是 J-Link 或 ST-Link 用户一般不会遇到这条因为它们的授权机制和 FlexLM 不同。遇到时先不要怀疑新版本工具先检查授权侧。5.2 Keil 里看不到变量先检查这两件事“Keil 怎么用 Debug 查看变量”是论坛上高频问题。最常见的两个原因一是变量被优化掉了。解决办法是切换到 Debug 配置把优化等级设为-O0或 Keil 里的Level 0并打开-g调试信息。如果是局部变量必须在当前作用域内才能看到如果希望在断点处看到某个变量直接把变量名加到 Watch 窗口。二是符号加载路径不对。Keil 有时候会因为工程路径变了找不到 ELF 文件的符号信息所以调试时能看到汇编但无法解析变量名。在 Debug 设置里重新指定firmware.axf或firmware.elf路径再重新加载即可。有一点容易被忽略即使是全局变量如果程序尚未运行到初始化它的语句它的值也是未定义的。很多人在 main 函数入口打印全局变量发现值不对其实此时变量的初始化还没完成。这种问题不是工具出 bug而是调试时机不对。5.3 “no stack trace available” 时怎么自救程序跑飞或者 HardFault 时GDB 经常显示no stack trace available。这并不代表真的没有调用栈而是栈信息被破坏了。可能原因有这么几个栈指针被异常值覆盖无法根据 SP 回溯。函数编译时没有开启 frame pointer导致回溯信息缺失。使用中断嵌套时栈切换调试器没跟上。这时我先看核心寄存器特别是r13 (sp)、lr、pc。用info registers sp lr pc可以快速定位。如果 SP 已经指向非法地址恢复栈回溯基本不可能需要靠记录 Trace 来找最后一刻发生了什么。如果只是某个库函数触发的 HardFaultSP 还在正常范围可以尝试手动指定栈底再回溯(gdb) set $sp _estack (gdb) backtrace_estack是链接脚本里定义的栈顶地址需要你根据实际工程修改。这个方法不一定完全准确但很多时候能帮你看清楚异常发生前的大致调用路径。5.4 SWO 输出乱码先别怀疑工具先检查接线和频率Trace 输出乱码是最容易让人误判的故障。我遇到过几次最终原因都不是工具问题而是硬件和配置。优先按这个顺序排查确认 SWO 引脚确实连通到了调试器。有些开发板上 SWO 是独立跳线默认没插。检查 MCU 侧 SWO 引脚复用配置。即使 CubeMX 里开启 Serial Wire也不代表 SWO 已经复用正确。核对 SWO 输出频率。如果主频改过但工具端频率没有跟着改乱码是必然的。检查调试器和板子的共地。SWO 信号参考地不一致波形会严重畸变。还有一个冷门问题多路 ITM 端口同时输出时如果工具只监听了端口 0来自端口 1 的数据会被忽略看起来像丢数据但乱码不会特别明显。你可以把工具配置为显示所有启用端口毕竟调试阶段信息量越大越好。6. 把 Trace 用出价值进阶打法6.1 用 Trace 定位 HardFault而不是瞎猜HardFault 排查是 Cortex-M 开发绕不开的痛点。传统方法是在 HardFault_Handler 里读寄存器或者断点看现场。现在有了 Trace可以更优雅地解决开启 ITM 的异常事件输出让 HardFault 发生时自动触发一个事件包然后 Trace 记录下进入异常前的最后几条指令。具体做法是在工具里使能 ITM 的 global timestamp并把异常的 event counter 打开。这样即使没有打日志Trace 也能体现出 CPU 是在哪一段代码失去控制的。接着结合backtrace的有限信息就能缩小到具体模块。对没有 ETM 的低端芯片来说Trace 不能给你完整指令流但异常事件加周期计数已经很有帮助。比如你发现异常发生在两条 ISR 之间且周期计数差很小就能判断是中断打断时机的问题而不是主循环逻辑错误。6.2 在持续集成中跑自动化调试命令行工具支持 GDB Server 之后最爽的用法是把调试测试写进脚本跑在 CI 上。比如刷完固件后自动执行一段冒烟测试判断某个 GPIO 电平是否满足预期。只需要用工具连接目标板加载测试固件然后从脚本里发 GDB 命令读取寄存器。一个基本的 CI 调试脚本可以这样组织./tool gdbserver --port 3333 --elf build/test.elf sleep 2 arm-none-eabi-gdb -batch \ -ex target remote :3333 \ -ex load \ -ex break test_case_1 \ -ex continue \ -ex print test_result这个流程对回归测试特别有用。固件每次提交后可以在真实硬件上跑一遍关键路径发现回归就立刻失败。相比人肉点 IDE这种自动化方式能快速暴露问题。不过这要求测试板通电并连接好调试器CI 环境需要做专门管理。如果硬件资源不足可以只在 nightly build 里跑把运行时间控制在可接受范围。6.3 给不同芯片留一套 Trace 模板最后分享一个小经验不同 Cortex-M 芯片的 Trace 配置差异不小建议每个项目维护一份 Trace 配置模板。比如 STM32F4 和 STM32H7 的 SWO 分频范围就不同H7 的主频更高默认分频需要更大工具端参数也要跟着改。把每个板子的配置保存成文件换项目时直接加载省去反复试错的痛苦。如果你刚接触这套工具不要急着把所有 Trace 功能全部打开。先选一个最简单的例子跑通 SWO printf再慢慢加入事件跟踪和周期计数。一条链路通一个功能比一次性全开遇到问题不知道从哪里查要高效得多。我自己的习惯是先在 CubeMX 里把 Serial Wire 打开生成工程后直接编译下载用工具连上去查看 Trace 输出。等这一条稳定了再开始动 ITM 端口和事件配置。步骤虽然慢一点但每一步的反馈都是确定的遇到问题也能直接定位到是 MCU 侧还是工具侧。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门