四个 Fault 全部采集现场:MemManage/BusFault/UsageFault 由空死循环升级
原状:HardFault 已有崩溃采集,但 MemManage / BusFault / UsageFault 仍是空
while(1) —— 这三类故障发生时日志完全静默,且因 SHCSR 默认未使能,实际会
被升级成 HardFault,报告出笼统的 "HARDFAULT"、看不出根因。
改动:
- Src/stm32f4xx_it.c:三个 Handler 升级为手工 __asm 版(取 MSP/PSP 帧指针)
-> B 到 per-fault C 入口 fault_entry_{mem,bus,usage} -> hard_fault_report()。
hard_fault_report 增加 tag 参数,日志统一为
"[ERR] <TAG> PC=.. LR=.. xPSR=.. R0-R3=.. CFSR=.. HFSR=..[/BFAR][/MMFAR]"。
生成实体经 #define X X_Gen 改名保留,链接器各移除 2 字节(.map 已验)。
三个各带独立锚点 MEMFAULT/BUSFAULT/USAGEFAULT-ANCHOR。
- 新增 fault_handler_enable()(USER CODE BEGIN 1 区):使能 SHCSR 的
MEMFAULTENA/BUSFAULTENA/USGFAULTENA。声明入 Inc/stm32f4xx_it.h,调用在
app_main_init() 第一行(纯寄存器写,不依赖日志/时钟)。
- 刻意不启用 CCR.UNALIGN_TRP / CCR.DIV_0_TRP:会把普通 bug 升级成死机,
对本项目弊大于利(说明写进函数注释)。
实现约束(本轮踩坑):ARMCC 的 __asm 块取不到 C 的数据符号(ADR r1, xxx_tag
报 A1516E: Bad symbol,去 static 也无效),也无法给 C 函数传字符串参数,
故故障名必须由 C 侧 per-fault 入口带上。
配套:
- check_cubemx_survival.py:新增 fault-handlers-3 / fault-handler-enable 两项,
UNPROTECTED_LEGACY 清掉三个 Fault(WARN 2->1),清单 16 -> 18 项。
- AGENTS.md:新增"4 个 Fault 全部采集"条目;ARMCC 汇编条目补 A1516E 第 3 坑;
校验标记补 [ERR](四个 Fault 日志都以它开头 -> 一律判 FAIL,刻意)。
- 看门狗设计说明.md:§6.1 补 4 条 Fault 标记与 @test 禁项关系;
§10.2 L3 清单与四个 Fault 采集小节;§11 结论表与变更记录。
- 待处理内容.md:新增 N-03d(已完成),N-03c 补第二次重生成结论。
验证:@build 0 错误 0 警告(Flash +156 B,RAM 无变化);pre-commit 13 钩子
全通过;审计 18 通过 / 1 警告 / 0 失败。
This commit is contained in:
+130
-22
@@ -92,9 +92,25 @@ static void hard_fault_puthex(uint32_t v)
|
||||
hard_fault_write(out, 8u);
|
||||
}
|
||||
|
||||
void hard_fault_report(uint32_t *frame)
|
||||
/* 崩溃现场报告核心(四个 Fault 共用)。
|
||||
* 入口参数:tag - 故障名(ASCII 字面量,如 "HARDFAULT" / "MEMFAULT")
|
||||
* frame - 异常栈帧 {R0,R1,R2,R3,R12,LR,PC,xPSR}
|
||||
* 说明:CFSR 一次性打出,其子域已含 MMARVALID(bit7)/BFARVALID(bit15),
|
||||
* 故 MMFAR/BFAR 的"是否有意义"由 CFSR 判定,四个 Fault 都能看全。
|
||||
*
|
||||
* ⚠ 本函数**不直接**被异常向量调用——异常入口是下方的 `__asm` 版 Handler,
|
||||
* 它们只负责取异常帧指针,然后 `B` 到下面 4 个 per-fault 入口之一
|
||||
* (每个入口自带 tag 字面量后再调本函数)。
|
||||
* 这样做的原因:ARMCC 的 `__asm` 块**取不到 C 的 static 数据符号**
|
||||
* (实测:`ADR r1, xxx_tag` 报 `A1516E: Bad symbol`,去掉 static 也无效),
|
||||
* 而 `__asm` 里也无法给 C 函数传结构体/字符串参数。故把"带 tag"这一步
|
||||
* 放在普通 C 函数里做。
|
||||
*/
|
||||
void hard_fault_report(const char *tag, uint32_t *frame)
|
||||
{
|
||||
HF_PUTS("\r\n[ERR] HARDFAULT PC=");
|
||||
HF_PUTS("\r\n[ERR] ");
|
||||
hard_fault_write(tag, 16u);
|
||||
HF_PUTS(" PC=");
|
||||
hard_fault_puthex(frame[6]);
|
||||
HF_PUTS(" LR=");
|
||||
hard_fault_puthex(frame[5]);
|
||||
@@ -127,10 +143,38 @@ void hard_fault_report(uint32_t *frame)
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
|
||||
/* 四个 per-fault 入口:__asm Handler 取到帧指针后 `B` 到这里,由 C 补上故障名。
|
||||
* 名字用 `fault_entry_*` 前缀,与向量符号(`*_Handler`)区分开。 */
|
||||
void fault_entry_hard(uint32_t *frame)
|
||||
{
|
||||
hard_fault_report("HARDFAULT", frame);
|
||||
}
|
||||
|
||||
void fault_entry_mem(uint32_t *frame)
|
||||
{
|
||||
hard_fault_report("MEMFAULT", frame);
|
||||
}
|
||||
|
||||
void fault_entry_bus(uint32_t *frame)
|
||||
{
|
||||
hard_fault_report("BUSFAULT", frame);
|
||||
}
|
||||
|
||||
void fault_entry_usage(uint32_t *frame)
|
||||
{
|
||||
hard_fault_report("USAGEFAULT", frame);
|
||||
}
|
||||
|
||||
/* 为生成函数改名,避免与下方手工 `__asm` 实现符号冲突。
|
||||
* 生成实体改名为 HardFault_Handler_Gen(永不执行,仅为占用名字不冲突,
|
||||
* 保持生成区代码零删除)。 */
|
||||
* 生成实体改名为 <原名>_Gen(永不执行,仅为占名字不冲突,
|
||||
* 保持生成区代码零删除;未被引用 → 链接器自动移除)。
|
||||
*
|
||||
* 覆盖的 4 个 Cortex-M4 异常:HardFault / MemManage / BusFault / UsageFault。
|
||||
* 后三者须在 SHCSR 中使能对应位才会真正触发(见 fault_handler_enable)。 */
|
||||
#define HardFault_Handler HardFault_Handler_Gen
|
||||
#define MemManage_Handler MemManage_Handler_Gen
|
||||
#define BusFault_Handler BusFault_Handler_Gen
|
||||
#define UsageFault_Handler UsageFault_Handler_Gen
|
||||
/* USER CODE END 0 */
|
||||
|
||||
/* External variables --------------------------------------------------------*/
|
||||
@@ -184,16 +228,20 @@ void NMI_Handler(void)
|
||||
* 命名:`#undef HardFault_Handler` 后重定义为 __asm 版本,与向量表符号一致;
|
||||
* 为避免与下方生成实体"重复定义"(ARMCC 报 #247,其"已定义"检查
|
||||
* 早于 C 作用域),已在文件头用 `#define HardFault_Handler
|
||||
* HardFault_Handler_Gen` 把生成实体改名为不冲突的符号。 */
|
||||
* 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)
|
||||
{
|
||||
extern hard_fault_report
|
||||
extern fault_entry_hard
|
||||
TST lr, #4
|
||||
ITE EQ
|
||||
MRSEQ r0, MSP
|
||||
MRSNE r0, PSP
|
||||
B hard_fault_report
|
||||
B fault_entry_hard
|
||||
}
|
||||
|
||||
void HardFault_Handler_Gen(void)
|
||||
@@ -206,13 +254,24 @@ void HardFault_Handler_Gen(void)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief This function handles Memory management fault.
|
||||
*/
|
||||
void MemManage_Handler(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)
|
||||
{
|
||||
/* USER CODE BEGIN MemoryManagement_IRQn 0 */
|
||||
extern fault_entry_mem
|
||||
TST lr, #4
|
||||
ITE EQ
|
||||
MRSEQ r0, MSP
|
||||
MRSNE r0, PSP
|
||||
B fault_entry_mem
|
||||
}
|
||||
|
||||
/* USER CODE END MemoryManagement_IRQn 0 */
|
||||
void MemManage_Handler_Gen(void)
|
||||
{
|
||||
while (1)
|
||||
{
|
||||
/* USER CODE BEGIN W1_MemoryManagement_IRQn 0 */
|
||||
@@ -221,13 +280,24 @@ void MemManage_Handler(void)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief This function handles Pre-fetch fault, memory access fault.
|
||||
*/
|
||||
void BusFault_Handler(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)
|
||||
{
|
||||
/* USER CODE BEGIN BusFault_IRQn 0 */
|
||||
extern fault_entry_bus
|
||||
TST lr, #4
|
||||
ITE EQ
|
||||
MRSEQ r0, MSP
|
||||
MRSNE r0, PSP
|
||||
B fault_entry_bus
|
||||
}
|
||||
|
||||
/* USER CODE END BusFault_IRQn 0 */
|
||||
void BusFault_Handler_Gen(void)
|
||||
{
|
||||
while (1)
|
||||
{
|
||||
/* USER CODE BEGIN W1_BusFault_IRQn 0 */
|
||||
@@ -236,13 +306,25 @@ void BusFault_Handler(void)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief This function handles Undefined instruction or illegal state.
|
||||
*/
|
||||
void UsageFault_Handler(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)
|
||||
{
|
||||
/* USER CODE BEGIN UsageFault_IRQn 0 */
|
||||
extern fault_entry_usage
|
||||
TST lr, #4
|
||||
ITE EQ
|
||||
MRSEQ r0, MSP
|
||||
MRSNE r0, PSP
|
||||
B fault_entry_usage
|
||||
}
|
||||
|
||||
/* USER CODE END UsageFault_IRQn 0 */
|
||||
void UsageFault_Handler_Gen(void)
|
||||
{
|
||||
while (1)
|
||||
{
|
||||
/* USER CODE BEGIN W1_UsageFault_IRQn 0 */
|
||||
@@ -250,6 +332,32 @@ void UsageFault_Handler(void)
|
||||
}
|
||||
}
|
||||
|
||||
/* USER CODE BEGIN 1 */
|
||||
|
||||
/* 使能 MemManage / BusFault / UsageFault 三个异常。
|
||||
*
|
||||
* 为什么必须做:复位后 SCB->SHCSR 只开了 HardFault,后三者处于**禁用**状态——
|
||||
* 它们触发时会被升级成 HardFault,`hard_fault_report` 打出来的是 "HARDFAULT"
|
||||
* 且看不出根因。这里逐个置使能位,日志里才能直接看到 MEMFAULT / BUSFAULT /
|
||||
* USAGEFAULT 字样。
|
||||
*
|
||||
* ⚠ 刻意**不**打开以下两项(都会把普通 bug 升级成死机,对本项目弊大于利):
|
||||
* - CCR.UNALIGN_TRP :非对齐访问陷入。Cortex-M4 硬件本可处理非对齐访问,
|
||||
* 打开后协议解析里常见的非对齐取字段会直接死机。
|
||||
* - CCR.DIV_0_TRP :除零陷入。除零应由业务代码自己判,不要靠硬件兜。
|
||||
* 打开它们只会让"本该只是算错个值"变成"整机复位"。
|
||||
*
|
||||
* 调用时机:app_main_init() 早期(日志可用前即可,越早越好)。
|
||||
*/
|
||||
void fault_handler_enable(void)
|
||||
{
|
||||
SCB->SHCSR |= SCB_SHCSR_MEMFAULTENA_Msk |
|
||||
SCB_SHCSR_BUSFAULTENA_Msk |
|
||||
SCB_SHCSR_USGFAULTENA_Msk;
|
||||
}
|
||||
|
||||
/* USER CODE END 1 */
|
||||
|
||||
/**
|
||||
* @brief This function handles Debug monitor.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user