1. 程式人生 > >Linux核心分析:實驗一

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程式的彙編碼:

g函式
f函式
main函式

-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程式的反彙編程式碼,對程式在計算內部的執行過程有了一個直觀的認識,在函式呼叫中,棧起了舉足輕重的作用,很多電腦保安方面的漏洞都直接間接的利用了棧。