結構化程式的一個最基本的單元就是“函數”或者叫“過程”。在彙編這一層自然也相應的有支援這些概念的指令操作,如棧操作和棧幀的概念。
首先這裡要為“開啟彙編之門”那篇blog補充一點的是:組合語言是與機器相關,這裡的一切都是基於IA-32機器平台的。
1、定址方式
我們已經知道在運算元表示中有一種是用來指示記憶體位址的內容的,在GNU Assembly中指示記憶體位址有多種方式,這些方式被統稱“定址方式”。通用的定址格式為:“Imm(Eb, Ei, s)”[1]。解釋一下:該運算式的計算方式為Imm + R[Eb] + R[Ei] * s,這一串的結果是什麼呢?是一個儲存空間的地址,操作指令通過該運算元運算式計算出來的記憶體位址來訪問記憶體。
由通用形式演化幾種常見特殊形式如下:
1) Imm - 注意與$Imm區別,後者為立即數,而前者是以立即數形式承載的一個記憶體位址,這種方式叫絕對定址;
2) (Ex) - 注意與Ex區別,後者為寄存器內容,而前者是以寄存器內容形式承載的一個記憶體位址,這種方式叫間接定址;
3) Imm(Eb) - 其表示結果是記憶體位址為Imm + R[Eb];
4) (Eb, Ei) - 其表示結果是記憶體位址為R[Eb] + R[Ei];
5) Imm((Eb, Ei) - 其表示結果是記憶體位址為Imm + R[Eb] + R[Ei]。
2、寄存器使用
在“開啟彙編之門”中曾經提過雖然寄存器的專用性已經降低,但是某些寄存器還是有其專用場合的。GNU為我們制定了一個寄存 器使用規則,規則規定:“%eax、%ecx和%edx是由調用者負責儲存的,而%ebx、%ebi和%esi則由被調用者保護,而%esp和%ebp都 是棧操作專用的”。
3、棧操作
棧,實際上是一塊兒專用的記憶體地區,每個進程地址空間都有其專有的棧區。地球人都知道關於棧有兩種操作:Push和Pop。相應 的GNU Assembly分別定義了“pushl S”和“popl D”分別來完成壓棧和出棧操作。每個操作都包含兩個步驟:移動棧頂指標和資料傳送。
pushl S <=> R[%esp] <-- R[%esp] - 4 ;M[R[%esp]]<-- S
popl D <=> D <-- M[R[%esp]];R[%esp] <-- R[%esp] + 4
4、棧幀的形成
提到函數或者程序呼叫就不能離開棧操作。而每個函數或者程序呼叫也都離不開一個叫“棧幀”的概念。棧是用來傳遞參數、儲存返回結果等作用的,而棧幀則是1對1映射到某個程序呼叫的。棧幀由%ebp來標識。我們來看看一個例子,通過該例子看看棧幀裡到底有些什麼東西?
void callee(int x, int y) {
x = 1;
y = 2;
}
void caller(int m, int n) {
callee(m, n);
}
翻譯為彙編代碼為:
_callee:
pushl %ebp //儲存調用者的棧幀地址
movl %esp, %ebp //初始化callee棧幀地址
movl $1, 8(%ebp) //擷取參數x資訊
movl $2, 12(%ebp) //擷取參數y資訊
popl %ebp
ret
... ...
... ...
_caller:
pushl %ebp //儲存調用者的棧幀地址
movl %esp, %ebp //初始化caller棧幀地址
subl $8, %esp
movl 12(%ebp), %eax
movl %eax, 4(%esp)
movl 8(%ebp), %eax
movl %eax, (%esp)
call _callee
leave
ret
看 看callee的彙編碼:進入callee後首先儲存其調用者caller的棧幀地址,然後讀取其調用者caller棧幀中的參數資訊進行計算。可以看出 一個過程的棧幀中起碼包括其上一個棧幀的起始地址,然後是一些參數資訊,按照CS.APP說法,棧幀在儲存參數資訊之前還有可能儲存一些本地變數或臨時變 量等。在每個過程的棧幀的結尾處都記錄著過程返回地址,這個返回地址是由call執行時自動加入的。callee都是通過%ebp +/- 位移量來擷取參數資訊的。用下面的圖可以小結一下棧幀的模樣(起始:%ebp所指的位元組--> 終止:返回地址所在位元組):
+ +
| |
+----------+
| old %ebp | <--- %ebp
+----------+
| 本地變數 |
+----------+
| 參數n |
+----------+
| 參數...|
+----------+
| 參數1 |
+----------+
| 返回地址 |
+----------+
| ... |
| |<-- %esp
[注1]
這裡採用了CS.APP中的表示方法,Eb表示基底位址暫存器,Ei表示變址寄存器,s為伸縮因子。我們使用R來表示引用某個寄存器的值,使用M來表示引用某記憶體位址。