最近在上孟寧老師的《Linux內核分析》,本文是該課程的實驗做業,經過分析彙編代碼來理解C程序在計算機中是如何工做的。分析的實驗代碼以下:程序員
右邊爲經過gcc -S main.c -o main.s -m32
命令轉成的x86彙編代碼,下文分析以右邊代碼爲準
C代碼數據結構
int g(int x) { return x + 31; } int f(int x) { return g(x); } int main(void) { return f(52) + 33; }
x86彙編代碼函數
g: pushl %ebp movl %esp, %ebp movl 8(%ebp), %eax addl $31, %eax popl %ebp ret f: pushl %ebp movl %esp, %ebp subl $4, %esp movl 8(%ebp), %eax movl %eax, (%esp) call g leave ret main: pushl %ebp movl %esp, %ebp subl $4, %esp movl $52, (%esp) call f addl $33, %eax leave ret
因爲C程序的入口爲main函數,因此這段代碼的起始點爲第17行,eip(Extended Instruction Pointer, 指令寄存器)指向第18行(eip指向下一條指令)。程序在啓動時,系統會爲程序分配一個堆棧空間,此時程序的堆棧爲空,ebp(Extended Base Pointer, 棧基指針寄存器)和esp(Extended Stack Pointer, 棧指針寄存器)都指向棧底。這裏使用的內存堆棧模型爲更常見的由下至上,而非課程視頻中由上之下的結構。同時須要注意的是,右邊的數字並不是內存的實際地址,這裏只是將內存作了簡單的編號。spa
18 pushl %ebp
,將ebp寄存器中的值壓棧,同時esp向上移一個單位指針
19 movl %esp, %ebp
將esp中的至賦值給ebp。此時,esp和ebp都指向1code
20 subl $4, %esp
這條指令的直接做用是將esp中的值減去4,而後把結果存回esp中。這裏須要說明兩點:視頻
這裏的4指的是4個字節,也就是內存中真實地址移動4個單位(至關於本文模型中的1個單位)ip
由於棧是向低地址擴展的數據結構。對應本文內存模型就是,1的地址比0要小4個單位,2的地址比1要小4個單位,以此類推。這也是爲何這裏用了減法指令內存
因此這條指令的執行結果就是將esp指向2get
21 movl $52, (%esp)
這條指令的含義是將52這個數傳入esp指向的內存地址中,也就是內存2
22 call f
call是一個宏指令,其對應的兩個指令爲pushl %eip
和movl f, %eip
。上面說過eip的指表明下一條指令的位置,這裏也就是第23行代碼(記做EIP23)。pushl %eip
就是將EIP23壓棧,而後經過movl f, %eip
將f函數的地址(EIP8)傳入eip,使得下一條指令從f函數開始,從而實現C函數的調用。
9 pushl %ebp
將ebp的值入棧,也就是將EBP 1放入內存4中,同時esp上移一個單位
10 movl %esp, %ebp
將esp的值傳入ebp中,此時esp 和 ebp同時指向內存4
11 subl $4, %esp
將esp上移一個單位,指向內存5
12 movl 8(%ebp), %eax
8(%ebp) = (8 + %ebp)
也就是ebp指針下移兩個單位,指向內存2,而後將內存2中的值(也就是52)傳入eax(Extended Accumulator X,累加寄存器)。這條指令執行完後堆棧中並沒有變化,只是將52這個數傳給了eax
13 movl %eax, (%esp)
將%eax中的數值傳入%esp指向的內存位置(內存5)
14 call g
一樣的,call至關於pushl %eip
和movl g, %eip
,此時eip指向第15條指令(記做EIP15)
2 pushl %ebp
將ebp的值入棧
3 movl %esp, %ebp
將esp的值傳入ebp,執行後ebp和esp都指向內存7
4 movl 8(%ebp), %eax
將內存5中的數據(也就是52)傳入eax。此時堆棧不變化
5 addl $31, %eax
將eax中的數據加上31,並把結果存入eax,因此此時eax中的值爲83(52+31)
6 popl %ebp
將棧頂的數據彈出,並傳入ebp,因此執行後ebp指向內存4。同時esp下移一個單位,指向內存6
7 ret
ret也是一個宏指令,實際執行的效果爲popl %eip
,就是將棧頂的數據傳入eip,同時esp下移一個單位,此時eip指向第15行指令
15 leave
leave指令對應movl %ebp, %esp
和popl %ebp
,先將ebp的值傳入esp,執行後ebp和esp都指向內存4,而後將內存4的數據彈出並傳入ebp中。因此執行leave執行後ebp指向內存1,esp指向內存3
16 ret
也就是popl %eip
,執行後eip指向第23行代碼,esp指向內存2
23 addl $33, %eax
將33累加到eax中,結果爲116(83+33)
24 leave
即movl %ebp, %esp
和popl %ebp
,執行後ebp和esp均指向內存0。至此,改程序的堆棧又從新變爲空棧
25 ret
該程序執行結束,經過popl %eip
將eip指向上個程序的指令
總結:經過分析能夠看出,C語言實際上是對彙編語言作了一層抽象,以方便程序員編寫和閱讀代碼。計算機在執行程序時,也只能循序漸進的逐條執行,這中間其實多了不少看似繁瑣的過程。好比每次進入一個函數,都要先保存ebp指針。同時系統分配給每一個程序的棧空間是有限的,若是調用的函數過多,則會致使棧溢出,引起程序異常。