diff --git a/AGENTS.md b/AGENTS.md index 5075cf29..2e3077a1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -40,6 +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` 触发)——清单 18 项 + 哈希基线漂移检测,直接列出"被覆盖了哪几处"。⚠️ **不要依赖"自造标记区"**:2026-09-29 实测 CubeMX **换了一代中断函数模板**(旧 `HardFault_Handler`/`HardFault_Report` 自造区 → 新函数体内 `HardFault_IRQn 0`/`W1_HardFault_IRQn 0`),旧区**连同内容一起消失**(看起来仍像"遵守了规则")。**生成区内无标记位可用的改动只能 L3 兜底**(如 `Src/iwdg.c` 的 `HAL_IWDG_Init()` 调用、4 个 Fault Handler 本体——本代模板整函数重写),一律加锚点注释(`IWDG-DEFER-START-ANCHOR` / `HARD-FAULT-ANCHOR` / `MEMFAULT-ANCHOR` / `BUSFAULT-ANCHOR` / `USAGEFAULT-ANCHOR`)供审计检出。方法论详见 `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] 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 归一化成它本机风格的旧式写法(`` → ``、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 文件名` 计数变化)才提交 - **ARMCC 内联汇编语法(2026-09-29 踩坑,共 3 坑)**:① `__asm volatile ("MRS %0, lr" : "=r" (x))` 是 **GCC 语法**,ARMCC 报 `#18: expected a ")"`;② `__asm { MRS x, lr }` 块内联会把寄存器名当 C 标识符报 `#20: identifier "lr" is undefined`;③ `__asm` 块里**取不到 C 的数据符号**,`ADR r1, xxx_tag` 报 `A1516E: Bad symbol 'xxx_tag'`(去掉 `static` 也无效——汇编器只认自己作用域内的符号)。**结论**:整个函数用 `__asm void f(void) { ... }`;要传"字符串/表格"这类 C 侧数据,就让 `__asm` 只取寄存器参数然后 `B` 到一个普通 C 函数,由 C 侧补上。若要覆盖 CubeMX 生成的同名函数,用 `#define f f_Gen`(在生成实体之前)把生成实体改名——链接器会把未被引用的 `f_Gen` 自动移除(`.map` 可见 `Removing ... (N bytes)`),**零代码开销且不删生成区任何一行** - FreeRTOS V10.3.1(CMSIS-RTOS V2);HAL 时基 = TIM7(非 SysTick),tick = 1ms;NVIC 4 位优先级分组,外设中断优先级 ≥ 5 diff --git a/docs/子系统设计/看门狗设计说明.md b/docs/子系统设计/看门狗设计说明.md index 905f530b..d844e91f 100644 --- a/docs/子系统设计/看门狗设计说明.md +++ b/docs/子系统设计/看门狗设计说明.md @@ -867,7 +867,32 @@ external pin → "pin reset" (新增) | `configCHECK_FOR_STACK_OVERFLOW` | 在 `USER CODE BEGIN FREE_RTOS_CONFIG_HARDENING` 区(L2) | ⚠️ CubeMX 的 FreeRTOS 界面**没有**这一项的勾选框,只能靠 L2 + 审计。**保持现状** | | `configUSE_MALLOC_FAILED_HOOK` | 同上 | 同上 | | `configASSERT` 定义 | 在 `USER CODE BEGIN 1` 区(L2) | 同上。CubeMX 无对应界面项 | -| `MemManage/BusFault/UsageFault_Handler` | `stm32f4xx_it.c` 标记区**外**(L3) | ⚠️ CubeMX 为这些函数**不生成** `USER CODE` 区,无法 L2。见下方"存量清单" | +| 4 个 Fault Handler 实体 | `stm32f4xx_it.c` 标记区**外**(L3) | ⚠️ CubeMX 为这些函数**不生成** `USER CODE` 区,无法 L2。见下方"存量清单" | +| `fault_handler_enable()`(SHCSR 使能三位) | `Src/stm32f4xx_it.c` 的 `USER CODE BEGIN 1` 区(L2) | ❌ **CubeMX 无此配置项**(已查模板源码证实,见下) | + +##### 为什么 `fault_handler_enable()` 写不进 `.ioc`(2026-10-01 查模板源码证实) + +用户问"能否通过 CubeMX 配置",逐条查证 CubeMX 安装目录 +`db/templates/*.ftl` 与 `db/mcu/IP/NVIC-STM32F417_Modes.xml` +(F407 复用 F417 定义),**三条可能路径全部否定**: + +| 路径 | 查证结果 | +|---|---| +| ① NVIC 面板勾选三个 Fault 的 `Enabled` | `tpl_main_c.ftl` 的 `MX_NVIC_Init()`:`[#if vector.systemHandler=="false"]` **才**生成 `HAL_NVIC_EnableIRQ(...)`。而 `NVIC-STM32F417_Modes.xml` 中 `MemoryManagement/BusFault/UsageFault_IRQn` 都标 `Y`(= `systemHandler`)→ **永不生成使能代码**(`HardFault_IRQn` 标 `N` 是个例外,但它恒使能、无需使能) | +| ② 任何模板里找 `SHCSR` 置位 | 全部模板中 `SHCSR` **只出现 1 次**(`tpl_main_c.ftl:2269`),且是 MPU 清理时的 `SCB->SHCSR &= ~SCB_SHCSR_MEMFAULTENA_Msk`(**禁用**)。**无任何 `\|=` 置位** | +| ③ 启用 MPU(`HAL_MPU_Enable` 内部会置 `MEMFAULTENA`) | `.ioc` 的 `Mcu.IP0..13` 列表**无 MPU**;且该函数**只置 `MEMFAULTENA` 一位**,`BUSFAULTENA`/`USGFAULTENA` 仍拿不到 | + +**实测印证**:`Src/stm32f4xx_hal_msp.c` 的 `HAL_MspInit()` 里只有 +`HAL_NVIC_SetPriority(PendSV_IRQn, 15, 0)`(因它优先级 ≠ 0 才生成), +三个 Fault **一行代码都没生成**——与模板逻辑完全吻合。 + +> **结论**:`fault_handler_enable()` **只能手工写**。但它是 **L2**(放在 +> `USER CODE BEGIN 1` 区)——**重生成不会丢**,所以这条不是风险,只是 +> "无法享受 L1 的'CubeMX 自己生成'待遇"。**已挂审计项 `fault-handler-enable` 兜底。** + +> ⚠️ 顺带提醒:`.ioc` 里那三行 `NVIC.MemoryManagement_IRQn=true\:0\:0\:false\:...` +> **不代表"已使能"**——第 1 段 `true` 只是"该向量在 NVIC 表里可见/已配置优先级", +> 与 `SHCSR` 使能位无关。**不要误以为勾了 CubeMX 的 Enabled 就等于使能了故障。** > **判断口诀**:能在外设/中间件的图形界面里找到对应输入框的 → 一定写 `.ioc`; > 找不到(如 FreeRTOS 的两个加固宏、断言定义、故障异常处理函数体)→ 只能 L2/L3。 @@ -932,6 +957,12 @@ external pin → "pin reset" (新增) 声明在 `Inc/stm32f4xx_it.h` 的 `USER CODE BEGIN EFP` 区),在 `app_main_init()` **第一行**调用(纯寄存器写,不依赖日志/时钟)。 +> ⚠️ **这三位没法在 CubeMX 里配**(2026-10-01 查模板源码证实,详见 §10.2 +> 「为什么 `fault_handler_enable()` 写不进 `.ioc`」)。**不要以为在 NVIC 面板 +> 勾了 `Enabled` 就使能了故障**——CubeMX 对 System Exception 只生成优先级 +> (且仅当优先级 ≠ 0),**从不生成使能代码**。好在函数体在 `USER CODE BEGIN 1` +> 区,重生成不会丢(L2),另有审计项 `fault-handler-enable` 兜底。 + **刻意不打开的两项**(都会把普通 bug 升级成死机,对本项目弊大于利): - `CCR.UNALIGN_TRP`:非对齐访问陷入。Cortex-M4 硬件本可处理非对齐访问, @@ -1050,3 +1081,4 @@ python scripts/check_cubemx_survival.py --write # 重建基线(确认改动 | 2026-09-29 | **新增 §10.2 三层手段 + 审计脚本** | 沉淀通用方法论(L1 `.ioc` / L2 `USER CODE` 区 / L3 审计+基线);新建 `scripts/check_cubemx_survival.py`(16 项清单 + 漂移检测)+ `.cubemx_survival_baseline.json` + pre-commit `cubemx-survival` 钩子;`AGENTS.md` 与 `docs/代码规范/pre-commit使用说明.md` 同步 | | 2026-09-29 | **第二次重生成验证 + 机制修正** | 审计脚本**实测有效**(准确检出 5 处覆盖)。⚠️ **关键发现**:CubeMX 换了一代中断函数模板(旧 `HardFault_Handler`/`HardFault_Report` 自造区 → 新函数体内 `HardFault_IRQn 0`/`W1_HardFault_IRQn 0`),**"生成区外的自造标记区"同样不被保留**——上一轮假设被证伪。据此升级恢复方案:报告实现迁入 `USER CODE BEGIN 0`(标准保护位);Handler 本体改为生成区整函数替换(`#define` 改名保留生成实体,链接器自动移除,`.map` 已验证)+ `HARD-FAULT-ANCHOR` 锚点。同步修正审计脚本(`hardfault-handler` 改 `wholefile` + 锚点)、`UNPROTECTED_LEGACY` 清单、§10.1/§10.2 全文。`@build` 0 错误 0 警告 | | 2026-09-29 | **3 个 Fault 升级为崩溃采集** | `MemManage` / `BusFault` / `UsageFault` 由空 `while(1)`(死机静默)升级为与 `HardFault` 同一套采集:生成区整函数替换(各带独立锚点 `MEMFAULT-ANCHOR` / `BUSFAULT-ANCHOR` / `USAGEFAULT-ANCHOR`)+ `#define X X_Gen` 改名保留生成实体(链接器各移除 2 字节)。新增 `fault_handler_enable()` 使能 `SHCSR` 后三者(默认只开 HardFault,不开则故障被升级、看不出根因),声明入 `Inc/stm32f4xx_it.h`、调用在 `app_main_init()` 第一行。因 ARMCC `__asm` 取不到 C 数据符号,改为 per-fault C 入口 `fault_entry_{hard,mem,bus,usage}` 带故障名调 `hard_fault_report(tag, frame)`。**刻意不启用** `UNALIGN_TRP` / `DIV_0_TRP`(避免把普通 bug 升级成死机)。审计脚本新增 `fault-handlers-3` / `fault-handler-enable` 两项、`UNPROTECTED_LEGACY` 清掉 3 个 Fault(WARN 2→1)、清单 16→18 项。`@build` 0 错误 0 警告;审计 18 通过 / 1 警告 / 0 失败 | +| 2026-10-01 | **查证 `fault_handler_enable()` 的 CubeMX 可行性** | 用户问"能否通过 CubeMX 配置"→ **否**。查 CubeMX 安装目录模板源码得三重证据:① `tpl_main_c.ftl` 的 `MX_NVIC_Init()` 只在 `systemHandler=="false"` 时生成 `HAL_NVIC_EnableIRQ`,而 `NVIC-STM32F417_Modes.xml` 中三个 Fault 都标 `Y`(=systemHandler)→ 永不生成使能代码;② **全部模板中 `SHCSR` 只出现 1 次**且是 MPU 清理的 `&= ~MEMFAULTENA`(禁用),**无任何 `\|=` 置位**;③ `.ioc` 无 MPU(`Mcu.IP` 列表),且 `HAL_MPU_Enable` 只置 `MEMFAULTENA` 一位。实测印证:`HAL_MspInit()` 里三个 Fault 一行代码都没生成。结论:只能手工写,但属 **L2**(`USER CODE BEGIN 1` 区)→ 重生成不丢,有审计项兜底。另新增警示:`.ioc` 的 `NVIC.MemoryManagement_IRQn=true` **不代表已使能**(第 1 段只是"向量可见")。写入 §10.2 与四个 Fault 采集小节 | diff --git a/scripts/check_cubemx_survival.py b/scripts/check_cubemx_survival.py index 889f4780..9999a1bd 100644 --- a/scripts/check_cubemx_survival.py +++ b/scripts/check_cubemx_survival.py @@ -133,6 +133,9 @@ CHECKS = [ must_contain=["fault_handler_enable", "SCB_SHCSR_MEMFAULTENA_Msk"], hint="MemManage/BusFault/UsageFault 默认**禁用**,须在 SHCSR 置使能位才会独立触发;" "否则它们被升级成 HardFault、日志只显示 HARDFAULT。" + "⚠ 这三位**无法在 CubeMX 配置**(2026-10-01 查模板源码证实:System Exception " + "只生成优先级、从不生成使能代码;.ioc 里的 NVIC.xxx=true 只是'向量可见'," + "不代表已使能)→ 只能手工写,故必须在本审计清单里兜底。" ), dict( id="hardfault-report-impl",