当前位置:首页 > 嵌入式 > 嵌入式分享
[导读]栈溢出在MCU里最麻烦:它不报警,先把全局变量、堆管理块、函数返回地址踩坏,表现为偶发HardFault、参数随机跳变、看门狗无故复位。仅靠“把栈开大点”治标不治本,工程上要用两层防线——编译期静态检查筛高危代码,运行期MPU硬件拦截越界写。


栈溢出在MCU里最麻烦:它不报警,先把全局变量、堆管理块、函数返回地址踩坏,表现为偶发HardFault、参数随机跳变、看门狗无故复位。仅靠“把栈开大点”治标不治本,工程上要用两层防线——编译期静态检查筛高危代码,运行期MPU硬件拦截越界写。

一、MPU做栈底红区,越界即MemManage

Cortex-M3/M4/M7带MPU,可把“栈底再往下”一小段配成不可访问。向下增长的主栈/任务栈一旦越界,就会先碰到红区,触发MemManage而不是静默改内存。

#include "core_cm3.h"


#define STACK_BOTTOM  0x20001000u   // 链接脚本里的栈最低地址

#define GUARD_SIZE    256u          // 2^n,MPU区域必须2幂对齐


void mpu_stack_guard_init(void){

   SCB->SHCSR |= SCB_SHCSR_MEMFAULTENA_Msk;   // 使能MemManage

   ARM_MPU_Disable();

   /* 区域0:栈底红区,NO_ACCESS,禁止执行 */

   ARM_MPU_SetRegion(0,

       ARM_MPU_RBAR(0, STACK_BOTTOM - GUARD_SIZE),

       ARM_MPU_RASR(1, ARM_MPU_AP_NONE, 0, 0, 0, 0,

                    ARM_MPU_XN, (31 - __CLZ(GUARD_SIZE-1)) - 1));

   /* 区域1:正常栈区,读写、不可执行;大小按实际栈容量配 */

   ARM_MPU_SetRegion(1,

       ARM_MPU_RBAR(1, STACK_BOTTOM),

       ARM_MPU_RASR(1, ARM_MPU_AP_FULL, 0, 1, 1, 0,

                    ARM_MPU_XN, (31 - __CLZ(0x1000-1)) - 1));

   ARM_MPU_Enable(MPU_CTRL_PRIVDEFENA_Msk);

   __DSB(); __ISB();

}


void MemManage_Handler(void){

   /* 读CFSR/MMFAR,记录越界地址后安全处理 */

   uint32_t mmfsr = SCB->CFSR & 0xFF;

   uint32_t addr  = (SCB->CFSR & (1u<<7)) ? SCB->MMFAR : 0;

   (void)mmfsr; (void)addr;

   while(1);   // 实际项目写日志、切安全态、请求复位

}

要点:红区基址必须按区域大小2幂对齐;使能MPU后必须DSB+ISB,否则可能用旧属性取指。 RTOS多任务可把“任务栈区”配成独立region、“栈底红区”配成NO_ACCESS,切换任务时重载RNR/RBAR/RASR。

二、静态检查先筛“大栈帧/深调用/VLA”

MPU负责运行时拦截,但最好别等跑飞再修。GCC可输出每函数栈用量:

CFLAGS += -fstack-usage -Wstack-usage=256 -Wframe-larger-than=512 \

         -fstack-protector-strong -Os

编译后每个目标文件生成.su,例如:

uart_parse.su: uart_parse  256  static

json_build.su:  json_build 1024  dynamic,bounded

用脚本按调用链累加最坏路径:main→uart_parse→json_build若共占1.2KB,而任务栈只有1KB,就提前爆出问题。 对中断嵌套要单独建模:最高优先级ISR栈帧+被它调用的函数一并计入,避免“主循环没超、进中断就踩”。

静态规则建议写进CI:

禁止无深度上限的递归,树遍历改显式栈;

禁止VLA/alloca,大缓冲改静态池或环形缓冲;

单函数局部数组超256字节告警;

ISR内局部变量超32字节告警;

用Cppcheck/PC-Lint扫strcpy、大结构体按值传参、取址后越界写。

三、无MPU场景降级

M0/M0+没有MPU,用三招兜底:链接脚本在栈底填哨兵0xDEADBEEF,定时任务或空闲钩子校验;FreeRTOS开configCHECK_FOR_STACK_OVERFLOW=2用栈底填充法+溢出钩子;关键函数加-fstack-protector-strong插canary。 这些不如MPU硬实时,但能挡住大部分开发期问题。

四、落地顺序

先开-fstack-usage和栈告警,把递归、VLA、大局部数组清掉;按调用图+中断嵌套算各任务/主栈最坏深度,留30%余量配链接脚本;有MPU的芯片再配“栈区读写+XN、栈底红区NO_ACCESS”,MemManage里记录MMFAR;无MPU用哨兵+水位线。静态检查管“写代码别埋雷”,MPU管“真越界立刻停”,两套叠起来,栈相关幽灵Bug基本可归零。

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