安全启动和安全固件更新(SBSFU)10:双镜像机制 – 活动槽与下载槽的协同工作

第10篇:双镜像机制 -- 活动槽与下载槽的协同工作

本文基于 X-CUBE-SBSFU v2.6.2,硬件平台 NUCLEO-G474RE,加密方案 ECC384 + AES256-CBC + SHA384。

本文面向零基础读者,深入解释 SBSFU 双镜像(2_Images)配置的核心机制。


1. 为什么需要双镜像?

在嵌入式系统中,固件更新是一个高风险操作。我们先来看看不同方案在面对断电、传输错误等场景时的表现。

1.1 单镜像的致命缺陷

单镜像方案只有一个固件存储区域。更新流程只能是:

复制代码
Step 1: 设备进入 Bootloader 模式(停止正常运行)
Step 2: 擦除唯一的固件存储区
Step 3: 接收新固件 → 写入 → 验证
Step 4: 跳转执行新固件

这带来了三个致命问题:

问题一:下载期间"死机"

擦除旧固件后、写入新固件之前,设备内没有可执行的程序。如果此时掉电,设备将永远无法正常启动,俗称"变砖"(bricked)。对于部署在偏远地区的 IoT 设备,这意味着一笔昂贵的现场维护成本。

问题二:下载时无法执行业务逻辑

从擦除到写入完成,设备处于"Loader 模式",不能执行任何用户任务。对一个需要 7x24 小时运行的工业控制设备,几分钟的停机时间可能不可接受。

问题三:不支持固件验证和回滚

如果新固件有 bug,没有旧固件可以退回去。单镜像方案中没有"上一版本"可供恢复。

1.2 双镜像如何解决这些问题

双镜像方案的核心思想是:下载和运行分离

复制代码
┌──────────────────────────────────────────────────────┐
│                     Flash 存储区                       │
│                                                      │
│  ┌─────────────┐   ┌─────────────┐   ┌────────────┐ │
│  │ Active Slot  │   │  Swap Area  │   │Download Slot│ │
│  │  (运行中)    │   │  (交换缓存)  │   │  (新固件)   │ │
│  │  216 KB     │   │   8 KB      │   │  216 KB     │ │
│  └─────────────┘   └─────────────┘   └────────────┘ │
│        ↑                                      ↑       │
│        │                                      │       │
│   正常执行用户程序                   后台下载新固件     │
│   (不能擦除/覆盖)                   (不影响当前运行)    │
└──────────────────────────────────────────────────────┘
  • 下载期间不中断业务:UserApp 从 Active Slot 运行,新固件写入 Download Slot,两者在物理 Flash 上完全隔离。
  • 断电安全:如果在下载过程中断电,Download Slot 中的数据可能损坏,但 Active Slot 中的旧固件完好无损。下次上电后可以重新下载。
  • 支持回滚:新固件安装后如果验证失败,可以将 Active Slot 中的旧固件换回来。

回顾 readme.txt 中的关键说明:

To download a PartialUserApp.sfb, it is mandatory to have previously installed the UserApp.sfb identified as reference into the device.

这说明双镜像机制不仅支持完整更新,还支持"有依赖关系的差分更新"。


2. 双镜像的内存布局

2.1 实际地址映射

mapping_fwimg.h 中提取的真实地址分配:

c 复制代码
/* Active slot #1  (216 kbytes) */
#define SLOT_ACTIVE_1_START    0x08010000
#define SLOT_ACTIVE_1_END      0x08045FFF

/* swap  (8 kbytes) */
#define SWAP_START             0x08046000
#define SWAP_END               0x08047FFF

/* Dwl slot #1  (216 kbytes) */
#define SLOT_DWL_1_START       0x08048000
#define SLOT_DWL_1_END         0x0807DFFF

/* Slots not configured (本配置中全部为0) */
#define SLOT_ACTIVE_2_START    0x00000000
#define SLOT_ACTIVE_3_START    0x00000000
#define SLOT_DWL_2_START       0x00000000
#define SLOT_DWL_3_START       0x00000000

2.2 可视化布局

复制代码
STM32G474RE Flash: 512 KB (0x08000000 - 0x0807FFFF)

