STM32MP157 M4 核 + LiteOS-M 调试踩坑:OpenOCD + GDB Windows 环境系列问题

技术博客|面向 STM32MP157 M4 核 + LiteOS-M 移植的开发者
一句话结论:M4 跑在内部 SRAM、用 OpenOCD + GDB 在 Windows 上调试,坑几乎全在「GDB 与 OpenOCD 的协作方式」上,不在你的代码。 本文把 No registers、10054、UsageFault、CP1252 编码、CFSR 排查这些高频问题一次性讲透。

摘要

STM32MP157 的 M4 内核调试,程序下载到 M4 内部 SRAM。Windows 环境下 OpenOCD + GDB 组合会遇到一堆细碎坑:新建 GDB 连接后报 No registers 无法修改 PC/SP;关闭 GDB 重连出现 10054 套接字错误;刚 attach 直接进入 UsageFault_Handler;print &Reset_Handler 出现 CP1252 编码转换警告;时而看到 ?? () 无符号,时而直接显示异常函数符号。本文记录完整现象、截图说明、根因、标准调试流程,同时补充 CFSR 寄存器排查 UsageFault 的实战方法。

💡 适用场景:STM32MP157 M4 独立运行在内部 SRAM、LiteOS-M 操作系统、GCC + Makefile 裸金属开发,不使用 Keil、不使用 STM32CubeIDE


1、报错现象 1:set $pc = 0x1000ddf4 → No registers(含编码警告)

复现控制台完整输出

(gdb) print &Reset_Handler
warning: could not convert 'Reset_Handler' from the host encoding (CP1252) to UTF-32.
This normally should not happen, please file a bug report.
$1 = (<text variable, no debug info> *) 0x1000ddf4 <Reset_Handler>
(gdb) set $pc =  0x1000ddf4
No registers.
(gdb) set $pc = 0x1000ddf4
No registers.

根因

1)编码警告部分

warning: could not convert 'Reset_Handler' from the host encoding (CP1252) to UTF-32.

这是 Windows 版 arm-none-eabi-gdb 工具链的一个老 bug:Windows 系统默认代码页是 CP1252,GDB 在解析符号名称做编码转换时失败。

这个警告可以忽略,不影响调试,打印出来的函数地址 0x1000ddf4 是完全正确可用的。

⚠️ 坑点:不要直接写 set $pc = &Reset_Handler,继续使用符号会再次触发编码异常。直接写物理地址数字,彻底绕开符号解析问题。

2)No registers 报错

GDB 网络层面已经连上 OpenOCD 服务端,但是 M4 CPU 内核处于运行状态,没有被硬件 halt 暂停。CPU 正在运行时,GDB 无法读写内核通用寄存器、PC、SP,因此执行 set $pcset $sp 直接返回 No registers

⚠️ 注意:GDB 内置的 halt 命令在 OpenOCD 场景下无效。必须使用 monitor halt,把命令转发给 OpenOCD,由 OpenOCD 在硬件层面暂停 M4 内核。只有内核 halt 之后,才具备读写寄存器权限。

正确操作顺序

# 硬件暂停 M4 内核,拿到寄存器访问权限
monitor halt

# 读取向量表 0x10000000,获取初始栈指针 SP
x/w 0x10000000

# 填入上面 x/w 读到的栈地址
set $sp = 0xXXXXXXXX

# 使用物理地址设置程序入口,规避 Windows gdb 编码 bug
set $pc = 0x1000ddf4

# 恢复 CPU 运行
continue

2、报错现象 2:OpenOCD 窗口 WSAGetLastError==10054,远程主机强迫关闭了一个现有的连接

**【此处插入 OpenOCD 控制台 10054 报错截图】**

控制台输出:

Error: Error on socket 'GDB': WSAGetLastError==10054,message: 远程主机强迫关闭了一个现有的连接。
Info : dropped 'gdb' connection

根因

