stm32的栈监控与HardFault_Handler问题排查
参考
rtx5源码位置
D:\soft\keil\armpack\ARM\CMSIS\5.8.0\CMSIS\RTOS2\RTX\Source
常见问题
内存碎片
频繁 malloc/free 或 new/delete 引起内存碎片,造成分配内存失败
解法
- 保证只在启动时一次性 new malloc完的,不再释放 (可提前报错)
- 单片机内存分配/释放通常线程不安全,应在主线程(启动线程)中串行完成资源或线程的创建。
- 容器长度分配足够, 不要动态扩容
- 用自定义内存池,自己控制内存分配和释放
- 用静态内存分配
- 尽可能用栈变量
字节对齐(类型强转)
指针强转 #define NLENGTH( p ) (*((uint16_t *) ( p ))) 即当左值,又当右值
解法
左值和右值分别实现
#define NLENGTH(p) ((uint16_t)((p)[0] | ((p)[1] << 8)))
#define NLENGTH_SET(p, value) do { \
(p)[0] = (uint8_t)((value) & 0xFF); /* 低字节 */ \
(p)[1] = (uint8_t)(((value) >> 8) & 0xFF); /* 高字节 */ \
} while(0)
字节对齐( 结构体 sizeof 大小与实际不符)
解法
#pragma pack(1)
类型定义到此处
#pragma pack()
栈溢出
解法
- 栈估算
- 栈监控
- 栈分配稍微大于长时运行栈的最大用量
- 避免深递归,深层调用
辅助命令
# 配置环境变量
PS D:\USART_TFT> $env:PATH += ";D:\soft\keil\ARM\ARMCC\bin"
# axf 转 汇编
PS D:\USART_TFT> fromelf --text -c -d --output=my_USART_TFT.s ./USART_TFT.axf
# axf 转 bin
PS D:\USART_TFT> fromelf --bin -o my_USART_TFT.bin ./USART_TFT.axf
# 崩溃地址它还原到C代码位置, 0x0800FBCE是崩溃地址PC, 0x080133E7是返回地址LR
PS D:\USART_TFT> addr2line -e USART_TFT.axf -afpiC 0x0800FBCE 0x080133E7
或
PS D:\USART_TFT> arm-none-eabi-addr2line -e USART_TFT.axf -afpiC 0x0800FBCE 0x080133E7
0x0800fbce: ManPost at man.cpp:77
0x080133e7: Leave at mythread.cpp:55
# 列出符号表 名称、地址、类型
# 查询是否存在某个函数或变量
$ nm USART_TFT.axf | grep user_malloc
08014819 T user_malloc
# 显示符号表
$ objdump -t USART_TFT.axf
# 显示段信息
$ objdump -h USART_TFT.axf
# 显示文件各部分大小
PS D:\USART_TFT> size USART_TFT.axf
text data bss dec hex filename
71868 164 25192 97224 17bc8 USART_TFT.axf
bsp_stack.h
#ifndef BSP_STACK_H
#define BSP_STACK_H
#include "stdint.h"
/** 线程栈和裸机栈是独立的 **/
#ifdef __cplusplus
extern "C" {
#endif
// 填充裸机栈底区域为 MAGIC_VALUE,用于标识栈的使用情况
void BspStackFillMagic(void);
// 获取当前栈使用的空间,即从栈顶到栈指针的距离
uint32_t BspGetCurrentStackUsage(void);
// 获取裸机系统占用的最大栈空间
// 这个函数通过遍历栈底区域来找到栈的最大使用空间
uint32_t BspGetMaxStackUsage(void);
// 获取栈的总大小,通常与栈大小宏 BSP_STACK_SIZE 相同
uint32_t BspGetStackTotal(void);
// 获取当前任务的剩余栈空间
// 此函数用于监控任务栈剩余空间,防止栈溢出
uint32_t BspOsThreadGetStackSpace(void);
// 检查裸机栈是否发生溢出
// 如果栈底的 MAGIC_VALUE 被修改,则表示发生了栈溢出
uint8_t BspIsStackOverflow(void);
#ifdef __cplusplus
}
#endif
#endif
bsp_stack.c
/* bsp_stack.c */
#include "bsp_stack.h"
#include "stm32g0xx_hal.h"
#include "cmsis_os2.h"
/* 与 startup_stm32g070xx.s 中 Stack_Size EQU 保持一致 */
#define BSP_STACK_SIZE 0x1000U
#define MAGIC_VALUE 0xDEADBEEFU
/* __initial_sp 在 __MICROLIB 下已默认导出,无需改汇编 */
extern uint32_t __initial_sp;
#define STACK_TOP ((uint32_t)&__initial_sp)
#define STACK_BOTTOM (STACK_TOP - BSP_STACK_SIZE)
void BspStackFillMagic(void)
{
uint32_t *bottom = (uint32_t *)STACK_BOTTOM;
uint32_t *current_sp = (uint32_t *)__get_MSP();
for (uint32_t *p = bottom; p < current_sp; p++) {
*p = MAGIC_VALUE;
}
}
uint32_t BspGetCurrentStackUsage(void)
{
uint32_t sp = __get_MSP();
if (sp >= STACK_TOP) return 0U;
return STACK_TOP - sp;
}
uint32_t BspGetMaxStackUsage(void)
{
uint32_t *bottom = (uint32_t *)STACK_BOTTOM;
uint32_t *top = (uint32_t *)STACK_TOP;
uint32_t *p = bottom;
while ((p < top) && (*p == MAGIC_VALUE)) {
p++;
}
return STACK_TOP - (uint32_t)p;
}
uint32_t BspGetStackTotal(void)
{
return BSP_STACK_SIZE;
}
uint8_t BspIsStackOverflow(void)
{
uint32_t *bottom = (uint32_t *)STACK_BOTTOM;
for (int i = 0; i < 4; i++) {
if (bottom[i] != MAGIC_VALUE) return 1U;
}
return 0U;
}
uint32_t BspOsThreadGetStackSpace(void){
//线程栈大小
//osThreadGetStackSize(osThreadGetId());
return osThreadGetStackSpace(osThreadGetId());
}
用法
main.c
main入口填充裸机栈 BspStackFillMagic();
stackBuf 测试裸机栈, 让裸机栈至少占用300
int main(void)
{
volatile uint8_t stackBuf[300];
uint32_t i;
for (i = 0; i < sizeof(stackBuf); i++)
{
stackBuf[i] = 0xAA;
}
BspStackFillMagic();
BspInit();
osKernelInitialize();
__ctMain.Start();
osKernelStart();
while (1)
{
}
}
打开cmsis_os2 堆栈水印开关
RTX5 要求栈大小必须是 8 字节倍数 800可以 , 801不行
#define OS_STACK_WATERMARK 1
//当前线程栈容量
osThreadGetStackSize(osThreadGetId());
//当前线程剩余栈
osThreadGetStackSpace(osThreadGetId());
Keil链接脚本
STM32G070CB 36 KB RAM+ 128KB ROM
BOOT (28KB)
0x8000000,0x7000
USART_BOOT.sct
LR_IROM1 0x08000000 0x00007000 { ; load region size_region
ER_IROM1 0x08000000 0x00007000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
.ANY (+XO)
}
RW_IRAM1 0x20000000 0x00009000 { ; RW data
.ANY (+RW +ZI)
}
}

