程序运行时堆栈溢出检测及失效安全策略

在前面的文章中,我们介绍了在开发调试过程中如何在IAR Embedded Workbench中进行堆栈静态分析和动态监控,然后设置合理的堆栈大小,避免堆栈溢出。但是即使设置了合理的堆栈大小,在程序运行时最好有对应的失效安全(fail-safe)策略:如果在运行过程中出现堆栈溢出,程序应该要能检测到并执行相应的策略。

本文主要介绍如何在程序运行时进行堆栈溢出检测及检测到堆栈溢出之后的失效安全策略。

程序运行时堆栈溢出检测主要通过检查对应的堆栈指针(SP)是否超出指定的堆栈范围来实现,一般可以分为硬件检测和软件检测。

硬件堆栈溢出检测

硬件堆栈溢出检测的原理:设置对应的硬件单元,如果在程序运行过程中,SP超出指定的堆栈范围,会触发对应的硬件错误。目前主要有如下两种方式:

  • 使用堆栈限制寄存器(Stack Limit registers)
  • 使用内存保护单元(MPU:Memory Protection Unit)

使用堆栈限制寄存器(Stack Limit registers)

ARMv8-M架构中包含了堆栈限制寄存器(Stack Limit registers),当SP的值小于(ARMv8-M架构中堆栈是向下生长的)对应Stack Limit registers的值时,会触发UsageFault或者HardFault:

使用内存保护单元(MPU:Memory Protection Unit)

为了提高系统的安全性,越来越多的MCU集成了内存保护单元(MPU:Memory Protection Unit),可以通过设置MPU的属性,当SP访问超出指定的堆栈范围时,产生MemManageFault或者HardFault:

软件堆栈溢出检测

软件堆栈溢出检测的原理:在程序开始之前,往堆栈防护区域填充特定的值(比如0xCD),然后在程序运行时去检查堆栈防护区域中之前填充的值是否被篡改:如果被篡改了,说明堆栈溢出。

RTOS任务堆栈溢出检测

通常情况下,RTOS会提供任务堆栈溢出检测功能,只需要使能对应的宏定义,打开堆栈溢出检测功能。当检测到堆栈溢出时,RTOS会调用相应的堆栈溢出钩子函数(hook)。

注意:RTOS一般会计算各个任务堆栈的使用情况,所以在新建任务时,RTOS会把任务堆栈所有区域都填充特定的值,然后在运行过程中去检测和计算对应的堆栈使用情况。

 

在RTOS里面进行任务堆栈溢出检测一般需要如下操作(下面以Azure RTOS为例):

  • 定义对应的宏使能堆栈检测功能
/* Determine whether or not stack checking is enabled.

By default, ThreadX stack checking is disabled.

When the following is defined, ThreadX thread stack checking is enabled.

If stack checking is enabled (TX_ENABLE_STACK_CHECKING is defined),

the TX_DISABLE_STACK_FILLING define is negated,

thereby forcing the stack fill which is necessary for the

stack checking logic. */


#define TX_ENABLE_STACK_CHECKING
  • 定义并注册堆栈溢出钩子函数(hook)

void my_stack_error_handler(TX_THREAD *thread_ptr);


/* Register the "my_stack_error_handler" function with ThreadX

so that thread stack errors can be handled by the application. */

status = tx_thread_stack_error_notify(my_stack_error_handler);


系统堆栈溢出检测

系统堆栈溢出检查需要开发人员手动添加对应的代码:在程序开始之前,往堆栈防护区域填充特定的值(比如0xCDCDCDCD),然后在程序运行时去检查堆栈防护区域中之前填充的值是否被篡改。

#define STACK_FILL_PATTERN 0xCDCDCDCD

/* Linker generated symbols */
extern uint32_t CSTACK$$Base;

/* Fill the stack base with dedicated pattern */
*((uint32_t *) &CSTACK$$Base) = STACK_FILL_PATTERN;


/* Check system stack overflow */
/* If the filled pattern is changed, there is stack overflow */
if(STACK_FILL_PATTERN != *((uint32_t *) &CSTACK$$Base))
{

}

注意:软件堆栈溢出检查由于是通过软件代码进行检测,可能没有那么及时,也有可能在堆栈溢出的地方已经造成了其它的硬件错误(比如从堆栈POP出的PC指向了不可执行的地址)。

堆栈溢出之后的措施

前面介绍了程序运行时进行堆栈溢出检测的方法。那么当检测到堆栈溢出之后程序应该怎么处理呢?

由于堆栈溢出可能会造成程序运行需要的重要数据的破坏,而这种破坏是未知的。所以当发生堆栈溢出时,程序通常是很难继续正常运行的,往往需要通过系统复位来重新回到正常的状态。但是需要注意的是,在系统复位之前,需要确保系统进入安全状态,避免造成更严重的损害。下面是发生堆栈溢出之后一般的处理策略:

  • 确保系统进入安全状态(比如关掉对应的执行器)
  • 如果条件允许,Log堆栈溢出事件到非易失性存储器(Data Flash或者EEPROM),以便后续分析
  • 系统复位

总结

本文主要介绍了如何在程序运行时进行堆栈溢出检测及检测到堆栈溢出之后的失效安全策略。开发人员可以根据对应系统的具体情况选择合适的方法进行程序运行时堆栈溢出。当检测到堆栈溢出之后,程序需要执行相应的失效安全策略确保系统能够安全地回到正常的运行状态。

 

参考文献:

  1. Mastering stack and heap for system reliability:https://www.iar.com/knowledge/learn/programming/mastering-stack-and-heap-for-system-reliability/ 
  2. How much stack memory do I need for my Arm Cortex-M applications?: https://community.arm.com/arm-community-blogs/b/architectures-and-processors-blog/posts/how-much-stack-memory-do-i-need-for-my-arm-cortex--m-applications
  3. Detecting Stack Overflows (Part 2 of 2): https://weston-embedded.com/support/media-articles/99-detecting-stack-overflows-part-2-of-2
  4. Joseph Yiu, Tim Menasveta. Utilizing Features in an ARM® Cortex®-M Processor to Create Robust Systems
  5. Express logic. Detecting and Avoiding Stack Overflow in IoT Embedded Systems
  6. Understand Azure RTOS ThreadX
  7. How to Prevent and Detect Stack Overflow | Barr Group: https://barrgroup.com/embedded-systems/how-to/prevent-detect-stack-overflow

We do no longer support Internet Explorer. To get the best experience of iar.com, we recommend upgrading to a modern browser such as Chrome or Edge.