0x08000000 ┌──────────────────────────┐
           │ SE Core + SBSFU          │ ← 安全引导代码(64KB)
0x08010000 ├──────────────────────────┤
           │                          │
           │  Active Slot #1          │ ← 当前运行的固件
           │  (216 KB)               │    经过签名验证+解密
           │  0x08010000-0x08045FFF   │    用户程序从这里执行
           │                          │
0x08046000 ├──────────────────────────┤
           │  Swap Area (8 KB)        │ ← 原子交换的临时缓冲区
           │  0x08046000-0x08047FFF   │    存放交换过程中被搬移的页面
0x08048000 ├──────────────────────────┤
           │                          │
           │  Download Slot #1        │ ← 新固件下载区
           │  (216 KB)               │    用户程序通过YMODEM写入
           │  0x08048000-0x0807DFFF   │    收到的是加密的.sfb文件
           │                          │
0x0807E000 ├──────────────────────────┤
           │  (8 KB 保留/未使用)       │ ← 对齐预留
0x08080000 └──────────────────────────┘ ← Flash 结束

2.3 各区域功能详解

区域 起始地址 大小 功能 保护级别
Active Slot 0x08010000 216 KB 存储已安装且通过验证的固件。SBSFU 每次启动时验证其签名,验证通过后才跳转执行。 正常(不可写保护,因为 Swap 时需要擦除和重写)
Download Slot 0x08048000 216 KB 接收新固件的目标区域。UserApp 在运行期间通过 YMODEM 协议将 .sfb 文件写入这里。 无保护(UserApp 可以自由写入)
Swap Area 0x08046000 8 KB 固件安装时的交换缓冲区。类似于"交换两个变量的中间变量",保证交换过程即使断电也能恢复。 仅 SBSFU 访问

3. 固件安装流程(Swap 方式详解)

当 UserApp 完成下载并复位后,SBSFU 进入安装流程。以下是每一步的详细分解。

3.1 整体流程概览

复制代码
UserApp 下载完成 → SFU_APP_InstallAtNextReset() → NVIC_SystemReset()
    ↓
SBSFU 启动
    ↓
检测 Download Slot 中有新固件(FwImageState == NEW)
    ↓
[SE] 验证新固件头 ECDSA 签名
    ↓
[SE] 解密新固件(AES256-CBC)
    ↓
[SE] 验证新固件完整性(SHA384 对比 FwTag)
    ↓
执行 Swap:将 Download Slot 内容交换到 Active Slot
    ↓
验证交换后的 Active Slot 固件
    ↓
跳转执行新固件

3.2 Swap 过程逐步详解

Swap 是双镜像机制中最精妙的设计。它不是简单地"把 Download Slot 复制到 Active Slot",而是通过分页的原子交换来保证断电安全。

为什么不能直接覆盖?
复制代码
问题场景:直接覆盖 Active Slot
━━━━━━━━━━━━━━━━━━━━━━━━━━━
Step 1: 擦除 Active Slot 第 0 页     ← 旧固件已经损坏
Step 2: 写入新固件第 0 页            ← 掉电!!!
结果:Active Slot 破损,Download Slot 内容还在但无法单独启动
Swap 的分页交换策略

Swap 的每一步都保证"至少有一个完整固件存在于某个槽中"。使用 Swap Area 作为 8KB 的中间缓冲区:

复制代码
初始状态:
  Active Slot:  [A0][A1][A2][A3]...[An]    (旧固件,每页 4KB)
  Swap Area:    [空]
  Download Slot: [D0][D1][D2][D3]...[Dn]   (新固件)

交换过程(逐页进行):
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Step 1: 页 0 的交换
  1a. 将 Active Slot 页 0 复制到 Swap Area
      Active:  [A0][A1][A2]...    Swap: [A0]    DWL: [D0][D1][D2]...
      状态标记:ACTIVE_PAGE0→SWAP (如果此时断电,Active 完整)

  1b. 将 Download Slot 页 0 复制到 Active Slot 页 0 的位置
      (先擦除 Active 页 0,再写入 D0)
      Active:  [D0][A1][A2]...    Swap: [A0]    DWL: [D0][D1][D2]...
      状态标记:DWL_PAGE0→ACTIVE

  1c. 将 Swap Area 内容复制到 Download Slot 页 0
      (先擦除 DWL 页 0,再写入 A0)
      Active:  [D0][A1][A2]...    Swap: [A0]    DWL: [A0][D1][D2]...
      状态标记:SWAP→DWL_PAGE0

