系統棧的工作原理
1.內存的不同用途
簡單來說,緩沖區溢出就是在大緩沖區的數據復制到小緩沖區中,由於沒注意小緩沖區的邊界,”撐爆“了小緩沖區。從而沖掉了小緩沖區相鄰內存區域的數據。
根據不同的操作系統,一個進程可能被分配到不同內存區域中去執行,但是不管什么樣的系統,什么計算機架構,進程使用的內存都可以按照功能分為4部分:
代碼區:可執行指令
數據區:用於存儲全局變量
堆區: 進程可以在堆區動態的請求一定大小的內存,並在用完之后歸還給堆區。動態分配和回收是堆區的特點
棧區: 用於動態的存儲函數之間的調用關系,以保證被調用函數返回時恢復到母函數中繼續執行
這只是簡單的內存划分,如果想了解關於內存更詳細的論述,請參考《深入理解計算機系統》,windows下,PE文件代碼段中包含的二進制機器代碼會被裝入內存的代碼區,處理器將到這里一條一條的取出指令和操作數,送入算術邏輯單元運算,如果代碼請求開辟動態內存,則會在內存的堆區分配一塊區域返回給代碼區的代碼使用,當函數調用發生時,函數的調用關系等信息會動態的保存在棧區。
程序中所使用的緩沖區可以是在堆區、棧區、數據區,不同地方的緩沖區利用方法不同。
2.棧與系統棧
棧是一種數據結構,是一種先進后出的數據表,用於標識棧的屬性兩個:棧頂、棧底 。內存中的棧區指的就是系統棧,由系統自動維護。
3.函數調用時發生了什么
請看如下C代碼:
int B(int b1,int b2){ int var_b1=b1+b2; int var_b2=b1-b2; return var_b1*var_b2; } int A(int a1,int a2){ int var_a1; var_a=B(a1,a2)+a1; return a1; } int main(int argc,char **argv,char **envp){ int var_main; var_main=A(4,3); return var_main; }
根據操作系統的不同、編譯器和編譯選項的不同,同一文件不同函數的代碼在內存代碼區中的分布可能相鄰,也可能不相鄰,可能有先后順序,也可能沒有,但他們都是在代碼所映射的節里
函數調用時,伴隨的系統棧中的操作如下:
在main函數調用A的時候,首先在自己的棧幀中壓入函數返回地址,然后位A創建新棧幀並壓入系統棧 ,
在函數A調用B的時候,同樣先在自己的棧幀中壓入返回地址,然后為B創建新棧幀並壓入系統棧
B返回時,B的棧幀被彈出系統棧,A棧幀的返回地址被露在棧頂,處理器跳到返回地址處執行
在A返回時,A的棧幀被彈出系統棧,main函數棧幀中的返回地址被露在棧頂。處理器跳到返回地址執行
4.寄存器與函數棧幀
每一個函數獨占自己的棧幀空間,當前正在運行的函數總是在棧頂,win32系統提供兩個寄存器用於標識位於系統棧頂端的棧幀
ESP:棧指針寄存器,存放一個指針,該指針永遠指向系統棧最上面的棧幀的棧頂
EBP:基址指針寄存器,該指針永遠指向系統棧最上面的棧幀的底部
函數棧幀:ESP和EBP之間內存空間為當前棧幀
在函數棧幀中一般包含以下幾種信息:、
局部變量:為函數舉報變量開辟的內存空間
棧幀狀態值:保存前棧幀的頂部和底部(實際上只保存前棧幀的底部,前棧幀的頂部可以通過堆棧平衡得到)
函數返回地址:保存當前函數調用前的“斷點”信息,也就是函數調用前的指令位置
函數棧幀的大小不固定,一般和局部變量的多少有關
5.函數調用約定與相關指令
調用約定描述了函數傳遞參數的方式和棧協同工作的技術細節,下面列出幾種調用方式:
C Syscall Stdcall BASIC FORTRAN PASCAL
參數入棧順序 右→左 右→左 右→左 左→右 左→右 左→右
誰恢復棧平衡 母函數 子函數 子函數 子函數 子函數 子函數
對於Visual C++,支持3種函數調用約定:
調用約定聲明 參數入棧順序 誰恢復棧平衡
__cdecl 右→左 母函數
__fastcall 右→左 子函數
__stdcall 右→左 子函數
除了入棧方向和恢復平衡不同之外,參數傳遞有時也會有所不同。例如,每一個C++類成員函數都有一個this指針,在windows下,這個指針保存在ECX中
,但如果用GCC編譯,這個指針會作為最后一個參數入棧
函數調用大致包括以下幾個步驟:
參數入棧
返回地址入棧
代碼區跳轉
棧幀調整:具體包括
保存當前棧幀狀態值(push ebp)
將當前棧幀切換到新棧幀(mov ebp,esp)
給新棧幀分配空間(把ESP減去所需空間大小,抬高棧頂)
函數返回大致包括以下幾個步驟:
保存返回值(通常保存在EAX中)
彈出當前棧幀,恢復上一個棧幀:具體包括
在堆棧平衡的基礎上給ESP加上棧幀的大小,降低棧頂,回收當前棧幀空間
將當前棧幀底部保存的前棧幀EBP值彈入EBP,恢復出上一個棧幀
將函數返回地址彈給EIP
跳轉