為了識別運行的嵌入式系統中的堆棧溢出問題,SEGGER編譯器通過為每個函數生成檢測代碼的方式來檢查堆棧溢出。該功能可以使用命令行開關-mstack-overflow-check來使能。對于安全系統,必須在溢出的堆棧破壞內存之前檢測到堆棧溢出,因此需要在更改堆棧指針和需大量堆棧空間之前進行檢查。
一,Embedded Studio的堆棧溢出預防
在Embedded Studio中啟用堆棧溢出預防功能,僅需將工程選項Code->Code Generation->Enable Stack Overflow Prevention設置為“Yes”。
如果工程中沒有實現錯誤回調函數__SEGGER_STOP_X_OnError,它默認保持在一個無限循環中。Embedded Studio安裝目錄$(StudioDir)/samples下的SEGGER_STOP.c中包含一個錯誤處理的示例實現。
二,編譯器生成的代碼
如果在編譯器中使能堆棧溢出檢查,生成的代碼將被改變,如下所示:
1、不使用堆棧的函數不會被更改。
2、使用本地堆棧幀但不使用R3傳遞參數的函數中:堆棧幀的設置(通常使用sub sp, #size指令)被替換為將所需棧大小加載到寄存器R3中,然后調用函數__SEGGER_STOP_GROW_R3()。
3、使用本地堆棧幀并使用R3傳遞參數的函數中,操作與2相同,但是使用寄存器R4傳值,并調用函數__SEGGER_STOP_GROW_R4()。這意味著,R4必須在進入函數時必須入棧。
4、不使用本地堆棧但需在堆棧中保存寄存器的函數中,在寄存器壓棧后將調用不帶參數的函數__SEGGER_STOP_GROW_0()。
5、需要動態分配堆棧的函數(如使用alloc()函數或可變大小的數組),編譯后代碼也將調用__SEGGER_STOP_GROW_R3()。因為分配可能發生在函數執行中,需指示寄存器分配器,確保R3可以使用。
然后,被調用的函數可以使用存儲在全局變量中的堆棧上限值檢查堆棧是否溢出。檢查函數將在需要保存的寄存器被壓棧后調用。因此,必須計算堆棧上限,以便始終有棧空間用于:
在函數入口處,入棧所有的通用寄存器(R0 - R11, LR),有一些優化可能導致R0 - R3入棧。
所有被調用者需保存的浮點/矢量寄存器(D8 - D15)
中斷入口需保存的寄存器 (8個字)
3個(備用)字用于對齊和緊急溢出緩存?
可以使用__attribute__((no_stack_overflow_check))禁止生成單個函數的堆棧檢查代碼。
三、堆棧檢查和錯誤處理
啟用“防止堆棧溢出”功能時,必須實現下列堆棧檢查函數。在Embedded Studio中,這些函數已添加到標準庫中。
__SEGGER_STOP_GROW_R3
__SEGGER_STOP_GROW_R4
__SEGGER_STOP_GROW_0
堆棧溢出時,堆棧檢查函數應該跳轉到用戶提供的錯誤處理回調函數__SEGGER_STOP_X_OnError()。
堆棧檢查函數
編譯器生成的代碼調用檢查函數檢查剩余的堆棧大小,在堆棧溢出的情況下函數不能返回。為了提高效率,這些函數沒有遵循標準調用約定。因此,函數不能修改除了R12和包含堆棧大小參數的寄存器之外的任何寄存器,函數在返回之前還必須調整堆棧指針。
錯誤處理回調
為了確保錯誤處理回調不使用溢出堆棧,它應該在純匯編中實現,并且在禁用堆棧溢出檢查功能狀態下進行編譯。
在默認實現中,__SEGGER_STOP_X_OnError定義為:
__attribute__((naked, no_stack_overflow_check)) void __SEGGER_STOP_X_OnError(void);
它在堆棧檢查函數尾部調用,不遵循常規調用約定。堆棧上限值、新的堆棧指針值和調用者通過R3、R12和LR傳遞。
錯誤處理回調可能會將溢出堆棧重置為安全值。同時,它可能會調用其他函數,比如記錄錯誤和重置系統。
示例:
?
void __SEGGER_STOP_X_OnError(void) { asm( "cpsid i " // Disable interrupts "mov r0, r12 " // Save overflowed SP "mov r1, r3 " // Save SP limit "sub r2, lr, #5 " // Save caller "mrs r3, CONTROL " // Get currently used stack "lsls r3, #30 " "ittee pl " // Reset this stack "ldrpl r12, =__stack_end__ " "msrpl msp, r12 " "ldrmi r12, =__stack_process_end__ " "msrmi psp, r12 " "bl _HandleStackError " // Call error handler "b . " // Stay here ); }
?
四、堆棧上限
啟動代碼必須初始化堆棧上限,至少初始化主堆棧上限變量__SEGGER_STOP_Limit_MSP。在默認實現中,該符號由運行時初始化代碼自動初始化為默認值。
為了調整上限值,例如改變為保存寄存器保留的空間,以及初始化__SEGGER_STOP_Limit_PSP,應該實現并調用__SEGGER_STOP_X_InitLimits。
使用SEGGER鏈接器,運行時初始化代碼將自動調用__SEGGER_STOP_X_InitLimits:
initialize by?calling __SEGGER_STOP_X_InitLimits ???{ section .data.stop.* };
使用GNU鏈接器時,應該在main中的開始位置調用__SEGGER_STOP_X_InitLimits
?
int main(void) { ? ?int NumItems; ? ?#if !defined (__SEGGER_LINKER) ? ?// ? ?// Optionally initialize stack limits if not done by runtime init. ? ?// ? ?__SEGGER_STOP_X_InitLimits(); ?#endif ?... }
?
在運行初始化之前調用函數
當系統在運行初始化之前調用函數時,Cortex-M上默認在Reset_Handler中調用SystemInit, __SEGGER_STOP_Limit_MSP應設置為0,以禁用堆棧檢查。
?
Reset_Handler: ? ? ? ?.extern __SEGGER_STOP_Limit_MSP ? ? ? ?// ? ? ? ?// Initialize main stack limit to 0 to disable stack checks before runtime init ? ? ? ?// ? ? ? ?movs ? ?R0, #0 ? ? ? ?ldr ? ? R1, =__SEGGER_STOP_Limit_MSP ? ? ? ?str ? ? R0, [R1] ? ? ? ?// ? ? ? ?// Call SystemInit ? ? ? ?// ? ? ? ?bl ? ? ?SystemInit ? ? ? ?...
?
使用RTOS
當使用RTOS或其他多任務機制時,任務切換程序必須在切換堆棧時更新堆棧上限變量(通常為__SEGGER_STOP_Limit_PSP)。
?
ChangeTask: ? ?... ? ?ldr ? ? ?r0, [r1, #0] ? // OS.pCurTask ? ldr ? ? ?r3, [r0, #8] ? // OS.pCurTask->pStackBottom ? ?add ? ? ?r3, #100 ? ? ? // Buffer before stack overflow ? ?ldr ? ? ?r2, =__SEGGER_STOP_Limit_PSP ? ?str ? ? ?r3, [r2] ? ? ? // Update stack limit ? ? ?...
?
建議對所有任務啟用堆棧檢查。RTOS可以通過將limit變量設置為0來禁用某些任務的堆棧檢查。
堆棧溢出幾乎在每個系統中都可能遇到。Embedded Studio提供了使用示例,展示簡單系統和多任務系統的堆棧溢出行為及處理,下載鏈接:?https://wiki.segger.com/images/2/2f/STOP_Examples.zip。
?
評論
查看更多