Step 2: 页 1 的交换
  2a. Active 页 1 → Swap Area
  2b. DWL 页 1 → Active 页 1
  2c. Swap → DWL 页 1
      Active:  [D0][D1][A2]...    Swap: [A1]    DWL: [A0][A1][D2]...

... 重复直到所有页交换完成 ...

最终状态:
  Active Slot:  [D0][D1][D2][D3]...[Dn]    (新固件)
  Swap Area:    [空/任意]
  Download Slot: [A0][A1][A2][A3]...[An]   (旧固件)

4. Swap 机制的原子性保证

4.1 状态标记机制

SBSFU 在每一小步操作前后都会更新状态标记 。这些标记存储在 Flash 的特定位置(固件头中的 FwImageState 字段),确保断电后恢复执行时能够识别当前进度。

关键概念:状态标记必须是原子的,且存储在非易失性存储器中。SBSFU 使用 Flash 中专门的区域存储交换进度。

4.2 断电恢复场景分析

复制代码
场景 A:在 Step 1a 之后断电(Active 页0 → Swap Area 完成,尚未擦除 Active 页0)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
上电后 SBSFU 检测到:
  - Swap Area 中有数据(状态标记指示"正在交换")
  - Active Slot 页 0 未被擦除

恢复策略:继续执行 Step 1b,将 DWL 页 0 写入 Active 页 0
最终结果:Active 完整,交换正常完成

场景 B:在 Step 1b 之后断电(DWL 页 0 已写入 Active 页 0,尚未写回 A0 到 DWL)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
上电后 SBSFU 检测到:
  - Active 页 0 已更新为 D0
  - Swap Area 中仍有 A0
  - 状态标记指示"需要将 Swap 写回 DWL"

恢复策略:执行 Step 1c,将 Swap Area 中的 A0 写入 DWL 页 0
最终结果:交换完整(Active 有 D0,DWL 有 A0)

场景 C:在 Step 1c 之后断电(页 0 交换完全完成)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
上电后 SBSFU 检测到:
  - 页 0 交换完成标记
  - 继续页 1 的交换

恢复策略:从页 1 开始继续交换
最终结果:所有页交换完成

核心原则:任何时候,旧固件的所有数据页要么在 Active Slot 中,要么在 Download Slot 中,要么在 Swap Area 中。永远不会出现"一页数据同时丢失"的情况。Swap Area 充当了一页数据的"安全中转站"。

4.3 SFU_NO_SWAP 编译选项

如果定义了 SFU_NO_SWAP,则跳过 Swap 机制,直接将新固件写入 Active Slot。这种模式下没有断电保护,但节省了 Swap Area 的空间。本项目使用默认的 Swap 方式。

4.1 Trailer 结构大小公式

Swap Area 底部的 Trailer 区域存储交换进度和状态。其大小计算遵循以下公式:

复制代码
Trailer_Size = (HeaderSize × 2) + (32 × 2) + ((N1 + N2) × Flash_Write_Len)

其中:
  HeaderSize       = SE_FW_HEADER_TOT_LEN (232 字节,本方案)
  32 × 2           = Clean 标记 (32B) + Image Resume (32B) --- 两段 0x55 编码
  N1               = Active Slot 的 Flash 页数 (108)
  N2               = Download Slot 的 Flash 页数 (108)
  Flash_Write_Len  = FLASH_IF_MIN_WRITE_LEN (8 字节,G4 双字编程)
  
  实际计算:
    = 232×2 + 32×2 + (108+108)×8
    = 464 + 64 + 216×8
    = 464 + 64 + 1728
    = 2256 字节 ≈ 2.2KB

Trailer 占据了 Swap Area (8KB) 中的最后约 2.2KB,其余约 5.8KB 用于逐页缓冲。这个大小经过精心计算,确保即使是最坏情况(全部 216 页都需要记录交换状态),也有足够的存储空间。

Clean 标记为什么是 0x55?

