本文主要聊聊關(guān)于堆棧的內(nèi)容。包括堆棧和內(nèi)存的基本知識。常見和堆棧相關(guān)的 bug,如棧溢出,內(nèi)存泄漏,堆內(nèi)存分配失敗等。后面介紹軟件中堆棧統(tǒng)計(jì)的重要性,以及如何使用工具工具軟件中堆棧使用的范圍,并給出在軟件開發(fā)中,如何降低堆棧問題,優(yōu)化堆棧的一些實(shí)踐。
隨著代碼行數(shù)從幾千增長到百萬甚至更多,嵌入式軟件變得日益復(fù)雜,但總體目標(biāo)依然是實(shí)現(xiàn)軟件的需求,達(dá)到穩(wěn)健、正確且快速執(zhí)行。快速執(zhí)行需要以最優(yōu)方式管理可用的 CPU 和內(nèi)存資源,這對內(nèi)存空間(尤其是 RAM)有限的嵌入式系統(tǒng)來說是一項(xiàng)挑戰(zhàn)。
為此,必須通過執(zhí)行堆棧和堆分析對 RAM 的使用情況進(jìn)行詳細(xì)了解。開發(fā)人員手動估計(jì)堆棧和堆負(fù)載是一項(xiàng)艱巨的任務(wù),哪怕對于小程序來說也是這樣。如果估計(jì)不正確,則可能會導(dǎo)致堆棧溢出和一些未定義的行為。因此,常見的編碼規(guī)范要求強(qiáng)制執(zhí)行內(nèi)存分配使用最佳實(shí)踐來避免不必要的開銷。但是,堆棧仍是 RAM 的必要組成部分,需要得到優(yōu)化利用。
堆棧和內(nèi)存
先簡單聊聊內(nèi)存,內(nèi)存是計(jì)算機(jī)中重要的單元,而堆棧是內(nèi)存中最重要的應(yīng)用組成。
C 語言的內(nèi)存分配
在嵌入式系統(tǒng)中,內(nèi)存通常被分為幾個主要區(qū)域,每個區(qū)域存儲不同類型的數(shù)據(jù),這些區(qū)域在使用方式、性能以及目的上各不相同。
下面主要說說堆區(qū)和棧區(qū)
1. 堆區(qū)
堆區(qū)用于動態(tài)內(nèi)存分配,程序運(yùn)行時(shí)可以從堆區(qū)動態(tài)地分配和釋放內(nèi)存。其管理通常由程序的內(nèi)存管理子系統(tǒng)(如 C 語言的 malloc 和 free 函數(shù))負(fù)責(zé)。堆的大小和使用效率直接影響程序的性能和穩(wěn)定性。而內(nèi)存管理子系統(tǒng)通常都是程序員主動調(diào)用申請和釋放的。堆區(qū)位于 RAM 中,因此其內(nèi)容在斷電后會丟失。
2. 棧區(qū)
棧區(qū)主要用于存儲局部變量、函數(shù)參數(shù)和返回地址等。棧具有后進(jìn)先出(LIFO)的特性,每當(dāng)調(diào)用新的函數(shù)時(shí),函數(shù)的局部變量和返回地址就會被壓入棧中,函數(shù)返回時(shí)這些數(shù)據(jù)又會被彈出。棧區(qū)的系統(tǒng)自動管理的。棧區(qū)雖然高效但空間有限,這也導(dǎo)致棧溢出成為嵌入式系統(tǒng)中常見的問題之一。
堆區(qū)(Heap Memory)和棧區(qū)(Stack Memory)有以下幾個主要的區(qū)別:
1. 管理方式:
棧是自動管理的,由編譯器控制。當(dāng)函數(shù)調(diào)用時(shí),棧幀(Stack Frame)被自動創(chuàng)建和銷毀。
堆是手動管理的,需要程序員使用特定的函數(shù)(如malloc、free在C中)來分配和釋放內(nèi)存。
2. 分配和釋放速度:
棧的分配和釋放速度通常較快,因?yàn)樗鼈兪沁B續(xù)分配的,并且不需要復(fù)雜的內(nèi)存管理。
堆的分配和釋放速度較慢。差距的時(shí)間主要用在操作系統(tǒng)查找空閑內(nèi)存塊,并可能涉及將內(nèi)存塊從非連續(xù)的區(qū)域移動到連續(xù)的區(qū)域。
3. 內(nèi)存碎片:
棧內(nèi)存是連續(xù)分配的,通常不會產(chǎn)生內(nèi)存碎片。
堆內(nèi)存是動態(tài)分配的,每次大小不同,這導(dǎo)致堆上分配內(nèi)存不連續(xù),從而出現(xiàn)內(nèi)存的碎片化。這需要定期進(jìn)行內(nèi)存整理(Memory Compaction)來優(yōu)化性能。
堆棧統(tǒng)計(jì)為什么如此重要?
從上面關(guān)于堆棧的知識,我們知道堆棧的大小是有限的,堆棧對于程序運(yùn)行極為重要。
當(dāng)可用堆棧小于代碼需求時(shí),就會發(fā)生堆棧溢出。然而,當(dāng)為系統(tǒng)配置的堆棧大于需求時(shí),內(nèi)存又會被浪費(fèi)。開發(fā)人員必須持續(xù)一致地估計(jì)安全關(guān)鍵型應(yīng)用中最差情形下的堆棧使用量,以防止軟件運(yùn)行時(shí)發(fā)生 RAM 不足的情況。
在設(shè)計(jì)嵌入式系統(tǒng)時(shí),合理規(guī)劃和管理內(nèi)存區(qū)域?qū)τ诖_保系統(tǒng)的性能、穩(wěn)定性和可靠性十分重要。開發(fā)者需要根據(jù)應(yīng)用的具體需求和硬件資源,做出恰當(dāng)?shù)膬?nèi)存使用決策。合理分配堆棧大小,通過堆棧統(tǒng)計(jì)來優(yōu)化堆棧使用,對于確保系統(tǒng)的可靠性和安全性至關(guān)重要。
內(nèi)存管理不當(dāng),會導(dǎo)致內(nèi)存泄露(堆泄露)。而內(nèi)存泄漏可能會堆棧的不足,進(jìn)而出現(xiàn)堆棧溢出,這些是編程中常見的錯誤之一,而且極其嚴(yán)重。對于常規(guī)的桌面級應(yīng)用,這些錯誤發(fā)生會導(dǎo)致程序卡頓或重啟崩潰。而對于涉及生命安全和重大財(cái)產(chǎn)安全的關(guān)鍵應(yīng)用系統(tǒng)和軟件,堆棧不足可能導(dǎo)致數(shù)據(jù)損壞,系統(tǒng)不穩(wěn)定和崩潰,進(jìn)而導(dǎo)致人員傷亡和財(cái)產(chǎn)損失。
典型的內(nèi)存問題
Memory Leak (內(nèi)存泄漏)
內(nèi)存泄漏更準(zhǔn)確的說法是,堆內(nèi)存泄漏 (heap leak),是程序員在分配一段內(nèi)存后,分配的內(nèi)存未被釋放且無法再次訪問時(shí)發(fā)生。
#includevoid leakMemory() { int *leak = (int*)malloc(sizeof(int) * 100); // 漏掉了釋放操作 } int main() { leakMemory(); return 0; }
例子中,指針 leak 作為局部變量,在退出 leakMemory 函數(shù)后,沒有釋放且找不到地址無法再次訪問。
Stack Leak (棧泄漏)
當(dāng)程序中的局部變量大量消耗棧資源,而又沒有退出該函數(shù),導(dǎo)致 stack 溢出,大量的溢出可能會導(dǎo)致棧的不足,從而發(fā)生 overflow 的情況。這種一般發(fā)生在遞歸函數(shù)或者函數(shù)中有大循環(huán),其有定義局部變量,比如下面的代碼
void stackLeak(int n) {
char buffer[1024];
printf("Leaking stack memory %d,%p\n", n, (void *)buffer);
if(n>1)
stackLeak(n - 1);
}
int main() {
stackLeak(500);
return 0;
}
在 32G 內(nèi)存的筆記本上,運(yùn)行到 373 次就棧溢出了。
?
Buffer overflow(緩沖區(qū)溢出)
Buffer overflow(緩沖區(qū)溢出)是一種常見的安全漏洞,通常發(fā)生在當(dāng)程序試圖向一個固定長度的緩沖區(qū)寫入過多數(shù)據(jù)時(shí),一般發(fā)生在字符操作的時(shí)候。盡管緩沖區(qū)溢出通常與堆棧溢出有所區(qū)別——前者涉及對固定大小緩沖區(qū)的寫操作超出其邊界,后者是函數(shù)調(diào)用和局部變量使用過多堆棧空間——但在實(shí)踐中,緩沖區(qū)溢出經(jīng)常導(dǎo)致堆棧上的數(shù)據(jù)被覆蓋,因此可以視為一種堆棧不足引發(fā)的問題。
以下是一個使用C語言的示例,展示了一個簡單的緩沖區(qū)溢出漏洞:
#include#include void vulnerableFunction(char *str) { char buffer[10]; strcpy(buffer, str); // 不安全的拷貝,拷貝應(yīng)該指定大小 printf("Buffer content: %s ", buffer); } int main() { char largeData[] = "這是一個超長的字符串,遠(yuǎn)遠(yuǎn)超過了buffer的容量"; vulnerableFunction(largeData); return 0; }
在這個例子中,vulnerableFunction函數(shù)定義了一個長度為 10 的字符數(shù)組buffer作為緩沖區(qū)。然后,它使用strcpy函數(shù)將傳入的字符串str拷貝到buffer中。如果str的長度超過了buffer的容量(在這個例子中是 10 個字符),就會發(fā)生緩沖區(qū)溢出。strcpy不會檢查目標(biāo)緩沖區(qū)的大小,所以它會繼續(xù)寫入數(shù)據(jù),直到遇到源字符串的結(jié)束符\0。
這種溢出可能會覆蓋堆棧上的其他重要數(shù)據(jù),比如其他局部變量、函數(shù)返回地址等,導(dǎo)致程序行為異常,甚至允許黑客執(zhí)行任意代碼。
為了避免這種安全漏洞,應(yīng)該使用更安全的函數(shù),如strncpy,它允許指定目標(biāo)緩沖區(qū)的最大長度,從而避免溢出:
strncpy(buffer, str, sizeof(buffer) - 1); buffer[sizeof(buffer) - 1] = '?'; // 確保字符串以 null 結(jié)束
這樣就可以顯著減少因緩沖區(qū)溢出導(dǎo)致的安全風(fēng)險(xiǎn)。
Stack Frame Corruption (棧幀破壞)
棧幀中是函數(shù)的局部變量和函數(shù)調(diào)用時(shí)候的相關(guān)開銷。當(dāng)我們對局部變量進(jìn)行錯誤的操作時(shí)候,可能會破壞棧幀,導(dǎo)致函數(shù)的返回地址或其他重要數(shù)據(jù)被覆蓋。舉例如下。 #include
上面的例子中,有明顯的數(shù)組越界的問題。同時(shí),由于該數(shù)組是局部變量,對數(shù)組外的數(shù)進(jìn)行操作,可能會導(dǎo)致周邊的棧幀給改寫,從而導(dǎo)致系統(tǒng)崩潰。
當(dāng)然棧幀是否被改寫可能涉及很多系統(tǒng)的很多方面。這里不詳細(xì)討論。
Memory Allocation Failed
當(dāng)請求的內(nèi)存無法被分配時(shí)發(fā)生。當(dāng)請求大量內(nèi)存時(shí),可能會因?yàn)閮?nèi)存不足或者沒有足夠的連續(xù)內(nèi)存導(dǎo)致分配內(nèi)存失敗。
#includeint main() { //分配大量內(nèi)存 int *bigArray = (int*)malloc(sizeof(int) * 1000000000); if (bigArray == NULL) { printf("Memory allocation failed ") //內(nèi)存分配失敗 } free(bigArray); return 0; }
其他內(nèi)存管理方面的問題還包括重復(fù)分配內(nèi)存,野指針問題等,都會直接或間接的導(dǎo)致可用堆棧的減少。
歷史上,許多著名的軟件漏洞,如 Heartbleed、Spectre 等,都與堆棧管理不當(dāng)有關(guān)。通過對堆棧進(jìn)行統(tǒng)計(jì),我們可以提前發(fā)現(xiàn)潛在的安全隱患,避免類似問題的發(fā)生。
Heartbleed 漏洞是由于 OpenSSL 庫中的堆棧管理錯誤導(dǎo)致的。該漏洞允許攻擊者讀取內(nèi)存中的敏感信息,甚至可以修改內(nèi)存內(nèi)容。通過對堆棧進(jìn)行統(tǒng)計(jì)和分析,可以發(fā)現(xiàn) OpenSSL 庫中的堆棧使用不當(dāng),從而避免 Heartbleed 漏洞的產(chǎn)生。
堆棧統(tǒng)計(jì)促進(jìn)軟件開發(fā)和性能優(yōu)化
堆棧統(tǒng)計(jì)不僅可以幫助開發(fā)者確定程序在運(yùn)行時(shí)堆棧的使用情況,還可以指導(dǎo)開發(fā)者進(jìn)行性能優(yōu)化。通過準(zhǔn)確的堆棧使用數(shù)據(jù),開發(fā)者可以合理分配堆棧大小,既避免了堆棧溢出的風(fēng)險(xiǎn),也確保了系統(tǒng)資源的高效利用。
性能瓶頸定位:堆棧統(tǒng)計(jì)可以幫助開發(fā)者快速定位應(yīng)用程序中的性能瓶頸。通過分析哪些函數(shù)調(diào)用最頻繁或哪些調(diào)用耗時(shí)最長,開發(fā)者可以集中優(yōu)化這些熱點(diǎn)區(qū)域,從而提高整體應(yīng)用性能。
資源使用分析:它可以幫助開發(fā)者理解應(yīng)用程序如何使用系統(tǒng)資源,例如CPU和內(nèi)存。這對于識別和修復(fù)內(nèi)存泄漏、過度的CPU使用等問題非常重要。
代碼質(zhì)量改進(jìn):通過堆棧統(tǒng)計(jì),開發(fā)者可以識別代碼中的不良實(shí)踐或設(shè)計(jì)模式,如不必要的遞歸、過度復(fù)雜的函數(shù)調(diào)用等,進(jìn)而重構(gòu)代碼以提高其可讀性和可維護(hù)性。
優(yōu)化決策依據(jù):堆棧統(tǒng)計(jì)提供了量化數(shù)據(jù),幫助開發(fā)團(tuán)隊(duì)做出基于數(shù)據(jù)的決策。這些數(shù)據(jù)可以用來確定優(yōu)化的優(yōu)先級,決定哪些優(yōu)化措施可以帶來最大的性能提升。
性能回歸檢測:在軟件開發(fā)周期中,新的代碼提交可能會引入性能回歸。定期進(jìn)行堆棧統(tǒng)計(jì)可以幫助及時(shí)發(fā)現(xiàn)性能下降,確保軟件性能持續(xù)穩(wěn)定。
用戶體驗(yàn)提升:應(yīng)用程序的響應(yīng)速度直接影響用戶體驗(yàn)。通過優(yōu)化那些影響性能的關(guān)鍵部分,可以顯著提升應(yīng)用的響應(yīng)速度和流暢度,從而提高用戶滿意度。
成本效益分析:對于需要大量計(jì)算資源的應(yīng)用程序,堆棧統(tǒng)計(jì)可以幫助識別和優(yōu)化資源密集型操作,從而減少對硬件資源的需求,降低運(yùn)營成本。
總之,堆棧統(tǒng)計(jì)是理解和優(yōu)化軟件性能的強(qiáng)大工具。通過定期進(jìn)行堆棧統(tǒng)計(jì)和分析,開發(fā)團(tuán)隊(duì)可以確保他們的應(yīng)用程序運(yùn)行高效,提供優(yōu)秀的用戶體驗(yàn),并以最佳的資源使用效率運(yùn)作。
人工 VS 工具統(tǒng)計(jì)
在有相關(guān)統(tǒng)計(jì)工具之前,嵌入式系統(tǒng)的堆棧都是人工統(tǒng)計(jì)。此舉雖然可行,但是實(shí)際操作中存在許多問題和不足。
耗時(shí)耗力燒腦。人工統(tǒng)計(jì)需要徹底了解函數(shù)調(diào)用的深度、所有局部變量的細(xì)節(jié),以及執(zhí)行過程中隨時(shí)發(fā)生的中斷幀的大小對于復(fù)雜的嵌入式系統(tǒng)而言幾乎是不可行的。
漫長而又容易出錯的過程
沒法保證準(zhǔn)確性,特別是在面對大量并發(fā)執(zhí)行的任務(wù)和復(fù)雜的函數(shù)調(diào)用關(guān)系時(shí),更是這樣。
實(shí)時(shí)更新,則更是無法做到,每一次代碼的變更,可能都需要做大量的重新統(tǒng)計(jì)。
工具分析能夠詳細(xì)分析出函數(shù)調(diào)用深度、局部變量和返回參數(shù)的堆棧估計(jì)、嵌套中斷以及執(zhí)行期間發(fā)生的中斷幀的大小,解決人工統(tǒng)計(jì)的上述問題之外,還有另外兩個優(yōu)勢。
動態(tài)分析:一些高級的堆棧統(tǒng)計(jì)工具支持運(yùn)行時(shí)分析,能夠?qū)崟r(shí)監(jiān)控堆棧的使用情況,及時(shí)發(fā)現(xiàn)潛在的堆棧溢出風(fēng)險(xiǎn)。這種一般是芯片廠家或者合作廠家提供的調(diào)試工具中。
能夠?qū)δ繕?biāo)機(jī)進(jìn)行測試和測量。對戰(zhàn)統(tǒng)計(jì)最好在真實(shí)硬件上獲得實(shí)際的堆棧使用量數(shù)據(jù)。許多開發(fā)環(huán)境都具有硬件模擬功能,并提供實(shí)時(shí)堆棧分析功能。在實(shí)際硬件上執(zhí)行堆棧分析,并創(chuàng)建溢出場景來測試故障安全例程
可視化效果好。許多工具提供圖形化界面,直觀展示堆棧的使用情況,函數(shù)調(diào)用情況以及隨著函數(shù)調(diào)用,堆棧使用的變化,幫助開發(fā)者更容易理解和分析數(shù)據(jù)。
市面上常見的堆棧統(tǒng)計(jì)軟件
堆棧統(tǒng)計(jì)工具有以下幾類
一類是由編譯和 調(diào)試工具,有芯片廠商提供的,也有第三方的。比如 ARM Keil MDK,IAR Embedded Workbench 等等
第二類是第三方的調(diào)試工具,GNU 項(xiàng)目,如 GNU Debugger,Lauterbach Trace32
第三類是開源堆棧工具,如 Valgrind,FreeRTOS 中的 vTaskList
最后一類是靜態(tài)分析工具。這一類以 polyspace code prover 為代表。
以上就是關(guān)于內(nèi)存、堆棧方面的一些介紹,以及常見的一些內(nèi)存泄露相關(guān)的例子,這些例子會直接或間接到導(dǎo)致內(nèi)存泄露,進(jìn)而導(dǎo)致可用堆棧的減少,軟件長時(shí)間運(yùn)行會導(dǎo)致性能下降,甚至崩潰等問題。處理好這些問題是解決堆棧問題的前提。
另外并介紹了堆棧統(tǒng)計(jì)的收益。
下一篇我們看看,如何使用形式化工具進(jìn)行堆棧統(tǒng)計(jì)。
-
嵌入式
+關(guān)注
關(guān)注
5082文章
19126瀏覽量
305193 -
計(jì)算機(jī)
+關(guān)注
關(guān)注
19文章
7494瀏覽量
87953 -
內(nèi)存
+關(guān)注
關(guān)注
8文章
3025瀏覽量
74047 -
C語言
+關(guān)注
關(guān)注
180文章
7604瀏覽量
136823 -
堆棧
+關(guān)注
關(guān)注
0文章
182瀏覽量
19761
原文標(biāo)題:堆棧統(tǒng)計(jì)知多少(一)關(guān)于內(nèi)存,堆棧和常見的 BUG
文章出處:【微信號:MATLAB,微信公眾號:MATLAB】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論