第五篇 STM32MP157-M4:链接脚本 lds + OpenOCD 调试烧录
STM32MP157-M4:链接脚本 lds + OpenOCD 调试烧录
【高危前置提醒】中断向量表必须 512 字节对齐;禁止读写
RCC_MP_GCR寄存器;GDB 必须先file后target remote;不要在 GDB 里手动set $sp(它会触发 SRAM 边界读,扰乱 OpenOCD 状态机)。
0 前言
本系列第五篇——链接脚本定义 M4 全部 SRAM 内存分区,OpenOCD + GDB 实现 SRAM 临时下载调试(断电程序丢失),不依赖 STM32CubeProgrammer 图形工具。所有调试过程均用命令行完成,可集成到 Makefile 的 make flash。
1 链接脚本核心要点
1.1 入口和栈顶
ENTRY(Reset_Handler)
_estack = 0x10060000; /* M4 SRAM 顶部 384KB */
_Min_Heap_Size = 0; /* 无 libc 堆,LiteOS 管理自己的堆 */
1.2 内存区域
MEMORY
{
SRAM (rwx) : ORIGIN = 0x10000000, LENGTH = 384K
}
M4 的 384KB SRAM(地址 0x10000000 - 0x10060000)全部映射为可读、可写、可执行。
1.3 中断向量表
.isr_vector :
{
. = ALIGN(512); /* 关键!必须 512 字节对齐 */
KEEP(*(.isr_vector));
KEEP(*(.startup_copro_fw*)); /* STM32MP1 专用段 */
} > SRAM
STM32MP1 M4 中断向量表必须 512 字节对齐——这是硬件强制要求。不加 . = ALIGN(512) 会导致外设中断随机 HardFault 且 CFSR/HFSR 寄存器无任何标志位。
1.4 BSS 与堆的 ASSERT 保护
.bss :
{
_sbss = .;
*(.bss)
*(COMMON)
_ebss = .;
} > SRAM
ASSERT(_ebss <= 0x10014000, "BSS overflow into LiteOS heap! Reduce code/data.")
如果代码量 / 全局数据增长导致 .bss 越界到 0x10014000(堆起始地址),链接阶段直接报错——不会等到运行时才 HardFault。
2 编译后内存校验(查看 m4_liteos.map)
make 后在 build/m4_liteos.map 中检查:
| 检查项 | 标准 | 实测值(本工程) |
|---|---|---|
.isr_vector 起始地址 |
0x10000000 |
✓ |
.isr_vector 大小 |
512 字节 | ✓ |
_ebss 地址 |
< 0x10014000 |
0x1001151C(约 69KB) |
| 堆起始 | 0x10014000 |
✓ |
| 所有段不超 SRAM 上限 | < 0x10060000 |
✓ |
3 OpenOCD 启动
# 本机路径(需根据实际安装位置调整)
D:/tools/xpack-openocd-0.12.0-5-win32-x64/xpack-openocd-0.12.0-5/bin/openocd.exe \
-s D:/tools/xpack-openocd-0.12.0-5-win32-x64/xpack-openocd-0.12.0-5/openocd/scripts \
-f openocd/board_stm32mp157_m4.cfg
注意:OpenOCD 0.12 以上版本需要 transport select dapdirect_swd 模式(旧版的 HLA 模式已废弃,用于 STM32MP1 会 shutdown 报错)。我们的 board_stm32mp157_m4.cfg 已配置好。
4 双核端口陷阱——3333 是 A7,3334 才是 M4
STM32MP157 是双核芯片,OpenOCD 启动后同时监听两个 GDB 端口:
| 端口 | 连接目标 | 用途 |
|---|---|---|
| 3333 | Cortex-A7 | Linux 调试 |
| 3334 | Cortex-M4 | M4 裸机 / RTOS 调试 ← 我们要用的 |
新手最常踩的坑:GDB 连 3333 → 所有 load / continue 全作用在 A7 上 → M4 始终停在 hold 状态 → LED 完全不闪、GDB 无任何报错。
本工程配套的 openocd/board_stm32mp157_m4.cfg 中已用 -gdb-port 3334 固定 M4 的 GDB 端口,但仍需手动确保 target remote :3334。
5 GDB 标准调试固定顺序(不可颠倒)
arm-none-eabi-gdb -x m4_debug.gdb # 一键脚本(推荐)
# 或手动逐步执行(顺序不可错)
arm-none-eabi-gdb
(gdb) file build/m4_liteos.elf # ① 先加载符号表
(gdb) target remote localhost:3334 # ② 再连接目标
(gdb) monitor halt
(gdb) monitor load_image build/m4_liteos.elf
(gdb) set $pc = Reset_Handler # ③ 或者monitor reg pc 0x1000f5bd
(gdb) continue
命令顺序铁律
file 必须排第一,早于 target remote 和一切 set $pc = <符号>。
| 顺序错误 | 后果 |
|---|---|
先 target remote 后 file |
set $pc = Reset_Handler 报 No symbol table is loaded,PC 保持 0x00000008 旧值 |
file 漏掉 |
continue 后 M4 跑的是残留旧地址(可能是 HalSysExit 死循环) |
monitor load_image 漏掉 |
M4 跑 SRAM 里的垃圾数据,立刻 double fault / lockup(PC 飞到 0xe7cce93c) |
monitor load_image只把固件字节写进目标内存,不会给 GDB 装符号表。装符号只能靠file。二者独立:file= 符号给 GDB,load_image= 字节给 M4。