复制代码
0xFF = Flash 擦除后的自然状态(全 1)
0x00 = 全 0,可能与其他"已编程为 0"的状态混淆
0x55 = 0b01010101,交替的 0/1 位模式

优势:
  · 可以明确区分"从未写入"(0xFF) vs "已编程为 Clean"(0x55)
  · 0xAA (0b10101010) 也常用,但 0x55 在噪声环境下的误判概率更低
  · 任何中间状态(部分编程)既不是 0xFF 也不是 0x55
    → SBSFU 可判定为"Swap 被中断,需要恢复"

5. 固件状态管理(FwImageState)

5.1 固件头的 FwImageState 字段

每个固件槽的头部包含 FwImageState[3][32],这是一个 3x32 字节的数组,存储了 3 个槽位中各状态的副本。这些状态定义了固件的生命周期阶段。

5.2 六种固件状态

c 复制代码
// 固件状态定义(位于 se_def_metadata.h)
FWIMG_STATE_INVALID     // 状态无效(初始值或验证失败)
FWIMG_STATE_NEW         // 新固件已下载,等待安装
FWIMG_STATE_INSTALLING  // 正在安装(交换进行中)
FWIMG_STATE_INSTALLED   // 已安装,等待用户验证
FWIMG_STATE_SELFTEST    // 自检模式(首次启动)
FWIMG_STATE_VALID       // 验证通过,正常运行
FWIMG_STATE_VALID_ALL   // 所有固件验证通过(多槽位场景)

5.3 状态转换图

复制代码
                           下载新固件到 DWL Slot
  STATE_EMPTY / ──────────────────────────────────→ STATE_NEW
  STATE_INVALID                                          │
       ↑                                                 │ SBSFU 检测到后开始安装
       │                                                 ↓
       │                                          STATE_INSTALLING
       │                                                 │
       │                                        Swap 完成 │
       │                                                 ↓
       │                                          STATE_INSTALLED
       │                                                 │
       │                                   用户程序显式验证│
       │                                                 ↓
       │                                          STATE_SELFTEST
       │                                                 │
       │                              ┌──────────────────┤
       │                              │                  │
       │                         超时未验证          自检通过
       │                              │                  │
       │                              ↓                  ↓
       │                      STATE_INVALID      STATE_VALID
       │                              │                  │
       │                              │                  │
       └──────────────────────────────┘                  │
         回滚:SBSFU 执行反向 Swap                         │
                                                       正常运行

6. 固件验证与回滚

6.1 ENABLE_IMAGE_STATE_HANDLING 编译开关

这是控制固件状态管理功能的关键编译开关。默认情况下在 UserApp 示例中被注释掉(从 readme.txt 可知)。

当启用时:

  • 新固件安装后处于 SELFTEST 状态
  • 用户程序必须显式调用"固件验证"功能
  • 验证通过 → 状态变为 VALID
  • 超时未验证(如看门狗触发复位)→ 状态变为 INVALID → 触发回滚

当禁用时:

  • 安装完成后直接认为固件有效
  • 没有自检和验证环节
  • 没有自动回滚功能

6.2 验证流程代码

fw_update_app.c 中的 FW_VALIDATE_RunMenu() 函数:

c 复制代码
void FW_VALIDATE_RunMenu(void)
{
  // 仅当 ENABLE_IMAGE_STATE_HANDLING 已定义时此功能生效
  // 否则输出 "Feature not supported !"

  // 验证子菜单:
  //   0 - 验证所有固件
  //   1 - 验证 SLOT_ACTIVE_1
  //   2 - 验证 SLOT_ACTIVE_2 (本配置不适用)
  //   3 - 验证 SLOT_ACTIVE_3 (本配置不适用)

  // 核心调用: SE_APP_ValidateFw() 或 FW_VALIDATE_ValidateFw()
  se_retCode = SE_APP_ValidateFw(&se_Status, slot_number);
}

