st-util 使用指南:用 stlink 的 GDB Server 调试 STM32

原创2026-10-03 12:46:331,056 阅读
文章标签:嵌入式开发工具调试器

st-util 使用指南:用 stlink 的 GDB Server 调试 STM32

导读

st-util 是 stlink 开源工具集(Open source STM32 MCU programming toolset)中负责调试的核心组件:它在主机上启动一个 GDB 远程服务器(GDB Server / GDB stub),把 ST-Link 编程器与 STM32 目标板之间的 SWD/JTAG 连接封装为标准的 GDB 远程调试协议,从而让 arm-none-eabi-gdb 等调试器通过 TCP 端口对目标芯片执行加载、断点、单步、寄存器/内存读写与复位等操作。读完本文,你将掌握 st-util 的全部命令行参数、默认行为与适用场景,了解其源码级的调试协议实现与内存映射机制,并能独立搭建一条「st-util + GDB」的完整调试链路。

本文以仓库中的 st-util 手册(及同源 st-util.1 man 页面)为骨架,结合 src/st-util/ 目录下的实际实现展开讲解。

一、st-util 是什么

st-util 的功能定义非常明确:Run GDB server to interact with STM32 device(运行 GDB 服务器以与 STM32 设备交互)。它启动一个 GDB 远程调试服务器,使本机或局域网内的 GDB 客户端能够连接并调试通过 ST-Link 连接的 STM32 目标。

从源码看,st-util 的入口位于 src/st-util/gdb-server.c,其 main() 函数(gdb-server.c)完整串联了以下流程:

  1. 初始化状态、解析命令行参数(parse_options);
  2. 打印版本号并初始化芯片 ID 表(init_chipids,读取 config/chips/ 目录下的 .chip 定义);
  3. 打开 ST-Link 连接(stlink_open_usb 或远程 stlink_open_remote_str),并检查 chip_id 是否为未知设备(STM32_CHIPID_UNKNOWN),未知则报错退出;
  4. 注册 SIGINT/SIGTERM/SIGSEGV 信号清理(Windows 下为 CtrlHandler),保证退出前把目标切回正常运行模式(stlink_run)并退出调试模式(stlink_exit_debug_mode);
  5. 进入 serve() 主循环,创建 TCP socket、绑定监听端口并接受 GDB 客户端连接,然后循环处理 GDB 远程协议数据包。

st-util 与同仓库的 st-flash(烧录/读取)、st-info(查询设备信息)并列,构成 stlink 工具集的三大命令行工具,且这三者共享底层的 src/stlink-lib/ 库(USB 通信、芯片识别、内存读写等)。

二、安装与运行前提

st-util 随 stlink 工具集一起编译安装。仓库根目录 CMakeLists.txt 定义了整个项目的构建,编译后会在 st-util 目录生成可执行文件。安装/构建后可直接运行:

$ st-util

或从构建目录运行(参考 doc/tutorial.md 中的用法):

$ make && [sudo] ./st-util

运行前提

  • Linux udev 权限:普通用户运行前需确保 ST-Link 的 udev 规则已正确安装。仓库提供了 config/udev/rules.d/ 下的 49-stlinkv1.rules、49-stlinkv2.rules、49-stlinkv2-1.rules、49-stlinkv3.rules 四套规则文件,需复制到 /lib/udev/rules.d(或 /etc/udev/rules.d),并执行 sudo udevadm control --reload-rules 与 sudo udevadm trigger 使其生效(详见 doc/tutorial.md 的 HowTo 部分)。
  • 目标芯片:连接的目标必须能被识别(Chip ID 存在于 config/chips/ 的芯片定义表中),否则 st-util 会打印 Unsupported Target (Chip ID is ...) 并退出。
  • GDB 客户端:必须是 ARM 版 GDB(如 arm-none-eabi-gdb),普通 x86 GDB 无法解析 Cortex-M 目标(doc/tutorial.md 中明确提示此点)。

设备选择的环境变量

在连接多台设备或希望固定设备时,st-util 支持通过环境变量 STLINK_DEVICE 以 <USB_BUS>:<USB_ADDR> 格式指定具体设备(见 gdb-server.c 帮助文本)。

三、命令行参数详解

st-util 的参数解析由 parse_options() 完成(gdb-server.c),使用 getopt_long 支持长短两种写法。手册(st-util.md)中的参数如下:

