在嵌入式系統中尋找最痛苦的錯誤之一是堆棧溢出其邊界并開始覆蓋附近的內存區域。堆棧溢出的癥狀通常在發生完美的中斷和函數調用風暴時隨機出現,這導致它們難以檢測。為了通過使用堆棧監視器來防止堆棧溢出,嵌入式開發人員可以采取七個步驟來確保堆棧保留在其分配的內存區域中。
步驟 1 – 執行最壞情況堆棧分析
許多編譯器和工具鏈會自動將堆棧大小設置為 0x400 字節,相當于一千字節的 RAM。對于許多應用程序來說,一千字節的堆棧大小通常就足夠了,但這是計算機科學而不是猜謎游戲,那么工程師如何確保堆棧大小合適呢?答案是執行最壞情況堆棧分析。
最壞情況堆棧分析可以通過許多不同的方式執行,超出了本文的范圍。一般來說,開發人員有許多項目需要完全理解。首先,有必要了解其應用程序的調用深度;有多少函數正在調用在返回鏈之前調用函數的函數。每個返回地址都存儲在堆棧中。其次,開發人員需要了解這些函數中每個變量的數量和大小,以估計每個函數將使用多少堆棧空間。最后,嵌入式開發人員需要確定可以同時觸發多少個中斷以及每個中斷幀的大小。
步驟 2 – 設置堆棧大小
最壞情況堆棧分析的輸出將導致堆棧的大小。盡管對系統進行了仔細分析,但計算堆棧大小可能很困難且很難做到,將最終數字乘以 1.5 并沒有什么壞處,只是為了確保包含一個合理的緩沖區以應對不可預見的情況。然后可以通過項目屬性或鏈接器文件更改堆棧大小,具體取決于首選項和工具功能。
步驟 3 – 選擇一種保護方法
正確調整堆棧大小是防止堆棧溢出和破壞附近內存區域的良好進展,但它仍然不允許檢測此類溢出事件。在嵌入式系統中,有多種方法可以檢測到此類事件。首先,是使用內存保護單元并設置堆棧的邊界,以便如果堆棧越過邊界,則會觸發中斷,然后系統可以記錄問題并按照程序恢復系統。其次,如果正在使用 RTOS,開發人員可以啟用堆棧溢出檢測。默認情況下,許多 RTOS 都啟用了此檢測,但我看到有文章建議關閉此功能以提高性能!不建議開發人員禁用堆棧溢出檢測,否則你可能會感到堆棧溢出錯誤的冷落。最后,在沒有 MPU 或使用 RTOS 的資源受限系統中,開發人員可以非常輕松地創建自己的堆棧監視器。
步驟 4 – 將保護部分添加到鏈接器
嵌入式開發人員可以通過多種不同的方式創建堆棧保護部分,但指定保護大小和位置的一種有用方法是使用鏈接器文件。可以更新鏈接器文件以包含保護大小和位置。大小是完全任意的。一個經驗法則是讓它足夠大,這樣如果發生溢出,它就不會溢出防護區域。在圖 1 中可以看到保護部分的示例。
圖1
步驟 5 – 使用模式填充保護空間
創建保護部分很棒,但除非其中填充了已知模式,否則它并不是非常有用。然后可以稍后由應用程序代碼檢查保護模式。任何模式都可以放置在防護區域中,但我發現使用人類可讀的模式很有用。使用 0xC0DE 模式是我最喜歡使用的模式之一。圖 1 顯示了一個人口密集的警戒區域的示例。確切的實現將根據所使用的工具鏈而有所不同。
步驟 6 – 定期檢查模式
應用程序代碼應設置為定期檢查整個保護部分是否仍然包含正確的模式。模式的變化將由堆棧溢出引起。此檢查的應用程序代碼相對簡單。嵌入式開發人員只需要遍歷每個模式并驗證它是否仍然正確。圖 2 顯示了一個使用指針檢查堆棧保護填充模式的示例循環。如果檢測到更改,則應用程序可以分支并嘗試記錄系統堆棧并開始恢復過程。
圖2
步驟 7 – 測試警衛
創建堆棧監視器的最后一步當然是測試它!測試它的最佳方法之一是編寫一小段代碼來修改堆棧保護模式。堆棧保護的定期檢查應該檢測到模式已經改變,這表明堆棧已經溢出。
經過測試的堆棧監視器對提高系統的可靠性和穩健性大有幫助。一旦監控的堆棧能夠檢測到溢出,就需要額外的應用程序代碼來決定如何處理該信息。記錄調用深度、寄存器值和應用程序狀態將幫助開發人員重復溢出并發現根本原因。
結語
開發人員在開始軟件開發時經常會忽略堆棧。堆棧溢出是難以發現的錯誤之一,除非嵌入式開發人員努力對其進行監控。檢測堆棧溢出并不困難,監視器的輕微性能損失非常值得!