st-util 使用指南:用 stlink 的 GDB Server 调试 STM32
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)完整串联了以下流程:
- 初始化状态、解析命令行参数(
parse_options); - 打印版本号并初始化芯片 ID 表(
init_chipids,读取 config/chips/ 目录下的.chip定义); - 打开 ST-Link 连接(
stlink_open_usb或远程stlink_open_remote_str),并检查chip_id是否为未知设备(STM32_CHIPID_UNKNOWN),未知则报错退出; - 注册
SIGINT/SIGTERM/SIGSEGV信号清理(Windows 下为CtrlHandler),保证退出前把目标切回正常运行模式(stlink_run)并退出调试模式(stlink_exit_debug_mode); - 进入
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 扩展模式复用监听 |
相关文档
- 手册页: st-util.md、st-util.1
- 完整教程(GDB server 章节、连接模式、复位行为):doc/tutorial.md
- 核心实现:src/st-util/gdb-server.c、src/st-util/gdb-remote.c、src/st-util/semihosting.c
- 内存映射模板:src/st-util/memory-map.h
- 配套工具:
st-flash(doc/man/st-flash.md)、st-info(doc/man/st-info.md)、远程服务st-server(doc/man/st-server.md)