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