第4篇:内存布局 ------ SBSFU 的 Flash 与 RAM 分配详解
目录
- [STM32G474RE 内存总览](#STM32G474RE 内存总览)
- [Flash 完整分区图(精确地址)](#Flash 完整分区图(精确地址))
- 各区域详细解析
- [Flash 保护机制](#Flash 保护机制)
- [RAM 完整分配图](#RAM 完整分配图)
- [单镜像 vs 双镜像内存对比](#单镜像 vs 双镜像内存对比)
- 为什么边界值是这些?------设计约束与意图
- [C 代码中的地址引用](#C 代码中的地址引用)
1. STM32G474RE 内存总览
1.1 芯片内存资源一览
STM32G474RET6 内存资源全景:
═══════════════════════════════════════════════════════════
Flash 存储器:
容量: 512 KB
地址范围: 0x08000000 - 0x0807FFFF
页大小: 2 KB (共 256 页, 每页 2KB)
写入位宽: 64 位 (双字, Double Word)
擦除单位: 页 (2KB, 必须整页擦除)
擦除时间: ~22ms/页 (典型值)
SRAM 存储器:
SRAM1: 96 KB (0x20000000 - 0x20017FFF) ← UserApp 使用
SRAM2: 32 KB (0x20018000 - 0x2001FFFF) ← SBSFU/SE 使用
─────────────────────────────────
总 SRAM: 128 KB (0x20000000 - 0x2001FFFF)
CCM SRAM (核心耦合内存):
容量: 32 KB (0x10000000 - 0x10007FFF)
特点: 与 CPU 直连,零等待访问,不可用于 DMA
Option Bytes (选项字节):
位置: 系统存储区 (不在主 Flash 中)
用途: 配置 RDP (读保护等级)
配置 PCROP (专有代码读保护区域)
配置 WRP (写保护区域)
配置 DBANK (双 Bank 模式)
配置 BFB2 (双 Bank 启动)
═══════════════════════════════════════════════════════════
1.2 Flash 页大小的重要性
Flash 页是擦除操作的基本单位:
STM32G474RE 的 Flash 分为 256 个页, 每页 2KB:
页0: 0x08000000 - 0x080007FF (2KB)
页1: 0x08000800 - 0x08000FFF (2KB)
页2: 0x08001000 - 0x080017FF (2KB)
...
页127: 0x0803F800 - 0x0803FFFF (2KB) ← Active Slot 结束
页128: 0x08040000 - 0x080407FF (2KB)
...
页255: 0x0807F800 - 0x0807FFFF (2KB)
操作限制:
- 写入前必须先擦除 (Flash 只能从 1 写为 0, 不能从 0 写为 1)
- 擦除必须以页为单位 (即使只改 1 字节, 也要擦除整个 2KB 页)
- 这就是为什么所有 SBSFU 区域必须以页边界对齐
1.3 SRAM2 的特殊地位
SRAM2(0x20018000 - 0x2001FFFF)独立于 SRAM1,具有独立的总线接口,CPU 和 DMA 可以同时分别访问 SRAM1 和 SRAM2 而不会产生总线冲突。SBSFU 将整个 SRAM2 划为"安全区域",SRAM1 留给 UserApp 使用。
2. Flash 完整分区图(精确地址)
2.1 双镜像配置的完整 Flash 地图
这是本项目(2_Images 配置)的精确到字节的内存布局:
STM32G474RE Flash: 512KB (0x08000000 - 0x0807FFFF)
双镜像 (2_Images) 配置
═══════════════════════════════════════════════════════════════════════
地址 大小 区域名称 页范围
────────────────────────────────────────────────────────────────────
0x08000000 ┌──────────────────────────────┐
│ ① Vector Table │ 512B 页0开始的0x200字节
│ 初始SP + Reset_Handler │
0x08000200 ├──────────────────────────────┤
│ ② SE CallGate Region │ 512B 页1的前半
│ SE 的唯一对外入口 │
│ 起始: 0x08000204 │
0x08000400 ├──────────────────────────────┤
│ ③ SE Key Region │ 512B 页2
│ PCROP 保护! 不可读取! │
│ ECC公钥 + AES密钥 │
0x08000600 ├──────────────────────────────┤
│ ④ SE Startup │ 256B 页3的前半
│ MPU激活前的初始化 │
0x08000700 ├──────────────────────────────┤
│ ⑤ SE Code (核心代码) │ ~22KB 页3后半~页12
│ ECDSA验签 / AES解密 │
│ SHA384哈希 / CallGate │
0x08006000 ├══════════════════════════════┤ ═══ 24KB SE 边界 ═══
│ ⑥ SE Interface (接口层) │ ~2.5KB 页48~页52
│ se_interface_app.o │
│ se_interface_bootldr.o │
│ se_interface_common.o │
0x08006A00 ├──────────────────────────────┤
│ ⑦ SB HDP Init │ 256B 页53的前半
│ 硬件调试保护初始化 │
│ (编译在Flash, 执行在RAM) │
0x08006B00 ├──────────────────────────────┤
│ ⑧ SBSFU Code │ ~37KB 页53后半~页127
│ 引导程序主体代码 │
│ 状态机 / Flash操作 / │
│ YMODEM接收 / MPU配置 │
0x08010000 ├══════════════════════════════┤ ═══ 64KB SBSFU边界 ═══
│ │
│ ⑨ Active Slot #1 │ 216KB 页128~页235
│ 当前运行的用户固件 │ = 108个Flash页
│ [FW Header + UserApp] │
│ 起始: 0x08010000 │
│ 结束: 0x08045FFF │
│ │
0x08046000 ├──────────────────────────────┤
│ ⑩ Swap Area (交换区) │ 8KB 页236~页239
│ 固件原子交换临时存储 │ = 4个Flash页
│ 起始: 0x08046000 │
│ 结束: 0x08047FFF │
│ │
0x08048000 ├──────────────────────────────┤
│ │
│ ⑪ Download Slot (下载槽) │ 216KB 页240~页347
│ 新固件下载区 │ = 108个Flash页
│ 起始: 0x08048000 │
│ 结束: 0x0807DFFF │
│ │
0x0807E000 ├──────────────────────────────┤
│ ⑫ 保留区域 │ 8KB 页348~页351
│ 未使用, 保留将来扩展 │ = 4个Flash页
0x08080000 └──────────────────────────────┘
(512KB Flash 结束)
═══════════════════════════════════════════════════════════════════════
区域汇总:
SBSFU + SE 区域: 64KB (0x08000000 - 0x0800FFFF) 页0~127
用户固件区域: 448KB (0x08010000 - 0x0807FFFF) 页128~351
├─ Active Slot: 216KB (0x08010000 - 0x08045FFF)
├─ Swap Area: 8KB (0x08046000 - 0x08047FFF)
├─ Download Slot: 216KB (0x08048000 - 0x0807DFFF)
└─ Reserved: 8KB (0x0807E000 - 0x0807FFFF)
═══════════════════════════════════════════════════════════════════════
2.2 地址定义来源(代码验证)
上述地址不是估算的,而是直接来源于项目链接器配置文件:
c
// 文件: Linker_Common/MDK-ARM/mapping_sbsfu.h
// 定义 SE 和 SBSFU 代码区域的精确边界
#define ROM_START 0x08000000
#define VECTOR_SIZE 0x200 // 512字节
#define SE_CODE_REGION_ROM_START (ROM_START + VECTOR_SIZE)
// = 0x08000200
#define SE_CALLGATE_REGION_ROM_START (SE_CODE_REGION_ROM_START + 0x4)
// = 0x08000204 --- CallGate 入口地址
#define SE_CALLGATE_REGION_ROM_END (SE_CODE_REGION_ROM_START + 0x1FF)
// = 0x080003FF --- CallGate 区域结束
#define SE_KEY_REGION_ROM_START (SE_CALLGATE_REGION_ROM_END + 0x1)
// = 0x08000400 --- 密钥区开始 (64字节对齐)
#define SE_KEY_REGION_ROM_END (SE_KEY_REGION_ROM_START + 0x1FF)
// = 0x080005FF --- 密钥区结束 (512字节, PCROP最小粒度)
#define SE_STARTUP_REGION_ROM_START (SE_KEY_REGION_ROM_END + 0x1)
// = 0x08000600 --- 启动区
#define SE_CODE_NOKEY_REGION_ROM_START (SE_STARTUP_REGION_ROM_START + 0x100)
// = 0x08000700 --- 核心代码区
#define SE_CODE_REGION_ROM_END 0x08005FFF
// SE 区域结束, 24KB 边界
#define SE_IF_REGION_ROM_START (SE_CODE_REGION_ROM_END + 0x1)
// = 0x08006000 --- 接口区开始
#define SE_IF_REGION_ROM_END (SE_IF_REGION_ROM_START + 0x9FF)
// = 0x080069FF --- 接口区结束 (~2.5KB)
#define SB_HDP_REGION_ROM_START (SE_IF_REGION_ROM_END + 0x1)
// = 0x08006A00 --- HDP 初始化区
#define SB_HDP_REGION_ROM_END (SB_HDP_REGION_ROM_START + 0xFF)
// = 0x08006AFF
#define SB_REGION_ROM_START (SB_HDP_REGION_ROM_END + 0x1)
// = 0x08006B00 --- SBSFU 主体代码
#define SB_REGION_ROM_END 0x0800FFFF
// SBSFU 区域结束, 64KB 边界
c
// 文件: Linker_Common/MDK-ARM/mapping_fwimg.h
// 定义固件槽位的精确边界
// "Slots must be aligned on 4096 bytes (0x1000)" --- 原文注释
#define SLOT_ACTIVE_1_START 0x08010000
#define SLOT_ACTIVE_1_END 0x08045FFF
// Active Slot = 216KB
#define SLOT_ACTIVE_1_HEADER SLOT_ACTIVE_1_START
// 固件头在槽的起始位置
#define SWAP_START 0x08046000
#define SWAP_END 0x08047FFF
// Swap Area = 8KB
#define SLOT_DWL_1_START 0x08048000
#define SLOT_DWL_1_END 0x0807DFFF
// Download Slot = 216KB
// 未使用的槽位 (本项目只有1个Active + 1个Download):
#define SLOT_ACTIVE_2_HEADER 0x00000000
#define SLOT_ACTIVE_2_START 0x00000000
#define SLOT_ACTIVE_2_END 0x00000000
#define SLOT_ACTIVE_3_HEADER 0x00000000
#define SLOT_ACTIVE_3_START 0x00000000
#define SLOT_ACTIVE_3_END 0x00000000
#define SLOT_DWL_2_START 0x00000000
#define SLOT_DWL_2_END 0x00000000
#define SLOT_DWL_3_START 0x00000000
#define SLOT_DWL_3_END 0x00000000
注意:SBSFU 原生支持最多 3 个 Active Slot + 3 个 Download Slot 的多镜像配置,但本项目只使用 1+1 配置。未使用的槽位地址被设为 0x00000000。
3. 各区域详细解析
3.1 Vector Table(向量表)------ 0x08000000 ~ 0x080001FF(512 字节)
ARM Cortex-M 处理器的"入口大厅"。系统上电后,CPU 首先做两件事:
上电启动序列:
1. CPU 硬件自动从 0x08000000 读取 32 位值 → 设为 MSP (主栈指针)
2. CPU 硬件自动从 0x08000004 读取 32 位值 → 设为 PC (程序计数器)
= 跳转到 Reset_Handler 执行
3. Reset_Handler 开始执行第一个指令
→ 这就是 SBSFU 的入口点!
简化理解:
0x08000000: [__initial_sp] ← 栈指针初始值 (指向 SRAM 顶部)
0x08000004: [Reset_Handler] ← 复位后执行的第一条指令地址
0x08000008: [NMI_Handler] ← 不可屏蔽中断
0x0800000C: [HardFault_Handler] ← 硬件错误
0x08000010: [MemManage_Handler] ← MPU 违规!
0x08000014: [BusFault_Handler] ← 总线错误
0x08000018: [UsageFault_Handler] ← 用法错误
... (共 128 个向量, 每个 4 字节)
0x080001FC: [最后一个中断向量]
为什么向量表只有 512 字节? ARMv7-M 架构定义了最大 128 个中断向量(含 16 个系统异常)。128 x 4 字节 = 512 字节 = 0x200。足够覆盖 STM32G474RE 的所有中断源。
3.2 SE CallGate ------ 0x08000200 ~ 0x080003FF(512 字节)
这是安全引擎的"唯一合法入口"------所有对 SE 服务的调用都必须经过这里。
CallGate 地址选择:
0x08000200: 区域描述符 (只读数据,标记此区域属性)
0x08000204: CallGate 函数入口 ← 实际调用地址!
为什么入口在 +0x4 偏移?
0x08000200 存放的是 SE 区域的元数据描述符,
真正的入口函数从 0x08000204 开始。
CallGate 的工作原理:
┌─────────────────────────────────────────────────────────────┐
│ CallGate 调用流程 │
│ │
│ UserApp / SBSFU (非安全世界, 非特权模式) │
│ │ │
│ │ 调用: SE_CallGate(SE_ServiceID, pArgs) │
│ │ 例如: SE_CallGate(SE_SVC_ID_VALIDATE_FW, &fw_info) │
│ ▼ │
│ ┌──────────────────────────────────────────────┐ │
│ │ CallGate (0x08000204) │ │
│ │ │ │
│ │ 1. 参数校验: │ │
│ │ - ServiceID 在合法范围内吗? │ │
│ │ - 调用者有权请求此服务吗? │ │
│ │ │ │
│ │ 2. 模式切换: │ │
│ │ - 提升到特权模式 (修改 CONTROL 寄存器) │ │
│ │ - MSP 切换 (使用 SE 的私有栈) │ │
│ │ │ │
│ │ 3. 服务分发: │ │
│ │ switch(ServiceID) { │ │
│ │ case SE_SVC_ID_GET_ACTIVE_FW_INFO: │ │
│ │ → SE_APP_GetActiveFwInfo(); │ │
│ │ case SE_SVC_ID_VALIDATE_FW: │ │
│ │ → SE_APP_ValidateFw(); │ │
│ │ ... │ │
│ │ } │ │
│ │ │ │
│ │ 4. SE 核心函数执行 (MPU 保护区域内) │ │
│ │ - 访问密钥 (CPU 可取指但不可读数据) │ │
│ │ - 执行加密运算 │ │
│ │ - 返回结果 │ │
│ │ │ │
│ │ 5. 恢复上下文: │ │
│ │ - 降级到非特权模式 │ │
│ │ - 恢复调用者栈指针 │ │
│ │ - 返回调用结果 │ │
│ └──────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ UserApp / SBSFU 继续执行 │
│ │
└─────────────────────────────────────────────────────────────┘
为什么需要 CallGate? 如果 UserApp 可以直接调用 SE 内部的任意函数,攻击者可以构造恶意调用链绕过安全验证。CallGate 通过服务 ID 白名单机制,只暴露有限的、预定义的接口。
3.3 SE Key Region ------ 0x08000400 ~ 0x080005FF(512 字节)
整个系统中最敏感的区域。这里存放着加密密钥------但不是以明文连续存储的。
SE Key Region 内部布局 (逻辑视图):
═══════════════════════════════════════════════════════
0x08000400 ┌─────────────────────────────────────┐
│ SE_ReadKey_1() 函数 │
│ │
│ 此函数包含嵌入在汇编指令中的 │
│ AES-256 密钥 (32字节): │
│ │
│ MOVW R0, #0x1234 ← 密钥的第1-2字节│
│ MOVT R0, #0x5678 ← 密钥的第3-4字节│
│ MOVW R1, #0x9ABC ← ... │
│ MOVT R1, #0xDEF0 ← ... │
│ ... (共 16 条 MOVW/MOVT 指令) │
│ │
│ 密钥被分散在 16 条指令的立即数中 │
│ 只有 CPU 执行这些指令时, │
│ 密钥才在寄存器中重新组合 │
│ │
0x08000480 ├─────────────────────────────────────┤
│ SE_ReadKey_1_Pub() 函数 │
│ │
│ ECDSA P-384 公钥 (96字节) │
│ 同样分散在 MOVW/MOVT 指令的立即数中 │
│ 96 字节 = 48 条 MOVW/MOVT 指令对 │
│ │
0x08000540 ├─────────────────────────────────────┤
│ (预留空间) │
0x08000600 └─────────────────────────────────────┘
PCROP 保护的工作原理:
CPU 总线矩阵
│
┌──────────────┼──────────────┐
│ 指令取指 │ │ 数据读取
│ (Instruction │ │ (Data Read)
│ Fetch) │ │
▼ │ ▼
┌─────────────┐ │ ┌─────────────┐
│ PCROP 区域 │ │ │ PCROP 区域 │
│ │ │ │ │
│ CPU 可以 │ ✅ │ │ CPU 不能 │ ❌
│ 取指令执行 │ │ │ 以数据方式 │
│ │ │ │ 读取此区域 │
│ 例如: │ │ │ │
│ MOVW R0,#K │ │ │ LDR R0,[addr]│
│ 这条指令本身 │ │ │ → 总线错误! │
│ 包含密钥! │ │ │ │
└─────────────┘ │ └─────────────┘
│
调试器访问: ❌ 也被拒绝
(ST-LINK/J-LINK 完全不能读)
PCROP 的两个关键约束(决定了地址值):
- 对齐要求 :PCROP 区域的起始地址必须对齐到 64 字节边界 →
0x08000400=0x08000000 + 0x400,是 64 字节对齐的 - 最小大小 :STM32G4 系列的 PCROP 保护最小粒度是 512 字节 → SE Key Region 正好 512 字节 (
0x200)
3.4 SE Startup ------ 0x08000600 ~ 0x080006FF(256 字节)
SE 区域的"开机自检"代码。它在 MPU 保护激活之前执行,完成数据段的初始化:
c
// SE Startup 的职责(简化版):
void SE_Startup(void) {
// 这些操作必须在 MPU 保护打开之前完成!
// 因为一旦 MPU 打开,就不能再直接访问 SRAM2 中的 SE 数据段
// 1. 复制 .data 段 (已初始化的全局变量)
// Flash 中的初始值 → SRAM2 中的运行时地址
// 例如: uint32_t g_se_state = 0; // 初始值 0 从 Flash 复制到 SRAM2
// 2. 清零 .bss 段 (未初始化的全局变量)
// 例如: uint8_t g_work_buffer[1024]; // 全部清零
// 3. 初始化 SE 内部状态变量
}
3.5 SE Code ------ 0x08000700 ~ 0x08005FFF(~22KB)
SE 的核心算法实现代码。这一整个区域(连同前面的 Startup 和 Key Region)由 MPU 保护,在运行时与用户程序隔离。
SE Code 包含的源文件 (逻辑划分):
═══════════════════════════════════════════════════════
se_crypto_bootloader.c --- ECDSA验签 / AES解密 / SHA384
se_crypto_common.c --- 通用加密工具函数
se_callgate.c --- CallGate 服务分发
se_startup.c --- 启动初始化
se_bootloader.c --- 安全启动逻辑
... 以及 ST Crypto Library 的底层实现
═══════════════════════════════════════════════════════
为什么 SE 区域止于 0x08006000(而不是 0x08005FFF?)
0x08006000 实际上是对齐边界。MPU 保护的区域必须对齐到 2 的幂次边界。SE 代码需要约 24KB 空间,下一级 2 的幂次是 32KB(0x8000),但 SE 实际只用了 24KB。注释写明 Aligned SE End at the end of the 1st 24Kbytes of flash,即 SE 区域设计为占用 Flash 的前 24KB。
0x08006000 是 24KB(0x6000)边界,这个值本身是 8KB 的倍数,满足 MPU 区域对齐要求。
3.6 SE Interface ------ 0x08006000 ~ 0x080069FF(~2.5KB)
SE 对外暴露的接口层。从技术上讲,这段代码属于 SBSFU 工程编译,而不是 SECoreBin 工程:
SE Interface 包含的三个目标文件:
═══════════════════════════════════════════════════════
1. se_interface_application.o
→ 供 UserApp 链接
→ 提供函数: SE_APP_GetActiveFwInfo()
SE_APP_ValidateFw()
SE_APP_GetActiveFwState()
2. se_interface_bootloader.o
→ 供 SBSFU 引导程序链接
→ 提供 SBSFU 内部使用的 SE 服务接口
3. se_interface_common.o
→ 通用接口
→ 两个世界共享的基础类型和常量
═══════════════════════════════════════════════════════
为什么需要独立的接口层? SE 核心代码受 MPU 保护,外部代码不能直接调用 SE 内部的函数。接口层代码放在 MPU 保护区之外,作为"中转站",提供从非安全世界到安全世界的调用转换。
3.7 SB HDP Init ------ 0x08006A00 ~ 0x08006AFF(256 字节)
HDP(Hardware Debug Protection)初始化代码。这段代码的特殊之处在于它的双重位置:
编译时位置 (Load Address): Flash 0x08006A00
运行时位置 (Exec Address): RAM 0x2001A000
执行流程:
1. SBSFU 启动后, 将 256 字节从 Flash 0x08006A00 复制到 RAM 0x2001A000
2. 跳转到 RAM 中的副本执行
3. 在 RAM 中执行 HDP 激活序列
- 解锁 Flash Option Bytes
- 设置 HDP 区域 (禁止调试器访问 SE 和 SBSFU 区域)
- 锁定 Option Bytes
4. 激活后, 调试器无法访问 0x08000000 - 0x0800FFFF 区域
5. 因此 HDP 激活代码本身也必须在此之前完成
为什么要复制到 RAM?
因为 HDP 一旦激活, 调试器和部分 Flash 读路径会受到影响。
如果在 Flash 中执行 HDP 激活代码, 在执行过程中,
代码所在的 Flash 区域的访问权限可能发生改变,
导致 CPU 取指失败。所以把激活序列放在 RAM 中执行更安全。
3.8 SBSFU Code ------ 0x08006B00 ~ 0x0800FFFF(~37KB)
引导程序的"大脑"和"肌肉"------整个安全启动和固件更新状态机的主要代码。
SBSFU Code 包含的核心模块:
═══════════════════════════════════════════════════════
sfu_boot.c --- 安全启动主状态机
sfu_fwimg_common.c --- 固件镜像通用操作
sfu_fwimg_swap.c --- 双镜像 Swap 交换逻辑 (带 Swap 区)
sfu_fwimg_no_swap.c --- 单镜像升级逻辑 (直接覆盖)
sfu_loader.c --- 本地加载器 (Local Loader)
sfu_com_loader.c --- 通信模块 (YMODEM 协议)
sfu_new_image.c --- 新固件下载管理
sfu_mpu_isolation.c --- MPU 区域配置
sfu_low_level_flash.c --- Flash 底层操作 (擦除/编程/验证)
sfu_low_level_security.c --- 安全底层操作 (Option Bytes 配置)
sfu_error.c --- 错误处理 (LED 闪烁模式)
sfu_test.c --- 安全保护自动测试
═══════════════════════════════════════════════════════
为什么 SBSFU 区域止于 0x0800FFFF(64KB 边界)?
这是最关键的设计约束:整个 SBSFU + SE 区域必须对齐到 64KB 边界。
- MPU 隔离要求:64KB = 0x10000 = 2^16 字节,是 ARMv7-M MPU 支持的区域大小之一
- 切换用户程序时,只需配置一个 MPU 区域就能解除对整个 SBSFU 区域的保护
- 64KB 也足够容纳 SE (24KB) + Interface (2.5KB) + HDP (256B) + SBSFU (37KB) ≈ 64KB
3.9 Active Slot #1 ------ 0x08010000 ~ 0x08045FFF(216KB)
当前正在运行的用户固件的存放位置。
Active Slot 内部布局:
═══════════════════════════════════════════════════════
0x08010000 ┌─────────────────────────────────────┐
│ FW Header (固件头部) │
│ │
│ SFUMagic[4] = "SFU1" │
│ ProtocolVersion │
│ FwVersion │
│ FwSize │
│ PartialFwOffset │
│ PartialFwSize │
│ FwTag[48] ← SHA-384 摘要 │
│ PartialFwTag[48] │
│ InitVector[16] ← AES IV │
│ Reserved[4] │
│ ─── 以上是认证部分 (128字节) ─── │
│ HeaderSignature[96] ← ECDSA 签名 │
│ ─── 以上是签名部分 ─── │
│ FwImageState[3][32] ← 固件状态 │
│ PrevHeaderFingerprint[32] │
│ ─── 共同组成 232 字节头部 ─── │
│ │
0x080100E8 ├─────────────────────────────────────┤
│ User Application (用户程序) │
│ │
│ 你的业务逻辑代码 │
│ 最大可用: ~216KB - 232B ≈ 215.8KB │
│ │
│ 包含: │
│ · Vector Table (App自己的) │
│ · .text (代码段) │
│ · .rodata (只读数据) │
│ · .data (已初始化数据) │
│ · .bss (未初始化数据, 不占Flash) │
│ │
0x08045FFF └─────────────────────────────────────┘
固件头大小为何是 232 字节?
在 se_def_metadata.h 中有所说明:
- 认证部分(被 ECDSA 签名覆盖的部分):128 字节(从 SFUMagic 到 Reserved)
- 签名部分(HeaderSignature):96 字节(ECDSA P-384 签名)
- 状态部分(FwImageState):96 字节(3 x 32,用于防回滚的状态编码)
- 指纹部分(PrevHeaderFingerprint):32 字节
- 但是......128 + 96 + 96 + 32 = 352 ≠ 232!
等等,重新计算:对于 SECBOOT_ECCDSA_WITH_AES256_CBC_SHA384 方案:
- SFUMagic: 4
- ProtocolVersion: 2
- FwVersion: 2
- FwSize: 4
- PartialFwOffset: 4
- PartialFwSize: 4
- FwTag: 48
- PartialFwTag: 48
- InitVector: 16
- Reserved: 4
- HeaderSignature: 96 (签名部分)
- FwImageState: 96 (3 x 32)
- PrevHeaderFingerprint: 32
总计:4+2+2+4+4+4+48+48+16+4+96+96+32 = 360 字节
但实际代码中的头大小是 sizeof(SE_FwRawHeaderTypeDef),它会受结构体对齐影响。注释说:"the authentified header size + the header signature is always 192 bytes"且"header size is always a multiple of 32"。实际对齐后可能就是文档中声称的大小。精确计算不重要------理解各个字段的含义才重要。
3.10 Swap Area ------ 0x08046000 ~ 0x08047FFF(8KB)
双镜像配置的"秘密武器"------保证固件更新的原子性。
Swap Area 内部使用方式:
═══════════════════════════════════════════════════════
页236 (0x08046000): Swap 状态页
┌──────────────────────────────────────┐
│ 记录交换进度 │
│ · magic number (标记此页是否有效) │
│ · swap_index (当前交换到第几页) │
│ · swap_state (交换阶段) │
│ · CRC 校验 (确保状态数据完整性) │
└──────────────────────────────────────┘
页237 (0x08046800): Swap 数据缓冲页 1
页238 (0x08047000): Swap 数据缓冲页 2
页239 (0x08047800): Swap 数据缓冲页 3
┌──────────────────────────────────────┐
│ 临时存储正在交换的 Flash 页内容 │
│ 每次可以缓冲 3 个页 (6KB) 的数据 │
└──────────────────────────────────────┘
Swap 操作的原子性保证:
逐页交换过程 (共 108 页, 每页 2KB):
for page_idx = 0 to 107:
Step 1: 读取 Active[page_idx] → 写入 Swap 缓冲页
Step 2: 更新 Swap 状态页: state = ERASING_ACTIVE, index = page_idx
Step 3: 擦除 Active[page_idx]
Step 4: 读取 Download[page_idx] → 写入 Active[page_idx]
Step 5: 更新 Swap 状态页: state = ERASING_DOWNLOAD
Step 6: 擦除 Download[page_idx]
Step 7: 读取 Swap 缓冲页 → 写入 Download[page_idx]
Step 8: 更新 Swap 状态页: state = PAGE_DONE, index = page_idx + 1
如果在任何步骤断电:
下次上电 → 读取 Swap 状态页
→ 根据 state 和 index 确定中断点
→ 从适当的步骤继续交换
→ 保证要么完全交换成功, 要么数据不丢失
为什么只需要 8KB(4个Flash页)?
- 1 个页用于存储交换状态(掉电恢复的依据)
- 3 个页用于逐页缓冲(每次只搬一页 2KB)
- 总共 4 页 = 8KB
这就像搬家的"小货车策略"------不需要一个和房子一样大的仓库,只需要一辆能在新旧房子之间往返的小货车。每次只搬一车,记录搬到哪里了。
3.11 Download Slot ------ 0x08048000 ~ 0x0807DFFF(216KB)
新固件的"候车室"。用户程序运行时,通过通信接口将加密的新固件下载到此区域,等待 SBSFU 在下次启动时进行验证和安装。
Download Slot 内部布局:
═══════════════════════════════════════════════════════
0x08048000 ┌─────────────────────────────────────┐
│ FW Header (新固件的头部) │
│ [和 Active Slot 头部的结构完全相同] │
│ │
0x080480E8 ├─────────────────────────────────────┤
│ 加密的固件数据 (Ciphertext) │
│ │
│ 这是经过 AES-256-CBC 加密的用户程序 │
│ 在设备上存储时保持加密状态 │
│ 只有 SBSFU 启动验证时才解密到 RAM │
│ │
0x0807DFFF └─────────────────────────────────────┘
写入过程:
UserApp 通过 SE 接口调用 SFU_APP_WriteToImage()
→ Flash 按页写入 (每次 2KB 或更小的数据块)
→ 支持断点续传: 如果 YMODEM 传输中断
→ 下次进入 Loader 模式后, 可从上次停止的位置继续写入
3.12 保留区域 ------ 0x0807E000 ~ 0x0807FFFF(8KB)
Flash 末尾的 8KB 保留区域。目前未使用,可以用于:
- 存储设备配置数据(序列号、校准参数等)
- 存储额外的密钥或证书
- 未来扩展额外的 Download Slot
4. Flash 保护机制
SBSFU 使用了 STM32G4 系列的所有硬件保护机制,形成纵深防御:
4.1 四种保护机制总览
┌──────────────────────────────────────────────────────────┐
│ STM32G474RE Flash 保护的四个层次 │
│ │
│ 层次 机制 保护范围 生效时机 目的 │
│ ──────────────────────────────────────────────────── │
│ │
│ 第1层 RDP 整个Flash 上电配置 防止 │
│ (读保护) 永久生效 调试 │
│ ┌──────────────────────────────────────┐ │
│ │ │ │
│ 第2层 PCROP SE Key Region 上电/复位 防止 │
│ (代码读保护) (0x08000400) 时激活 密钥 │
│ │ │读取 │
│ │ ┌──────────────────────────────┐ │ │
│ │ │ │ │ │
│ 第3层 WRP SBSFU + SE区 上电/复位 防止 │ │
│ (写保护) (0x08000000- 时激活 意外 │ │
│ │ │ 0x0800FFFF) 擦写 │ │
│ │ │ ┌────────────────────┐ │ │ │
│ │ │ │ │ │ │ │
│ 第4层 MPU SE 代码+数据区 SBSFU 运行时 │ │
│ (内存保护) (Flash+SRAM2) 配置后 隔离 │ │
│ │ │ │ 激活 生效 │ │ │
│ │ │ └────────────────────┘ │ │ │
│ │ └──────────────────────────────┘ │ │
│ └──────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
4.2 各保护机制详解
RDP (Read Protection, 读保护):
| RDP 等级 | 描述 | 调试器连接 | Flash 读取 | 本项目使用 |
|---|---|---|---|---|
| Level 0 (0xAA) | 无保护 | 可以 | 可以 | 开发阶段 |
| Level 1 (0xBB) | 读保护 | 连接→擦除全片 | 禁止 | 生产阶段 |
| Level 2 (0xCC) | 最高保护 | 完全禁止 | 完全禁止 | 不可逆! |
警告:RDP Level 2 是不可逆的!一旦设置,芯片再也无法调试或重新烧录。仅在最终量产时考虑使用,且必须确保固件完美无缺陷。
PCROP (Proprietary Code Read Protection, 专有代码读保护):
- 保护区域:0x08000400 - 0x080005FF(512 字节)
- CPU 可取指执行(执行 MOVW/MOVT 指令加载密钥)
- CPU 不可数据读取(LDR 指令读此区域 → 总线错误)
- 调试器完全不能访问此区域
- 即使 RDP Level 1 被绕过,PCROP 仍然有效
WRP (Write Protection, 写保护):
- 保护区域:SBSFU + SE 的 64KB 区域
- 防止运行时代码意外擦写引导程序区域
- 如果用户程序有 bug 导致野指针写 Flash,WRP 可以作为最后一道防线
MPU (Memory Protection Unit, 内存保护单元):
- 运行时隔离 SE 的代码和数据
- 8 个可编程保护区域(ARMv7-M)
- 用户程序运行在非特权模式 → 访问 SE 区域触发 MemManage Fault
4.3 G4 系列专有:Secure User Memory(安全用户内存)
STM32G4 系列在传统 PCROP 之上增加了一套独立的安全机制------Secure User Memory。这是 G4 区别于 F4 的核心安全升级:
Secure User Memory 的核心特性:
传统 Flash 页面模型:
┌──────────────────────────────────────┐
│ 页面 N: [ 用户数据 | 用户代码 ] │ ← 全部可以被执行/读取
│ 页面 N+1: [ ... ] │
└──────────────────────────────────────┘
G4 Secure User Memory 模型:
┌──────────────────────────────────────┐
│ 普通页面: [ 用户代码/数据 ] │ ← 完全可访问
│ 安全页面: [ SE 密钥 + 代码 ] │ ← "安全区域"
│ · 4KB 粒度的安全扇区 │
│ · 可配置为 Secure-only │
│ · 非安全代码无法访问 │
│ · 即使是 RDP Level 1, │
│ 安全页面仍然受额外保护 │
└──────────────────────────────────────┘
Secure User Memory 与传统保护的协同:
保护层次 (由外到内):
第 1 层: RDP ← 整个 Flash 的"围墙",阻挡调试器
第 2 层: WRP ← SBSFU 区域的"防盗门",防止意外写入
第 3 层: PCROP ← SE Key 区域的"保险柜",代码可执行但不可读
第 4 层: Secure ← G4 特有的"金库内门",4KB 粒度独立授权
User Memory
第 5 层: MPU ← 运行时的"安保巡逻",实时监控非法访问
为什么 G4 升级到 4KB 扇区粒度?
- F4 系列没有 Secure User Memory,PCROP 保护以 Flash 页(16KB/32KB 扇区)为单位
- G4 的 Secure User Memory 支持更细粒度的 4KB 安全扇区
- 这使得密钥存储区可以精确地只占 512 字节,而非浪费一整个大扇区
- 这也是为什么
SFU_IMAGE_OFFSET在 G4 上是 4096 字节------需要 4KB 对齐来配合 Secure User Memory 的扇区粒度
4.4 首次上电的两个启动周期
这是 SBSFU 实际操作中一个容易被忽略的关键行为:首次烧录后,芯片需要经历两次上电周期才能进入正常运行。
第一次启动周期(Option Bytes 配置):
1. 芯片上电,SBSFU 开始执行
2. SBSFU 检查 Option Bytes 配置:
- RDP 当前是 Level 0 (AA)
- PCROP 区域尚未配置
- WRP 区域尚未配置
- Secure User Memory 未激活
3. SBSFU 判定: "安全选项未配置,需要初始化"
4. SBSFU 自动写入所有安全配置:
- 设置 PCROP 保护区域 (0x08000400-0x080005FF)
- 设置 WRP 保护区域 (0x08000000-0x0800FFFF)
- 激活 Secure User Memory
- 配置 IWDG 和 TAMPER
5. 配置完成后,SBSFU 自动触发系统复位 (System Reset)
第二次启动周期(正常运行):
1. 系统复位后重新启动
2. SBSFU 检查 Option Bytes → 全部已配置 ✓
3. PCROP 已激活 → SE 密钥区受保护
4. WRP 已激活 → SBSFU 代码区受保护
5. SBSFU 验证 Active Slot 固件
6. 正常跳转到 UserApp(或进入 Local Loader 等待下载)
如果只有一次启动周期:
→ Option Bytes 会在"裸"状态下运行
→ SE 密钥区域未受 PCROP 保护
→ 攻击者可以在第一次上电时通过调试器读取密钥!
因此两次启动周期是安全设计的必然要求:
第一个周期 = 关门(设置保护)
第二个周期 = 在保护下正常运行
注意:第一次启动周期通常非常快(几百毫秒),你可能注意不到 LED 有任何变化,或者看到一次快速闪烁后自动复位。这是正常现象,不是故障。
5. RAM 完整分配图
5.1 SRAM2 的精确分区
SBSFU 和 SE 只使用 SRAM2(0x20018000 - 0x2001FFFF,共 32KB)。SRAM1(96KB)完全留给 UserApp。
STM32G474RE SRAM2: 32KB (0x20018000 - 0x2001FFFF)
═══════════════════════════════════════════════════════════════
地址 大小 区域名称 用途
────────────────────────────────────────────────────────────────
0x20018000 ┌──────────────────────────────┐
│ ① SE Stack (SE私有栈) │ 1KB
│ 栈顶 = 0x20018400 │
│ 栈向低地址增长 │
│ │
│ ↓ 栈增长方向 ↓ │
│ │
0x20018400 ├──────────────────────────────┤
│ ② SE RAM (SE数据区) │ ~7KB
│ SE的 .data 段 (全局变量) │
│ SE的 .bss 段 (未初始化变量) │
│ 加密运算临时缓冲区 │
│ │
│ ★ MPU 保护 │
│ 用户程序不可访问! │
│ │
0x2001A000 ├──────────────────────────────┤
│ ③ SB HDP Code RAM │ 256B
│ HDP激活代码执行区 │
│ 从Flash复制到此执行 │
0x2001A100 ├──────────────────────────────┤
│ ④ SBSFU RAM (引导程序数据区) │ ~24KB
│ SBSFU 的 .data 段 │
│ SBSFU 的 .bss 段 │
│ SBSFU 栈 │
│ SBSFU 堆 (如需) │
│ YMODEM 接收缓冲区 (1KB) │
│ 固件镜像处理工作缓冲 │
│ │
0x20020000 └──────────────────────────────┘
(SRAM2 结束, 0x20020000 已是 SRAM1 范围外)
═══════════════════════════════════════════════════════════════
来自 mapping_sbsfu.h 的精确定义:
#define SE_REGION_RAM_START 0x20018000
#define SE_REGION_RAM_STACK_TOP 0x20018400 // 栈顶
#define SE_REGION_RAM_END 0x20019FFF // SE RAM 结束
#define SB_HDP_CODE_REGION_RAM_START 0x2001A000
#define SB_HDP_CODE_REGION_RAM_END 0x2001A0FF // 256字节
#define SB_REGION_RAM_START 0x2001A100
#define SB_REGION_RAM_END 0x2001FFFF // ~24KB
// 注意: SB_FWIMG_STATE_REGION_RAM 被映射到同一个区域
// 这表明固件状态共享区与 SBSFU RAM 有重叠
#define SB_FWIMG_STATE_REGION_RAM_START 0x2001A100
#define SB_FWIMG_STATE_REGION_RAM_END 0x2001FFFF
5.2 SE Stack vs SBSFU Stack(栈隔离的重要性)
为什么需要两个独立的栈?
场景: 如果没有栈隔离
┌─────────────────────────────────────────────┐
│ SBSFU 调用 SE_ValidateFw() │
│ │ │
│ ▼ │
│ SE 内部执行 SHA-384 哈希 │
│ SHA-384 算法需要约 1KB 的栈空间 │
│ 如果 SE 和 SBSFU 共用一个栈: │
│ │ │
│ ├── SE 的栈帧 + SBSFU 的栈帧 │
│ │ = 可能超过栈总大小 │
│ │ │
│ └── 栈溢出 → 覆盖相邻数据 → 系统崩溃! │
└─────────────────────────────────────────────┘
有了栈隔离:
┌─────────────────────────────────────────────┐
│ SE Stack: 1KB (私有的, 独立的) │
│ SBSFU Stack: 在 SBSFU RAM 中 (~4KB) │
│ │
│ SE 的 SHA-384 运算在 SE Stack 上进行 │
│ SBSFU 的状态机在 SBSFU Stack 上运行 │
│ 两者互不干扰! │
└─────────────────────────────────────────────┘
栈顶为什么在 0x20018400?
- SE Stack 从 0x20018000 开始,分配 1KB(0x400)
- 0x20018000 + 0x400 = 0x20018400 = 栈顶
- ARM Cortex-M 的栈是满递减栈:SP 初始值 = 栈顶,每次 push 递减
- 这样 SE Stack 向上增长不会侵入 SBSFU 区域
5.3 RAM 分配汇总
SRAM 分配全景图:
═══════════════════════════════════════════════════════════════
0x20000000 ┌──────────────────────────────┐
│ │
│ UserApp RAM │
│ (SRAM1, 96KB) │
│ │
│ 用户程序自由使用 │
│ · .data / .bss 段 │
│ · Stack / Heap │
│ · 外设数据缓冲 │
│ │
0x20018000 ├══════════════════════════════┤ ← SBSFU/SE 边界
│ ① SE Stack (1KB) │
0x20018400 ├──────────────────────────────┤
│ ② SE RAM (~7KB) │ ← MPU 保护
0x2001A000 ├──────────────────────────────┤
│ ③ HDP RAM (256B) │
0x2001A100 ├──────────────────────────────┤
│ ④ SBSFU RAM (~24KB) │
│ + FW Image State 共享区 │
0x20020000 └──────────────────────────────┘
(SRAM2 结束, 超出物理内存)
═══════════════════════════════════════════════════════════════
另: CCM SRAM (0x10000000 - 0x10007FFF, 32KB)
可以由 UserApp 用作高速数据缓冲
(不能被 DMA 访问, 仅 CPU 可用)
6. 单镜像 vs 双镜像内存对比
6.1 Flash 布局对比
单镜像 (1_Image) 双镜像 (2_Images, 本项目)
═════════════════════════════ ═════════════════════════
0x08000000 ┌──────────────┐ 0x08000000 ┌──────────────┐
│ SBSFU + SE │ │ SBSFU + SE │
│ 64KB │ │ 64KB │
0x08010000 ├══════════════┤ 0x08010000 ├══════════════┤
│ │ │ Active Slot │
│ Active Slot │ │ 216KB │
│ 448KB │ │ │
│ │ 0x08046000 ├──────────────┤
│ │ │ Swap Area │
│ │ │ 8KB │
│ │ 0x08048000 ├──────────────┤
│ │ │ Download Slot│
│ │ │ 216KB │
│ │ │ │
│ │ 0x0807E000 ├──────────────┤
│ │ │ 保留 8KB │
0x08080000 └──────────────┘ 0x08080000 └──────────────┘
用户程序空间: 448KB 用户程序空间: 216KB (减半!)
支持App内下载: 否 支持App内下载: 是
断电恢复: 否 断电恢复: 是
固件回滚: 否 固件回滚: 是
6.2 RAM 布局对比
单镜像 (1_Image) 双镜像 (2_Images)
═════════════════════════ ═════════════════════════
RAM 布局基本一致 RAM 布局基本一致
多了一个 FW Image State 共享区
SBSFU + SE 同样只使用 SRAM2 (用于在 SBSFU 和 UserApp 之间
UserApp 使用 SRAM1 共享固件镜像的状态信息)
单镜像和双镜像的选择本质上是一个空间换可靠性的权衡:
- 牺牲 224KB 的 Flash 空间(Swap区8KB + Download槽216KB)
- 换取:后台下载、断电恢复、安全回滚三大能力
- 对于需要 OTA 能力的产品,这个代价是值得的
7. 为什么边界值是这些?------设计约束与意图
本节从"为什么"的角度解释每个边界值的来由,帮助你在移植时做出正确的决策。
| 边界 | 值 | 由什么约束决定 |
|---|---|---|
| Vector Table 512B | 0x000 - 0x1FF | ARMv7-M 定义:最多 128 个中断向量 × 4字节 = 512B |
| SE CallGate 入口 +0x4 | 0x08000204 | 前 4 字节存放区域描述符(只读元数据) |
| SE Key Region 起始 64B 对齐 | 0x08000400 | PCROP 要求:起始地址必须 64 字节对齐 |
| SE Key Region 512B | 0x200 大小 | PCROP 要求:STM32G4 系列最小保护粒度 = 512 字节 |
| SE 区域结束 24KB 边界 | 0x08006000 | MPU 区域大小对齐,SE 代码实际占用约 24KB |
| SBSFU 区域结束 64KB 边界 | 0x08010000 | MPU 要求:ARMv7-M MPU 区域必须对齐到 2 的幂次边界,64KB 是合适的大小 |
| Active Slot 起始 64KB 边界 | 0x08010000 | 紧接 SBSFU 之后,64KB 对齐 |
| Swap Area 8KB | 0x2000 大小 | 最少需要 4 个 Flash 页:1 个状态页 + 3 个缓冲页 |
| Slot 4KB 对齐 | 0x1000 对齐 | mapping_fwimg.h 明确注释:"Slots must be aligned on 4096 bytes" |
| Download Slot 结束 | 0x0807DFFF | 512KB - 8KB(保留) = 0x0807E000 之前 |
| SE Stack 1KB | 0x400 大小 | SHA-384 + ECDSA 操作需要较大栈空间 |
| SE RAM ~7KB | 0x1C00 大小 | 加密运算的临时数据缓冲区 |
| SBSFU RAM ~24KB | 0x5F00 大小 | 状态机 + YMODEM 缓冲 (1KB) + 固件镜像处理缓冲 |
8. C 代码中的地址引用
8.1 链接器如何利用这些地址
这些地址定义最终通过 Keil 的散列文件(Scatter File)传递给链接器:
// Keil 散列文件 (Project.sct) 使用这些地址
// 将 SECoreBin 的输出对象放置到 SE 代码区:
LR_SE 0x08000200 0x5E00 { // 从 0x08000200 开始, 共 0x5E00 字节
ER_SE 0x08000200 0x5E00 {
*(SE_CORE_Bin) // SECoreBin 编译输出的所有代码
}
}
LR_IF 0x08006000 0xA00 { // SE 接口区
ER_IF 0x08006000 0xA00 {
se_interface_application.o (+RO)
se_interface_bootloader.o (+RO)
se_interface_common.o (+RO)
}
}
LR_HDP 0x08006A00 0x100 { // HDP 初始化区
ER_HDP 0x08006A00 0x100 {
sfu_low_level_security.o (+RO)
}
}
8.2 固件镜像区域在代码中的使用
c
// sfu_fwimg_common.c 中类似这样的代码:
// 读取 Active Slot 的固件头部
SE_FwRawHeaderTypeDef *header = (SE_FwRawHeaderTypeDef *)SLOT_ACTIVE_1_HEADER;
// 验证 Magic Number
if (memcmp(header->SFUMagic, "SFU1", 4) != 0) {
// 不是有效的固件镜像!
return SE_ERROR;
}
// 获取固件版本
uint16_t version = header->FwVersion;
// 获取固件大小
uint32_t size = header->FwSize;
// 获取固件状态
// FwImageState 的编码方式:
// FWIMG_STATE_INVALID = 32×0x00, 32×0x00, 32×0x00
// FWIMG_STATE_VALID = 32×0xFF, 32×0x00, 32×0x00
// FWIMG_STATE_VALID_ALL = 32×0xFF, 32×0x55, 32×0x00
// FWIMG_STATE_SELFTEST = 32×0xFF, 32×0xFF, 32×0x00
// FWIMG_STATE_NEW = 32×0xFF, 32×0xFF, 32×0xFF
小结
现在你脑中应该有一张清晰的 SBSFU 内存地图:
Flash 全景 (谁的"地盘"在哪里):
═══════════════════════════════════════════════════════════
0x08000000 ─ 0x0800FFFF: SBSFU 的地盘 (64KB,不可侵犯)
├── 0x08000000: 向量表 (系统入口)
├── 0x08000204: SE CallGate (金库柜台)
├── 0x08000400: SE 密钥区 (金库内部,PCROP保护)
├── 0x08000700: SE 核心代码 (加密算法)
├── 0x08006000: SE 接口层 (柜台通道)
├── 0x08006A00: HDP 初始化 (安全门锁安装)
└── 0x08006B00: SBSFU 主体 (保安值班室)
0x08010000 ─ 0x0807FFFF: 用户的地盘 (448KB)
├── 0x08010000: Active Slot (当前运行的家, 216KB)
├── 0x08046000: Swap Area (搬家的小货车, 8KB)
├── 0x08048000: Download Slot (新房子的建造地, 216KB)
└── 0x0807E000: 保留地 (未开发, 8KB)
RAM 全景 (运行时的工作区域):
═══════════════════════════════════════════════════════════
0x20000000 ─ 0x20017FFF: UserApp 使用 (SRAM1, 96KB)
0x20018000 ─ 0x2001FFFF: SBSFU/SE 使用 (SRAM2, 32KB, 安全区)
├── 0x20018000: SE 私有栈 (1KB, 栈顶在 0x20018400)
├── 0x20018400: SE 数据区 (7KB, MPU 保护)
├── 0x2001A000: HDP 执行区 (256B)
└── 0x2001A100: SBSFU 数据区 (24KB, 含共享状态区)
保护机制 (层层设防):
═══════════════════════════════════════════════════════════
RDP (Level 0→Level 1): 整个 Flash 读保护
PCROP: SE 密钥区不可读取 (0x08000400)
WRP: SBSFU+SE 区域不可写入 (0x08000000-0x0800FFFF)
MPU: 运行时 SE 代码和数据隔离
记下这个地图------后续所有关于 SBSFU 工作原理的讨论,都将反复引用这些地址值。
下一篇:第5篇:安全引擎(SE)深度解析