4 个 Fault Handler 迁入 USER CODE BEGIN 0(L3→L2):生成区恢复 CubeMX 原样,重生成零 diff
CI / L0 静态门禁 (pre-commit) (push) Has been cancelled
CI / L0.5 主机侧单测 (MSVC) (push) Has been cancelled

This commit is contained in:
2026-10-10 02:01:14 +08:00
parent e2e1d0fd42
commit 29df7bf8fb
6 changed files with 117 additions and 113 deletions
+14 -10
View File
@@ -859,7 +859,7 @@ external pin → "pin reset" (新增)
| 组件 | 上一轮落点 | **本轮最终落点** | 稳定性 |
|---|---|---|---|
| 崩溃报告实现(`hard_fault_report` 等) | 自造区 `HardFault_Report` | **`USER CODE BEGIN 0`**(文件头部,CubeMX 标准保护位,名字从未变过) | ⭐⭐ L2 真有效 |
| `HardFault_Handler` 实体 | 自造区 `HardFault_Handler` | **生成区内整函数替换** + `HARD-FAULT-ANCHOR` 锚点;生成实体经 `#define HardFault_Handler HardFault_Handler_Gen` 改名保留,链接器自动移除(`.map` 实测 `Removing ... (2 bytes)`) | ⭐ L3 审计兜底 |
| `HardFault_Handler` 实体 | 自造区 `HardFault_Handler` | 2026-10-09 起:`__asm` 实现放 **`USER CODE BEGIN 0` 保护区**(必须在 4 个 `#define` **之前**);生成实体经 `#define HardFault_Handler HardFault_Handler_Gen` 改名让位,链接器自动移除(`.map` 实测 `Removing ... (2 bytes)`),生成区保持 CubeMX 原样 | ⭐ **L2**(重生成不丢) |
> **为什么 Handler 只能放生成区**:CubeMX 本代模板把整个函数体(含 `HardFault_IRQn 0` /
> `W1_HardFault_IRQn 0`)连同函数一起重写;函数体之外的自造区不保留。查询 `.map`
@@ -948,7 +948,7 @@ external pin → "pin reset" (新增)
|---|---|---|
| `Inc/FreeRTOSConfig.h` | `Header` / `Includes` / `MESSAGE_BUFFER_LENGTH_TYPE` / `1` / `Defines` | `configASSERT`(`1`)。加固宏已迁 L1(写 `.ioc`、由模板生成),不再占标记区 |
| `Src/freertos.c` | 共 25 个,含 `Includes` / `PD` / `PTD` / `Variables` / `4` / `5` / `Application` / … | 栈溢出钩子(`4`)、malloc 失败钩子(`5`)、helper `hook_ptr_ok`(`PD`)、**任务栈核算依据 + `timeSyncTask` 属性定义(`Variables`)**、断言钩子及其余应用代码(`Application`) |
| `Src/stm32f4xx_it.c` | 共 52 个,含文件头 `0`(标准保护位)+ 各中断函数**内部**的 `<Func>_IRQn 0` / `W1_<Func>_IRQn 0` | 崩溃报告实现(`0`) |
| `Src/stm32f4xx_it.c` | 共 52 个,含文件头 `0`(标准保护位)+ 各中断函数**内部**的 `<Func>_IRQn 0` / `W1_<Func>_IRQn 0` | 崩溃报告实现 + **4 个 `__asm` Handler** + 4 个 `#define X X_Gen`(均在 `0`,**Handler 必须写在 `#define` 之前**)、`fault_handler_enable()`(`1`) |
| `Src/main.c` | 共 19 个,含 `Init` / `SysInit` / `2` / `PV` / `PFP` | 复位原因锁存(`Init`) |
| `Src/iwdg.c` | 共 7 个,含 `IWDG_Init 0/1/2` | ❌ **本次改动用不上**(见下方特例) |
| `Inc/main.h` | 共 7 个 | 引脚宏等 |
@@ -982,12 +982,13 @@ external pin → "pin reset" (新增)
| 文件 | 内容 | 为什么无法 L1/L2 |
|---|---|---|
| `Src/stm32f4xx_it.c` | 4 个 Fault Handler 实体(`HardFault` / `MemManage` / `BusFault` / `UsageFault`) | 本代模板整函数重写、函数体外无区。各带独立锚点(`HARD-FAULT-ANCHOR` / `MEMFAULT-ANCHOR` / `BUSFAULT-ANCHOR` / `USAGEFAULT-ANCHOR`)+ 审计 FAIL 项。⚠ 可用与 iwdg 同一招根治(把实现搬进 `USER CODE BEGIN 0`、写在 4 个 `#define` **之前**),尚未做 |
| `Src/freertos.c` | `.stack_size` 行内的中文注释 | 该行由 CubeMX 按 `.ioc` 生成,"值"受保护、"注释"不受。**注释是锦上添花**,丢失不影响功能(值在 `.ioc` 里);核算依据已集中到 `USER CODE BEGIN Variables` 区(L2) |
> **结论**:L3 清单只剩 **1 项**(4 个 Fault Handler 实体)——`Src/iwdg.c` 已于
> 2026-10-09 用宏改名升级为 L2(见上方硬教训②)。其余要么已在 `.ioc`(L1),
> 要么已进 `USER CODE` 区(L2)。
> **结论**:**L3 清单已清空(0 项)**——`Src/iwdg.c`(宏改名让生成调用变空操作)与
> 4 个 Fault Handler 实体(`__asm` 实现搬进 `USER CODE BEGIN 0`)已于 2026-10-09
> 先后升级为 L2(见上方硬教训②)。所有定制现在要么在 `.ioc`(L1),要么在
> `USER CODE` 区(L2)——**CubeMX 重生成应当零 diff**(只剩 `.ioc` 里没表达、
> 且落在生成区的注释类内容,见上表)。
> 剩余 1 项 WARN 是 `Src/freertos.c` 的 `timeSyncTask` 手工定义(属性块在
> `USER CODE BEGIN Variables` 区,本身受保护;WARN 只是提示"它不在 `.ioc` 里")。
@@ -1041,7 +1042,7 @@ python scripts/check_cubemx_survival.py --check # 门禁模式(pre-commit
python scripts/check_cubemx_survival.py --write # 重建基线(确认改动无误后)
```
- **清单**:18 项覆盖 P0/P1 全部防线(iwdg 延后启动、4 个 Fault Handler 采集、
- **清单**:21 项覆盖 P0/P1 全部防线(iwdg 延后启动、4 个 Fault Handler 采集、
故障使能、两个加固宏、断言改造 ×2、看门狗模块与工程挂载、复位原因锁存、
HAL 驱动与使能宏、`.ioc` 关键项)
- **已实测有效**:2026-09-29 第二次重生成时,脚本**准确检出全部 5 项被覆盖**(5 FAIL)
@@ -1053,9 +1054,11 @@ python scripts/check_cubemx_survival.py --write # 重建基线(确认改动
- **已挂 pre-commit**(`cubemx-survival` 钩子):改动 `.ioc` / `Src/` / `Inc/` /
`task_watchdog` / `.uvprojx` 时自动触发
- **注释过滤**:断言前先剥掉 C 注释,避免"注释里提到 `HAL_IWDG_Init`"被误判
(本项目多处注释刻意引用历史代码,不做过滤会恒定误报);
但**正项检查(`must_contain`)在 `wholefile` 模式下不剥注释**,
因为锚点本身就是注释(`IWDG-DEFER-START-ANCHOR` / `HARD-FAULT-ANCHOR`)。
(本项目多处注释刻意引用历史代码,不做过滤会恒定误报)。
⚠ 由此推论(2026-10-09 定):**`protected` 项的 `must_contain` 只能断言代码符号**——
锚点注释会被剥掉,断言它等于没断言。故 `iwdg-defer-start` / `hardfault-handler` /
`fault-handlers-3` 三项已全部改为 `protected`(区 `0`)+ 代码符号断言
(如 `iwdg_deferred_start`、`__asm void HardFault_Handler`、`#define X X_Gen`)。
脚本内以 `probe`(正项)/ `probe_neg`(禁项)分离处理
> **推荐工作流**:**每次 CubeMX 重生成后,立刻跑一次
@@ -1138,3 +1141,4 @@ python scripts/check_cubemx_survival.py --write # 重建基线(确认改动
| 2026-10-02 | **P0 根因修复:IWDG 超时被静默配成复位默认值(比设计短 5 倍)** | round 3~8 反复出现的"空 STOR(0 字节截断)→ IWDG 误复位"**根因不是"poller 自身停摆"**。真因:`Src/iwdg.c` 为实现**延后启动**(§4.5 决策 C)删掉了 `HAL_IWDG_Init()`(`IWDG-DEFER-START-ANCHOR`),而 `HAL_IWDG_Init()` 是**唯一**会把 `hiwdg.Init.Prescaler/Reload` 写进 IWDG 寄存器的路径——删掉后 `MX_IWDG_Init()` 只填结构体,`WD_IWDG_PRESCALER`/`WD_IWDG_RELOAD` 沦为**死宏**,寄存器停在**复位默认值** `PR=0`(÷4)/`RLR=4095`。实测(SWD 回读)实际超时 = 4×4096/LSI ≈ **349~512 ms**,而设计是 64×1250/LSI ≈ **1701~2498 ms**。两个后果:① `WD_MISS_TOLERANCE`(8×100ms=800ms)**永远等不到**就被 IWDG 复位 → **停喂判定分支成死代码**(这正是 §7"STALL 从未执行"的真正原因,当时据此误判为"poller 停摆");② 任何 >400ms 的心跳缺口都直接误复位(空 STOR 的 FTL flush 使 NET/FTP/UI 心跳缺 ~400ms)。**修复**:`wd_platform_start()`(`__weak`,**不在 CubeMX 生成区**,重生成不丢)补上按设计值配置:解锁(`KR=0x5555`) → `PR` → 等 `SR.PVU` 清 → `RLR` → 等 `SR.RVU` 清 → `KR=0xCCCC`(不等同步会静默丢弃后写的那只,SWD 实测)。`.map` 证实 `Removing stm32f4xx_hal_iwdg.o(i.HAL_IWDG_Init), (94 bytes)`,故这是全固件**唯一**写 PR/RLR 的点。**验证**:SWD 现场改回设计值后,同一空 STOR 由 **5/5 复位 → 100% 成功**;固件烧录后 3 次开机回读均为 `PR=4/RLR=1249`。`@build` 0/0;本地 CI L0+L0.5 全绿(278/278 断言) |
| 2026-10-03 | **V2/V4/V5 台架验收闭环(N-03 销项)** | 三用例全部通过。**V2 任务饿死**:`netTask` 循环插 `while(1)` → 800ms 声明 `[WD] task stall: mask=0x00000001 req=0x00000037`,≤2.5s 触发 IWDG 复位(`reset cause: IWDG`,`RCC_CSR=0x24000000`)。⚠️ 首轮实测**不复位**(stall 已声明、`miss` 累至 1157≈115s):根因是**停摆证据落盘自身喂狗**——`wd_stall_evidence()`→`sys_log_evidence()` 在停喂窗口里每 100ms 轮都写 NAND(`/log/wdev.log` + `SYSLOG_RUN` flush 诱导),经 `nand_ftl_wd_feed()` **直接重装 IWDG**,"停喂"语义被证据机制抵消。修复:加 `s_stall_evidenced` **每 episode 门闩**(每轮停摆只落盘一次,此后不再触碰 NAND)→ 复测复位正常。**V4 关中断死循环**:`__disable_irq()` 后插 `while(1)` → 设备每 ~4.9s 稳定复位(20+ 周期,`reset cause: IWDG`),证实 IWDG **独立于中断**(PRIMASK 屏蔽 TIM7/SysTick/PendSV 仍能复位)。**V5 长期不误杀**:FTP 512KB 上/下传 + `e02_probe` 网络探针持续 **30 min** → **120/120 次 FTP 传输全 PASS(MATCH)、零 IWDG 复位**(唯一复位为起始烧录的 `software (NVIC_SystemReset)`)。Step 4 堆告警全程未触发(`min_ever=4728 B` > 3072 B)。**遗留观察(已登记 `待处理内容.md`)**:V5 期间 44 次 `task stall`(`mask=0x00000001` 持续 ≥800ms)**100% 与 `e02_probe` 的 30s 挂起客户端→`time_sync` 掉线+监听重建(`listen FAIL`×2,约 4s)重合**;隔离实验证实「仅 FTP(无探针)120s = 0 次 stall」→ 该停摆为**探针场景专属**,非一般负载误杀。因缺口(~1s)< IWDG 最坏超时(1701ms)而未复位,与 §4.3 包含关系一致 |
| 2026-10-09 | **IWDG 延后启动由 L3 升级为 L2:不再需要每次重生成后手工删那一句** | 起因是提问"能否在初始化函数下方的自定义区里先反初始化"。查证:**反初始化不可行**——IWDG 一旦启动就**无法停止**(参考手册:唯一停止手段是复位),故不能在 `IWDG_Init 2` 区撤销。改用等价且免手工的做法:`Src/iwdg.c` 的 `USER CODE BEGIN 0`(标准保护位)放空实现 `iwdg_deferred_start()` + `#define HAL_IWDG_Init(a_h) iwdg_deferred_start(a_h)`,把**本编译单元里**那句生成调用 `HAL_IWDG_Init(&hiwdg)` 改名到空实现 → 生成区保持 CubeMX 原样、**重生成零 diff**。`.map` 证实 `Removing stm32f4xx_hal_iwdg.o(i.HAL_IWDG_Init), (94 bytes)`、`iwdg.o(i.MX_IWDG_Init)` 只引用 `.bss`(空实现被编译器消掉)。审计项 `iwdg-defer-start` 相应由 `wholefile`+锚点注释改为 `protected`(区 `0`)+ 代码符号断言(`iwdg_deferred_start` / `#define HAL_IWDG_Init`——`protected` 比对前会剥掉注释,锚点注释不能当断言)。§10.2 硬教训②与 L3 存量清单同步:L3 只剩 4 个 Fault Handler 实体(同一招可根治,尚未做)。`@build` 0 错误 0 警告;审计 21 通过 / 1 警告 / 0 失败 |
| 2026-10-09 | **4 个 Fault Handler 由 L3 升级为 L2:重生成完全零 diff** | 用与 iwdg 同一招。`Src/stm32f4xx_it.c` 的 4 个 `__asm void X_Handler` 从**生成区**搬进 `USER CODE BEGIN 0`,且必须写在文件头那 4 个 `#define X X_Gen` **之前**(写在之后会被自己的宏改名);配套的 `#undef X` 随之删除(不再需要)。生成区恢复 CubeMX 原样(`void HardFault_Handler(void)` + `HardFault_IRQn 0` / `W1_HardFault_IRQn 0` 标记区),4 个锚点注释(`HARD-FAULT-ANCHOR` / `MEMFAULT-ANCHOR` / `BUSFAULT-ANCHOR` / `USAGEFAULT-ANCHOR`)全部退役。`.map` 复核:`startup_stm32f407xx.o(RESET) refers to stm32f4xx_it.o(.emb_text) for HardFault_Handler`(向量表仍指向我们的 `__asm` 版)、`.emb_text → i.fault_entry_* → i.hard_fault_report`、4 个 `*_Gen` 各 `Removing ... (2 bytes)`。审计项 `hardfault-handler` / `fault-handlers-3` 由 `wholefile`+锚点改为 `protected`(区 `0`)+ 代码符号断言。**L3 清单清空**,重生成预期零 diff。`@build` 0 错误 0 警告;审计 21 通过 / 1 警告 / 0 失败 |