本文轉(zhuǎn)自公眾號,歡迎關(guān)注
https://mp.weixin.qq.com/s/uzaGLFTDBAn8wyR84yaiIw
1. 前言
RTOS的環(huán)境開發(fā)中,棧的溢出檢測是一個(gè)重要的工作。棧溢出檢測我們可以借助硬件的MPU等實(shí)現(xiàn),也可以使用軟件檢測。這里分享Freertos中的實(shí)現(xiàn)。這里基于Cortex-M4硬件平臺,一些具體的代碼就未貼出了,順便介紹了一下Cortex-M4棧相關(guān)的基礎(chǔ)知識。
2. 棧初始化
2.1任務(wù)啟動(dòng)前棧
復(fù)位后匯編代碼
IMPORT __main
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
會(huì)進(jìn)入__main將棧內(nèi)容寫為0。該部分由編譯器產(chǎn)生代碼實(shí)現(xiàn)。
棧的位置是鏈接腳本中指定。
2.2任務(wù)棧
xTaskCreate -> prvInitialiseNewTask將任務(wù)棧填充為tskSTACK_FILL_BYTE = ( 0xa5U )
然后調(diào)用pxPortInitialiseStack初始化任務(wù)棧上下文
任務(wù)初始化時(shí) |
---|
高地址 |
->任務(wù)切出時(shí)棧指針 |
低地址 |
任務(wù)運(yùn)行一段時(shí)間后 |
---|
高地址 |
已使用部分 |
->任務(wù)切出時(shí)棧指針 |
未使用部分 |
低地址 |
對應(yīng)實(shí)際中斷后的棧如下:
3.任務(wù)切換
vPortSVCHandler函數(shù)模擬中斷返回
__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即指向任務(wù)棧表中R4位置
ldmia r0!, {r4-r11, r14}是恢復(fù)R4-R11和portINITIAL_EXC_RETURN
msr psp, r0,更新棧指針,指向指向任務(wù)棧表中R0位置
bx r14模擬中斷返回 恢復(fù)R0-R3 R12 PC xPSR(硬件實(shí)現(xiàn))。
由于R14=portINITIAL_EXC_RETURN=0xfffffffd
根據(jù)手冊描述
返回時(shí)使用PSP棧,返回后使用PSP棧。與初始化對應(yīng)。
4.任務(wù)return
棧初始化時(shí)LR = prvTaskExitError 進(jìn)入子函數(shù)時(shí)LR會(huì)入棧,退出子函數(shù)時(shí)LR出棧。
所以如果任務(wù)不是while(1)形式而是在最后return則最終會(huì)進(jìn)入
prvTaskExitError執(zhí)行。一般rtos的任務(wù)都是while(1)結(jié)構(gòu) 不return。
5.棧指針
復(fù)位后使用MSP,任務(wù)根據(jù)返回時(shí)的LR值portINITIAL_EXC_RETURN使用PSP見“2.任務(wù)切換”。
中斷中固定使用MSP。
6.棧使用
中斷函數(shù)和mian使用中斷向量第一個(gè)字指向的棧區(qū)域。
任務(wù)使用任務(wù)棧。
在os啟動(dòng)前默認(rèn)時(shí)使用msp,根據(jù)中斷向量的第一個(gè)字加載msp
硬件實(shí)現(xiàn),或者bootloader跳轉(zhuǎn)到應(yīng)用時(shí)配置。
啟動(dòng)os時(shí)prvStartFirstTask,又重新將中斷向量第一個(gè)字加載到msp。
今后中斷就使用msp對應(yīng)的棧,即os啟動(dòng)前main使用的棧。
因?yàn)閙ain一去不復(fù)返,所以這里覆蓋使用main時(shí)的棧,這樣可以節(jié)約內(nèi)存。
/* 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任務(wù)棧檢測
棧初始化時(shí)全部初始化為0xA5,運(yùn)行一段時(shí)間后棧頂部分使用變?yōu)槠渌怠?/p>
檢查棧底有多少連續(xù)的0xA5即可知道棧剩余多少。
Freertos提供接口函數(shù)uxTaskGetSystemState獲取棧信息。
Shell中輸入ps查看(具體代碼未貼出)。
7.2中斷棧/main函數(shù)棧檢測
根據(jù)4.和5.的分析,中斷和main函數(shù)棧使用中斷向量第一個(gè)字對應(yīng)的棧區(qū)域。
由于__main.c會(huì)將棧內(nèi)容清除為0.所以在啟動(dòng)第一個(gè)任務(wù)前將棧重新填充為0xa5。
有__main.c之前將棧填充為0xa5又會(huì)被清除為0,將填充代碼放在了任務(wù)啟動(dòng)前prvStartFirstTask函數(shù)中。這樣main函數(shù)到prvStartFirstTask之前的棧使用大小不可監(jiān)控。
只能監(jiān)控后續(xù)中斷使用的棧大小。如果要檢測main函數(shù)棧使用則要將填充代碼放在main函數(shù)執(zhí)行的第一條代碼后,需要嵌入?yún)R編影響代碼閱讀和可移植性,所以不按這種方式。
實(shí)際上main函數(shù)棧溢出也沒關(guān)系 ,但是編程必須要求提供手動(dòng)初始化變量的代碼,而不是依賴于編譯器的初始化。
比如有一個(gè)變量static int i =0;
編譯器提供代碼在__main中會(huì)對該變量初始化,如果main函數(shù)棧溢出覆蓋了這個(gè)變量的值。
那么在任務(wù)函數(shù)執(zhí)行時(shí)提供 void mode_init(void)函數(shù)
手動(dòng)再次初始化該變量i=0.
就可以避免問題。
建議在模塊任務(wù)啟動(dòng)時(shí)對屬于模塊的全局變量再次提供”構(gòu)造函數(shù)”手動(dòng)初始化。
修改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需要根據(jù)實(shí)際設(shè)置的棧大小修改。0xE000ED08為中斷向量表地址。
/*****************************************************************************
* fn uint32_t bsp_sys_getstack(void)
* brief 獲取棧大小.
* note .
* return 剩余棧字節(jié)數(shù)
*****************************************************************************
*/
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. 總結(jié)
簡單來說軟件實(shí)現(xiàn)棧檢測,就是將棧初始化為固定值。如果棧有使用則初始化值會(huì)變化,軟件從棧底開始查找看剩余多少內(nèi)容沒有被改寫就是剩余多少棧未使用。軟件檢測不是可靠的,因?yàn)橐绯隹赡苁翘S的,即棧底一部分實(shí)際未用指針直接跳到了更后面的溢出位置,軟件檢測還存在延遲,所以軟件檢測一般可用于評估棧使用大小。使用硬件MPU更可靠,設(shè)置只有本任務(wù)只能訪問本任務(wù)棧對應(yīng)的空間,一旦訪問其他空間就可以觸發(fā)MPU中斷這樣更及時(shí)可靠檢測。
審核編輯:湯梓紅
-
嵌入式
+關(guān)注
關(guān)注
5082文章
19123瀏覽量
305143 -
MPU
+關(guān)注
關(guān)注
0文章
359瀏覽量
48794 -
函數(shù)
+關(guān)注
關(guān)注
3文章
4331瀏覽量
62604 -
代碼
+關(guān)注
關(guān)注
30文章
4788瀏覽量
68601 -
FreeRTOS
+關(guān)注
關(guān)注
12文章
484瀏覽量
62171
發(fā)布評論請先 登錄
相關(guān)推薦
評論