函式呼叫 函式棧

2022-08-21 10:09:14 字數 3972 閱讀 9638

函式呼叫大家都不陌生,呼叫者向被呼叫者傳遞一些引數,然後執行被呼叫者的**,最後被呼叫者向呼叫者返回結果,還有大家比較熟悉的一句話,就是函式呼叫是在棧上發生的,那麼在計算機內部到底是如何實現的呢?

對於程式,編譯器會對其分配一段記憶體,在邏輯上可以分為**段,資料段,堆,棧

**段:儲存程式文字,指令指標eip就是指向**段,可讀可執行不可寫

資料段:儲存初始化的全域性變數和靜態變數,可讀可寫不可執行

bss:未初始化的全域性變數和靜態變數

如圖所示

暫存器

eax:累加(accumulator)暫存器,常用於函式返回值

ebx:基址(base)暫存器,以它為基址訪問記憶體

ecx:計數器(counter)暫存器,常用作字串和迴圈操作中的計數器

edx:資料(data)暫存器,常用於乘除法和i/o指標

esi:源變址暫存器

dsi:目的變址暫存器

esp:堆疊(stack)指標暫存器,指向堆疊頂部

ebp:基址指標暫存器,指向當前堆疊底部

源**

int print_out(int begin, int

end)

int add(int a, intb)

int pass(int a, int b, int

c) ;

int sum = 0;

int *ret;

ret = (int*)(buffer+28);

//(*ret) += 0xa;

sum = a + b +c;

return

sum;}

intmain()

printf(

"%d\n

", __sum);

system(

"pause");

}

函式初始化

28: int

main()

29: {

011c1540 push ebp

//壓棧,儲存ebp,注意push操作隱含esp-4

011c1541 mov ebp,esp //

把esp的值傳遞給ebp,設定當前ebp

011c1543 sub esp,0f0h //

給函式開闢空間,範圍是(ebp, ebp-0xf0)

011c1549 push ebx

011c154a push esi

011c154b push edi

011c154c lea edi,[ebp-0f0h] //

把edi賦值為ebp-0xf0

011c1552 mov ecx,3ch //

函式空間的dword數目,0xf0>>2 = 0x3c

011c1557 mov eax,0cccccccch

011c155c rep stos dword ptr es:[edi]

//rep指令的目的是重複其上面的指令.ecx的值是重複的次數.

//stos指令的作用是將eax中的值拷貝到es:edi指向的位址,然後edi+4

一般所用函式的開頭都會有這段命令,完成了狀態暫存器的儲存,堆疊暫存器的儲存,函式記憶體空間的初始化

函式呼叫

30: print_out(0, 2

);013d155e push 2//

第二個實參壓棧

013d1560 push 0

//第乙個實參壓棧

013d1562 call print_out (13d10fah)//

返回位址壓棧,本例中是013d1567,然後呼叫print_out函式

013d1567 add esp,8

//兩個實參出棧

//

除了vs可能增加一些安全性檢查外,print_out的初始化與main函式的初始化完全相同

被呼叫函式返回

013d141c mov eax,1

//返回值傳入eax中

013d1421 pop edi

013d1422 pop esi

013d1423 pop ebx

//暫存器出棧

013d1424 add esp,0d0h //

以下3條命令是呼叫vs的__rtc_checkesp,檢查棧溢位

013d142a cmp ebp,esp

013d142c call @ilt+315

(__rtc_checkesp) (13d1140h)

013d1431 mov esp,ebp

//ebp的值傳給esp,也就是恢復呼叫前esp的值

013d1433 pop ebp //

彈出ebp,恢復ebp的值

013d1434 ret //

把返回位址寫入eip中,相當於pop eip

call指令隱含操作push eip,ret指令隱含操作 pop eip,兩條指令完全對應起來 

寫到這裡我們就可以分析一下main函式呼叫print_out函式前後堆疊(stack)發生了什麼變化,下面用一系列圖說明

接下來是返回過程,從上面的013d1431 行**開始

print_out函式呼叫前後,main函式的棧幀完全一樣,perfect!

下面我們來看看print_out函式到底做了什麼事情

int *p;

p = (int*)(int(&begin) - 4

);if(begin <=end)

*p -= 5;

根據上面呼叫print_out函式後的示意圖,可以知道p實際上是指向了函式的返回位址addr,然後把addr-5,這又會發生什麼?

再回頭看一下反彙編的**,

013d1560 push 0

//第乙個實參壓棧

013d1562 call print_out (13d10fah)//

返回位址壓棧,本例中是013d1567,然後呼叫print_out函式

013d1567 add esp,8

//兩個實參出棧

分析可知,返回位址addr的值是013d1567 ,addr-5為013d1562 ,把返回位址指向了call指令,結果是再次呼叫print_out函式,

從而print_out函式實現了列印從begin到end之間的所有數字,可以說是迴圈呼叫了print_out函式

對於add函式,主要是為了說明返回值存放於暫存器eax中。

另外,vs自身會提供一些安全檢查

checkstackvar安全檢查通過ecx和edx傳遞引數, 區域性變數有陣列時使用

__security_check_cookie返回位址檢查, 陣列長度大於等於5時使用

__rtc_checkesp程式棧檢查,printf函式用使用

函式呼叫棧

當程式進行函式呼叫的時候,系統會用到下面三種暫存器 3.ebp ebp暫存器裡儲存的是棧基址,是在函式呼叫之前,由esp賦值給ebp的。棧底方向,高位位址 call fun arg1,arg2,arg3 修改esp,棧向下增長,引數入棧,返回位址入棧 arg3 arg2 arg1 返回位址 上一層e...

棧 函式呼叫

編譯以下程式,分析此程式以得出棧的精髓 1 主函式被上層呼叫者呼叫後,執行push ebp,esp 4 因為ebp入棧 ebp值沒有改變,值得注意的是剛開始分配站的時候,第乙個入棧的是return,主函式的返回位址,然後是ebp 2 然後是mov ebp,esp,將esp的值賦給ebp,該語句未執行...

函式呼叫棧恢復

cdecl 引數從右到左壓入,由呼叫者彈出,函式名 前置 stdcall 引數從右到左壓入,由被呼叫者彈出,函式名無變化 pascal 引數從左到右壓入,由呼叫者彈出,函式名大寫 fastcall 引數從左到右壓入,由被呼叫者彈出,函式名 前置 void fun 1,2 1。cdecl 是這樣的 p...