短选项 长选项 参数 说明
-h --help 无 打印帮助信息
--version 无 打印版本信息(源码中为 -V/--version,输出 v<版本号> 后退出)
-v XX --verbose=XX 0..99 指定具体日志级别
-v --verbose 无 启用一般性详细日志
-p 4242 --listen_port=1234 端口号 设置 GDB 服务器监听端口,默认 4242
-m --multi 无 扩展模式(extended mode),断开连接后持续监听
-n --no-reset 无 连接时不复位目标板
--semihosting 无 在 stdout 上启用 ARM Semihosting 输出

各选项的源码行为

  • --listen_port / -p:源码中默认端口常量 DEFAULT_GDB_LISTEN_PORT 定义为 4242(gdb-server.h)。若未指定端口即使用 4242;参数为非整数或负数会报错退出(gdb-server.c)。
  • --verbose / -v:不带数值时设置日志级别为 DEBUG_LOGGING_LEVEL(100),带数值时按 atoi 解析;默认日志级别为 DEFAULT_LOGGING_LEVEL(50,见 gdb-server.h)。
  • --multi / -m:设置 st->persistent = true。主循环在 serve() 返回后会依据 persistent 决定是否继续等待下一次连接(gdb-server.c);此外当 GDB 客户端发送 ! 数据包进入扩展模式时,st-util 也会自动置位 persistent(gdb-server.c)。
  • --no-reset / -n:设置连接模式为 CONNECT_HOT_PLUG(热插拔,不执行复位),-n 同时被注册为 --hot-plug 的别名(gdb-server.c)。
  • --serial:按序列号选择特定 ST-Link 设备(多设备场景),手册中提及的 --serial 参数在源码中同样存在(SERIAL_OPTION,gdb-server.c)。
  • --semihosting:置位 st->semihosting = true,使目标固件通过 ARM Semihosting 机制产生的 SYS_WRITE0 等输出转发到 st-util 的 stdout。

手册之外的补充选项

