本文轉自公眾號,歡迎關注
https://mp.weixin.qq.com/s/uzaGLFTDBAn8wyR84yaiIw
1. 前言
RTOS的環境開發中,棧的溢出檢測是一個重要的工作。棧溢出檢測我們可以借助硬件的MPU等實現,也可以使用軟件檢測。這里分享Freertos中的實現。這里基于Cortex-M4硬件平臺,一些具體的代碼就未貼出了,順便介紹了一下Cortex-M4棧相關的基礎知識。
2. 棧初始化
2.1任務啟動前棧
復位后匯編代碼
IMPORT __main
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
會進入__main將棧內容寫為0。該部分由編譯器產生代碼實現。
棧的位置是鏈接腳本中指定。
2.2任務棧
xTaskCreate -> prvInitialiseNewTask將任務棧填充為tskSTACK_FILL_BYTE = ( 0xa5U )
然后調用pxPortInitialiseStack初始化任務棧上下文
任務初始化時 |
---|
高地址 |
->任務切出時棧指針 |
低地址 |
任務運行一段時間后 |
---|
高地址 |
已使用部分 |
->任務切出時棧指針 |
未使用部分 |
低地址 |
對應實際中斷后的棧如下:
3.任務切換
vPortSVCHandler函數模擬中斷返回
__asm void vPortSVCHandler( void )
{
PRESERVE8
/* Get the location of the current TCB. */
ldr r3, =pxCurrentTCB
ldr r1, [r3]
ldr r0, [r1]
/* Pop the core registers. */
ldmia r0!, {r4-r11, r14}
msr psp, r0
isb
mov r0, #0
msr basepri, r0
bx r14
}
其中
ldr r3, =pxCurrentTCB
ldr r1, [r3]
ldr r0, [r1]
是獲取棧指針r0即指向任務棧表中R4位置
ldmia r0!, {r4-r11, r14}是恢復R4-R11和portINITIAL_EXC_RETURN
msr psp, r0,更新棧指針,指向指向任務棧表中R0位置
bx r14模擬中斷返回 恢復R0-R3 R12 PC xPSR(硬件實現)。
由于R14=portINITIAL_EXC_RETURN=0xfffffffd
根據手冊描述
返回時使用PSP棧,返回后使用PSP棧。與初始化對應。
4.任務return
棧初始化時LR = prvTaskExitError 進入子函數時LR會入棧,退出子函數時LR出棧。
所以如果任務不是while(1)形式而是在最后return則最終會進入
prvTaskExitError執行。一般rtos的任務都是while(1)結構 不return。
5.棧指針
復位后使用MSP,任務根據返回時的LR值portINITIAL_EXC_RETURN使用PSP見“2.任務切換”。
中斷中固定使用MSP。
6.棧使用
中斷函數和mian使用中斷向量第一個字指向的棧區域。
任務使用任務棧。
在os啟動前默認時使用msp,根據中斷向量的第一個字加載msp
硬件實現,或者bootloader跳轉到應用時配置。
啟動os時prvStartFirstTask,又重新將中斷向量第一個字加載到msp。
今后中斷就使用msp對應的棧,即os啟動前main使用的棧。
因為main一去不復返,所以這里覆蓋使用main時的棧,這樣可以節約內存。
/* Use the NVIC offset register to locate the stack. */
ldr r0, =0xE000ED08
ldr r0, [r0]
ldr r0, [r0]
/* Set the msp back to the start of the stack. */
msr msp, r0
7棧檢測
7.1任務棧檢測
棧初始化時全部初始化為0xA5,運行一段時間后棧頂部分使用變為其他值。
檢查棧底有多少連續的0xA5即可知道棧剩余多少。
Freertos提供接口函數uxTaskGetSystemState獲取棧信息。
Shell中輸入ps查看(具體代碼未貼出)。
7.2中斷棧/main函數棧檢測
根據4.和5.的分析,中斷和main函數棧使用中斷向量第一個字對應的棧區域。
由于__main.c會將棧內容清除為0.所以在啟動第一個任務前將棧重新填充為0xa5。
有__main.c之前將棧填充為0xa5又會被清除為0,將填充代碼放在了任務啟動前prvStartFirstTask函數中。這樣main函數到prvStartFirstTask之前的棧使用大小不可監控。
只能監控后續中斷使用的棧大小。如果要檢測main函數棧使用則要將填充代碼放在main函數執行的第一條代碼后,需要嵌入匯編影響代碼閱讀和可移植性,所以不按這種方式。
實際上main函數棧溢出也沒關系 ,但是編程必須要求提供手動初始化變量的代碼,而不是依賴于編譯器的初始化。
比如有一個變量static int i =0;
編譯器提供代碼在__main中會對該變量初始化,如果main函數棧溢出覆蓋了這個變量的值。
那么在任務函數執行時提供 void mode_init(void)函數
手動再次初始化該變量i=0.
就可以避免問題。
建議在模塊任務啟動時對屬于模塊的全局變量再次提供”構造函數”手動初始化。
修改freertos底層移植代碼
__asm void prvStartFirstTask( void )
{
PRESERVE8
/* Use the NVIC offset register to locate the stack. */
ldr r0, =0xE000ED08
ldr r0, [r0]
ldr r0, [r0]
/* Set the msp back to the start of the stack. */
msr msp, r0
//;初始化棧為0xA5A5A5A5
MOV R2,#0xA5A5A5A5
LDR R0, =0x4000
MRS R1, MSP
SUBS R1,R1,#4
LOOP STR R2,[R1,#0x00]
SUBS R0,R0,#4
SUBS R1,R1,#4
CMP R0,#0x00
BNE LOOP
增加檢測代碼
其中0x4000需要根據實際設置的棧大小修改。0xE000ED08為中斷向量表地址。
/*****************************************************************************
* fn uint32_t bsp_sys_getstack(void)
* brief 獲取棧大小.
* note .
* return 剩余棧字節數
*****************************************************************************
*/
uint32_t bsp_sys_getstack(void)
{
uint32_t size = 0;
uint32_t* p = (uint32_t*)(*(uint32_t*)(*(uint32_t*)0xE000ED08) - 0x4000);
while(*p == (uint32_t)0xA5A5A5A5)
{
size += 4;
p++;
}
return size;
}
Shell中輸入stack命令查看(具體代碼未貼出)
8. 總結
簡單來說軟件實現棧檢測,就是將棧初始化為固定值。如果棧有使用則初始化值會變化,軟件從棧底開始查找看剩余多少內容沒有被改寫就是剩余多少棧未使用。軟件檢測不是可靠的,因為溢出可能是跳躍的,即棧底一部分實際未用指針直接跳到了更后面的溢出位置,軟件檢測還存在延遲,所以軟件檢測一般可用于評估棧使用大小。使用硬件MPU更可靠,設置只有本任務只能訪問本任務棧對應的空間,一旦訪問其他空間就可以觸發MPU中斷這樣更及時可靠檢測。
審核編輯:湯梓紅
-
嵌入式
+關注
關注
5082文章
19122瀏覽量
305109 -
MPU
+關注
關注
0文章
359瀏覽量
48790 -
函數
+關注
關注
3文章
4331瀏覽量
62596 -
代碼
+關注
關注
30文章
4787瀏覽量
68591 -
FreeRTOS
+關注
關注
12文章
484瀏覽量
62166
發布評論請先 登錄
相關推薦
評論