組合語言與 CPU
指令層級的堆疊(push/pop、call/ret)
當你呼叫一個函式,而它稍後返回到你離開的確切位置,必定有東西記住了「你離開的地方」。指令層級的堆疊正是這份記憶。它是一塊被當作後進先出便條紙使用的行程記憶體,由堆疊指標(rsp)標記其頂端,也正是使呼叫與返回函式得以運作的機制。
兩對指令在運轉它。push value 把 rsp 減去並在新頂端寫入該值;pop dest 讀取頂端的值並把 rsp 加回——兩者合起來在會破壞某暫存器的程式碼前後保存與還原值。呼叫/返回機制建立在此之上:call target 把緊接 call 之後那條指令的位址(返回位址)推上堆疊再跳到 target;ret 把那個返回位址從堆疊彈進程式計數器,讓執行恰好從離開處續行。由於每次呼叫都推入自己的返回位址,巢狀與遞迴呼叫就只是在堆疊上層層相疊,每次 ret 便以相反順序解開。
這就是呼叫堆疊何以運作、又何以有深度上限的原因。每一個活躍的函式呼叫,其返回位址(通常還有它的區域變數)都坐在堆疊上;最深的呼叫在頂端,最外層的在底部。無窮遞迴只推不彈,直到堆疊用盡空間——這就是堆疊溢位。它也說明了為何被竄改的返回位址如此危險:覆寫堆疊上保存的返回位址(例如透過緩衝區溢位),ret 就會跳到被竄改值所指之處,這正是堆疊破壞攻擊背後的經典機制。
call f 推入返回位址並跳進 f;當 f 結束,它的 ret 把那個位址彈回 rip,執行便在原本 call 之後的那條指令繼續。
call 把返回位址存到堆疊;ret 再讀回——這就是整個呼叫/返回機制。
這是堆疊記憶體區域表現得像後進先出,而非抽象的堆疊資料結構本身——且在 x86-64 上它向下增長,所以 push 會降低 rsp。返回位址以一般可寫記憶體的形式存在堆疊上,這正是覆寫它成為堆疊破壞攻擊核心的原因。
又稱
另見