修正 CubeMX 重生成防丢失机制:审计脚本实测有效,恢复 5 处被覆盖定制
第二次重生成验证了审计机制,并证伪了上一轮的一处假设: - 审计脚本 check_cubemx_survival.py 准确检出全部 5 处覆盖(16 项中 5 FAIL), 机制有效;Src/main.c 复位原因锁存(USER CODE BEGIN Init 区)完好, 是"USER CODE 区有效"的正向证据。 - 关键发现:CubeMX 本次换了一代中断函数模板(旧 HardFault_Handler / HardFault_Report 自造区 → 新函数体内 HardFault_IRQn 0 / W1_HardFault_IRQn 0), "生成区之外的自造标记区"同样不被保留,旧假设被证伪。 恢复方案(第二次升级): - stm32f4xx_it.c:报告实现迁入 USER CODE BEGIN 0(区名从未变过的标准保护位); Handler 本体改为生成区整函数替换,用 #define HardFault_Handler HardFault_Handler_Gen 把生成实体改名保留,链接器自动移除(.map 实证 Removing ... (2 bytes)),向量表指向手工 __asm 版(0x080001a1,18 字节)。 - iwdg.c:去掉 HAL_IWDG_Init() 调用,补 IWDG-DEFER-START-ANCHOR 锚点注释。 - FreeRTOSConfig.h:恢复 FREE_RTOS_CONFIG_HARDENING 块(两个加固宏)。 - freertos.c:补回两处任务栈中文注释(栈值未丢,.ioc 已同步)。 配套: - check_cubemx_survival.py:hardfault-handler 改 wholefile + 锚点检查; UNPROTECTED_LEGACY 清单更新。 - AGENTS.md:新增"CubeMX 换模板"与"ARMCC 内联汇编语法"两条铁律, 以及"uvprojx 被 Keil 自动改写不入库"条目。 - 看门狗设计说明.md:§10.1 补第二次重生成验证结论与模板换代发现, §10.2 L2/L3 全面改写(自造区名不可依赖)。 验证:@build 0 错误 0 警告;pre-commit 13 钩子全通过;审计 16 通过 / 2 警告 / 0 失败。
This commit is contained in:
@@ -31,6 +31,13 @@ CubeMX 重生成存活审计(check_cubemx_survival.py)
|
||||
退出码:0 = 通过;1 = 有 FAIL 项。
|
||||
|
||||
设计依据:docs/子系统设计/看门狗设计说明.md §10.1;AGENTS.md「平台约束」。
|
||||
|
||||
注意
|
||||
----
|
||||
`MDK-ARM/*.uvprojx` **不参与漂移检测**(`no_baseline=True`):该文件每次
|
||||
`@build` 后被 Keil UV4 自动改写(XML 格式归一化 + `PackID` 降级到本机 DFP
|
||||
版本),内容每次构建都不同。若参与检测,则**每次构建后提交都会被拦**。
|
||||
本项只保留 `must_contain` 断言("源文件仍挂载")。
|
||||
"""
|
||||
|
||||
import argparse
|
||||
@@ -55,32 +62,55 @@ BASELINE = os.path.join(REPO, ".cubemx_survival_baseline.json")
|
||||
|
||||
CHECKS = [
|
||||
# ---------------- P0:开机死循环防线 ----------------
|
||||
# ⚠ 这一项是**生成区内的手工改动**(无 USER CODE 区可用):
|
||||
# 需要改的是 `hiwdg.Init.*` 赋值与 `HAL_IWDG_Init()` 调用之间的位置,
|
||||
# 而 CubeMX 只在该调用**之前**(IWDG_Init 0/1)和**之后**(IWDG_Init 2)
|
||||
# 生成标记区——正中间没有。塞进 IWDG_Init 1 区也不能阻止区外那行被覆盖
|
||||
# (另见 `usr init 1` 区的注释)。
|
||||
# 故本项按"文件整体 + 特征串"检查,并靠一个稳定锚点注释定位。
|
||||
dict(
|
||||
id="iwdg-defer-start",
|
||||
kind="protected",
|
||||
kind="wholefile",
|
||||
path="Src/iwdg.c",
|
||||
region="IWDG_DEFER_START",
|
||||
must_not_contain=["HAL_IWDG_Init(&hiwdg)"],
|
||||
must_contain=["IWDG-DEFER-START-ANCHOR"], # 手工锚点注释,重生成后会消失
|
||||
must_not_contain=["HAL_IWDG_Init(&hiwdg)"], # 真实调用不得存在
|
||||
severity="FAIL",
|
||||
hint="MX_IWDG_Init() 不得调 HAL_IWDG_Init()(内部立即启动,"
|
||||
"而它跑在 app_main_init 之前 → 开机必复位)。启动移交 task_wd_start()。",
|
||||
"而它跑在 app_main_init 之前 → 开机必复位)。启动移交 task_wd_start()。"
|
||||
"恢复后补回 `IWDG-DEFER-START-ANCHOR` 锚点注释。",
|
||||
),
|
||||
# ---------------- P1:崩溃可观测性 ----------------
|
||||
# ⚠ 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`(生成区内的标准保护位,重生成不丢)
|
||||
dict(
|
||||
id="hardfault-report",
|
||||
kind="protected",
|
||||
id="hardfault-handler",
|
||||
kind="wholefile",
|
||||
path="Src/stm32f4xx_it.c",
|
||||
region="HardFault_Handler",
|
||||
must_contain=["hard_fault_report"],
|
||||
hint="HardFault_Handler 须为手工汇编版(取 MSP/PSP 交 hard_fault_report)。"
|
||||
"被打回空 while(1) 后死机将完全静默。",
|
||||
must_contain=[
|
||||
"HARD-FAULT-ANCHOR", # 手工锚点注释,重生成后会消失
|
||||
"__asm void HardFault_Handler", # 手工汇编 Handler
|
||||
"hard_fault_report", # 跳转目标
|
||||
],
|
||||
must_not_contain=["HardFault_Handler(void)\r\n{\r\n while (1)"],
|
||||
hint="HardFault_Handler 须为手工汇编版(取 MSP/PSP 交 hard_fault_report),"
|
||||
"生成实体改名 HardFault_Handler_Gen。被打回空 while(1) 后死机将完全静默。"
|
||||
"恢复后补回 `HARD-FAULT-ANCHOR` 锚点注释。",
|
||||
),
|
||||
dict(
|
||||
id="hardfault-report-impl",
|
||||
kind="protected",
|
||||
path="Src/stm32f4xx_it.c",
|
||||
region="HardFault_Report",
|
||||
must_contain=["CFSR"],
|
||||
hint="HardFault 现场报告实现(打印 PC/LR/xPSR/CFSR/HFSR/BFAR/MMFAR)。",
|
||||
region="0",
|
||||
must_contain=["hard_fault_report", "CFSR"],
|
||||
hint="HardFault 现场报告实现(打印 PC/LR/xPSR/CFSR/HFSR/BFAR/MMFAR)"
|
||||
"须在 USER CODE BEGIN 0 区内(生成区内的标准保护位)。",
|
||||
),
|
||||
# ---------------- P1:FreeRTOS 加固 ----------------
|
||||
dict(
|
||||
@@ -138,6 +168,7 @@ CHECKS = [
|
||||
kind="generated",
|
||||
path="MDK-ARM/STM32F407-Demo.uvprojx",
|
||||
must_contain=["task_watchdog.c"],
|
||||
no_baseline=True, # 见下方说明:本文件每次 @build 后被 Keil 改写
|
||||
hint="task_watchdog.c 须挂在 MDK 工程 App 组;CubeMX 重生成可能重建 .uvprojx。",
|
||||
),
|
||||
dict(
|
||||
@@ -268,6 +299,10 @@ def evaluate():
|
||||
continue
|
||||
|
||||
# ---- 取待检内容 ----
|
||||
# `probe_neg`:用于 must_not_contain(禁项)——**总是剥注释**,
|
||||
# 避免"注释里提到历史代码"被误判为真实调用。
|
||||
# `probe` :用于 must_contain(正项)——`wholefile` 模式下不剥注释,
|
||||
# 因为锚点本身可能就是注释(如 IWDG-DEFER-START-ANCHOR)。
|
||||
if c["kind"] == "protected":
|
||||
body = region_of(text, c["region"])
|
||||
if body is None:
|
||||
@@ -276,16 +311,28 @@ def evaluate():
|
||||
)
|
||||
snap[cid] = None
|
||||
continue
|
||||
probe = strip_comments(body) # 只对保护区做注释过滤
|
||||
probe = strip_comments(body)
|
||||
probe_neg = probe
|
||||
elif c["kind"] == "wholefile":
|
||||
body = text
|
||||
probe = text # 正项:整文件原文(锚点可能是注释)
|
||||
probe_neg = strip_comments(text) # 禁项:仍剥注释
|
||||
else:
|
||||
body = text
|
||||
probe = text
|
||||
probe_neg = text
|
||||
|
||||
snap[cid] = sha(probe)
|
||||
# ---- 基线快照 ----
|
||||
# `no_baseline=True` 的项不参与漂移检测。理由:`MDK-ARM/*.uvprojx`
|
||||
# 每次 `@build` 后被 Keil UV4 自动改写(XML 格式归一化 + PackID 降级到
|
||||
# 本机 DFP 版本),内容每次构建都不同 → 若参与漂移检测,则**每次构建后
|
||||
# 提交都会被拦**。本项只保留 must_contain 断言("源文件仍挂载")。
|
||||
if not c.get("no_baseline"):
|
||||
snap[cid] = sha(probe)
|
||||
|
||||
# ---- 断言 ----
|
||||
bad = [n for n in c.get("must_contain", []) if n not in probe]
|
||||
bad += [n for n in c.get("must_not_contain", []) if n in probe]
|
||||
bad += [n for n in c.get("must_not_contain", []) if n in probe_neg]
|
||||
if bad:
|
||||
results.append(
|
||||
(level, cid, "%s:%s" % (c["region"] if c["kind"] == "protected" else path,
|
||||
@@ -318,15 +365,15 @@ UNPROTECTED_LEGACY = [
|
||||
(
|
||||
"Src/freertos.c",
|
||||
[
|
||||
("任务栈注释(.stack_size 行内中文说明)", r"\.stack_size\s*=\s*\d+\s*\*\s*4"),
|
||||
("timeSyncTask 手工定义(不在 .ioc FREERTOS.Tasks01 里)", r"timeSyncTask_attributes"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Src/stm32f4xx_it.c",
|
||||
[
|
||||
("MemManage_Handler 空 while(1)", r"void MemManage_Handler\(void\)"),
|
||||
("BusFault_Handler 空 while(1)", r"void BusFault_Handler\(void\)"),
|
||||
("UsageFault_Handler 空 while(1)", r"void UsageFault_Handler\(void\)"),
|
||||
("MemManage_Handler 仍为空 while(1),死机静默", r"void MemManage_Handler\(void\)"),
|
||||
("BusFault_Handler 仍为空 while(1),死机静默", r"void BusFault_Handler\(void\)"),
|
||||
("UsageFault_Handler 仍为空 while(1),死机静默", r"void UsageFault_Handler\(void\)"),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user