前言
嵌入式C语言执行的软件中,stack溢出会导致程序执行异常,严重可能导致直接进硬件异常中断(hardfault)。软件执行过程中的stack监控是非常有必要的,ETAS的StackM模块实现了运行过程中的stack监控,对于多核系统,对每个核的stack进行监控。本文介绍StackM配置及使用。
Stack基本介绍
栈是用来存储函数调用时的局部变量、函数参数以及返回地址等信息的数据结构。栈的生长方向一般是向下生长的,栈顶为高地址。
当一个函数被调用时,它的局部变量和函数调用的参数都会被压入栈中,这会导致栈指针(通常称为栈顶指针或SP)向下移动,即向更低的内存地址移动。当函数执行完毕并返回时,栈中的这些数据会被弹出,栈指针则会向上移动回到之前的值。
示例如下:
栈的原则:栈中的数据元素遵守 后进先出(LIFO)的原则
栈的两个经典操作:
压栈:栈的插入操作叫做 进栈 / 压栈 / 入栈 (入数据在栈顶)
出栈:栈的删除操作叫做出栈。(出数据也在栈顶)
StackM配置
以下配置都基于stack向下生长,即STACKM_GROWDOWN = TRUE
StackMTarget
配置将在运行时监控的堆栈属性,例如内核所属、起始地址和结束地址
StackMStackID:配置stack id,实际没啥用
StackMStackLimit1:Stack监控限制阈值1,固定为12.5%。
StackMStackLimit2:Stack监控限制阈值2,固定为25%。
StackMStackLimit3:Stack监控限制阈值3,固定为50%。
StackMStackCoreID:关联的核id
StackMStackName:关联的stack内存名
StackName在linker模块中配置,包括stack的大小
对应代码配置为:
#define Stack0_StackId (0)
#if (STACKM_GROWDOWN == TRUE)
#define Stack0_Start ((uint32*)((uint32)&STACK_START_SYSCORE_STACK - 0x00000001))
#define Stack0_End ((uint32*)((uint32)&STACK_END_SYSCORE_STACK + 0x00000000))
#define Stack0_Limit0 ((uint32*)((uint32)&STACK_START_SYSCORE_STACK - (STACK_LEN_SYSCORE_STACK>>5))) /* convert to uint32,limit boundary at 12.5% */
#define Stack0_Limit1 ((uint32*)((uint32)&STACK_START_SYSCORE_STACK - (STACK_LEN_SYSCORE_STACK>>4))) /* convert to uint32,limit boundary at 25% */
#define Stack0_Limit2 ((uint32*)((uint32)&STACK_START_SYSCORE_STACK - (STACK_LEN_SYSCORE_STACK>>3))) /* convert to uint32,limit boundary at 50% */
/* Check whether the Address to check is out of address space of current stack */
LOCAL_INLINE boolean STACKM_CBK_Stack0( const uint32* currAddr) {return (((currAddr > Stack0_Start) || (currAddr == 0))? TRUE:FALSE);}
#else
#error "Stack growing direction not supported in current StackM version !"
#endif
上面的limit是转化为uint32之后的计算,不是基于byte的,所以多除了4。
StackMGeneral
StackMDevErrorDetect:使能错误检测
StackMEnable:使能StackM
StackMTraceEnable:使能trace跟踪功能
StackMPatternEnable:使能stack填充功能
StackMPattern:stack默认填充值
StackMLimitHigh:limit处填充的64字节的高32字节
StackMLimitHigh:limit处填充的64字节的低32字节
对应代码配置为:
#define STACKM_DEV_ERROR_DETECT STD_ON
/* Set to STD_ON if this feature has to be enabled, otherwise set STD_OFF */
#define STACKM_CFG_ENABLE STD_ON /* Enable StackM component. */
#define STACKM_CFG_ENABLETRACE STD_ON /* Enable Stack utilization tracing feature. */
#define STACKM_CFG_PATTERNINIT STD_ON /* Enable Stack filling for tracing feature. */
#define STACKM_CFG_DEFAULTFILLUPVALUE ((uint32)0x4C4C4946) /* "FILL" */
#define STACKM_CFG_LIMITHIGH ((uint32)0x3179656B) /* "key1" */
#define STACKM_CFG_LIMITLOW ((uint32)0x3279656B) /* "key2" */
Linker配置
配置对应stack的ld段,及stacksize
对应代码配置为:
#define STACK_LEN_SYSCORE_STACK 12288
#if (LINKER_STACK_GROWDOWN == TRUE)
#define STACK_START_SYSCORE_STACK __Stack_start_c0
#define STACK_END_SYSCORE_STACK __Stack_end_c0
#else
#define STACK_START_SYSCORE_STACK __SYSCORE_STACK_START
#define STACK_END_SYSCORE_STACK __SYSCORE_STACK_END
#endif
#define STACK_LEN_COMCORE_STACK 12288
#if (LINKER_STACK_GROWDOWN == TRUE)
#define STACK_START_COMCORE_STACK __Stack_start_c1
#define STACK_END_COMCORE_STACK __Stack_end_c1
#else
#define STACK_START_COMCORE_STACK __COMCORE_STACK_START
#define STACK_END_COMCORE_STACK __COMCORE_STACK_END
#endif
此处__Stack_start_c0为ld文件中定义的宏,表示的stack的高地址
__Stack_start_c0 = ADDR(int_sram_stack_c0) + SIZEOF(int_sram_stack_c0);
EcuM配置
配置各个核的Stack初始化函数
主要就是对stack区域赋一个指定值
RTE配置
将对应的StackM的主函数映射到1ms任务上,或者在idle task中调用也是可以的
集成与测试
需要确认ld文件中的stack定义与StackM中的一致。
通过调用StackM_GetStackInfo函数获取stack监控的结果,对应结果参数定义结构体为StackM_StackMeasureType
typedef struct {
uint32 FreeSpace;
float32 FreePercent;
uint8 CheckWordReached;
} StackM_StackMeasureType;
FreeSpace为当前剩余stack字节数,FreePercent为当前剩余stack百分比,CheckWordReached为1时说明Stack使用量达到过12.5%,为3时说明使用量达到过25%,为7时说明使用量达到过50%.
调试结果如下:
总结
在栈使用量超过一定值时可以做出一些报警动作,在调试阶段对stack监控后合理分配stack空间,也是非常有意义的。