4 个 Fault Handler 迁入 USER CODE BEGIN 0(L3→L2):生成区恢复 CubeMX 原样,重生成零 diff
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"snapshot": {
|
||||
"iwdg-defer-start": "ca839ffc1cda439f",
|
||||
"hardfault-handler": "1dfa875a1b75bea4",
|
||||
"fault-handlers-3": "1dfa875a1b75bea4",
|
||||
"hardfault-handler": "f3afab8d2be18794",
|
||||
"fault-handlers-3": "f3afab8d2be18794",
|
||||
"fault-handler-enable": "be87fcd3a7926138",
|
||||
"hardfault-report-impl": "7a4d1c2b4a4b6fee",
|
||||
"hardfault-report-impl": "f3afab8d2be18794",
|
||||
"stack-overflow-check": "d4735e3a265e16ee",
|
||||
"malloc-failed-hook": "6b86b273ff34fce1",
|
||||
"assert-not-deadloop": "d4a00718ab2bd071",
|
||||
|
||||
@@ -40,7 +40,7 @@ STM32F407ZGTx @168MHz(HSE 8MHz → PLL M=4/N=168/P=2);**仅 Keil MDK-ARM v
|
||||
## 平台约束
|
||||
|
||||
- `Inc/`/`Src/` 中 CubeMX 生成文件带 `USER CODE BEGIN/END` 标记:自定义代码**只写标记区之间**;`Drivers/BSP/` 为纯手工代码
|
||||
- **CubeMX 重生成会覆盖标记区之外的一切**(`.ioc` 的 `KeepUserCode=true` **只**保护 `USER CODE` 区;2026-09-29 两次重生成各覆盖 4~5 处定制,其中 P1 两处**不报编译错**、只在真出故障时暴露)。防止定制丢失按三层优先级:**L1 写进 `.ioc`**(凡 CubeMX 界面能表达的配置,如任务栈/优先级/队列/事件组/堆大小/IWDG 参数——权限最高,重生成只会照写)→ **L2 搬进 `USER CODE` 区**(**只信 CubeMX 当前模板真实生成的区名**,优先文件头 `USER CODE BEGIN 0` 这类从未变过的标准位)→ **L3 审计兜底**。**每次重生成后立刻跑 `python scripts/check_cubemx_survival.py`**(已挂 pre-commit `cubemx-survival` 钩子;改动 `.ioc`/`Src/`/`Inc/`/`task_watchdog`/`.uvprojx` 触发)——清单 21 项 + 哈希基线漂移检测,直接列出"被覆盖了哪几处"。**2026-10-09 第三次重生成(启用 ADC1)补充**:`Inc/FreeRTOSConfig.h` 的自造 `FREE_RTOS_CONFIG_HARDENING` 区**第二次**被抹 → 已改走 **L1**:`configCHECK_FOR_STACK_OVERFLOW=2` / `configUSE_MALLOC_FAILED_HOOK=1` 在 GUI 的 **FreeRTOS → Config parameters → Hook function related definitions** 里设(写进 `.ioc` 由模板生成,永不再丢;教训:**别凭印象断言"界面里没有该项"**,先翻标签页或查 `db/mcu/IP/*_Modes.xml`)。⚠️ 启用后 CubeMX 会在 `Src/freertos.c` 生成 `USER CODE BEGIN 4 / 5` 区(默认内容是**空桩**)——**钩子实现必须整体写进这两个区**(原先在 `USER CODE Application` 区的实现要搬走,否则同名函数两处定义 = **编译错**;配套 helper `hook_ptr_ok()` 须在钩子之前 → 放 `USER CODE BEGIN PD` 区)。审计项 `stack-overflow-hook-impl` / `malloc-failed-hook-impl` / `stack-overflow-hook-helper` 分别校验 4 / 5 / PD 三个区。⚠️ **不要依赖"自造标记区"**:2026-09-29 实测 CubeMX **换了一代中断函数模板**(旧 `HardFault_Handler`/`HardFault_Report` 自造区 → 新函数体内 `HardFault_IRQn 0`/`W1_HardFault_IRQn 0`),旧区**连同内容一起消失**(看起来仍像"遵守了规则")。**生成区内无标记位可用的改动**分两种处理(2026-10-09 更新):① 要**去掉/替换生成区里的一句调用** → 用宏改名升级为 **L2**:例 `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)` 自动变空操作(`.map` 证实 `Removing ... HAL_IWDG_Init, 94 bytes`),生成区保持原样、**重生成零 diff**,不必再手工删。⚠ **IWDG 启动后无法停止**(唯一手段是复位),所以不能"反初始化",只能在启动前拦截。② 要**替换生成实体的定义**且该位置无区可用(4 个 Fault Handler 本体——本代模板整函数重写)→ 仍 L3 兜底,加锚点注释(`HARD-FAULT-ANCHOR` / `MEMFAULT-ANCHOR` / `BUSFAULT-ANCHOR` / `USAGEFAULT-ANCHOR`)供审计检出;我们的定义必须写在 `#define X X_Gen` **之前**,否则被自己的宏改写(此招也可升级为 L2,尚未做)。方法论详见 `docs/子系统设计/看门狗设计说明.md` §10.1/§10.2
|
||||
- **CubeMX 重生成会覆盖标记区之外的一切**(`.ioc` 的 `KeepUserCode=true` **只**保护 `USER CODE` 区;2026-09-29 两次重生成各覆盖 4~5 处定制,其中 P1 两处**不报编译错**、只在真出故障时暴露)。防止定制丢失按三层优先级:**L1 写进 `.ioc`**(凡 CubeMX 界面能表达的配置,如任务栈/优先级/队列/事件组/堆大小/IWDG 参数——权限最高,重生成只会照写)→ **L2 搬进 `USER CODE` 区**(**只信 CubeMX 当前模板真实生成的区名**,优先文件头 `USER CODE BEGIN 0` 这类从未变过的标准位)→ **L3 审计兜底**。**每次重生成后立刻跑 `python scripts/check_cubemx_survival.py`**(已挂 pre-commit `cubemx-survival` 钩子;改动 `.ioc`/`Src/`/`Inc/`/`task_watchdog`/`.uvprojx` 触发)——清单 21 项 + 哈希基线漂移检测,直接列出"被覆盖了哪几处"。**2026-10-09 第三次重生成(启用 ADC1)补充**:`Inc/FreeRTOSConfig.h` 的自造 `FREE_RTOS_CONFIG_HARDENING` 区**第二次**被抹 → 已改走 **L1**:`configCHECK_FOR_STACK_OVERFLOW=2` / `configUSE_MALLOC_FAILED_HOOK=1` 在 GUI 的 **FreeRTOS → Config parameters → Hook function related definitions** 里设(写进 `.ioc` 由模板生成,永不再丢;教训:**别凭印象断言"界面里没有该项"**,先翻标签页或查 `db/mcu/IP/*_Modes.xml`)。⚠️ 启用后 CubeMX 会在 `Src/freertos.c` 生成 `USER CODE BEGIN 4 / 5` 区(默认内容是**空桩**)——**钩子实现必须整体写进这两个区**(原先在 `USER CODE Application` 区的实现要搬走,否则同名函数两处定义 = **编译错**;配套 helper `hook_ptr_ok()` 须在钩子之前 → 放 `USER CODE BEGIN PD` 区)。审计项 `stack-overflow-hook-impl` / `malloc-failed-hook-impl` / `stack-overflow-hook-helper` 分别校验 4 / 5 / PD 三个区。⚠️ **不要依赖"自造标记区"**:2026-09-29 实测 CubeMX **换了一代中断函数模板**(旧 `HardFault_Handler`/`HardFault_Report` 自造区 → 新函数体内 `HardFault_IRQn 0`/`W1_HardFault_IRQn 0`),旧区**连同内容一起消失**(看起来仍像"遵守了规则")。**生成区内无标记位可用的改动**分两种处理(2026-10-09 更新):① 要**去掉/替换生成区里的一句调用** → 用宏改名升级为 **L2**:例 `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)` 自动变空操作(`.map` 证实 `Removing ... HAL_IWDG_Init, 94 bytes`),生成区保持原样、**重生成零 diff**,不必再手工删。⚠ **IWDG 启动后无法停止**(唯一手段是复位),所以不能"反初始化",只能在启动前拦截。② 要**替换生成实体的定义**(4 个 Fault Handler 本体——本代模板整函数重写)→ 同一招升级为 **L2**(2026-10-09 已做):我们的 `__asm void X_Handler` 定义放 `USER CODE BEGIN 0`,**必须写在 `#define X X_Gen` 之前**(写在之后会被自己的宏改名),生成区恢复 CubeMX 原样(`HardFault_IRQn 0` / `W1_HardFault_IRQn 0` 标记区照留),不再用锚点注释。`.map` 复核口径:`startup_*.o(RESET) refers to stm32f4xx_it.o(.emb_text) for HardFault_Handler` + 4 个 `*_Gen` 各 `Removing ... (2 bytes)`。**L3 清单已清空:重生成应当零 diff**;若仍有 diff,跑审计脚本定位(`protected` 项的断言只能用代码符号,注释会被剥掉)。方法论详见 `docs/子系统设计/看门狗设计说明.md` §10.1/§10.2
|
||||
- **4 个 Cortex-M4 Fault 全部采集现场**(2026-09-29):`HardFault` / `MemManage` / `BusFault` / `UsageFault` 均为手工 `__asm` Handler(取 MSP/PSP 帧指针)→ `B` 到 per-fault C 入口 `fault_entry_{hard,mem,bus,usage}`(各自带故障名)→ `hard_fault_report(tag, frame)` 打 `[ERR] <TAG> PC/LR/xPSR/R0-R3/CFSR/HFSR[/BFAR][/MMFAR]` 后 `NVIC_SystemReset()`。**后三者默认禁用**(复位后 `SHCSR` 只开 HardFault),不使能就会被**升级成 HardFault**、日志看不出根因 → `fault_handler_enable()`(`Src/stm32f4xx_it.c` 的 `USER CODE BEGIN 1` 区)在 `app_main_init()` **第一行**调用。**刻意不启用** `CCR.UNALIGN_TRP` / `CCR.DIV_0_TRP`(会把普通 bug 升级成死机)。⚠️ **ARMCC `__asm` 取不到 C 的数据符号**(`ADR r1, xxx_tag` 报 `A1516E: Bad symbol`,去 static 也无效)且无法传字符串参数 → 故障名必须由 C 侧 per-fault 入口带上,不要在 `__asm` 里取 C 变量
|
||||
- **`SHCSR` 的 Fault 使能位无法在 CubeMX 配置**(2026-10-01 查模板源码证实):CubeMX 对 System Exception **只生成优先级(且仅当优先级 ≠ 0),从不生成使能代码**——`tpl_main_c.ftl` 的 `MX_NVIC_Init()` 用 `[#if vector.systemHandler=="false"]` 挡住 `HAL_NVIC_EnableIRQ`,而 `NVIC-STM32F417_Modes.xml` 里 `MemoryManagement/BusFault/UsageFault_IRQn` 都标 `Y`(=systemHandler);**全部模板中 `SHCSR` 只出现 1 次**且是 MPU 清理的 `&= ~MEMFAULTENA`(禁用),无任何 `|=` 置位;MPU 路径也堵死(`.ioc` 无 MPU,且 `HAL_MPU_Enable` 只置 `MEMFAULTENA` 一位)。⚠️ **`.ioc` 的 `NVIC.MemoryManagement_IRQn=true` 不代表已使能**(第 1 段只是"向量在 NVIC 表里可见")——别误以为勾了 Enabled 就等于开了故障。结论:只能手工写(现放 `USER CODE BEGIN 1` 区,属 L2,重生成不丢)+ 审计项 `fault-handler-enable` 兜底
|
||||
- **`MDK-ARM/*.uvprojx` 每次 `@build` 后被 Keil UV4 自动改写,改动一律不入库**(2026-09-29 实测):UV4 构建时会把工程 XML 归一化成它本机风格的旧式写法(`<Tag />` → `<Tag></Tag>`、XML 声明加 `standalone="no"`),并把 `PackID` 降到**本机装的 DFP 版本**(本机实测 `3.1.1` → `3.0.0`,属**降级**)。这些差异是环境相关的,提交后会把别人的 Pack 版本也一起改掉。**判断方法**:`git diff MDK-ARM/STM32F407-Demo.uvprojx` 里若只见 XML 格式/`PackID` 差异而**无源文件挂载增删** → `git checkout -- MDK-ARM/STM32F407-Demo.uvprojx` 丢弃。只有真的新增/删除源文件(`grep 文件名` 计数变化)才提交
|
||||
|
||||
@@ -191,8 +191,9 @@ void wd_platform_feed(void);
|
||||
* 返回值:无
|
||||
* 函数说明:MCU 默认实现 = 解锁 PR/RLR → 写 WD_IWDG_PRESCALER/WD_IWDG_RELOAD
|
||||
* (/64 + 1249 → 1701~2498ms,等 SR.PVU/RVU 同步)→ `IWDG->KR = 0xCCCC`。
|
||||
* ⚠ 配置**必须**在此完成:`Src/iwdg.c` 为实现延后启动删掉了
|
||||
* `HAL_IWDG_Init()`,而它是唯一把 `hiwdg.Init.*` 写进寄存器的路径;
|
||||
* ⚠ 配置**必须**在此完成:`Src/iwdg.c` 为实现延后启动,把生成的
|
||||
* `HAL_IWDG_Init(&hiwdg)` 经宏改名成空实现(见该文件 `USER CODE BEGIN 0`),
|
||||
* 而 `HAL_IWDG_Init()` 是 HAL 里唯一把 `hiwdg.Init.*` 写进寄存器的路径;
|
||||
* 缺了这一步 PR/RLR 停在复位默认值(PR=0/RLR=4095 ≈ 400ms),
|
||||
* 超时比设计短约 5 倍 → 800ms 容忍分支成死代码、易 IWDG 误复位。
|
||||
* 主机侧单测以强符号覆盖本钩子(不碰寄存器),故其行为不受影响。
|
||||
|
||||
+81
-78
@@ -165,7 +165,67 @@ void fault_entry_usage(uint32_t *frame)
|
||||
hard_fault_report("USAGEFAULT", frame);
|
||||
}
|
||||
|
||||
/* 为生成函数改名,避免与下方手工 `__asm` 实现符号冲突。
|
||||
/* ==== 4 个异常入口:__asm 版 Handler(符号与向量表一致)====
|
||||
* ⚠ **必须写在下面 4 个 `#define X X_Gen` 之前**——写在之后会被自己的宏改名。
|
||||
* 2026-10-09 从生成区迁入本保护区:此前放在生成区内(L3),每次 CubeMX 重生成
|
||||
* 都被抹掉、需 `git checkout HEAD -- Src/stm32f4xx_it.c` 复原;现在生成区保持
|
||||
* CubeMX 原样,重生成零改动。
|
||||
*
|
||||
* 原理:EXC_RETURN bit2(`TST lr, #4`)判断异常帧在 MSP 还是 PSP,取帧指针后
|
||||
* `B` 到上面的 per-fault C 入口。CubeMX 默认的空 `while(1)` 版会让死机在日志上
|
||||
* **完全静默**(2026-09-20 实证)。
|
||||
* ⚠ ARMCC 语法:内联汇编**只能**用整函数 `__asm void f(void) {...}` 形态。
|
||||
* `__asm volatile("MRS %0, lr" : "=r"(x))`(GCC 语法)报 #18;
|
||||
* `__asm { MRS x, lr }` 块内联会把寄存器名当 C 标识符报 #20(2026-09-29 实测)。
|
||||
*/
|
||||
__asm void HardFault_Handler(void)
|
||||
{
|
||||
extern fault_entry_hard
|
||||
TST lr, #4
|
||||
ITE EQ
|
||||
MRSEQ r0, MSP
|
||||
MRSNE r0, PSP
|
||||
B fault_entry_hard
|
||||
}
|
||||
|
||||
/* MEMFAULT:访问 MPU 禁止区 / 非法特权级访问。须 SHCSR 的 MEMFAULTENA(bit16) 使能。
|
||||
* 命中时 CFSR 的 MMARVALID(bit7) 置 1,MMFAR 记录出错地址(报告函数按此判定)。 */
|
||||
__asm void MemManage_Handler(void)
|
||||
{
|
||||
extern fault_entry_mem
|
||||
TST lr, #4
|
||||
ITE EQ
|
||||
MRSEQ r0, MSP
|
||||
MRSNE r0, PSP
|
||||
B fault_entry_mem
|
||||
}
|
||||
|
||||
/* BUSFAULT:非法总线访问 / 取指(外设时钟未开、错误指针、DMA 越界等)。
|
||||
* 须 SHCSR 的 BUSFAULTENA(bit17) 使能。命中时 CFSR 的 BFARVALID(bit15) 置 1。 */
|
||||
__asm void BusFault_Handler(void)
|
||||
{
|
||||
extern fault_entry_bus
|
||||
TST lr, #4
|
||||
ITE EQ
|
||||
MRSEQ r0, MSP
|
||||
MRSNE r0, PSP
|
||||
B fault_entry_bus
|
||||
}
|
||||
|
||||
/* USAGEFAULT:未定义指令 / 非对齐访问 / 除零(须 DIV_0_TRP)/ 非法状态。
|
||||
* 须 SHCSR 的 USGFAULTENA(bit18) 使能。
|
||||
* ⚠ 本项目**刻意不使用硬件除零陷入**(C 代码里除零应自己判,避免把普通 bug 升级为死机)。 */
|
||||
__asm void UsageFault_Handler(void)
|
||||
{
|
||||
extern fault_entry_usage
|
||||
TST lr, #4
|
||||
ITE EQ
|
||||
MRSEQ r0, MSP
|
||||
MRSNE r0, PSP
|
||||
B fault_entry_usage
|
||||
}
|
||||
|
||||
/* 为生成函数改名,避免与上面手工 `__asm` 实现符号冲突。
|
||||
* 生成实体改名为 <原名>_Gen(永不执行,仅为占名字不冲突,
|
||||
* 保持生成区代码零删除;未被引用 → 链接器自动移除)。
|
||||
*
|
||||
@@ -216,36 +276,13 @@ void NMI_Handler(void)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief This function handles Hard fault interrupt.
|
||||
*/
|
||||
/* HARD-FAULT-ANCHOR — 本锚点与下方改动一体,重生成会一并消失,
|
||||
* 供 scripts/check_cubemx_survival.py 检出(勿删):
|
||||
*
|
||||
* 崩溃现场采集(手工改写)。EXC_RETURN bit2 判断异常帧在 MSP 还是 PSP,
|
||||
* 取帧指针交给 C 报告函数 hard_fault_report()(实现在文件头 USER CODE BEGIN 0
|
||||
* 保护区内)。空 while(1) 版会让死机在日志上完全静默(2026-09-20 实证)。
|
||||
*
|
||||
* 命名:`#undef HardFault_Handler` 后重定义为 __asm 版本,与向量表符号一致;
|
||||
* 为避免与下方生成实体"重复定义"(ARMCC 报 #247,其"已定义"检查
|
||||
* 早于 C 作用域),已在文件头用 `#define HardFault_Handler
|
||||
* HardFault_Handler_Gen` 把生成实体改名为不冲突的符号。
|
||||
*
|
||||
* ⚠ ARMCC 语法:内联汇编**只能**用整函数 `__asm void f(void) {...}` 形态。
|
||||
* `__asm volatile("MRS %0, lr" : "=r"(x))`(GCC 语法)报 #18;
|
||||
* `__asm { MRS x, lr }` 块内联会把寄存器名当 C 标识符报 #20(2026-09-29 实测)。 */
|
||||
#undef HardFault_Handler
|
||||
__asm void HardFault_Handler(void)
|
||||
* @brief This function handles Hard fault interrupt.
|
||||
*/
|
||||
void HardFault_Handler(void)
|
||||
{
|
||||
extern fault_entry_hard
|
||||
TST lr, #4
|
||||
ITE EQ
|
||||
MRSEQ r0, MSP
|
||||
MRSNE r0, PSP
|
||||
B fault_entry_hard
|
||||
}
|
||||
/* USER CODE BEGIN HardFault_IRQn 0 */
|
||||
|
||||
void HardFault_Handler_Gen(void)
|
||||
{
|
||||
/* USER CODE END HardFault_IRQn 0 */
|
||||
while (1)
|
||||
{
|
||||
/* USER CODE BEGIN W1_HardFault_IRQn 0 */
|
||||
@@ -254,24 +291,13 @@ void HardFault_Handler_Gen(void)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief This function handles Memory management fault.
|
||||
*/
|
||||
/* MEMFAULT-ANCHOR — 见 HARD-FAULT-ANCHOR(同一套采集方案,勿删本锚点)
|
||||
* 触发条件:访问 MPU 禁止区 / 非法特权级访问。须 SHCSR 的 MEMFAULTENA(bit16) 使能。
|
||||
* 命中时 CFSR 的 MMARVALID(bit7) 置 1,MMFAR 记录出错地址(报告函数按此判定)。 */
|
||||
#undef MemManage_Handler
|
||||
__asm void MemManage_Handler(void)
|
||||
* @brief This function handles Memory management fault.
|
||||
*/
|
||||
void MemManage_Handler(void)
|
||||
{
|
||||
extern fault_entry_mem
|
||||
TST lr, #4
|
||||
ITE EQ
|
||||
MRSEQ r0, MSP
|
||||
MRSNE r0, PSP
|
||||
B fault_entry_mem
|
||||
}
|
||||
/* USER CODE BEGIN MemoryManagement_IRQn 0 */
|
||||
|
||||
void MemManage_Handler_Gen(void)
|
||||
{
|
||||
/* USER CODE END MemoryManagement_IRQn 0 */
|
||||
while (1)
|
||||
{
|
||||
/* USER CODE BEGIN W1_MemoryManagement_IRQn 0 */
|
||||
@@ -280,24 +306,13 @@ void MemManage_Handler_Gen(void)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief This function handles Pre-fetch fault, memory access fault.
|
||||
*/
|
||||
/* BUSFAULT-ANCHOR — 见 HARD-FAULT-ANCHOR(同一套采集方案,勿删本锚点)
|
||||
* 触发条件:非法总线访问 / 取指(外设时钟未开、错误指针、DMA 越界等)。
|
||||
* 须 SHCSR 的 BUSFAULTENA(bit17) 使能。命中时 CFSR 的 BFARVALID(bit15) 置 1。 */
|
||||
#undef BusFault_Handler
|
||||
__asm void BusFault_Handler(void)
|
||||
* @brief This function handles Pre-fetch fault, memory access fault.
|
||||
*/
|
||||
void BusFault_Handler(void)
|
||||
{
|
||||
extern fault_entry_bus
|
||||
TST lr, #4
|
||||
ITE EQ
|
||||
MRSEQ r0, MSP
|
||||
MRSNE r0, PSP
|
||||
B fault_entry_bus
|
||||
}
|
||||
/* USER CODE BEGIN BusFault_IRQn 0 */
|
||||
|
||||
void BusFault_Handler_Gen(void)
|
||||
{
|
||||
/* USER CODE END BusFault_IRQn 0 */
|
||||
while (1)
|
||||
{
|
||||
/* USER CODE BEGIN W1_BusFault_IRQn 0 */
|
||||
@@ -306,25 +321,13 @@ void BusFault_Handler_Gen(void)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief This function handles Undefined instruction or illegal state.
|
||||
*/
|
||||
/* USAGEFAULT-ANCHOR — 见 HARD-FAULT-ANCHOR(同一套采集方案,勿删本锚点)
|
||||
* 触发条件:未定义指令 / 非对齐访问 / 除零(须 DIV_0_TRP)/ 非法状态。
|
||||
* 须 SHCSR 的 USGFAULTENA(bit18) 使能。
|
||||
* ⚠ 本项目**刻意不使用硬件除零陷入**(C 代码里除零应自己判,避免把普通 bug 升级为死机)。 */
|
||||
#undef UsageFault_Handler
|
||||
__asm void UsageFault_Handler(void)
|
||||
* @brief This function handles Undefined instruction or illegal state.
|
||||
*/
|
||||
void UsageFault_Handler(void)
|
||||
{
|
||||
extern fault_entry_usage
|
||||
TST lr, #4
|
||||
ITE EQ
|
||||
MRSEQ r0, MSP
|
||||
MRSNE r0, PSP
|
||||
B fault_entry_usage
|
||||
}
|
||||
/* USER CODE BEGIN UsageFault_IRQn 0 */
|
||||
|
||||
void UsageFault_Handler_Gen(void)
|
||||
{
|
||||
/* USER CODE END UsageFault_IRQn 0 */
|
||||
while (1)
|
||||
{
|
||||
/* USER CODE BEGIN W1_UsageFault_IRQn 0 */
|
||||
|
||||
+14
-10
@@ -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 失败 |
|
||||
|
||||
@@ -83,38 +83,34 @@ CHECKS = [
|
||||
# ⚠ 2026-09-29 重要变更:CubeMX 把本文件的中断函数模板**换了一代**——
|
||||
# 旧模板在函数前后给自造区(HardFault_Handler / HardFault_Report),
|
||||
# 新模板改成函数体**内部**的 `HardFault_IRQn 0` / `W1_HardFault_IRQn 0`。
|
||||
# 实测"生成区之外的自造标记区"**不会被保留**。故现在的落点是:
|
||||
# Handler 实体 → 生成区内整函数替换(无 USER CODE 位可用),
|
||||
# 靠 `HARD-FAULT-ANCHOR` 锚点注释检出丢失;
|
||||
# 生成实体用 `#define HardFault_Handler HardFault_Handler_Gen`
|
||||
# 改名保留,链接器自动移除(map 已验证 2 bytes removed)
|
||||
# 报告实现 → `USER CODE BEGIN 0`(生成区内的标准保护位,重生成不丢)
|
||||
# 实测"生成区之外的自造标记区"**不会被保留**。
|
||||
# ✅ 2026-10-09 落点升级为 L2(与 iwdg 同一招):4 个 `__asm` Handler 与报告实现
|
||||
# 全部放 `USER CODE BEGIN 0`(标准保护位),生成区恢复 CubeMX 原样;
|
||||
# 生成实体靠 `#define X X_Gen` 改名让位(未被引用 → 链接器自动移除)。
|
||||
# ⚠ 顺序硬约束:我们的定义必须在 4 个 `#define` **之前**,否则被自己的宏改名。
|
||||
# 判据用区内的代码符号(protected 项比对前会剥掉注释,锚点注释不能当断言)。
|
||||
dict(
|
||||
id="hardfault-handler",
|
||||
kind="wholefile",
|
||||
kind="protected",
|
||||
path="Src/stm32f4xx_it.c",
|
||||
region="0",
|
||||
must_contain=[
|
||||
"HARD-FAULT-ANCHOR", # 手工锚点注释,重生成后会消失
|
||||
"__asm void HardFault_Handler", # 手工汇编 Handler
|
||||
"__asm void HardFault_Handler", # 手工汇编 Handler(取 MSP/PSP)
|
||||
"fault_entry_hard", # 跳转目标(C 侧带故障名)
|
||||
"#define HardFault_Handler HardFault_Handler_Gen", # 给生成实体让位
|
||||
],
|
||||
must_not_contain=["HardFault_Handler(void)\r\n{\r\n while (1)"],
|
||||
hint="HardFault_Handler 须为手工汇编版(取 MSP/PSP 交 fault_entry_hard),"
|
||||
"生成实体改名 HardFault_Handler_Gen。被打回空 while(1) 后死机将完全静默。"
|
||||
"恢复后补回 `HARD-FAULT-ANCHOR` 锚点注释。",
|
||||
"恢复位置 = `USER CODE BEGIN 0`,且必须在 4 个 #define 之前。",
|
||||
),
|
||||
# ⚠ 2026-09-29:MemManage / BusFault / UsageFault 三个也升级为崩溃采集。
|
||||
# 模板形态与 HardFault 相同(整函数重写、函数体外无标记区)→ 同一套方案:
|
||||
# 生成区整函数替换 + 独立锚点 + `#define X X_Gen` 改名保留生成实体。
|
||||
# 由一项统一检查(同文件、同 kind),避免 4 条重复项。
|
||||
# MemManage / BusFault / UsageFault 三个同样升级为崩溃采集(同文件、同方案,
|
||||
# 由一项统一检查,避免 3 条重复项)。
|
||||
dict(
|
||||
id="fault-handlers-3",
|
||||
kind="wholefile",
|
||||
kind="protected",
|
||||
path="Src/stm32f4xx_it.c",
|
||||
region="0",
|
||||
must_contain=[
|
||||
"MEMFAULT-ANCHOR",
|
||||
"BUSFAULT-ANCHOR",
|
||||
"USAGEFAULT-ANCHOR",
|
||||
"__asm void MemManage_Handler",
|
||||
"__asm void BusFault_Handler",
|
||||
"__asm void UsageFault_Handler",
|
||||
|
||||
Reference in New Issue
Block a user