我们直接关掉 GDB 客户端 CMD 窗口,TCP 连接被强制断开,OpenOCD 作为服务端打印这条日志。

10054 不等于硬件故障!

  • OpenOCD 进程仍然正常运行;
  • ST-Link 连接正常;
  • M4 的 SRAM 不会清空,上一次下载的程序还保留在内存里。

窗口分工(非常关键)

1)OpenOCD 窗口(服务端):尽量后台常驻,不要频繁关闭

负责 ST-Link 硬件交互。只有 ST-Link 识别失败、芯片 HardFault 锁死时才重启。出现 10054 直接无视,不需要重启 OpenOCD。

2)GDB 窗口(客户端):可以反复关闭、新建

每次新开 GDB 终端,必须重新执行连接命令,建立 TCP 会话。

连接命令选择

❌ 不推荐旧命令

target remote localhost:3334

✅ 推荐使用 extended-remote,支持多次断开重连,适配频繁开关 GDB 场景

target extended-remote localhost:3334

3、现象 3:attach 连上直接停在 UsageFault_Handler 异常

**【此处插入 GDB attach 直接进入 UsageFault_Handler 截图】
**

控制台现象:

0x10005a46 in UsageFault_Handler () at Core/Src/stm32mp1xx_it.c:143
143         while (1)

两种 attach 现场对比

场景 A:刚 attach,0x00000008 in ?? ()

芯片复位、SRAM 为空,还没有下载固件。GDB 读取该地址,找不到对应 elf 符号信息,显示问号 ?? ()

场景 B:刚 attach 直接停在 UsageFault_Handler (),有完整源码行号符号

复现条件:关闭 GDB 客户端,芯片不断电、不复位。M4 的 SRAM 是易失内存,但只有掉电才会清空;关闭 GDB 不会擦除 SRAM,上一次跑崩的程序仍然留在内存。上一次运行已经触发 UsageFault 异常,死循环在 Fault_Handler;重新 attach 上来直接读到内存代码,识别到函数符号,于是停在异常处理函数。

⚠️ 高频大坑:monitor load_image 重新下载新版 elf 镜像,仅仅是把新代码写入 SRAM,CPU 的 PC/SP 寄存器不会自动跳转到 Reset_Handler。CPU 还停留在之前 Fault 死循环。
下载完成后,必须手动 monitor halt,再手动设置 SP、PC,最后 continue,新程序才会正常启动。


4、实战:通过 CFSR 寄存器定位 UsageFault 故障根源

UsageFault 属于 Cortex-M 内核的用法错误异常,常见诱因:栈溢出、非对齐访问、跳转到非法指令地址、执行 Thumb 模式错误、访问受保护外设地址
进入 Fault_Handler 死循环之后,不要直接复位,读取 CFSR 寄存器可以直接定位故障类型。

操作步骤(GDB 中执行)

# 内核先暂停
monitor halt

# 打印全部内核寄存器,里面包含 CFSR 寄存器
monitor reg

在输出的一大段寄存器中找到 cfsr。CFSR 是 32 位组合故障状态寄存器,由三部分拼接(低位到高位):

  • MMFSR[7:0]:Memory Management Fault 状态(位 0~7)
  • BFSR[15:8]:Bus Fault 状态(位 8~15)
  • UFSR[31:16]:Usage Fault 状态(位 16~31)

UFSR(UsageFault)关键位说明

位(UFSR 内) 宏定义 CFSR 实际位 含义
BIT0 UNDEFINSTR bit16 执行了未定义的指令
BIT1 INVSTATE bit17 无效的 EPSR 状态,最常见:内核跑进 ARM 模式(Cortex-M 只支持 Thumb 指令集)
BIT2 INVPC bit18 异常返回时 PC 非法
BIT3 NOCP bit19 访问不存在的协处理器指令
BIT8 UNALIGNED bit24 非对齐内存访问触发故障
BIT9 DIVBYZERO bit25 除零错误

举例分析