static SE_ErrorStatus FW_VALIDATE_ValidateFw(SE_StatusTypeDef *peSE_Status, uint32_t SlotNumber)
{
  // 获取当前固件状态
  e_ret_status = FW_VALIDATE_GetActiveFwState(peSE_Status, current_slot, &current_fw_state);

  if (e_ret_status == SE_SUCCESS)
  {
    // 只有处于 SELFTEST 状态的固件才能被验证
    if (current_fw_state != FWIMG_STATE_SELFTEST)
    {
      printf("Firmware not is SELF_TEST state\r\n");
      e_ret_status = SE_ERROR;
    }
    else
    {
      if (next_fw_state == FWIMG_STATE_VALID_ALL)
        FWIMG_STATE[current_slot - SLOT_ACTIVE_1] = FWIMG_STATE_TAG_VALID_ALL;
      else
        FWIMG_STATE[current_slot - SLOT_ACTIVE_1] = FWIMG_STATE_TAG_VALID;

      e_ret_status = SE_SUCCESS;
    }
  }
  return e_ret_status;
}

注意状态标记的存储方式:FWIMG_STATE 实际上是一个位于 RAM 中的数组,用于快速访问。真实的状态存储在 Flash 固件头中。在受保护的配置下(SFU_MPU_PROTECT_ENABLESFU_SECURE_USER_PROTECT_ENABLE),这个 RAM 数组受 MPU 保护,只有通过 SE 接口才能修改。

6.3 回滚流程

当 SBSFU 启动检测到当前固件状态为 INVALID 时:

复制代码
SBSFU 检测到 Active Slot 固件状态 = INVALID
    ↓
触发反向 Swap:将 Download Slot 中的旧固件换回 Active Slot
    ↓
验证交换后的 Active Slot 固件(旧固件)的签名
    ↓
擦除 Download Slot 内容(清除无效的新固件)
    ↓
跳转到旧固件执行

7. 从用户程序触发固件下载

sfu_app_new_image.c 提供了三个核心 API,它们被编译在 UserApp 的上下文中:

7.1 SFU_APP_GetDownloadAreaInfo() -- 获取下载槽信息

c 复制代码
void SFU_APP_GetDownloadAreaInfo(uint32_t DwlSlot, SFU_FwImageFlashTypeDef *pArea)
{
  pArea->DownloadAddr = SlotStartAdd[DwlSlot];
  // DwlSlot = SLOT_DWL_1 → 返回 0x08048000

  pArea->MaxSizeInBytes = (uint32_t)SLOT_SIZE(DwlSlot);
  // 返回 216 * 1024 = 221184 字节

  pArea->ImageOffsetInBytes = SFU_IMG_IMAGE_OFFSET;
  // 固件头偏移,通常是 4096 (4KB 对齐)
}

SlotStartAdd[]SLOT_SIZE() 宏定义在 sfu_fwimg_regions.h 中,它们的值来自链接器映射文件(mapping_fwimg.h),确保 UserApp 下载的数据写入正确的物理地址。

7.2 SFU_APP_InstallAtNextReset() -- 标记安装请求

c 复制代码
HAL_StatusTypeDef SFU_APP_InstallAtNextReset(uint8_t *fw_header)
{
#if !defined(SFU_NO_SWAP)
  if (fw_header == NULL)
    return HAL_ERROR;

  // 将固件头写入 Swap Area 的起始位置
  if (WriteInstallHeader(fw_header) != HAL_OK)
    return HAL_ERROR;

  return HAL_OK;
#else
  return HAL_OK;  // 无 Swap 模式,不需要额外操作
#endif
}

static HAL_StatusTypeDef WriteInstallHeader(uint8_t *pfw_header)
{
  // 擦除 Swap Area 的固件头区域(SFU_IMG_IMAGE_OFFSET 字节)
  ret = FLASH_If_Erase_Size((void *)SlotStartAdd[SLOT_SWAP], SFU_IMG_IMAGE_OFFSET);

  if (ret == HAL_OK)
  {
    // 将新固件的头部写入 Swap Area
    ret = FLASH_If_Write((void *)SlotStartAdd[SLOT_SWAP], pfw_header, SE_FW_HEADER_TOT_LEN);
  }
  return ret;
}

这个函数将新固件的头部信息写入 Swap Area0x08046000),作为"安装请求"的标记。SBSFU 在下次启动时检查 Swap Area:

  • 如果 Swap Area 中有有效的固件头 → 说明有固件等待安装
  • SBSFU 读取这个头部,获取新固件的元数据(版本、大小、哈希等)
  • 从 Download Slot 读取加密固件,解密并验证
  • 执行 Swap 安装

