STM32MP157-M4:链接脚本 lds + OpenOCD 调试烧录

【高危前置提醒】中断向量表必须 512 字节对齐;禁止读写 RCC_MP_GCR 寄存器;GDB 必须先 filetarget 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 remotefile set $pc = Reset_HandlerNo 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_GCR0x5000010C)是 A7 域的受保护寄存器,外部调试器和 M4 都没有写权限。工程模式(拨码 001)根本不需要碰它——M4 上电后由调试器接管,直接下载运行即可。

9 本工程源码修复清单

调试过程中发现并修复了三处问题(详见 Core/Startup/startup_stm32mp15xx.sos_config/target_config.hCore/Src/main.c):

修复 文件 原因
PendSV_HandlerHalPendSV 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 运行说明

  1. 拨码 001,仅 M4 运行,A7 保持复位
  2. 固件下载至 SRAM(0x10000000),断电程序丢失,仅调试使用
  3. LED0(红)和 LED1(绿)交替闪烁
  4. 上电自启动(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 remotefile 严格遵循 file → remote 顺序
GDB 脚本报 CP1252 编码错 脚本含中文注释 脚本纯 ASCII,复制命令不带中文注释
monitor resumecontinue 混用导致 context restore failed GDB set 命令与 OpenOCD monitor resume 状态不同步 统一用 GDB 的 continue
set $sp = 0x10060000context 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、编码警告、调试掉线全套踩坑复盘
Logo

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

更多推荐