Linux核心分析:實驗一
計算機體系結構與程式執行過程
現代計算機大都採用的是“馮.諾依曼”體系結構,它的核心思想是:程式儲存,指令和資料不加區分的放在一個儲存器中。由指令指標暫存器儲存著下一條將要執行指令的地址,這個暫存器在32位系統中叫eip,64位系統中叫rip。
指令是用二進位制編碼的,難於記憶。為了更有效的編寫程式,人們就發明了組合語言,它是對二進位制指令的一種簡單對映。平常我們所寫的程式都需要編譯成二進位制,才能被機器執行。我們可以通過反彙編一個C程式,瞭解程式執行的過程。
反彙編C程式
實驗1給出的原始碼很簡單,如下所示:
int g( int x )
{
return x + 3;
}
int f( int x )
{
return g(x);
}
int main(void)
{
return f(8) + 1;
}
我們可以使用如下命令
gcc -S -o main.S main.c -m32
得到這個C程式的彙編碼:
-S :表示僅彙編
-m32 :表示生成32位的指令格式
當我們得到一個已經編譯好的C程式時,在Linux下可以使用objdump工具得到這個程式的反彙編指令。
彙編指令分析
函式的開始
可以觀察到每個函式的開始部分,都有這2條指令
pushl %ebp
movl %esp, %ebp
subl $4, %esp
第一條指令是把舊的ebp儲存下來,放在棧中。這是為了當函式結束時,能恢復到呼叫這個函式之前的棧空間。
第二條指令是將當前棧頂指標的位置儲存到ebp中,因為函式的引數以及函式內部的區域性變數,一般都根據ebp作為基址定址的。
第三條指令是開闢棧空間,
函式體部分
因為實驗給的原始碼很簡單,所以彙編指令很少。在f函式內部有一下三條指令
movl 8(%ebp), %eax
movl %eax, (%esp)
call g
第一條指令因為棧是向下增長的,8(%ebp)根據棧呼叫慣例,它表示f函式的第一個引數,即x。
第二條指令它以eax暫存器作為媒介,把它的值放在%esp中,也是函式g的引數。
第三條指令表示呼叫g函式
根據棧的使用慣例,假如fun有4個引數,如下:
void fun( int a, int b, int c, int d );
當呼叫fun函式的時候,會先開闢16位元組空間,如下:
subl $0x10, %esp
分別將a,b,c,d的值放到這個空間裡,然後將它們壓棧
pushl 0x0c(%esp) ; 這是 d 的值
pushl 0x08(%esp) ; 這是 c 的值
pushl 0x04(%esp) ; 這是 b 的值
pushl (%esp) ; 這是 a 的值
函式的引數是從最後一個引數開始壓棧的,第一個引數放在(%esp)中,倒數第二個引數放在4(%esp)…
函式的結束
從反彙編指令可以看出,函式的結束部分的指令為:
leave
ret
其中leave 是封裝之後的指令,其實它是由兩條指令組成的
movl %ebp, %esp
popl %ebp
這兩句話是恢復呼叫這個函式之前的棧空間,因為為了呼叫函式f,我們在棧中開闢了空間,修改了ebp、esp等。
最後ret 指令是把函式的返回地址放到eip中,使得在執行完函式f後,程式能繼續執行下去。
總結
通過分析C程式的反彙編程式碼,對程式在計算內部的執行過程有了一個直觀的認識,在函式呼叫中,棧起了舉足輕重的作用,很多電腦保安方面的漏洞都直接間接的利用了棧。