8. 多个下载槽的理论扩展

X-CUBE-SBSFU 理论上支持最多 3 个 Active Slot + 3 个 Download Slot。这在以下场景中很有用:

8.1 多固件场景

复制代码
示例:一个 IoT 设备包含
  Slot Active 1 + DWL 1 → 主控 MCU 固件
  Slot Active 2 + DWL 2 → 无线通信模块固件(如 LoRa/WiFi 协议栈)
  Slot Active 3 + DWL 3 → 协处理器固件

每个槽位可以独立更新,某个模块的固件升级不影响其他模块。

8.2 当前项目配置

本示例项目(2_Images)只配置了 1 个 Active Slot 和 1 个 Download Slot:

c 复制代码
// mapping_fwimg.h 中未配置的槽位全部为 0
#define SLOT_ACTIVE_2_START    0x00000000
#define SLOT_ACTIVE_2_END      0x00000000
#define SLOT_ACTIVE_3_START    0x00000000
// ...

fw_update_app.c 中的多镜像功能检查代码验证了这一点:

c 复制代码
if (several_dwl_area == 1)
{
  printf("  -- !!Only 1 download area configured - feature not available!! \r\n\n");
  return;
}

如果需要启用更多槽位,需要:

  1. 修改 mapping_fwimg.h 和对应的链接器脚本
  2. 调整 Flash 分区大小(总 Flash 只有 512KB,多槽位意味着每个槽位更小)
  3. 重新生成所有密钥(每个槽位对应不同的 fwid
  4. 重新编译全部 3 个工程

总结

双镜像机制是 SBSFU 安全固件更新体系中最核心的设计。它通过 Active Slot、Download Slot 和 Swap Area 三个区域的分工协作,以及精细的原子交换状态标记,实现了:

  1. 下载与运行并行:用户程序从 Active Slot 运行,新固件写入 Download Slot,互不干扰
  2. 断电安全:任何时刻断电,Active Slot 中始终有一个完整的固件,设备不会变砖
  3. 自动回滚:新固件安装后可验证,验证失败自动恢复到上一个版本
  4. 差分更新支持:PartialUserApp.sfb 描述了新固件与旧固件的差异,减少传输数据量

这些特性的核心代价是 Flash 空间利用率降低了约一半(Active Slot 和 Download Slot 大小相同)。但对于安全关键的应用场景,这个代价是完全值得的。

下一篇文章将详细解析固件从 .bin 到 .sfb 的完整打包流程。


本文基于 X-CUBE-SBSFU v2.6.2 实际源代码撰写,所有地址映射和代码片段均来自项目文件 mapping_fwimg.h 和 sfu_app_new_image.c。

相关推荐
XD7429716361 小时前
科技早报晚报|2026年5月10日:Agent 安全沙箱、可审计编程代理与持久化产品上下文,今晚更值得做的 3 个开源机会
科技·安全·开源·开源项目·ai agent·开发者工具
@insist1231 小时前
信息安全工程师-入侵阻断与网络流量清洗技术详解
网络·安全·软考·信息安全工程师·软件水平考试
小小测试开发1 小时前
LLM 文档处理安全指南:如何避免 AI 静默篡改你的重要数据
人工智能·安全
vortex52 小时前
无人机系统安全攻防技术深度解析
安全·系统安全·无人机
openKylin2 小时前
紧急安全通告|Linux内核Dirty Frag漏洞(CVE-2026-43284、CVE-2026-43500)
linux·安全·web安全
柠檬威士忌9852 小时前
2026-05-10 AI前沿日报:算力、模型与安全评测同时加速
人工智能·安全
鹿角片ljp2 小时前
全局哈希去重原理与数据集实践
算法·安全·哈希算法
Paranoid-up2 小时前
安全启动和安全固件更新(SBSFU)3:加密基础
算法·安全·哈希算法·iap·安全启动·安全升级·sbsfu
Paranoid-up2 小时前
安全启动和安全固件更新(SBSFU)2:环境搭建
安全·iap·安全启动·安全升级·sbsfu