你真的了解棧溢出么?
記得之前看過一篇文章說,最好查的bug是語法錯誤,因為編譯器會告訴你,最不好查的bug是棧溢出,因為啥,因為不僅編譯器不會告訴你,連你自己有可能都找不到原因出在哪。
經過了一段時間的摸索,算是基本搞清楚了棧溢出的原理,寫下來以防日后出現問題無從下手。
前言
開發過單片機的同學應該不陌生這個名詞,一般我們也說堆棧,其實這里有兩個意思:一般我們說堆棧其實指的就是幀本身,而說堆指的就是堆。這是兩個不同的分區。便于理解給出一張典型的C語言在linux系統下的占區圖:
可以看出,對于Linux系統下的,存儲空間的分配有著較為層次清晰的分層。單片機大概也遵循這個分區架構。
二進制代碼以及常量(CONST修飾)以及全局變量在最底層,存儲空間最靠前的部分
然后是堆區,堆區向上增長,我們常用到的molloc()、free()等函數操作的就是這個區,這也是芯片系統中唯一可以讓程序員通過代碼操作的一片存儲空間
再然后是動態鏈接庫
在往上(高地址)便是棧區。 最高地址一般為操作系統內核,用戶無法訪問
了解了這個之后我們開始詳解何為棧、棧為什么會溢出以及在代碼級如何預防棧溢出,最后說一下棧溢出攻擊的事情。
那么什么是棧呢
在計算機中,棧可以理解為一個特殊的容器,用戶可以將數據依次放入棧中,然后再將數據按照相反的順序從棧中取出。也就是說,先放入的數據最后才能取出,而最后放入的數據必須先取出。這稱為先進后出(First In Last Out)原則。
放入數據常稱為入棧或壓棧(Push),取出數據常稱為出棧或彈出(Pop)。
可以發現,棧底始終不動,出棧入棧只是在移動棧頂,當棧中沒有數據時,棧頂和棧底重合。
這里需要注意標識棧頂和棧底的兩個寄存器: ebp寄存器指向棧底,esp寄存器指向棧頂。從本質上來講,棧是一段連續的內存,需要同時記錄棧底和棧頂,才能對當前的棧進行定位。
棧溢出是怎么回事
了解了棧實際上也是一塊內存后,棧溢出就好理解了。
當我們定義的數據所需要占用的內存超過了棧的大小時,就會發生棧溢出。編譯器會報棧溢出錯誤。
如一塊芯片的內存RAM大小為4k,當我們定義了一個大數組,如下:
int buf[1024*5] = {0};
很明顯定義的數組超過了內存大小,這就導致了棧溢出。
預防棧溢出需要我們在編程時了解內存使用,盡可能不要定義特別大的數組,盡可能不要定義特別復雜的函數,如多個形參等。
函數調用棧
定義的數組會占用棧空間,同樣,定義的函數也會占用棧空間,一個簡單的例子便是函數的入棧和出棧。
舉個例子:
void func(int a, int b)
{
int p =12, q = 345;
}
int main()
{
func(90, 26);
return 0;
}
函數的進棧出棧過程如下圖所示:
函數進棧
1) main() 是主函數,也需要進棧,如步驟①所示。
2) 在步驟②中,執行語句func(90, 26);,先將實參 90、26 壓入棧中,再將返回地址壓入棧中,這些工作都由 main() 函數(調用方)完成。這個時候 ebp 的值并沒有變,僅僅是改變 esp 的指向。
3) 到了步驟③,就開始執行 func() 的函數體了。首先將原來 ebp 寄存器的值壓入棧中(也即圖中的 old ebp),并將 esp 的值賦給 ebp,這樣 ebp 就從 main() 函數的棧底指向了 func() 函數的棧底,完成了函數棧的切換。由于此時 esp 和ebp 的值相等,所以它們也就指向了同一個位置。
4) 為局部變量、返回值等預留足夠的內存,如步驟④所示。由于棧內存在函數調用之前就已經分配好了,所以這里并不是真的分配內存,而是將 esp 的值減去一個整數,例如 esp - 0XC0,就是預留 0XC0 字節的內存。
5) 將 ebp、esi、edi 寄存器的值依次壓入棧中。
6) 將局部變量的值放入預留好的內存中。
至此,func() 函數的活動記錄就構造完成了。可以發現,在函數的實際調用過程中,形參是不存在的,不會占用內存空間,內存中只有實參,而且是在執行函數體代碼之前、由調用方壓入棧中的。
未初始化的局部變量的值為什么是垃圾值
為局部變量分配內存時,僅僅是將 esp 的值減去一個整數,預留出足夠的空白內存,不同的編譯器在不同的模式下會對這片空白內存進行不同的處理,可能會初始化為一個固定的值,也可能不進行初始化。
函數出棧
步驟⑦到⑨是函數 func() 出棧過程:
7) 函數 func() 執行完成后開始出棧,首先將 edi、esi、ebx 寄存器的值出棧。
8) 將局部變量、返回值等數據出棧時,直接將 ebp 的值賦給 esp,這樣 ebp 和 esp 就指向了同一個位置。
9) 接下來將 old ebp 出棧,并賦值給現在的 ebp,此時 ebp 就指向了 func() 調用之前的位置,即 main() 活動記錄的 old ebp 位置,如步驟⑨所示。
這一步很關鍵,保證了還原到函數調用之前的情況,這也是每次調用函數時都必須將 old ebp 壓入棧中的原因。
最后根據返回地址找到下一條指令的位置,并將返回地址和實參都出棧,此時 esp 就指向了 main() 活動記錄的棧頂, 這意味著 func() 完全出棧了,棧被還原到了 func() 被調用之前的情況。
函數執行完局部變量的值真的不存在了?
經過上面的分析可以發現,函數出棧只是在增加 esp 寄存器的值,使它指向上一個數據,并沒有銷毀之前的數據。
棧上的數據只有在后續函數繼續入棧時才能被覆蓋掉,這就意味著,只要時機合適,在函數外部依然能夠取得局部變量的值。請看下面的代碼:
#include <stdio.h>
int *p;
void func(int m, int n)
{
int a = 18, b = 100;
p = &a;
}
int main()
{
int n;
func(10, 20);
n = *p;
printf("n = %d\n", n);
return 0;
}
運行結果:
n = 18
在 func() 中,將局部變量 a 的地址賦給 p,在 main() 函數中調用 func(),函數剛剛調用結束,還沒有其他函數入棧,局部變量 a 所在的內存沒有被覆蓋掉,所以通過語句n = *p;能夠取得它的值。
參考網址:C語言中文網
智能推薦
for循環用了那么多次,但你真的了解它么?
一、基礎的for循環 0、使用while也是一種循環方式,此處探究for相關的循環,就不做拓展了。 1、遍歷數組的時候,初學時是使用的如下樣式的for循環: 2、而遍歷集合的時候使用的都是Iterator迭代器: 給定一組人名,兩兩組隊(此處允許自己和自己組隊),實現如下: 想象中的寫法是: 但是執行過后你會發現這段代碼是有瑕疵的,出現的結果只有四組: 那么剩下的組合去哪里了呢? 這里程序并不會拋...
freemarker + ItextRender 根據模板生成PDF文件
1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...
電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!
Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...
requests實現全自動PPT模板
http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...
猜你喜歡
Linux C系統編程-線程互斥鎖(四)
互斥鎖 互斥鎖也是屬于線程之間處理同步互斥方式,有上鎖/解鎖兩種狀態。 互斥鎖函數接口 1)初始化互斥鎖 pthread_mutex_init() man 3 pthread_mutex_init (找不到的情況下首先 sudo apt-get install glibc-doc sudo apt-get install manpages-posix-dev) 動態初始化 int pthread_...
統計學習方法 - 樸素貝葉斯
引入問題:一機器在良好狀態生產合格產品幾率是 90%,在故障狀態生產合格產品幾率是 30%,機器良好的概率是 75%。若一日第一件產品是合格品,那么此日機器良好的概率是多少。 貝葉斯模型 生成模型與判別模型 判別模型,即要判斷這個東西到底是哪一類,也就是要求y,那就用給定的x去預測。 生成模型,是要生成一個模型,那就是誰根據什么生成了模型,誰就是類別y,根據的內容就是x 以上述例子,判斷一個生產出...
styled-components —— React 中的 CSS 最佳實踐
https://zhuanlan.zhihu.com/p/29344146 Styled-components 是目前 React 樣式方案中最受關注的一種,它既具備了 css-in-js 的模塊化與參數化優點,又完全使用CSS的書寫習慣,不會引起額外的學習成本。本文是 styled-components 作者之一 Max Stoiber 所寫,首先總結了前端組件化樣式中的最佳實踐原則,然后在此基...