1. 如果看到 UFSR BIT1=1(即 cfsr 的 bit17,典型值 0x00020000)→ INVSTATE

根源:程序跑入 ARM 指令模式。M4 只支持 Thumb-2 指令集,一般是链接脚本入口设置错误,或函数指针跳转地址最低位不是 1。Cortex-M 函数指针地址最低 bit 必须置 1,代表 Thumb 模式。

2. 如果看到 UFSR BIT8=1(bit24)→ UNALIGNED

根源:非对齐访问,比如对 uint32_t 变量做 4 字节读取,但地址不是 4 字节对齐。LiteOS-M 配置里可以关闭非对齐访问报错。

3. 如果看到 UFSR BIT0=1(bit16)→ UNDEFINSTR

根源:跑到非法代码地址,通常是栈溢出冲毁函数返回地址,跳转到随机内存,译码出非法指令。大概率任务栈配置太小,发生栈溢出。

补充:读取故障发生时的 PC 地址

monitor reg 输出里的 pc,是当前停在 Fault_Handler 的 PC,不是故障发生那一刻的 PC。发生异常时,内核会把故障现场的 xPSR、PC、LR、R0~R3 压入发生异常时的栈。如果要拿到出事那一刻的 PC,需要从 SP 指向的栈帧去回溯——这是 M4 内核调试排错很关键的技巧。

🔧 常见踩坑点:移植 LiteOS-M,任务栈大小配置过小,任务运行栈溢出,大概率直接触发 UsageFault。优先检查任务栈大小是否足够。


5、Windows OpenOCD + GDB M4 SRAM 调试完整模板(新开 GDB 直接整套复制执行)

文件名以你工程实际产物为准,本例为 build/m4_liteos.elf;若你的构建产物叫别的名字(如 liteos_m.elf),替换对应路径即可。

# 加载 elf 符号信息
file build/m4_liteos.elf

# 连接 OpenOCD 服务端
target extended-remote localhost:3334

# 硬件暂停 M4 内核,必须!否则无法修改 pc/sp
monitor halt

# 下载镜像写入 M4 内部 SRAM
monitor load_image build/m4_liteos.elf

# 读取向量表,拿到初始栈指针
x/w 0x10000000

# 将上面 x/w 打印出的栈地址替换此处
set $sp = 0xXXXXXXXX

# Reset_Handler 入口物理地址,规避 Windows gdb 编码警告 bug
set $pc = 0x1000ddf4

# 设置断点示例
b main

# 启动内核运行
continue

6、全套踩坑总结

  1. 修改 $pc$sp 寄存器之前,必须先执行 monitor halt 硬件暂停 M4 内核,否则报 No registers;GDB 自带 halt 命令无效。
  2. Windows GDB 打印 Reset_Handler 报 CP1252 编码警告属于工具链 bug,地址输出有效;直接使用物理数字地址,不要使用符号 &Reset_Handler
  3. OpenOCD 打印 10054 套接字错误,只是 GDB 客户端关闭,硬件没有问题,不要盲目重启 OpenOCD 服务端。
  4. M4 内部 SRAM 只有掉电才清空;关闭 GDB 不会擦除内存,重连会看到上一次程序崩溃现场。
  5. monitor load_image 下载镜像不会自动复位 CPU,下载完成务必手动设置 SP 与 PC,否则 CPU 继续跑之前的异常死循环。
  6. GDB 连接优先选用 target extended-remote,不要用老旧 target remote,更适合反复断开重连调试。
  7. 遇到 UsageFault 不要直接复位,monitor halt + monitor reg 查看 CFSR 寄存器,根据 UFSR 位定位:非法指令、Thumb 模式错误、非对齐访问、栈溢出等问题;LiteOS-M 移植优先检查任务栈大小是否足够。

适用场景:STM32MP157 M4 独立运行在内部 SRAM、LiteOS-M 操作系统、GCC + Makefile 裸金属开发,不使用 Keil、STM32CubeIDE。


Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