在開發除錯過程中,透過在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),以便後續分析
- 系統復位
總結
本文主要介紹如何在程式執行時進行堆疊溢位偵測及偵測到堆疊溢位之後的失效安全策略。開發人員可以根據對應系統的具體情況選擇合適的方法進行程式運作時堆疊溢位。當偵測到堆疊溢位之後,程式需要執行對應的失效安全策略確保系統能夠安全地回到正常的運作狀態。
參考文獻
- Mastering stack and heap for system reliability: https://www.iar.com/knowledge/learn/programming/mastering-stack-and-heap-for-system-reliability/
- 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
- Detecting Stack Overflows (Part 2 of 2): https://weston-embedded.com/support/media-articles/99-detecting-stack-overflows-part-2-of-2
- Joseph Yiu, Tim Menasveta. Utilizing Features in an ARM® Cortex®-M Processor to Create Robust Systems
- Express logic. Detecting and Avoiding Stack Overflow in IoT Embedded Systems
- Understand Azure RTOS ThreadX
- How to Prevent and Detect Stack Overflow | Barr Group: https://barrgroup.com/embedded-systems/how-to/prevent-detect-stack-overflow
本文由IAR提供