APP
0x8007000,0x20000 (128KB) (BOOT占用了28KB,实际剩余100KB,写大没关系)
USART_APP.sct
LR_IROM1 0x08007000 0x00020000 { ; 加载区域,大小为 0x20000 字节
ER_IROM1 0x08007000 0x00020000 { ; 加载地址 = 执行地址
*.o (RESET, +First) ; 包含所有目标文件(.o),包括 RESET 部分和第一个部分
*(InRoot$$Sections) ; 包含根部分的所有节
.ANY (+RO) ; 包含所有只读(RO)段
.ANY (+XO) ; 包含所有执行(XO)段
}
RW_IRAM1 0x20000000 0x00009000 { ; 可读写数据区域(RW),大小为 0x9000 字节
.ANY (+RW +ZI) ; 包含所有读写(RW)段和零初始化(ZI)段
}
}

裸机栈的返回
栈向下增长
Stack_Size EQU 0x1000
[ __initial_sp , __initial_sp-0x1000 )
观察裸机栈的占用
&__initial_sp-300
图中表明裸机栈大约使用了350

HardFault_Handler 问题排查
进入 HardFault_Handler 后外设中断不再进入
但 串口的阻塞式发送一般是能用的
如果不能用 就 io模拟串行协议 , 将 崩溃现场 写入存储器, 或 发出来, ming_msl就特别适合, 一个gpio就能把崩溃现场发出
注意: 崩溃的代码 和烧录进去的代码 一定是 完全一样的代码
这里是在崩溃以后,再次以debug进入
stm32g070cbtx 芯片
stm32g0xx_it.c 片段
/*
* 核心思路:HardFault_Handler 必须是 naked 函数,
* 这样编译器不会生成 prologue,LR 里还保留着 EXC_RETURN 值。
* 把 LR 作为参数传给 C 函数再做判断。
*/
void HardFault_Handler_C(uint32_t exc_return)
{
uint32_t* hardfault_args;
/*
* EXC_RETURN (进入异常时 LR 的值):
* bit[2] = 1 → 异常前用的是 PSP(线程模式)
* bit[2] = 0 → 异常前用的是 MSP(handler 模式或主线程)
*/
if (exc_return & 0x4) {
hardfault_args = (uint32_t*)__get_PSP(); // 普通任务触发
} else {
hardfault_args = (uint32_t*)__get_MSP(); // 中断/主循环触发
}
/* Cortex-M 异常帧(自动压栈顺序):
* [0]=R0 [1]=R1 [2]=R2 [3]=R3
* [4]=R12 [5]=LR [6]=PC [7]=xPSR
*/
uint32_t stacked_r0 = hardfault_args[0];
uint32_t stacked_r1 = hardfault_args[1];
uint32_t stacked_r2 = hardfault_args[2];
uint32_t stacked_r3 = hardfault_args[3];
uint32_t stacked_r12 = hardfault_args[4];
uint32_t stacked_lr = hardfault_args[5]; // 出错函数的返回地址
uint32_t stacked_pc = hardfault_args[6]; // ★ 出错指令的地址 ★
uint32_t stacked_psr = hardfault_args[7];
/* 防止编译器优化掉未使用变量(调试时在此打断点) */
(void)stacked_r0; (void)stacked_r1;
(void)stacked_r2; (void)stacked_r3;
(void)stacked_r12;
while (1)
{
printf("=== HardFault ===\r\n");
printf("IsStackOver = 0x%08lX \r\n", BspIsStackOverflow());
printf("MaxStackUsage = 0x%08lX \r\n", BspGetMaxStackUsage());
printf("PC = 0x%08lX \r\n", stacked_pc);
printf("LR = 0x%08lX \r\n", stacked_lr);
printf("PSR = 0x%08lX\r\n", stacked_psr);
printf("EXC_RETURN = 0x%08lX (%s stack)\r\n",
exc_return,
(exc_return & 0x4) ? "PSP" : "MSP");
for (int i = 0; i < 6000000; i++);
}
}
__asm void HardFault_Handler(void)
{
IMPORT HardFault_Handler_C /* 声明外部 C 符号 */
MOV R0, LR /* EXC_RETURN → 参数 R0 */
LDR R1, =HardFault_Handler_C /* 加载完整 32-bit 地址 */
BX R1 /* 无范围限制的跳转 */
}
串口打印崩溃现场
[19:57:01.811]收←◆=== HardFault ===
IsStackOver = 0x00000000
MaxStackUsage = 0x0000015C
PC = 0x08010996
LR = 0x08010FC5
PSR = 0x61000000
EXC_RETURN = 0xFFFFFFFD (PSP stack)
[19:57:02.576]收←◆=== HardFault ===
IsStackOver = 0x00000000
MaxStackUsage = 0x0000015C
PC = 0x08010996
LR = 0x08010FC5
PSR = 0x61000000
EXC_RETURN = 0xFFFFFFFD (PSP stack)
崩溃位置

