十年網(wǎng)站開(kāi)發(fā)經(jīng)驗(yàn) + 多家企業(yè)客戶 + 靠譜的建站團(tuán)隊(duì)
量身定制 + 運(yùn)營(yíng)維護(hù)+專業(yè)推廣+無(wú)憂售后,網(wǎng)站問(wèn)題一站解決
c/c++程序中進(jìn)行函數(shù)調(diào)用,需要在進(jìn)程地址空間的棧區(qū)為這個(gè)函數(shù)申請(qǐng)一塊棧空間,這一塊??臻g稱為函數(shù)的棧幀。除了內(nèi)聯(lián)函數(shù)之外,普通函數(shù)的調(diào)用都有創(chuàng)建函數(shù)棧幀和銷毀函數(shù)棧幀的過(guò)程,函數(shù)棧幀的創(chuàng)建與銷毀過(guò)程均在進(jìn)程地址空間中進(jìn)行,執(zhí)行一個(gè)函數(shù),需要CPU進(jìn)行計(jì)算并執(zhí)行相關(guān)指令。
函數(shù)的調(diào)用過(guò)程是CPU執(zhí)行相關(guān)指令的過(guò)程,在CPU中存在大量的寄存器,常用的寄存器有:
eax 通用寄存器,用于存放臨時(shí)數(shù)據(jù),例如函數(shù)的返回值 ebx 通用寄存器,存放臨時(shí)數(shù)據(jù) ebp 棧底寄存器,保存當(dāng)前正在調(diào)用函數(shù)的棧幀的棧底 esp 棧頂寄存器,保存當(dāng)前正在調(diào)用函數(shù)的棧幀的棧頂 eip 指令寄存器,指令寄存器中保存CPU當(dāng)前正在執(zhí)行的指令的下一條指令的地址,指令寄存器也稱為PC指針
以下面的代碼為例說(shuō)明函數(shù)棧幀的創(chuàng)建與銷毀過(guò)程:
#includeint MyAdd(int a, int b)
{int c = a + b;
return c;
}
int main()
{int x = 0xA;
int y = 0xB;
int z = 0;
z=MyAdd(x, y);
printf("%d\n", z);
return 0;
}
main函數(shù)為主函數(shù),在Linux下,main函數(shù)是由__libc_start_main
函數(shù)調(diào)用的,__libc_start_main
函數(shù)由操作系統(tǒng)進(jìn)行調(diào)用。
int x=0xA
這條語(yǔ)句轉(zhuǎn)化為匯編指令為
mov dword ptr [ebp-8],0Ah
對(duì)應(yīng)如圖
此時(shí)eip寄存器中存放
mov dword ptr [ebp-14h],0Bh
這條指令的地址
int y=0xB
這條語(yǔ)句轉(zhuǎn)化為匯編指令為
mov dword ptr [ebp-14h],0Bh
對(duì)應(yīng)如圖
此時(shí)eip寄存器中存放
mov dword ptr [ebp-20h],0
這條指令的地址。此時(shí),局部變量x和y已經(jīng)在main函數(shù)的棧幀中形成。
int z=0
這條語(yǔ)句轉(zhuǎn)化為匯編指令為
mov dword ptr [ebp-20h],0
對(duì)應(yīng)如圖
z=MyAdd(x,y)此時(shí)eip寄存器中存放
mov eax,dward ptr [ebp-14h]
這條指令的地址。可以看出,在棧上的變量地址不一定是連續(xù)的,可能是操作系統(tǒng)在設(shè)計(jì)的時(shí)候出于安全的考慮。
z=MyAdd(x,y)這條語(yǔ)句對(duì)應(yīng)多條匯編指令。
mov eax,dward ptr [ebp-14h]
對(duì)應(yīng)如圖
此時(shí)eip寄存器中存放
push eax
這條指令的地址
push eax
把eax的值壓入棧中,同時(shí)棧頂寄存器的值發(fā)生相應(yīng)變化。
對(duì)應(yīng)如圖
此時(shí)eip寄存器中存放
mov ecx,dward ptr [ebp-8]
這條指令的地址
mov ecx,dward ptr [ebp-8]
把ebp-8地址處的值拷貝一份到ecx通用寄存器中,實(shí)際上是將x的值拷貝到ecx中。
對(duì)應(yīng)如圖
此時(shí)eip寄存器中存放
push ecx
這條指令的地址
push ecx
把ecx的值壓入棧中,同時(shí)棧頂寄存器esp的值發(fā)生變化。
對(duì)應(yīng)如圖
此時(shí)eip寄存器中存放
call MyAdd
這條指令的地址.以上指令實(shí)際上是在進(jìn)行參數(shù)的拷貝,根據(jù)實(shí)參拷貝形參并把形參壓棧,此時(shí)MyAdd函數(shù)還沒(méi)有開(kāi)始正式調(diào)用,且通過(guò)觀察可以發(fā)現(xiàn),在進(jìn)行實(shí)參拷貝時(shí),先拷貝y,在拷貝x,說(shuō)明形參的實(shí)例化順序是從右到左的。
可以得出2點(diǎn)結(jié)論
- 形參實(shí)例化的順序是從右向左的,在傳參的過(guò)程中,是先實(shí)例化右邊的參數(shù),在實(shí)例化左邊的參數(shù)。形參實(shí)例化的順序與x,y定義的順序無(wú)關(guān)。
- 函數(shù)調(diào)用需要傳參形成臨時(shí)拷貝,臨時(shí)拷貝在函數(shù)正式調(diào)用之前已經(jīng)完成,是函數(shù)正式調(diào)用的前置工作。
call MyAdd
call MyAdd這一條匯編指令對(duì)應(yīng)2個(gè)操作:
壓入返回地址。MyAdd函數(shù)執(zhí)行結(jié)束以后需要返回到main函數(shù),返回地址就是返回到main函數(shù)棧幀的中對(duì)應(yīng)的地址,在轉(zhuǎn)移至目標(biāo)函數(shù)MyAdd之前,要把這個(gè)地址壓入棧中。否則MyAdd函數(shù)執(zhí)行完畢就無(wú)法回到main函數(shù)繼續(xù)執(zhí)行main函數(shù)后面的語(yǔ)句。在上面的代碼中,這個(gè)返回地址是
add esp 8
這條指令的地址,在MyAdd函數(shù)調(diào)用完畢會(huì)用到這個(gè)地址。
- 轉(zhuǎn)移至目標(biāo)函數(shù)。此時(shí)eip中保存的是
jump MyAdd
指令的地址
call MyAdd
指令的關(guān)鍵點(diǎn)是壓入返回地址,它確保了在函數(shù)調(diào)用完畢以后能正常的回到原來(lái)的函數(shù)的棧幀。
MyAdd函數(shù)調(diào)用jmp MyAdd
jmp MyAdd并非立馬跳到MyAdd函數(shù)開(kāi)始執(zhí)行MyAdd函數(shù)的語(yǔ)句,而是修改eip的值為MyAdd函數(shù)第一條匯編指令的地址。執(zhí)行jmp MyAdd之后,eip中保存的就是MyAdd函數(shù)第一條匯編指令的地址,至此,可以開(kāi)始正式調(diào)用MyAdd函數(shù)。
push ebp
把當(dāng)前棧底寄存器的值壓入棧中,注意:當(dāng)前棧底寄存器中保存的是main函數(shù)棧底的地址。
對(duì)應(yīng)如圖
此時(shí)eip寄存器中存放
mov ebp esp
這條指令的地址
mov ebp esp #是把esp的內(nèi)容拷貝到ebp
把esp的內(nèi)容拷貝到ebp中,這樣ebp就不在指向main函數(shù)的棧底。這條指令是把一個(gè)寄存器的內(nèi)容拷貝到另外一個(gè)寄存器,沒(méi)有進(jìn)行內(nèi)存的訪問(wèn),執(zhí)行速度很快。
對(duì)應(yīng)如圖
此時(shí)eip寄存器中存放
sub esp,0CCh
這條指令的地址
sub esp,0CCh
將當(dāng)前esp寄存器中的值減去一個(gè)值,讓esp和ebp維護(hù)一段??臻g,這一段??臻g就是MyAdd函數(shù)的棧幀。
對(duì)應(yīng)如圖
上面的匯編指令
sub esp,0CCh
是把esp的值減去0CCh,調(diào)用不同的函數(shù),這個(gè)值是不一樣的,并非都是減去0CCh,這個(gè)值取決于調(diào)用的函數(shù)的規(guī)模,例如該函數(shù)中定義了多少局部對(duì)象。編譯器是可以提前預(yù)知到函數(shù)的規(guī)模大小的,根據(jù)函數(shù)的規(guī)模,確定調(diào)用該函數(shù)需要多少的??臻g,編譯器可以通過(guò)sizeof
計(jì)算出函數(shù)中所有棧上變量所需空間的總和,從而決定應(yīng)該為該函數(shù)創(chuàng)建多大的棧幀。因此,sizeof
計(jì)算變量的大小是在編譯時(shí)完成的。此時(shí)eip寄存器中存放
mov dwrod ptr [ebp-8],0
這條指令的地址
int c=0
mov dwrod ptr [ebp-8],0
對(duì)應(yīng)如圖
此時(shí)eip寄存器中存放
mov eax,dword ptr [ebp+8]
這條指令的地址
c=a+b
這條語(yǔ)句轉(zhuǎn)化為匯編指令為
mov eax,dword ptr [ebp+8]
把ebp+8位置的值放到eax中,實(shí)際上就是把形參的值放到eax中
對(duì)應(yīng)如圖
此時(shí)eip寄存器中存放
add eax,dword ptr [ebp+0Ch]
這條指令的地址
add eax,dword ptr [ebp+0Ch]
把ebp+0Ch位置的值加到eax上,其實(shí)就是把形參b的值加到eax上,此時(shí)eax中存放的值就是a+b
對(duì)應(yīng)如圖
此時(shí)eip寄存器中存放
mov dword ptr [ebp-8],eax
這條指令的地址
mov dword ptr [ebp-8],eax
把eax的值給到ebp-8位置,就是把eax的值給到ebp-8
對(duì)應(yīng)如圖
至此,c=a+b計(jì)算完成。
此時(shí)eip寄存器中存放
mov eax,dword ptr [ebp-8]
這條指令的地址
mov eax,dword ptr [ebp-8]
即把c的值保存到寄存器中,這是在為函數(shù)返回做準(zhǔn)備。如果函數(shù)返回小對(duì)象,則是通過(guò)寄存器保存的。
對(duì)應(yīng)如圖
MyAdd函數(shù)棧幀銷毀至此MyAdd函數(shù)正式調(diào)用完畢,需要銷毀棧幀和返回main函數(shù)。
mov ebp esp
把ebp的值給esp。esp發(fā)生變化
對(duì)應(yīng)如圖
返回到main函數(shù)MyAdd函數(shù)的棧幀被釋放,但是MyAdd函數(shù)的棧幀數(shù)據(jù)并沒(méi)有被清空。注意此時(shí)ebp和esp指向位置存放的值是main函數(shù)的棧底。
此時(shí)eip寄存器中存放
pop ebp
這條指令的地址
pop ebp
把棧頂位置的數(shù)據(jù)(main函數(shù)棧底放入地址)給ebp寄存器,同時(shí)彈棧,esp發(fā)生改變。
對(duì)應(yīng)如圖
此時(shí)esp指向的棧頂位置的數(shù)據(jù)就是在
call MyAdd
時(shí)壓入的返回地址。此時(shí)eip寄存器中存放
ret
這條指令的地址
ret
ret是恢復(fù)返回地址的匯編指令,在這里ret的作用類似于pop eib,即把棧頂位置的數(shù)據(jù)放到eip中,此時(shí)棧頂位置的數(shù)據(jù)正是call MyAdd時(shí)壓入的返回地址。就是
add esp,8
這條指令的地址。對(duì)應(yīng)如圖
此時(shí)eip寄存器中存放
add esp,8
這條指令的地址
add esp,8
把esp的值+8
對(duì)應(yīng)如圖
這樣就正式的恢復(fù)到了MyAdd函數(shù)調(diào)用之前的樣子。
此時(shí)eip寄存器中存放
mov dword ptr [ebp-20h],eax
這條指令的地址
mov dword ptr [ebp-20h],eax
把eax的值給到ebp-20h的位置,eax中的值就是MyAdd函數(shù)的返回值。ebp-20h位置對(duì)應(yīng)z,就是把返回值給z.
這就是整個(gè)函數(shù)棧幀的創(chuàng)建和銷毀過(guò)程。
總結(jié)push ebp
你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機(jī)房具備T級(jí)流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級(jí)服務(wù)器適合批量采購(gòu),新人活動(dòng)首月15元起,快前往官網(wǎng)查看詳情吧