手册是精简版;从 gdb-server.c 的 long_options 与帮助文本看,当前源码还提供以下选项(均可在 st-util --help 中查看完整说明):

  • --connect-under-reset / -u:在复位状态下连接(CONNECT_UNDER_RESET),适用于目标固件会让芯片进入睡眠、禁用调试引脚等无法正常连接的情况;该选项同样在 st-flash、st-info 中可用(doc/tutorial.md)。
  • --freq=1M / -F:设置 SWD/JTAG 接口频率。由 arg_parse_freq() 解析(helper.c):纯数字或带 k/K 后缀按 kHz 解释,带 M 后缀乘以 1000 表示 MHz(如 -F 1800k 或 -F 1M)。参考 doc/tutorial.md,合法频率值包括 5k, 15k, 25k, 50k, 100k, 125k, 240k, 480k, 950k, 1200k, 1800k, 4000k,默认 1800 kHz。
  • --remote <host<a href="https://link.gitcode.com/i/c6a839e630339a0b31efd2999e0d1266" target="_blank">:port]>:驱动由 st-server 在其他机器上提供的 ST-Link(REMOTE_OPTION),此时 main() 走 stlink_open_remote_str() 分支([gdb-server.c)。st-server 是仓库中配套的远程服务程序,见 src/st-server/server.c。

注意:不同版本的手册与源码选项可能略有出入(如手册中 -v 带 *XX* 参数与 -v 无参数并存,源码通过 optional_argument 同时支持两种写法)。以当前仓库源码为准,完整清单请以 st-util --help 输出为准。

四、连接模式:复位行为的三种选择

st-util 对目标的连接方式由 enum connect_type 控制(inc/stlink.h):

enum connect_type {
    CONNECT_HOT_PLUG = 0,     // 热插拔:不复位
    CONNECT_NORMAL = 1,       // 常规:连接时复位(默认)
    CONNECT_UNDER_RESET = 2,  // 复位状态下连接
};

默认情况下(不传任何相关选项)state.connect_mode = CONNECT_NORMAL,即连接时复位目标板(gdb-server.c)。三种模式的语义:

  • CONNECT_NORMAL(默认):适合绝大多数常规调试场景;
  • CONNECT_HOT_PLUG(-n/--no-reset/--hot-plug):连接时不复位目标,适合不希望打断目标当前运行状态、或复位信号未接线的场景;
  • CONNECT_UNDER_RESET(-u/--connect-under-reset):在目标执行任何指令之前建立连接,专门应对目标固件进入低功耗、禁用 SWD 引脚等「锁死」调试口的情况。

连接模式的实现贯穿底层:stlink_open_usb() 与 stlink_target_connect() 都会依据该枚举选择不同的 SWD 操作序列(见 src/stlink-lib/usb.c 与 src/stlink-lib/common_legacy.c)。若连接后目标无法识别,doc/tutorial.md 建议尝试 --connect-under-reset 并按住目标板复位键。

五、典型使用示例

示例一:默认端口调试

启动服务器(默认监听 4242 端口):

$ st-util -p 4500

另开终端,进入 GDB 并连接(st-util.md 原始示例):

$ gdb
(gdb) target extended-remote localhost:4500

示例二:加载并运行固件

按 doc/tutorial.md 的 GDB server 章节,完整调试会话如下:

$ st-util

# 另开终端,使用 ARM 版 GDB
$ arm-none-eabi-gdb fancyblink.elf
(gdb) target extended-remote :4242
Remote debugging using :4242
(gdb) load
Loading section .text, size 0x458 lma 0x8000000
Loading section .data, size 0x8 lma 0x8000458
Start address 0x80001c1, load size 1120
(gdb) continue

要点说明:

  • 必须使用 arm-none-eabi-gdb(或等效 ARM 工具链 GDB);
  • target extended-remote 而非 target remote,这样才支持从 GDB 侧复位/重启目标(extended 模式与 -m/--multi 语义一致);
  • load 会根据 GDB 内置的内存映射,把 ELF 的各段加载到对应的 flash 或 SRAM 地址,并正确设置 PC;
  • 命令可缩写,如 tar ext :4242。

示例三:从 GDB 复位目标

(gdb) target extended-remote localhost:4242
(gdb) kill
(gdb) run
(gdb) monitor reset      # 软复位并保持暂停(等价于 stlink_reset RESET_SOFT_AND_HALT)
(gdb) monitor jtag_reset # 通过 NRST 引脚发送硬复位

从源码看,monitor 命令通过 GDB 的 qRcmd 查询实现(gdb-server.c),支持 resume、halt、jtag_reset、reset、semihosting enable|disable 等子命令。其中 reset 会执行软复位(RESET_SOFT_AND_HALT)并重新初始化断点/监视点寄存器;jtag_reset 走 RESET_HARD(NRST 引脚)。注意手册与教程均提示 st-link 不支持 GDB 的 run 直接加载程序,复位目标建议用 monitor reset。

示例四:调试 Thumb 代码与 SRAM 程序

Cortex-M 固件编译为 Thumb 指令,GDB 默认按 ARM 模式反汇编,可用奇数地址强制 Thumb 反汇编(doc/tutorial.md):

(gdb) disassemble 0x20000001

若固件链接到 SRAM(如 0x20000000),同样直接 (gdb) load firmware.elf 即可加载运行;链接到 0x08000000 的固件则会被 st-util 写入 flash。

六、GDB 协议实现与底层原理

st-util 本质上是一个精简的 GDB 远程协议 stub。协议收发由 src/st-util/gdb-remote.c 实现:

  • gdb_send_packet():按 $<data>#<checksum> 格式组包,逐字节累加校验和,等待客户端 + 确认;
  • gdb_recv_packet():状态机解析 $、数据、# 与两字节校验和,校验失败回 -(NACK)并重新等待;
  • gdb_check_for_interrupt():通过 poll 检测 0x03(Ctrl-C 中断字符),用于在目标运行期间请求暂停。

serve() 主循环(gdb-server.c)按首字母分发 GDB 数据包:

数据包 含义 st-util 处理
g / G 读/写全部寄存器 stlink_read_all_regs / 逐个 stlink_write_reg
p / P 读/写单个寄存器 支持 r0-r15、xpsr、msp/psp、control/faultmask/basepri/primask、s0-s31、fpscr
m / M 读/写内存 stlink_read_mem32 / stlink_write_mem32(未对齐时回退 write_mem8)
c 继续执行 stlink_run(RUN_NORMAL),循环查询目标状态,暂停时返回 S05
s 单步 stlink_step
? 查询状态 已连接返回 S05(TRAP),未连接返回 OK
Z / z 设置/移除断点与监视点 硬件断点(FPB)、数据监视点(DWT)
vFlashErase/Write/Done flash 编程 累积写块 → flash_go() 擦除+烧写
qSupported/qXfer 能力协商与 XML 传输 返回 PacketSize=3fff,支持 memory-map 与 features
! 进入扩展模式 置位 persistent,允许断连后继续监听
R 复位 stlink_reset(RESET_SOFT_AND_HALT) 并重装断点
k kill 目标恢复运行、退出调试模式、关闭并重开连接

硬件断点与数据监视点

  • 硬件断点:Cortex-M 的 FPB(Flash Patch and Breakpoint)单元。init_code_breakpoints() 读取 FP_CTRL 寄存器获取可用断点寄存器数量(最多 15 个,CODE_BREAK_NUM_MAX),并针对 Cortex-M7 处理 FP 寄存器写锁定(写 FP_LAR 解锁,gdb-server.c);
  • 数据监视点:DWT 单元。init_data_watchpoints() 先置位 DEMCR.TRCENA 使能 DWT,最多 4 个(DATA_WATCH_NUM),支持写(Z2)、读(Z3)、访问(Z4)三种监视类型(gdb-server.c)。

从 doc/tutorial.md 的已知限制看,GDB 依据内存映射中 rom/ram 属性决定使用软断点(Z0)还是硬断点;对于外部总线(如 STM32H735-DK 的 0x90000000 外部 Flash),因被标记为 ram 可能产生不触发的软断点,这是使用 GDB 断点时需注意的边界情况。

内存映射与目标描述

make_memory_map()(gdb-server.c)依据芯片 ID 选择不同的内存映射模板(模板定义在 src/st-util/memory-map.h,涵盖 F2/F4/F7/H7/L4/L496 等系列),以 XML 形式通过 qXfer:memory-map 提供给 GDB;同时通过 qXfer:features 提供寄存器描述(target_description,gdb-server.c),包含 Cortex-M 的通用寄存器、特殊寄存器与浮点寄存器定义。GDB 正是依据这份映射决定 load 时把段写入 flash 还是 SRAM、以及选择何种断点类型。

Flash 编程流程

GDB 发送 load 到 flash 地址时,st-util 依次处理 vFlashErase(登记擦除块)、vFlashWrite(解码二进制数据填充块)、vFlashDone(执行 flash_go())。flash_go()(gdb-server.c)按页擦除(stlink_erase_flash_page,页大小由 stlink_calculate_pagesize 动态计算)、启动 flash loader(stlink_flashloader_start)、逐页写入(stlink_flashloader_write),完成后 stlink_reset(RESET_SOFT_AND_HALT) 复位目标。Flash loader 汇编代码位于 flashloaders/(如 stm32f0.s、stm32f4.s、stm32l4.s 等,按内核系列区分)。

七、Semihosting 支持

启用 --semihosting 后,st-util 会在目标暂停时检查 PC 处的指令是否为 0xBEAB(ARM semihosting 断点指令),若是则调用 do_semihosting() 执行主机侧处理,并把返回值写回 r0、PC 跳过断点后继续运行(gdb-server.c)。

do_semihosting() 位于 src/st-util/semihosting.c,实现了 ARM semihosting 的部分系统调用,典型能力包括:

  • SYS_WRITE0 / SYS_WRITE:把目标内存中的字符串输出到 st-util 的 stdout(WRITE0_BUFFER_SIZE 为 64 字节分块读取);
  • SYS_READ:从主机 stdin 读取输入;
  • SYS_OPEN / SYS_CLOSE / SYS_READ/SYS_WRITE 文件操作:支持在主机侧打开/读写文件(open_mode_flags 定义了 open 模式标志映射);
  • SYS_EXIT / SYS_EXIT_EXTENDED:终止程序,st-util 在收到退出请求后恢复目标运行。

这样,目标固件无需串口硬件即可通过调试接口直接向终端打印日志。运行期间也可以用 (gdb) monitor semihosting enable / disable 动态开关(gdb-server.c)。

八、退出与清理行为

st-util 对退出行为做了细致处理(gdb-server.c):

  • 收到 SIGINT/SIGTERM/SIGSEGV(Windows 下为 Ctrl-C 处理器)时,先执行 _cleanup():把目标切换回正常运行模式(stlink_run(RUN_NORMAL))、退出调试模式(stlink_exit_debug_mode)、关闭 ST-Link 连接,再按信号类型以对应退出码结束;
  • 收到 GDB 的 k(kill)包时,同样会恢复目标运行、退出调试模式并关闭连接,随后重新打开 ST-Link 以准备下一次连接(gdb-server.c)。

九、故障排查速查

结合 doc/tutorial.md 与 gdb-server.c 的错误输出,常见问题与对策:

现象 可能原因与对策
Unsupported Target (Chip ID is ...) 目标芯片不在 config/chips/ 支持列表,或芯片处于 stop/standby、SWD 信号接反
连接失败、unknown chip id! 无目标连接、SWD 引脚被固件占用、编程器固件过旧;尝试 -u/--connect-under-reset 并按住复位键
普通用户无权限访问 ST-Link 未安装/未重载 config/udev/rules.d/ 下的 udev 规则
软断点不触发(外部 Flash 场景) 外部总线被映射为 ram 导致 GDB 使用软断点,参考教程 HowTo (e) 的说明
端口被占用 更换 -p 端口,或用 -m 扩展模式复用监听

相关文档

登录后查看全文
stlink