无法通过地址反推源码位置的情况
在0x08010996 打断点, 或在map文件 找 <= 0x08010996 最近的符号位置 0x08010971 是 appWriteFormProcess
或找找 LR=0x08010FC5 附近,崩溃的返回地址
appReadEventProcess(Z8ModRmsg*) 0x0801093d Thumb Code 50 appserverrouter.o(i._Z19appReadEventProcessP9Z8ModRmsg)
appWriteFormProcess(Z8ModRmsg*) 0x08010971 Thumb Code 60 appserverrouter.o(i._Z19appWriteFormProcessP9Z8ModRmsg)
appConfigFormProcess(Z8ModRmsg*) 0x080109c9 Thumb Code 80 appserverrouter.o(i._Z20appConfigFormProcessP9Z8ModRmsg)
appConfigZoneProcess(Z8ModRmsg*) 0x08010a1d Thumb Code 118 appserverrouter.o(i._Z20appConfigZoneProcessP9Z8ModRmsg)
stm32 通用芯片
//人造一个崩溃位置
*(volatile uint32_t*)0x5A5A5A5A = 0x5A5A5A5A;
stm32g0xx_it.c 片段
/* ============================================================
* HardFault_Handler — Compatible with M0+/M3/M4
* STM32G0 (Cortex-M0+) : no CFSR, no FPU
* STM32F103 (Cortex-M3) : CFSR present, no FPU
* STM32F407 (Cortex-M4) : CFSR present, FPU with extended frame
*
* Toolchain support:
* Keil AC5 (__CC_ARM)
* Keil AC6 (__ARMCC_VERSION >= 6000000)
* GCC / STM32CubeIDE (__GNUC__)
* ============================================================ */
#include <stdint.h>
#include <stdio.h>
/* ----------------------------------------------------------
* 1. SCB registers
* HFSR : available on all Cortex-M variants
* CFSR / MMFAR / BFAR : M3 and above only
* ---------------------------------------------------------- */
#define SCB_HFSR (*(volatile uint32_t*)0xE000ED2CU)
#if (__CORTEX_M >= 3)
#define SCB_CFSR (*(volatile uint32_t*)0xE000ED28U)
#define SCB_MMFAR (*(volatile uint32_t*)0xE000ED34U)
#define SCB_BFAR (*(volatile uint32_t*)0xE000ED38U)
#define SCB_CPACR (*(volatile uint32_t*)0xE000ED88U)
#endif
/* ----------------------------------------------------------
* 2. Fault reason decoder (M3 / M4 only)
* ---------------------------------------------------------- */
#if (__CORTEX_M >= 3)
static void decode_cfsr(uint32_t cfsr)
{
/* ---- MemManage Fault (bits[7:0]) ---- */
if (cfsr & 0x000000FFU) {
printf(" [MemManage]");
if (cfsr & (1U << 0)) printf(" IACCVIOL");
if (cfsr & (1U << 1)) printf(" DACCVIOL");
if (cfsr & (1U << 3)) printf(" MUNSTKERR");
if (cfsr & (1U << 4)) printf(" MSTKERR");
if (cfsr & (1U << 5)) printf(" MLSPERR");
if (cfsr & (1U << 7)) printf(" MMARVALID -> MMFAR=0x%08lX",
(unsigned long)SCB_MMFAR);
printf("\r\n");
}
/* ---- Bus Fault (bits[15:8]) ---- */
if (cfsr & 0x0000FF00U) {
printf(" [BusFault]");
if (cfsr & (1U << 8)) printf(" IBUSERR");
if (cfsr & (1U << 9)) printf(" PRECISERR");
if (cfsr & (1U << 10)) printf(" IMPRECISERR");
if (cfsr & (1U << 11)) printf(" UNSTKERR");
if (cfsr & (1U << 12)) printf(" STKERR");
if (cfsr & (1U << 13)) printf(" LSPERR");
if (cfsr & (1U << 15)) printf(" BFARVALID -> BFAR=0x%08lX",
(unsigned long)SCB_BFAR);
printf("\r\n");
}
/* ---- Usage Fault (bits[31:16]) ---- */
if (cfsr & 0xFFFF0000U) {
printf(" [UsageFault]");
if (cfsr & (1U << 16)) printf(" UNDEFINSTR");
if (cfsr & (1U << 17)) printf(" INVSTATE");
if (cfsr & (1U << 18)) printf(" INVPC");
if (cfsr & (1U << 19)) printf(" NOCP");
if (cfsr & (1U << 24)) printf(" UNALIGNED");
if (cfsr & (1U << 25)) printf(" DIVBYZERO");
printf("\r\n");
}
}
#endif /* __CORTEX_M >= 3 */
/* ----------------------------------------------------------
* 3. C fault handler (called from naked assembly entry)
* ---------------------------------------------------------- */
void HardFault_Handler_C(uint32_t exc_return)
{
uint32_t *frame;
/*
* EXC_RETURN bit[2]:
* 1 -> exception was using PSP (thread / task mode)
* 0 -> exception was using MSP (handler / privileged mode)
*/
if (exc_return & 0x4U) {
frame = (uint32_t *)__get_PSP();
} else {
frame = (uint32_t *)__get_MSP();
}
/*
* Standard Cortex-M exception frame (identical for M0+/M3/M4 basic frame):
* [0]=R0 [1]=R1 [2]=R2 [3]=R3
* [4]=R12 [5]=LR [6]=PC [7]=xPSR
*
* On M4 with FPU active (EXC_RETURN bit[4] == 0), an extended frame
* appends S0-S15 + FPSCR + reserved at [8..25].
* The basic frame offsets above are unchanged — PC is always at [6].
*/
const uint32_t r0 = frame[0];
const uint32_t r1 = frame[1];
const uint32_t r2 = frame[2];
const uint32_t r3 = frame[3];
const uint32_t r12 = frame[4];
const uint32_t lr = frame[5]; /* return address of faulting function */
const uint32_t pc = frame[6]; /* *** address of faulting instruction *** */
const uint32_t psr = frame[7];
/* Prevent compiler from optimizing these out.
* Set a breakpoint here and inspect the register view. */
(void)r0; (void)r1; (void)r2; (void)r3; (void)r12;
/* Read fault registers before any further stack activity */
const uint32_t hfsr = SCB_HFSR;
#if (__CORTEX_M >= 3)
const uint32_t cfsr = SCB_CFSR;
#endif
while (1)
{
printf("\r\n======== HardFault ========\r\n");
/* --- Fault context --- */
printf("PC = 0x%08lX <- disassemble this\r\n", (unsigned long)pc);
printf("LR = 0x%08lX\r\n", (unsigned long)lr);
printf("PSR = 0x%08lX\r\n", (unsigned long)psr);
printf("EXC_RETURN = 0x%08lX (%s stack)\r\n",
(unsigned long)exc_return,
(exc_return & 0x4U) ? "PSP" : "MSP");
/* --- Optional stack health (define these in your BSP) --- */
#if defined(BSP_STACK_CHECK_ENABLED)
printf("StackOverflow = %lu\r\n", (unsigned long)BspIsStackOverflow());
printf("MaxStackUsage = %lu B\r\n", (unsigned long)BspGetMaxStackUsage());
#endif
/* --- HFSR (all Cortex-M variants) --- */
printf("HFSR = 0x%08lX", (unsigned long)hfsr);
if (hfsr & (1U << 1)) printf(" VECTTBL");
if (hfsr & (1U << 30)) printf(" FORCED");
if (hfsr & (1U << 31)) printf(" DEBUGEVT");
printf("\r\n");
/* --- CFSR / MMFAR / BFAR (M3 and M4 only) --- */
#if (__CORTEX_M >= 3)
printf("CFSR = 0x%08lX\r\n", (unsigned long)cfsr);
decode_cfsr(cfsr);
#endif
/* --- FPU extended frame info (M4 only) --- */
#if (__CORTEX_M >= 4)
{
uint8_t fpu_enabled = (SCB_CPACR & (0xFU << 20U)) ? 1U : 0U;
uint8_t fpu_frame = fpu_enabled && !(exc_return & 0x10U);
printf("FPU enabled = %d extended_frame = %d\r\n",
fpu_enabled, fpu_frame);
}
#endif
for (volatile int i = 0; i < 6000000; i++);
}
}
/* ----------------------------------------------------------
* 4. Naked assembly entry point
* Passes EXC_RETURN (LR) as the first argument (R0) to the
* C handler. Must be naked — any prologue would corrupt the
* stack frame layout before we read it.
* ---------------------------------------------------------- */
/* ---- Keil AC5 (ARMCC) ---- */
#if defined(__CC_ARM)
__asm void HardFault_Handler(void)
{
IMPORT HardFault_Handler_C
MOV R0, LR /* EXC_RETURN -> arg0 (R0) */
LDR R1, =HardFault_Handler_C /* full 32-bit address, no range limit */
BX R1
}
/* ---- Keil AC6 (Clang-ARM) or GCC / STM32CubeIDE ---- */
#elif defined(__ARMCC_VERSION) || defined(__GNUC__)
__attribute__((naked)) void HardFault_Handler(void)
{
__asm volatile(
"MOV R0, LR \n"
"LDR R1, =HardFault_Handler_C \n"
"BX R1 \n"
);
}
#else
#error "Unsupported toolchain. Add a naked wrapper for your compiler."
#endif
崩溃日志
[13:26:17.826]收←◆
======== HardFault ========
PC = 0x0800326A <- disassemble this
LR = 0x08005425
PSR = 0x61000000
EXC_RETURN = 0xFFFFFFFD (PSP stack)
HFSR = 0x40000000 FORCED
CFSR = 0x00000400
[BusFault] IMPRECISERR
在这里插入图片描述
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)