函式呼叫大家都不陌生,呼叫者向被呼叫者傳遞一些引數,然後執行被呼叫者的**,最後被呼叫者向呼叫者返回結果,還有大家比較熟悉的一句話,就是函式呼叫是在棧上發生的,那麼在計算機內部到底是如何實現的呢?
對於程式,編譯器會對其分配一段記憶體,在邏輯上可以分為**段,資料段,堆,棧
**段:儲存程式文字,指令指標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除了vs可能增加一些安全性檢查外,print_out的初始化與main函式的初始化完全相同);013d155e push 2//
第二個實參壓棧
013d1560 push 0
//第乙個實參壓棧
013d1562 call print_out (13d10fah)//
返回位址壓棧,本例中是013d1567,然後呼叫print_out函式
013d1567 add esp,8
//兩個實參出棧
//
被呼叫函式返回
013d141c mov eax,1call指令隱含操作push eip,ret指令隱含操作 pop eip,兩條指令完全對應起來//返回值傳入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
寫到這裡我們就可以分析一下main函式呼叫print_out函式前後堆疊(stack)發生了什麼變化,下面用一系列圖說明
接下來是返回過程,從上面的013d1431 行**開始
print_out函式呼叫前後,main函式的棧幀完全一樣,perfect!
下面我們來看看print_out函式到底做了什麼事情
int *p;根據上面呼叫print_out函式後的示意圖,可以知道p實際上是指向了函式的返回位址addr,然後把addr-5,這又會發生什麼?p = (int*)(int(&begin) - 4
);if(begin <=end)
*p -= 5;
再回頭看一下反彙編的**,
013d1560 push 0分析可知,返回位址addr的值是013d1567 ,addr-5為013d1562 ,把返回位址指向了call指令,結果是再次呼叫print_out函式,//第乙個實參壓棧
013d1562 call print_out (13d10fah)//
返回位址壓棧,本例中是013d1567,然後呼叫print_out函式
013d1567 add esp,8
//兩個實參出棧
從而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...