GDB 脚本编码陷阱(中文 Windows)
GDB 脚本里不能有任何中文或非 ASCII 字符。中文 Windows 的 GDB host charset 是 CP1252,读不懂中文,会报:
warning: could not convert 'Reset_Handler' from the host encoding (CP1252) to UTF-32
这会导致 set $pc = Reset_Handler 整行解析失败、PC 没被设置。m4_debug.gdb 一律使用纯 ASCII,没有中文注释。
如果你手敲命令也报这个错,说明你把博客里的中文注释一起复制粘贴了——只复制 (gdb) 之后、# 之前的命令部分。
6 一键烧录脚本 flash-m4.bat
@echo off
cd /d %~dp0
REM 从 elf 动态读取 Reset_Handler 地址(永不硬编码)
for /f "tokens=1" %%a in (
'arm-none-eabi-readelf -s build\m4_liteos.elf ^| findstr /R " Reset_Handler$"'
) do set ENTRY=%%a
D:\tools\xpack-openocd-0.12.0-5-win32-x64\xpack-openocd-0.12.0-5\bin\openocd.exe ^
-s D:\tools\xpack-openocd-0.12.0-5-win32-x64\xpack-openocd-0.12.0-5\openocd\scripts ^
-f openocd\board_stm32mp157_m4.cfg ^
-c "init" -c "targets stm32mp15x.cm4" -c "halt" ^
-c "load_image build/m4_liteos.elf" ^
-c "reg pc %ENTRY%" -c "resume" -c "shutdown"
7 SP 不要手动设!
Reset_Handler 第一条指令就是 ldr sp, =_estack——固件自己会把 SP 设到 0x10060000。
如果你在 GDB 里手动 set $sp = 0x10060000,反而会触发一个坑:
0x10060000是 M4 SRAM 的精确边界(384KB 终点)- GDB 设完 SP 后会尝试读栈帧
[sp+4]即0x10060004 0x10060004不在 M4 SRAM 物理范围内 → OpenOCD 读失败 → 内部状态机��扰乱- 后续
continue全部报context restore failed, aborting resume
结论:永远不要在 GDB 里手动 set $sp,让固件自己来。
8 关于 RCC_MP_GCR:既不要调试器写,也不要固件写
很多 STM32MP1 ��程会让调试器执行 mww 0x5000010C 0x1 来 “释放” M4。在 STM32MP157 上这一定会失败:
(gdb) monitor mww 0x5000010C 0x1
Failed to write memory at 0x50000110
Protocol error with Rcmd: FC.
RCC_MP_GCR(0x5000010C)是 A7 域的受保护寄存器,外部调试器和 M4 都没有写权限。工程模式(拨码 001)根本不需要碰它——M4 上电后由调试器接管,直接下载运行即可。
9 本工程源码修复清单
调试过程中发现并修复了三处问题(详见 Core/Startup/startup_stm32mp15xx.s、os_config/target_config.h、Core/Src/main.c):
| 修复 | 文件 | 原因 |
|---|---|---|
PendSV_Handler → HalPendSV |
startup.s | 原 PendSV 向量指向空函数,调度器无效 |
LOSCFG_BASE_CORE_SWTMR=0 |
target_config.h | 软件定时器任务抢占首任务,导致调度链断裂 |
直接调用 led_task() + 忙等循环 |
main.c | 绕过 LOS_Start PendSV 阻塞问题 |
10 深入:为什么 PendSV 被阻塞(Cortex-M 异常优先级陷阱)
现象:按照标准 LiteOS API 调用 LOS_TaskCreate + LOS_Start,任务创建成功但从未被调度,系统掉进 HalSysExit 死循环。
根因:HalStartToRun(LiteOS 内核)的最后一行是 bx r6——普通跳转,不是异常返回。CPU 从此卡在 Reset 异常上下文(优先级 -3),PendSV 被永久阻塞。
Cortex-M 异常优先级层级:
优先级 -3:Reset(固定,最高)
优先级 -1:HardFault(固定)
优先级 0:SysTick(LiteOS tick)
优先级 15(0xF0):PendSV ← 最低!被 Reset 压住,永不执行
FreeRTOS 的解决方案:用 SVCall(系统调用)做一次真正的异常返回来退出 Reset 上下文,然后 PendSV 在 Thread 模式下接管第一个任务的上下文切换。这是 Cortex-M 上所有 RTOS 启动第一个任务的标准做法。
本工程当前方案:main() 不调用 LOS_Start(),直接调用 led_task() 函数。任务跑在 main 的调用栈上,不经过调度器。LOS_TaskDelay 改用忙等循环替代。LiteOS 内核子系统(堆、HWI 表、SysTick)全部初始化,但调度器未启动。后续实现多任务调度需要按 SVC + PendSV 标准流程改写 HalStartToRun。
11 运行说明
- 拨码 001,仅 M4 运行,A7 保持复位
- 固件下载至 SRAM(
0x10000000),断电程序丢失,仅调试使用 - LED0(红)和 LED1(绿)交替闪烁
- 上电自启动(QSPI Flash 固化)需要 A7 的 U-Boot / remoteproc 方案,不在本文范围
⚠️ 本节踩坑速览
| 现象 | 根因 | 修复 |
|---|---|---|
| 外设中断随机 HardFault | .isr_vector 未 512 字节对齐 |
段内添加 . = ALIGN(512) |
| 链接报 BSS overflow | lds ASSERT 与 heap 地址不匹配 | 两处同步为 0x10014000 |
调试写 0x5000010C 报 FC 错误 |
GCR 属 A7 保护域 | 删除所有读写 GCR 代码 |
GDB 无符号,set $pc 报错 |
先 target remote 后 file |
严格遵循 file → remote 顺序 |
| GDB 脚本报 CP1252 编码错 | 脚本含中文注释 | 脚本纯 ASCII,复制命令不带中文注释 |
monitor resume 与 continue 混用导致 context restore failed |
GDB set 命令与 OpenOCD monitor resume 状态不同步 |
统一用 GDB 的 continue |
set $sp = 0x10060000 后 context restore failed |
SP 踩在 SRAM 边界,GDB 读 [sp+4] 越界 |
永久删除手动设 SP |
LOS_Start 后任务不跑、系统卡 HalSysExit |
PendSV 被 Reset 异常上下文压住 | 绕开 LOS_Start 直接调用任务,或用 SVC+PendSV 标准启动 |
本系列配套资源
本文是「STM32MP157 M4 + LiteOS-M 入门」五篇系列的第五篇。配套提供两份下载资源:
| 资源 | 内容 | 适合读者 |
|---|---|---|
| 资源 1:完整工程源码 | 可编译运行的 m4_liteos_project/ 完整工程,含 Makefile、OpenOCD 脚本、GDB 调试脚本。解压后 make 即可编译 |
想直接跑代码、边跑边学的读者 |
| 资源 2:代码解释与框架文档 | 逐文件注释说明(main.c 执行流程、startup.s 寄存器分析、los_dispatch.S 汇编逐行解读、lds 内存分区详解),配合本系列五篇博文使用 | 想深入理解每一行代码为何这样写的读者 |
资源 1 是调通后可运行的项目,资源 2 是「为什么这样写」的注释版。建议先看本系列五篇博文,再下载代码对着跑——遇到不明白的地方查资源 2 的注释。
源代码下载链接:https://download.csdn.net/download/xiao089412/86539420
配套文档下载链接:https://download.csdn.net/download/xiao089412/93265595
系列完整目录
| 篇 | 标题 | 核心内容 |
|---|---|---|
| 1 | Windows GCC+Makefile 环境准备 | 工具链安装、拨码接线、烧录工具对比 |
| 2 | LiteOS‑M 源码裁剪与工程搭建 | 内核裁剪、文件拷贝规范、启动文件修改 |
| 3 | os_config 三板斧详解 | target_config.h / los_builddef.h / los_printf.h |
| 4 | Makefile 完整逐段详解 | 源文件收集、编译参数、flash 一键烧录 |
| 5 | 链接脚本 lds + OpenOCD 调试烧录(本文) | lds 内存分区、GDB 命令顺序、SP/GCR/PendSV 深坑、TIM6 未在内核初始化前关闭 |
| 5‑续篇 | LiteOS‑M PendSV 阻塞修复:从「绕开调度器」到「SVC + PendSV 标准启动」(LiteOS‑M移植⑤·续篇) | PendSV卡死根因、SVC异常处理、上下文切换修复、HalSysExit死机定位 |
| 6 | STM32MP157‑M4:OpenOCD 一键烧录踩坑实录——五个坑,从 Thumb 位到换行符(LiteOS‑M移植⑥) | OpenOCD脚本bug、Thumb位丢失、load_image、编码警告、调试掉线全套踩坑复盘 |
更多推荐




所有评论(0)