保护链:开发 / 量产 / SECURE_LOCK 三种模式
SBSFU 深耕系列 · 11
· 原理 + 源码双线 · 以 STM32G4 为主线
上一篇回顾 · 本篇路径
第 10 篇我们把 mapping_sbsfu.icf 的每个段都讲透了------所有 PCROP / WRP / Secure User Memory 的地址都已确定。
但「地址确定 」≠「保护已启用」。SBSFU 工程实际上有 3 种「保护强度」:
- 开发模式(DEV_MODE):保护几乎全关,方便调试
- 量产模式(PRODUCTION):保护全开,但 RDP 留在 L1(仍可调试)
- SECURE_LOCK 模式 :RDP 升 L2,调试口永久关闭------这一步是不可逆的
本篇要把这 3 种模式的完整状态机画出来,并讲清楚从一个模式过渡到下一个的具体步骤。
本篇核心问题:
- 3 种模式分别启用 / 禁用了哪些保护?工程师在每个模式下能做什么?
SECBOOT_OB_DEV_MODE这个宏到底改了哪些代码行为?- SBSFU 启动时的「保护检查」在不同模式下的反应是什么?
- 产线烧录的「最后一刀」(升 RDP-L2)什么时候打?怎么打?
- 如果中间某个保护配错了,怎么诊断 + 恢复?
读完后你应该能为你的产品设计完整的「开发 → 验证 → 量产」流水线。
1. 3 种模式速览表
| 模式 | RDP | PCROP | WRP | Secure User Mem | BOOT_LOCK | 调试器能干啥 |
|---|---|---|---|---|---|---|
| DEV_MODE | L1 或 L0 | 关闭 / 可读 | 关闭 / 可写 | 关闭 / 可见 | 可关 | 全功能:读 Flash / 改代码 / 单步调试 PCROP 区 |
| PRODUCTION | L1(仍可调试) | 启用 | 启用 | 启用(G4) | 启用 | 受限:连接 OK,但读不到 PCROP,不能改 SBSFU 段 |
| SECURE_LOCK | L2(不可逆) | 启用 | 启用 | 启用 | 启用 | 完全无法连接------SWD 永久断开 |
⚠️ 关键不可逆点:L1 → L2 是单向的,全世界没有任何方式能把它降回 L1(除了破坏芯片)。
SBSFU 的产线流程必须:
- DEV_MODE 开发功能
- PRODUCTION 验证所有功能
- SECURE_LOCK 量产烧录最后一步打
一旦上 SECURE_LOCK,这块板子就不能再返厂调试。所以这步要等 100% 验证 OK。
2. DEV_MODE:开发期间的「无保护」状态
2.1 代码层面:SECBOOT_OB_DEV_MODE 宏
打开 Projects/.../Inc/app_sfu.h,能看到这一行:
c
/* 开发模式开关 */
#define SECBOOT_OB_DEV_MODE /* 注释掉就是量产模式 */
打开这个宏后,SBSFU 内部很多代码行为会变化。最关键的一段在 sfu_low_level_security.c:
c
SFU_ErrorStatus SFU_LL_SECU_CheckApplyStaticProtections(void)
{
/* 读当前选项字节 */
HAL_FLASHEx_OBGetConfig(&obConfig);
if (obConfig.RDPLevel != OB_RDP_LEVEL_2) {
#ifdef SECBOOT_OB_DEV_MODE
/* 开发模式:自动把 RDP 升到 L1(如果当前是 L0) */
if (obConfig.RDPLevel == OB_RDP_LEVEL_0) {
obConfig.OptionType = OPTIONBYTE_RDP;
obConfig.RDPLevel = OB_RDP_LEVEL_1;
HAL_FLASH_OB_Unlock();
HAL_FLASHEx_OBProgram(&obConfig);
HAL_FLASH_OB_Launch(); /* 触发 reset,再次进入此函数时 RDP=L1 已生效 */
}
/* 注意:DEV_MODE 下不会自动升 L2 */
#else
/* 量产模式:RDP < L2 直接拒绝启动 */
return SFU_ERROR_RDP_NOT_OK;
#endif
}
/* 类似地处理 WRP / PCROP / SEC_SIZE 等其他选项字节 */
...
return SFU_SUCCESS;
}
💡 DEV_MODE 的「自动修复」逻辑 :开发模式下,SBSFU 启动时如果发现 OB 配置不对,会自动改成正确的值 + 触发 OBL_Launch reset。第二次启动时 OB 已经正确,SBSFU 继续走流程。
这种「自我修复」设计让开发者第一次烧入 SBSFU 后只需要复位一下,剩下的让 SBSFU 自己配。
2.2 DEV_MODE 启用 / 禁用的保护对照表
| 保护 | DEV_MODE 行为 |
|---|---|
| RDP | 自动升到 L1(不升到 L2) |
| WRP | 配置但不严格------可以 OB 改 |
| PCROP | 不启用(让调试器能读 SE Code 段) |
| Secure User Memory | 不启用(G4 上 SEC_SIZE = 0) |
| BOOT_LOCK | 通常关闭(允许从 SRAM 启动调试) |
| MPU | 启用,但 region 配置较松 |
| IWDG | 不启用(避免调试时 reset) |
| DAP 关闭 | 不调用 |
| Tamper | 不启用 |
2.3 DEV_MODE 适合干什么
- 第一次跑通 SBSFU 框架:让 3 工程编译 + 烧录 + 启动跑起来
- 调试 SE 内部逻辑:因为 PCROP 关闭,调试器能反汇编 SE 代码、设断点
- 调试 Swap 算法:可以在 Swap 中途断点、检查 Flash 内容
- 改 UserApp:业务代码调试和普通工程一样
2.4 DEV_MODE 不能做什么
- 不能验证「真实安全性」:因为保护都关着,攻击者也能轻易读 Flash
- 不能上量产:保护没启用就发货等于裸奔
- 不能验证 RDP-L2 之后的行为:那是 SECURE_LOCK 模式才有

