部分 IAP 安全场景需求:利用 STM32 系统 Bootloader,将一段临时代码加载到 SRAM 中执行;临时代码负责解密加密 APP/IAP 固件,完成 Flash 烧写;上电复位之后 SRAM 临时代码自动消失,提升固件安全性。
STM32 默认代码在内部 Flash 执行,把全部代码、向量表、常量数据完整部署至 SRAM 运行,需要修改链接脚本、编译宏、调试初始化脚本。该模式仅调试器加载可以直接跑通;文档不介绍系统 Bootloader 跳转 SRAM 的业务逻辑。本文基于 LAT1705,分别拆解 IAR、Keil 两套工具链完整配置、验证手段、高频踩坑。
重要提醒:SRAM 空间有限,全部代码 SRAM 运行,固件总大小不能超过芯片 SRAM 总容量。
资料获取:实战经验 | LAT1705 如何将全部代码放在SRAM中运行_v1.0
1 业务场景与测试准备
典型使用场景
IAP 安全方案:串口借助 System‑Bootloader 下载解密临时代码至 SRAM;SRAM 内运行解密逻辑,把解密后固件烧入 Flash;复位后 SRAM 内容丢失,临时代码不留痕迹。
本文聚焦调试器下载调试全 SRAM 运行工程,不覆盖系统 Bootloader 加载 bin 到 SRAM 的业务实现。
测试工程准备
- 使用 STM32CubeMX 生成基础工程,增加 LED 翻转点灯逻辑,用于运行状态观测;
- 分别生成 IAR (EWARM)、Keil‑MDK‑ARM 两套工程;
- 原始工程先在 Flash 下编译运行,确认 LED 点灯业务逻辑正常。
2 IAR‑EWARM 工程配置(全部代码 SRAM 运行)
2.1 修改链接 icf 脚本
- 复制原厂
stm32g431xx_flash.icf,另存为stm32g431xx_SRAM.icf; - 修改内存映射:ROM(只读代码段)地址由 Flash 起始地址修改为 SRAM 起始地址
0x20000000;RAM 段配置保留 SRAM 剩余空间,用于 RW/ZI、栈堆; - 向量表
.intvec段放置到 SRAM 起始位置。
2.2 工程 Linker 选项切换
工程选项‑>Linker,勾选Override default,指定新创建的stm32g431xx_SRAM.icf链接脚本。
2.3 预编译宏增加向量表切换
C/C++ 预处理器宏定义增加两个宏:
VECT_TAB_SRAM,USER_VECT_TAB_ADDRESS
作用:system_stm32g4xx.c的 SystemInit 函数根据宏配置,修改 SCB‑>VTOR 向量表偏移寄存器,把中断向量表指向 SRAM 地址;没有该宏,中断会依然访问 Flash 地址,程序跑飞。
2.4 验证方法
- 调试前将芯片 Flash 整体擦除,消除旧固件干扰;
- ST‑LINK 下载调试,观察 LED 翻转;汇编窗口查看 PC 程序计数器地址落在
0x2000xxxxSRAM 地址区间; - 手动硬件复位 MCU,程序停止运行(复位后 SRAM 数据丢失,证明代码完全运行在 SRAM)。
3. Keil MDK‑ARM 工程配置(全部代码 SRAM 运行)
Keil 配置比 IAR 复杂,需要自定义分散加载 sct 脚本、调试初始化 ini 脚本,移除 Flash 烧录算法。
3.1 编写 SRAM 分散加载脚本 *.sct
示例(NUCLEO‑G431RB),把 RO 代码段部署 SRAM 起始地址0x20000000,RW/ZI、堆栈放在 SRAM 后续空间。
#define STACKSEAL_SIZE 0x8
#define HEAP_SIZE 0x200
#define STACK_SIZE 0x400
LR_IROM1 0x20000000 0x4000
{
ER_IROM1 0x20000000 0x4000
{
* (InRoot$$Sections)
.ANY(+RO)
}
RW_RAM1 0x20004000 0x2000
{
.ANY(+RW +ZI)
}
ARM_LIB_HEAP AlignExpr(+0, 8) EMPTY HEAP_SIZE {}
ARM_LIB_STACK AlignExpr(+0, 8) EMPTY STACK_SIZE {}
STACKSEAL +0 EMPTY STACKSEAL_SIZE {}
}
根据目标芯片 SRAM 实际起始地址、总大小修改 sct 参数。
3.2 Target‑Linker 配置
取消勾选Use Memory Layout from Target Dialog;Scatter File 选择上面编写的 SRAM 版本 sct 脚本。
3.3 C/C++ 预编译宏
同样增加宏:VECT_TAB_SRAM,USER_VECT_TAB_ADDRESS,实现 VTOR 向量表重定向。
3.4 调试初始化脚本 run.ini(关键点)
Keil 调试需要 ini 脚本完成:设置 SP 栈指针、PC 程序计数器、VTOR 寄存器、加载 axf 镜像、跳转 main。
FUNC void Setup(void){
SP=_RDWORD(0x20000000);
PC=_RDWORD(0x20000004);
_WDWORD(0xE000ED08, 0x20000000);
}
FUNC void OnResetExec (void){
Setup();
LOAD %L INCREMENTAL
Setup();
g,main
}
OnResetExec();
在 Debug 选项页,Initialization File填入该 run.ini 脚本路径。
3.5 移除 Flash 下载算法
- Debug‑>Settings‑>Flash Download,删除全部 Flash 编程算法;
- Download Function 选择
Do not Erase,禁止 Flash 擦除编程动作; - Utilities 页面选择
Use External Tool for Flash Programming,关闭内置 Flash 烧录功能。
3.6 运行验证
擦除全部 Flash,下载调试;LED 正常翻转;汇编窗口确认 PC 指针处于 0x2000xxxx 地址;硬件复位,程序停止执行。
4. 工程落地排查清单
- 下载成功,一进中断程序直接崩溃 HardFault
- 核查预编译宏是否添加
VECT_TAB_SRAM;没有该宏 VTOR 仍然指向 Flash,中断向量访问非法地址; - 检查链接脚本,确认向量表段正确放置 SRAM 起始地址。
- 核查预编译宏是否添加
- 编译链接报错:SRAM 空间不足
- 全部代码 + 常量 + RW+ZI + 堆栈总大小,不能超过芯片 SRAM 实际大小;裁剪固件或者更换 SRAM 更大型号 MCU。
- Keil 复位之后无法重新跑起来
- 确认 run.ini 脚本已经正确配置,调试选项加载 ini 脚本;确认 Flash 下载算法全部移除。
- Flash 已经全部擦除,调试可以运行,断电复位不能运行
- 现象属于预期行为:本方案是调试器动态加载 AXF 镜像到 SRAM;硬件上电复位 SRAM 内容丢失,不能作为上电自启动方案;上电自启动需要 Bootloader 把 bin 拷贝进 SRAM 再跳转。
- IAR 修改 icf 之后编译报段放置错误
- 核对 icf 脚本 ROM 区域修改为 SRAM 起始地址,
.intvec向量表段放置 SRAM 首地址。
- 核对 icf 脚本 ROM 区域修改为 SRAM 起始地址,
5. 小结
- 将 STM32 全部代码运行于 SRAM,多用于 IAP 解密临时代码调试;该调试方案依靠调试器把镜像加载进 SRAM,芯片上电复位 SRAM 内容丢失,不能直接实现上电自动从 SRAM 启动;文档不介绍 System‑Bootloader 加载 bin 到 SRAM 业务流程。
- IAR 重点:修改 icf 链接脚本映射 ROM 到 SRAM,增加
VECT_TAB_SRAM向量表宏。 - Keil MDK 重点:自定义 SRAM 分散加载 sct 脚本,配置 run.ini 调试初始化脚本,移除 Flash 下载算法,增加向量表切换宏。
- 必须重定向中断向量表 VTOR;不配置 VECT_TAB_SRAM,触发中断直接 HardFault。
- 验证标准:擦除 Flash,调试下代码可运行;汇编窗口确认 PC 指针在 SRAM 地址;硬件复位程序停止执行。
6. FAQ
Q:调试器可以把代码下载 SRAM 跑,那产品上电可以直接从 SRAM 运行吗?
A:不行。SRAM 是非易失存储器,复位掉电数据丢失。产品上电想要 SRAM 运行,需要 Bootloader/System‑Bootloader 把 bin 镜像拷贝到 SRAM,再跳转执行,本文只讲调试器调试场景。
Q:只需要部分函数跑在 SRAM,需要这套全部配置吗?
A:不需要。部分函数 SRAM 运行,只需要修改链接脚本把指定函数段放到 RAM,向量表依然保留 Flash,不需要 VECT_TAB_SRAM 宏。
Q:VECT_TAB_SRAM 宏的作用是什么?
A:在 SystemInit 内部配置内核 VTOR 寄存器,告诉内核中断向量表现在存放在 SRAM,中断响应才可以正常寻址向量表。
免责声明:本文全部基于 ST 官方 LAT1705 文档,不同 STM32 系列 SRAM 起始地址、大小存在差异,链接脚本务必匹配芯片参考手册内存映射。
363