在嵌入式系統(tǒng)中,RAM 的大小是非常有限的。尤其是做器件選型時,更小 RAM 的芯片意味著更低的采購價格,產(chǎn)品才會更具競爭力,有更高的毛利。
在這樣極致的壓榨下,留給堆棧的空間更加少了。開發(fā)者不得不面對爆棧的巨大風(fēng)險。每個軟件工程師都想有一個工具能夠幫助他們檢驗(yàn)棧的使用情況,從而很好的評估風(fēng)險。
人們尋常采用的方法是把棧里都先寫滿一個特定的值,比如 0xAA。隨后在程序運(yùn)行一段時間之后看看還剩多少 0xAA 沒有被改掉。這種方法確實(shí)有一定的效果,但是顯然還不夠直觀,又比較麻煩。尤其是當(dāng)工程有不止一個棧的時候。
為此,新版本的 gcc 編譯器提供了一個有用的編譯選項(xiàng)-fstack-usage。使用這個選項(xiàng)后,編譯器會額外產(chǎn)生有關(guān)棧使用情況的信息,而 MCUXpresso 可以整理這些信息,并將它們非常清晰地顯示出來。
fstack-usage與Call Graph
先讓我們看看 GNU 關(guān)于-fstack-usage 的說明:(https://gcc.gnu.org/onlinedocs/gcc/Developer-Options.html)
它以每個函數(shù)為基礎(chǔ),使編譯器生成程序的堆棧使用信息。信息存放在后綴名為.su 的文件中。
下圖是編譯文件夾的內(nèi)容,可以看到有一個同名的 su 文件。
這個文件的內(nèi)容也很簡單,它列 出 了 文 件名(system_MIMXRT1052.c), 函數(shù)的 行號和列號, 函數(shù)的名稱, 如 SystemInit,堆棧的使用情況(8),以及如何分配(static)。
但是這樣單個顯示是沒有什么參考價值的,我們需要的是整個工程的全景,要把所有的 su 文件整理出來。 MCUXpresso IDE v11 版本提供了這一功能。在 Image Info 窗口中有一個 Call Graph 標(biāo)簽。單擊右上角的導(dǎo)入按鈕就可以導(dǎo)入當(dāng)前整個工程的 stack 使用情況。
前面帶“>”的函數(shù)名稱顯示“根”函數(shù):它們不能被從其他任何地方調(diào)用的。其中ResetISR就是 reset 入 口 函 數(shù) , 而 exception handlers 里 面 都 是 中 斷 服 務(wù) 程 序 。
HAL_UartReceiveBlocking()函數(shù)因?yàn)闆]有其它函數(shù)顯式的調(diào)用它,所以也被認(rèn)為是根函數(shù)。
這里我們可以看到這種分析的一個弱點(diǎn),如果函數(shù)是通過函數(shù)指針的方式來調(diào)用那么該功能就無能為例了。但是使用者可以自己分析程序給續(xù)上。
如果函數(shù)是遞歸的,則用一個特殊的雙箭頭標(biāo)記。成本估算將針對單級遞歸。
Full Cost 表示累積堆棧使用量(此函數(shù)加上所有被調(diào)用的)。
Local Cost 表示本層的堆棧使用量。
Depth 表示由該函數(shù)引起的調(diào)用級別數(shù)。
請注意Exception Handlers 這里,它集中了所有的中斷服務(wù)程序。由于沒有顯式的調(diào)用,它們都是根函數(shù),并且這里只統(tǒng)計(jì)非中斷嵌套情況下的最大用量。所以如果允許中斷嵌套, 那么對于棧的分配應(yīng)該更加保守。
此外,如果函數(shù)是用匯編語言寫的,那么工具是無法統(tǒng)計(jì)它們的棧使用情況, 一律會統(tǒng)計(jì)成‘4’。但如果調(diào)用到了其它函數(shù),深度和 Full cost 還是會被統(tǒng)計(jì)的。
需要注意這個堆棧使用報告僅涵蓋每個函數(shù)或調(diào)用樹的堆棧使用情況。它們不包括異常處理程序所需的額外堆??臻g。所以最后的堆棧計(jì)算是 ResetISR 棧+中斷棧,如果允許中斷嵌套,那么整個中斷嵌套最長的情況必須要被考慮。 該工具在基于 RTOS(例如 FreeRTOS)的系統(tǒng)中同樣運(yùn)行良好且開箱即用。因此,使用該工具,我們可以很好地估計(jì)每個任務(wù)堆棧的使用情況。棧計(jì)算可以使用下圖,它來自 Joseph Yiu,一位來自 ARM 的大牛。
其它同棧保護(hù)有關(guān)的編譯選項(xiàng)
GCC 除了提供 stack-usage 這個編譯選項(xiàng)外,還有其它一些相關(guān)的選項(xiàng)可供選擇。
- Wstack-usage
這是一個有用處的編譯選項(xiàng):-Wstack-usage。它能夠在堆棧使用超過限制時產(chǎn)生 warning
信息。用法是:
-Wstack-usage=256
它表示如果棧使用量超過 256 時產(chǎn)生警告。這樣就能更快速地知道哪個函數(shù)超了。只說它有些用處而不是非常有用是因?yàn)椋会槍蝹€函數(shù)的堆棧用量, 不會按調(diào)用樹累計(jì)被調(diào)用函數(shù)的堆??倲?shù)。
- fstack-protector 這個選項(xiàng)的解釋是: 產(chǎn)生額外的代碼來檢查緩沖區(qū)溢出,例如堆棧粉碎攻擊。這是通過向具有易受攻擊對象的函數(shù)添加保護(hù)變量來實(shí)現(xiàn)的。這包括調(diào)用 alloca 的函數(shù),以及緩沖區(qū)大于 8 字節(jié)的函數(shù)。在輸入函數(shù)時初始化保護(hù),然后在函數(shù)退出時檢查保護(hù)。如果保護(hù)檢查失敗,將打印錯誤消息,程序退出。
- fstack-protector-all
同-fstack-protector 基本相同, 區(qū)別是它為所有的函數(shù)都提供保護(hù)。
-fstack-protector和-fstack-protector-all在ebp和ip等信息的地址下面放一個保護(hù)數(shù), 如果棧溢出 ,那么這個 32 位數(shù)會被修改,就會導(dǎo)致函數(shù)進(jìn)入棧溢出錯誤處理函數(shù)。一旦檢測到溢出就會調(diào)用__stack_chk_fail()函數(shù)。這個函數(shù)需要用戶自己來寫,比如可以打印一個報錯信息,或者執(zhí)行其它一些保護(hù)措施。 下圖是在編譯選項(xiàng)里加入-fstack-protector-all 后一個普通C函數(shù)的匯編內(nèi)容。
當(dāng)然,可以想見,如果每個函數(shù)都加這么一段,編譯出來的二進(jìn)制文件會大上許多,執(zhí)行速度也會變慢一些。而如果僅僅使用-fstack-protector,則很少有函數(shù)會被保護(hù)。因?yàn)?alloca() 是在棧(stack)里面分配空間,而我們一般都是用 malloc()在堆(heap)里面分配。
小結(jié)
棧空間防溢出是軟件設(shè)計(jì)中非常關(guān)鍵的問題。MCUXpresso IDE 的 Call Graph 窗口為開發(fā)者提供了很好的可視化統(tǒng)計(jì)表格, 非常便于對堆棧使用情況的評估。論程序有沒有操作系統(tǒng),它都非常有效。 而GCC編譯器提供的其它一些選項(xiàng),雖然也有用處,但是在嵌入式軟件設(shè)計(jì)中還是用在 debug 階段會更好一些。開發(fā)者還是應(yīng)該盡量使用 call Graph 功能做到事先防范。
審核編輯:湯梓紅
-
mcu
+關(guān)注
關(guān)注
146文章
16667瀏覽量
347806 -
恩智浦
+關(guān)注
關(guān)注
14文章
5788瀏覽量
104729 -
IDE
+關(guān)注
關(guān)注
0文章
334瀏覽量
46540 -
編譯器
+關(guān)注
關(guān)注
1文章
1602瀏覽量
48896 -
mcuxpresso
+關(guān)注
關(guān)注
1文章
38瀏覽量
4116
原文標(biāo)題:MCUXpresso IDE 的棧分析功能
文章出處:【微信號:NXP_SMART_HARDWARE,微信公眾號:恩智浦MCU加油站】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論