图 1·开发模式下保护处于「禁用」状态(图源:ST《SBSFU 培训-2 · 安全模式》)
3. PRODUCTION 模式:保护全开但 RDP 留 L1
3.1 怎么进入 PRODUCTION 模式
第一步:在源码里注释掉 SECBOOT_OB_DEV_MODE 宏:
c
/* app_sfu.h */
// #define SECBOOT_OB_DEV_MODE /* 注释掉 */
第二步:手动用 STM32CubeProgrammer 配置所有选项字节:
bash
# 一次性把所有保护打开(除了 RDP,留 L1)
STM32_Programmer_CLI -c port=SWD -ob \
RDP=0xBB \ # RDP = L1(还能调试)
BOOT_LOCK=1 \
nSWBOOT0=0 \
nBOOT0=1 \
SEC_SIZE1=0x10 \ # Secure User Mem 64KB
WRP1A_STRT=0 \
WRP1A_END=15 \
PCROP1_STRT=0x10 \ # PCROP 起始 = 64B 块号 16,对应 0x08000400
PCROP1_END=0x17 \ # PCROP 终点
PCROP_RDP=1 \ # L1→L0 降级时连 PCROP 一起擦
DBANK=0 # G4 单 bank 模式
第三步:重新烧 SBSFU + UserApp------因为去掉 DEV_MODE 宏后代码变化了。
3.2 PRODUCTION 模式下能做什么
| 操作 | 是否可行 |
|---|---|
| 用 ST-Link 重新烧 Flash | ✓ 可以(L1 允许写 Flash) |
| 用 ST-Link dump SE Code | ✗ 不行(PCROP 拦) |
| 用 ST-Link 读 Slot0 用户数据 | ✗ 不行(RDP-L1 + Secure User Mem 拦) |
| 单步调试 SBSFU 代码 | △ 部分行(IWDG 启用会 reset) |
| 跑 OTA 测试 | ✓ 完整可行 |
| 跑 Anti-rollback 测试 | ✓ 可行 |
| 跑 Tamper / 入侵测试 | ✓ 可行 |
💡 PRODUCTION 模式的设计意图 :「保护全开 + 调试口尚可使用 」------这是「研发后期 + 客户试用 + 一致性测试」的最佳状态。
出了任何问题还能用 ST-Link 调试 / 重烧,但保护已经在工作。绝大多数 bug 在这个阶段能暴露出来。
3.3 SBSFU 启动期的「保护检查」会怎样反应
PRODUCTION 模式下 SBSFU 启动检查 OB:
c
#ifndef SECBOOT_OB_DEV_MODE
if (obConfig.RDPLevel != OB_RDP_LEVEL_2) {
return SFU_ERROR_RDP_NOT_OK; /* 直接拒绝 */
}
if (obConfig.WRP1AStartOffset != 0 || obConfig.WRP1AEndOffset != 0x0F) {
return SFU_ERROR_WRP_NOT_OK;
}
if ((obConfig.USERConfig & FLASH_OPTR_BOOT_LOCK) == 0) {
return SFU_ERROR_BOOT_LOCK_NOT_OK;
}
/* ... 等等 */
#endif
注意:上面这段代码默认要求 RDP = L2,但如果你想让 PRODUCTION 模式接受 RDP-L1,需要:
- 临时改这段检查,允许 RDP = L1 或 L2(自定义中间模式)
- 或者保留要求 RDP = L2,那就直接进 SECURE_LOCK 模式
ST 提供的标准 SBSFU 工程默认是「DEV_MODE 或 SECURE_LOCK 」二选一,PRODUCTION 是个中间状态,需要工程师自己定义。
4. SECURE_LOCK 模式:上 RDP-L2 之后
4.1 怎么进入 SECURE_LOCK
bash
# 假设当前是 PRODUCTION 模式(RDP=L1,其他保护已配)
# 量产线最后一刀:
STM32_Programmer_CLI -c port=SWD -ob RDP=0xCC
这一条命令瞬间发生:
- RDP 寄存器写 0xCC
- SWD/JTAG 调试口永久关闭
- 选项字节永久锁死------之后任何修改 OB 的尝试都会失败
- 复位后 CPU 从 0x08000000 起跑(BOOT_LOCK 强制)
4.2 SECURE_LOCK 模式能做什么
| 操作 | 是否可行 |
|---|---|
| ST-Link 连接 | ✗ 完全不行 |
| 重新烧 Flash | ✗ 不行(必须先降级,但 L2 不可降级) |
| OTA 升级 UserApp | ✓ 可以(这是 SBSFU 唯一允许的 Flash 写入路径) |
| Anti-rollback / Tamper / IWDG | ✓ 全部工作 |
| 出厂自检 | ✓(如果 UserApp 提供) |
| 返厂维修 | ✗ 只能换板子 |
SECURE_LOCK 是真正的「产品状态」------你卖出去的板子应该都在这个状态。
4.3 SECURE_LOCK 模式下「升级」是怎么完成的
虽然调试口关闭,但 OTA 还能工作,因为:
- SBSFU 自己跑在芯片里,有完整 Flash 写权限(属于内部代码,不通过 SWD)
- OTA 接收
.sfb→ 写 Slot1 → Swap → 验证 → 跳 UserApp,全程内部完成 - 不需要外部调试器介入
text
SECURE_LOCK 模式下的更新通道:
UserApp 业务代码
│
│ 通过 UART/USB/WiFi/BLE 接收 .sfb
▼
UserApp 调 SE_AppliRequestUpdate()
│
│ 把 .sfb 写到 Slot1 (UserApp 自己直接写 Flash)
▼
UserApp 触发 NVIC_SystemReset()
│
▼
SBSFU 重启
│ 发现 Slot1 PENDING_INSTALL
│ → Swap → 验签 → 跳新版 UserApp
▼
新版本运行

