棧和函數調用

def:棧是一種後入先出的數據結構,天然適合用來保存需要函數調用等需要保存的信息。在windows的用戶進程中都包含用戶棧和內核棧兩個棧。

每個線程都至少包含有一個棧,每個棧都對應內核中的一個_KTHREAD結構:


在線程開始運行之前需要創建這個線程的棧,創建過程如下:




棧作爲一個存儲數據的結構,在函數調用的過程中執行CALL和RET指令時分別以如下方式使用:



而用戶態調用內核態的過程如下(引用自連接):


棧中除了存儲跳轉的地址之外,還可以存儲和在函數內定義的局部變量,這樣隨着函數退出就不再需要清理內存了。因爲ESP經常變化,無法直接的表達局部變量的地址,所以CPU中引入了一個新的寄存器EBP,記錄棧中的某一個地址爲基礎地址(通常爲函數入口處的ESP地址),局部變量使用EBP的偏移來表示,從EBP到當前ESP的棧內容成爲當前函數的棧幀,到上個EBP的棧內容稱爲調用函數的棧幀。根據這些EBP的內容我們可以查找到函數調用的關係這是實現棧回溯的基礎。

有一些優化爲了減少目標程序的體積,提高運行數度會去掉棧指針的建立和維護,而在符號文件中記錄幀指針省略的信息,我們無法通過EBP方法來實現棧回溯,只能通過符號文件來實現,如果沒有符號文件則無法知道函數的調用關係。

函數調用和返回時ESP的值保持不變,我們稱爲棧平衡。如果ESP被意外破壞而指向了別的地址,(debug版)可以在函數返回時插入檢查函數_chkesp檢查是否被破壞。

我們根據函數調用時入棧順序,是否使用寄存器和誰負責清理約定不同的函數調用方式:


用戶棧的大小系統默認爲1M,但爲了節約內存並不會一次爲所有線程分配1M的物理內存,開始的時候提交連個內存頁,一個棧內容一個保護頁,當棧增長到保護頁的時候觸發一次訪問異常,則系統把保護頁設置爲棧內存,再提交一個頁爲保護頁,這樣就實現了棧的自動增長。保護頁會一直存在每次訪問保護頁都會觸發一次訪問異常,如果棧已經增長到最大值則不再提交新的內存,而拋出一個棧溢出異常(當還有最後一個頁的空間的時候也會拋出異常,但可以繼續運行)。而對於一次超出一個頁面大小的內存申請編譯器需要插入檢查函數_chkstk分多次提交,知道得到足夠大的內存。

對於棧中內存的讀寫,如果超出了內存邊界則有可能會破壞棧幀的內容,如果函數的返回地址被惡意修改到某個特定的位置,在這個位置中加入一段帶有惡意目的的代碼,這樣的方式成爲緩衝區溢出攻擊。

爲了避免緩衝區溢出的情況,編譯器通常在debug版本中爲每個變量增加cookie保護,並記錄每個變量的大小,在函數返回時不止檢查返回地址,同時也檢查變量保護的cookie是否被破壞,來減少緩衝區溢出的情況及早發現錯誤。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章