图 2·SECURE_LOCK 模式状态机(图源:ST《SBSFU 培训-2 · 状态切换》)
5. 3 个模式的完整状态机
text
芯片出厂态 (RDP=L0, 无保护)
│
│ 第一次烧 SBSFU + UserApp
▼
┌─────────────────┐
│ DEV_MODE │ ◀── 开发期反复迭代
│ RDP=L1, PCROP=关│
│ Secure Mem=关 │
└────────┬────────┘
│
│ 1. 注释 SECBOOT_OB_DEV_MODE 宏
│ 2. 重新编译 + 烧录
│ 3. STM32CubeProgrammer 配 OB(保护全开)
│ 4. 但 RDP 留 L1
▼
┌─────────────────┐
│ PRODUCTION │ ◀── 研发末期 + 量产验证
│ RDP=L1, 全保护开 │
│ 调试口仍可用 │
└────────┬────────┘
│
│ 升 RDP 到 L2
│ STM32_Programmer_CLI -ob RDP=0xCC
▼
┌─────────────────┐
│ SECURE_LOCK │ ◀── 量产 / 客户使用
│ RDP=L2 │
│ ⚠️ 不可逆 │
└─────────────────┘
│
│ 想回去?
▼
✗ 无路 --- 芯片报废或破坏性恢复
反向降级的代价:
- L2 → L1:不可能
- L1 → L0:全片擦除(包括 PCROP 区,PCROP_RDP=1 时)
- L0 → 出厂:擦完就行
所以 PRODUCTION → SECURE_LOCK 这一步是「最后一刀」,要在所有功能都验证 OK 后才打。
6. 量产烧录流水线(实务建议)
完整的产线烧录流程:
text
工厂烧录工位 (假设有 1000 块板子):
┌────────────────────────────────────────────────────────────┐
│ Stage 1: 全片擦 + 烧 SBSFU_UserApp.bin │
│ STM32_Programmer_CLI -c port=SWD -e all │
│ STM32_Programmer_CLI -c port=SWD -w SBSFU_UserApp.bin │
│ 时间:~5 秒/板 │
└────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────┐
│ Stage 2: 配置 Option Bytes(保护全开 + RDP=L1) │
│ STM32_Programmer_CLI -ob RDP=0xBB BOOT_LOCK=1 ... │
│ 时间:~2 秒/板 │
└────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────┐
│ Stage 3: 上电自检(开 board + 按测试按钮 + 等 LED 绿) │
│ - 检查 SBSFU 启动到 UserApp │
│ - 检查关键外设功能 │
│ - 检查产品功能(业务自测) │
│ 时间:~30 秒~5 分钟/板 │
│ 任何失败 → 标记为「待返修」,不进 Stage 4 │
└────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────┐
│ Stage 4: 「最后一刀」 --- 升 RDP 到 L2 │
│ STM32_Programmer_CLI -c port=SWD -ob RDP=0xCC │
│ 时间:~1 秒/板 │
│ 这一步执行完,板子立刻进 SECURE_LOCK 模式 │
└────────────────────────────────────────────────────────────┘
│
▼
发货!
💡 几个产线实务建议:
▸ Stage 3 必须严格------一旦 Stage 4 完成,板子就不能调试了。所以 Stage 3 的自检要尽量覆盖
▸ Stage 4 应该有「审核机制」------比如要求两个工位人员复核测试结果再触发 RDP=L2
▸ 保留 Stage 2 状态的板子------这是「待客户验证」时的最佳折中:保护已开但还能返厂调试
▸ 产品手册要明确 :客户拿到的板子在 SECURE_LOCK 状态,所有调试 / 维修都需要厂家
7. 模式切换时的「中途异常」处理
7.1 「我烧完 SBSFU 后没法跑 OTA」
最常见原因:Option Bytes 没配对。例如:
- WRP 配错了,SBSFU 没法擦写 Slot1
- Secure User Memory 起点不对,SBSFU 启动失败
- BOOT_LOCK 没启用,复位从其他地方启动
诊断方法:
bash
# Dump 当前 OB
STM32_Programmer_CLI -c port=SWD -ob displ
# 输出示例:
# RDP : 0xBB
# WRP1A_STRT : 0x00
# WRP1A_END : 0x0F
# PCROP1_STRT : 0x10
# PCROP1_END : 0x17
# SEC_SIZE : 0x10
# BOOT_LOCK : 1
# ...
把上面输出和预期值对比。
7.2 「我已经升 RDP-L2 了,发现 SBSFU 有 bug,能修吗?」
坏消息:板子完全无法调试,也无法重烧 SBSFU。
好消息 :只要 SBSFU 还能跑(哪怕功能不全),通过 OTA 仍然可以升级 UserApp------UserApp 业务代码可以解决很多问题。
真坏消息:如果 bug 在 SBSFU 自己里(无法启动到 UserApp):板子变砖,只能换。
教训:SBSFU 是「信任根」,任何 bug 都是产品级灾难。一定要在 DEV / PRODUCTION 模式下充分测试。
7.3 「DEV_MODE 烧上后跳不到 UserApp 怎么办」
可能原因:
- UserApp 的链接地址不对(不是 0x08010200)
- UserApp 的 Reset_Handler 改写时钟,导致 SBSFU 配的 PLL 失效
- UserApp 没有自己的 SCB->VTOR 设置
诊断方法:
- 在 SBSFU 的
SFU_BOOT_LaunchUserApp()前加 printf,看跳转地址 - 在 UserApp 的 Reset_Handler 第一条加 GPIO 拉高,看是否进来
- 用 ST-Link 在跳转后立刻 halt,看 PC 在哪
8. 安全测试 checklist:发货前必须验证
进入 SECURE_LOCK 之前,至少要验证完这些场景:
text
[ ] 正常启动 → UserApp 运行 → reset → 再次启动 OK
[ ] 故意改 Slot0 一个字节 → SBSFU 拒绝跳 UserApp
[ ] 故意改 Slot1 上的 .sfb header → SBSFU 拒绝装新 FW
[ ] OTA 升级新 FW → 装上 → 运行新版
[ ] OTA 升级中途断电 → 重新上电 → 恢复正确状态(旧版或新版)
[ ] OTA 升级新 FW → 新版自检失败 → SBSFU 回滚到旧版
[ ] 用比当前版本号低的 .sfb → SBSFU 拒绝装(anti-rollback)
[ ] 用错误的私钥签的 .sfb → SBSFU 验签失败 → 拒绝
[ ] 拔 ST-Link → 板子继续正常运行(无 ST-Link 启动 OK)
[ ] Tamper 检测:物理拆机 / 短接 Tamper 引脚 → 备份域擦除
[ ] IWDG 检测:让 SBSFU 卡 1 秒 → 自动 reset
[ ] PCROP 检测:在 UserApp 里读 SE Code 段地址 → HardFault
[ ] MPU 检测:UserApp 写 SBSFU RAM → HardFault
每一项都通过,再敲下 RDP=0xCC 的命令。
9. 「灰度切换」策略:分批升 RDP-L2
对于大批量出货,建议「分批 SECURE_LOCK」策略:
text
- 第一批 100 块板子 → PRODUCTION 模式(RDP=L1)→ 送客户「试用版」
- 收集 1-2 周客户反馈、有问题用 ST-Link 调试 / 重烧
- 改完所有 bug,再做第二批
- 第二批 1000 块 → PRODUCTION → 送客户「正式版」
- 客户反馈 OK 1 个月
- 第三批开始 → 直接 SECURE_LOCK 出货
虽然第一批没上 SECURE_LOCK,但物理隔离 + 测试合同 + 用户协议能控制风险。
10. 第 11 篇 Checklist
读完本篇你应该能不查资料回答:
- ☐ DEV_MODE / PRODUCTION / SECURE_LOCK 3 个模式分别启用 / 禁用了哪些保护?(§1)
- ☐ SECBOOT_OB_DEV_MODE 宏控制什么代码行为?(§2.1)
- ☐ DEV_MODE 适合干什么、不能干什么?(§2.3 / §2.4)
- ☐ 从 DEV_MODE 切到 PRODUCTION 需要做哪 3 件事?(§3.1)
- ☐ 为什么 PRODUCTION 是个「中间状态」?SBSFU 标准工程默认不接受怎么办?(§3.3)
- ☐ SECURE_LOCK 下还能干什么?还能 OTA 升级吗?(§4)
- ☐ 量产烧录的 4 个 Stage 分别是什么?为什么 Stage 4 要独立?(§6)
- ☐ 已经 SECURE_LOCK 的板子发现 bug 怎么办?(§7.2)
下一篇预告
《12 · 调试 / 移植 / 生产实务》------这是系列最后一篇,集中讲 3 件最「工程 」的事:① 调试技巧:怎么不复位调试 SBSFU 内部?怎么用 trace 看 SE 调用过程?② 移植:跨芯片移植的具体 step-by-step(G4 → F4 / L4 / H7)。③ 生产:签名服务器怎么搭?产线烧录工具怎么做?密钥怎么管理?读完那篇你应该能从「纸面理解 」走到「实际落地」。
本文示意图源 © STMicroelectronics,引用自 ST 官方培训资料 LAT1087/LAT1088 系列。文字部分为原创,欢迎转载并保留出处。