国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開(kāi)通VIP
C語(yǔ)言函數(shù)調(diào)用棧(一)
程序的執(zhí)行過(guò)程可看作連續(xù)的函數(shù)調(diào)用。當(dāng)一個(gè)函數(shù)執(zhí)行完畢時(shí),程序要回到調(diào)用指令的下一條指令(緊接call指令)處繼續(xù)執(zhí)行。函數(shù)調(diào)用過(guò)程通常使用堆棧實(shí)現(xiàn),每個(gè)用戶態(tài)進(jìn)程對(duì)應(yīng)一個(gè)調(diào)用棧結(jié)構(gòu)(call stack)。編譯器使用堆棧傳遞函數(shù)參數(shù)、保存返回地址、臨時(shí)保存寄存器原有值(即函數(shù)調(diào)用的上下文)以備恢復(fù)以及存儲(chǔ)本地局部變量。
不同處理器和編譯器的堆棧布局、函數(shù)調(diào)用方法都可能不同,但堆棧的基本概念是一樣的。
1 寄存器分配
寄存器是處理器加工數(shù)據(jù)或運(yùn)行程序的重要載體,用于存放程序執(zhí)行中用到的數(shù)據(jù)和指令。因此函數(shù)調(diào)用棧的實(shí)現(xiàn)與處理器寄存器組密切相關(guān)。
Intel 32位體系結(jié)構(gòu)(簡(jiǎn)稱IA32)處理器包含8個(gè)四字節(jié)寄存器,如下圖所示:
圖1 IA32處理器寄存器
最初的8086中寄存器是16位,每個(gè)都有特殊用途,寄存器名城反映其不同用途。由于IA32平臺(tái)采用平面尋址模式,對(duì)特殊寄存器的需求大大降低,但由于歷史原因,這些寄存器名稱被保留下來(lái)。在大多數(shù)情況下,上圖所示的前6個(gè)寄存器均可作為通用寄存器使用。某些指令可能以固定的寄存器作為源寄存器或目的寄存器,如一些特殊的算術(shù)操作指令imull/mull/cltd/idivl/divl要求一個(gè)參數(shù)必須在%eax中,其運(yùn)算結(jié)果存放在%edx(higher 32-bit)和%eax (lower32-bit)中;又如函數(shù)返回值通常保存在%eax中,等等。為避免兼容性問(wèn)題,ABI規(guī)范對(duì)這組通用寄存器的具體作用加以定義(如圖中所示)。
對(duì)于寄存器%eax、%ebx、%ecx和%edx,各自可作為兩個(gè)獨(dú)立的16位寄存器使用,而低16位寄存器還可繼續(xù)分為兩個(gè)獨(dú)立的8位寄存器使用。編譯器會(huì)根據(jù)操作數(shù)大小選擇合適的寄存器來(lái)生成匯編代碼。在匯編語(yǔ)言層面,這組通用寄存器以%e(AT&T語(yǔ)法)或直接以e(Intel語(yǔ)法)開(kāi)頭來(lái)引用,例如mov $5, %eax或mov eax, 5表示將立即數(shù)5賦值給寄存器%eax。
在x86處理器中,EIP(Instruction Pointer)是指令寄存器,指向處理器下條等待執(zhí)行的指令地址(代碼段內(nèi)的偏移量),每次執(zhí)行完相應(yīng)匯編指令EIP值就會(huì)增加。ESP(Stack Pointer)是堆棧指針寄存器,存放執(zhí)行函數(shù)對(duì)應(yīng)棧幀的棧頂?shù)刂?也是系統(tǒng)棧的頂部),且始終指向棧頂;EBP(Base Pointer)是棧幀基址指針寄存器,存放執(zhí)行函數(shù)對(duì)應(yīng)棧幀的棧底地址,用于C運(yùn)行庫(kù)訪問(wèn)棧中的局部變量和參數(shù)。
注意,EIP是個(gè)特殊寄存器,不能像訪問(wèn)通用寄存器那樣訪問(wèn)它,即找不到可用來(lái)尋址EIP并對(duì)其進(jìn)行讀寫的操作碼(OpCode)。EIP可被jmp、call和ret等指令隱含地改變(事實(shí)上它一直都在改變)。
不同架構(gòu)的CPU,寄存器名稱被添加不同前綴以指示寄存器的大小。例如x86架構(gòu)用字母“e(extended)”作名稱前綴,指示寄存器大小為32位;x86_64架構(gòu)用字母“r”作名稱前綴,指示各寄存器大小為64位。
編譯器在將C程序編譯成匯編程序時(shí),應(yīng)遵循ABI所規(guī)定的寄存器功能定義。同樣地,編寫匯編程序時(shí)也應(yīng)遵循,否則所編寫的匯編程序可能無(wú)法與C程序協(xié)同工作。
【擴(kuò)展閱讀】棧幀指針寄存器
為了訪問(wèn)函數(shù)局部變量,必須能定位每個(gè)變量。局部變量相對(duì)于堆棧指針ESP的位置在進(jìn)入函數(shù)時(shí)就已確定,理論上變量可用ESP加偏移量來(lái)引用,但ESP會(huì)在函數(shù)執(zhí)行期隨變量的壓棧和出棧而變動(dòng)。盡管某些情況下編譯器能跟蹤棧中的變量操作以修正偏移量,但要引入可觀的管理開(kāi)銷。而且在有些機(jī)器上(如Intel處理器),用ESP加偏移量來(lái)訪問(wèn)一個(gè)變量需要多條指令才能實(shí)現(xiàn)。
因此,許多編譯器使用幀指針寄存器FP(Frame Pointer)記錄棧幀基地址。局部變量和函數(shù)參數(shù)都可通過(guò)幀指針引用,因?yàn)樗鼈兊紽P的距離不會(huì)受到壓棧和出棧操作的影響。有些資料將幀指針?lè)Q作局部基指針(LB-local base pointer)。
在Intel CPU中,寄存器BP(EBP)用作幀指針。在Motorola CPU中,除A7(堆棧指針SP)外的任何地址寄存器都可用作FP。當(dāng)堆棧向下(低地址)增長(zhǎng)時(shí),以FP地址為基準(zhǔn),函數(shù)參數(shù)的偏移量是正值,而局部變量的偏移量是負(fù)值。
2 寄存器使用約定
程序寄存器組是唯一能被所有函數(shù)共享的資源。雖然某一時(shí)刻只有一個(gè)函數(shù)在執(zhí)行,但需保證當(dāng)某個(gè)函數(shù)調(diào)用其他函數(shù)時(shí),被調(diào)函數(shù)不會(huì)修改或覆蓋主調(diào)函數(shù)稍后會(huì)使用到的寄存器值。因此,IA32采用一套統(tǒng)一的寄存器使用約定,所有函數(shù)(包括庫(kù)函數(shù))調(diào)用都必須遵守該約定。
根據(jù)慣例,寄存器%eax、%edx和%ecx為主調(diào)函數(shù)保存寄存器(caller-saved registers),當(dāng)函數(shù)調(diào)用時(shí),若主調(diào)函數(shù)希望保持這些寄存器的值,則必須在調(diào)用前顯式地將其保存在棧中;被調(diào)函數(shù)可以覆蓋這些寄存器,而不會(huì)破壞主調(diào)函數(shù)所需的數(shù)據(jù)。寄存器%ebx、%esi和%edi為被調(diào)函數(shù)保存寄存器(callee-saved registers),即被調(diào)函數(shù)在覆蓋這些寄存器的值時(shí),必須先將寄存器原值壓入棧中保存起來(lái),并在函數(shù)返回前從棧中恢復(fù)其原值,因?yàn)橹髡{(diào)函數(shù)可能也在使用這些寄存器。此外,被調(diào)函數(shù)必須保持寄存器%ebp和%esp,并在函數(shù)返回后將其恢復(fù)到調(diào)用前的值,亦即必須恢復(fù)主調(diào)函數(shù)的棧幀。
當(dāng)然,這些工作都由編譯器在幕后進(jìn)行。不過(guò)在編寫匯編程序時(shí)應(yīng)注意遵守上述慣例。
3 棧幀結(jié)構(gòu)
函數(shù)調(diào)用經(jīng)常是嵌套的,在同一時(shí)刻,堆棧中會(huì)有多個(gè)函數(shù)的信息。每個(gè)未完成運(yùn)行的函數(shù)占用一個(gè)獨(dú)立的連續(xù)區(qū)域,稱作棧幀(Stack Frame)。棧幀是堆棧的邏輯片段,當(dāng)調(diào)用函數(shù)時(shí)邏輯棧幀被壓入堆棧, 當(dāng)函數(shù)返回時(shí)邏輯棧幀被從堆棧中彈出。棧幀存放著函數(shù)參數(shù),局部變量及恢復(fù)前一棧幀所需要的數(shù)據(jù)等。
編譯器利用棧幀,使得函數(shù)參數(shù)和函數(shù)中局部變量的分配與釋放對(duì)程序員透明。編譯器將控制權(quán)移交函數(shù)本身之前,插入特定代碼將函數(shù)參數(shù)壓入棧幀中,并分配足夠的內(nèi)存空間用于存放函數(shù)中的局部變量。使用棧幀的一個(gè)好處是使得遞歸變?yōu)榭赡埽驗(yàn)閷?duì)函數(shù)的每次遞歸調(diào)用,都會(huì)分配給該函數(shù)一個(gè)新的棧幀,這樣就巧妙地隔離當(dāng)前調(diào)用與上次調(diào)用。
棧幀的邊界由棧幀基地址指針EBP和堆棧指針ESP界定(指針存放在相應(yīng)寄存器中)。EBP指向當(dāng)前棧幀底部(高地址),在當(dāng)前棧幀內(nèi)位置固定;ESP指向當(dāng)前棧幀頂部(低地址),當(dāng)程序執(zhí)行時(shí)ESP會(huì)隨著數(shù)據(jù)的入棧和出棧而移動(dòng)。因此函數(shù)中對(duì)大部分?jǐn)?shù)據(jù)的訪問(wèn)都基于EBP進(jìn)行。
為更具描述性,以下稱EBP為幀基指針, ESP為棧頂指針,并在引用匯編代碼時(shí)分別記為%ebp和%esp。
函數(shù)調(diào)用棧的典型內(nèi)存布局如下圖所示:
圖2 函數(shù)調(diào)用棧的典型內(nèi)存布局
圖中給出主調(diào)函數(shù)(caller)和被調(diào)函數(shù)(callee)的棧幀布局,"m(%ebp)"表示以EBP為基地址、偏移量為m字節(jié)的內(nèi)存空間(中的內(nèi)容)。該圖基于兩個(gè)假設(shè):第一,函數(shù)返回值不是結(jié)構(gòu)體或聯(lián)合體,否則第一個(gè)參數(shù)將位于"12(%ebp)" 處;第二,每個(gè)參數(shù)都是4字節(jié)大小(棧的粒度為4字節(jié))。在本文后續(xù)章節(jié)將就參數(shù)的傳遞和大小問(wèn)題做進(jìn)一步的探討。 此外,函數(shù)可以沒(méi)有參數(shù)和局部變量,故圖中“Argument(參數(shù))”和“Local Variable(局部變量)”不是函數(shù)棧幀結(jié)構(gòu)的必需部分。
從圖中可以看出,函數(shù)調(diào)用時(shí)入棧順序?yàn)?
實(shí)參N~1→主調(diào)函數(shù)返回地址→主調(diào)函數(shù)幀基指針EBP→被調(diào)函數(shù)局部變量1~N
其中,主調(diào)函數(shù)將參數(shù)按照調(diào)用約定依次入棧(圖中為從右到左),然后將指令指針EIP入棧以保存主調(diào)函數(shù)的返回地址(下一條待執(zhí)行指令的地址)。進(jìn)入被調(diào)函數(shù)時(shí),被調(diào)函數(shù)將主調(diào)函數(shù)的幀基指針EBP入棧,并將主調(diào)函數(shù)的棧頂指針ESP值賦給被調(diào)函數(shù)的EBP(作為被調(diào)函數(shù)的棧底),接著改變ESP值來(lái)為函數(shù)局部變量預(yù)留空間。此時(shí)被調(diào)函數(shù)幀基指針指向被調(diào)函數(shù)的棧底。以該地址為基準(zhǔn),向上(棧底方向)可獲取主調(diào)函數(shù)的返回地址、參數(shù)值,向下(棧頂方向)能獲取被調(diào)函數(shù)的局部變量值,而該地址處又存放著上一層主調(diào)函數(shù)的幀基指針值。本級(jí)調(diào)用結(jié)束后,將EBP指針值賦給ESP,使ESP再次指向被調(diào)函數(shù)棧底以釋放局部變量;再將已壓棧的主調(diào)函數(shù)幀基指針彈出到EBP,并彈出返回地址到EIP。ESP繼續(xù)上移越過(guò)參數(shù),最終回到函數(shù)調(diào)用前的狀態(tài),即恢復(fù)原來(lái)主調(diào)函數(shù)的棧幀。如此遞歸便形成函數(shù)調(diào)用棧。
EBP指針在當(dāng)前函數(shù)運(yùn)行過(guò)程中(未調(diào)用其他函數(shù)時(shí))保持不變。在函數(shù)調(diào)用前,ESP指針指向棧頂?shù)刂?,也是棧底地址。在函?shù)完成現(xiàn)場(chǎng)保護(hù)之類的初始化工作后,ESP會(huì)始終指向當(dāng)前函數(shù)棧幀的棧頂,此時(shí),若當(dāng)前函數(shù)又調(diào)用另一個(gè)函數(shù),則會(huì)將此時(shí)的EBP視為舊EBP壓棧,而與新調(diào)用函數(shù)有關(guān)的內(nèi)容會(huì)從當(dāng)前ESP所指向位置開(kāi)始?jí)簵!?div style="height:15px;">
若需在函數(shù)中保存被調(diào)函數(shù)保存寄存器(如ESI、EDI),則編譯器在保存EBP值時(shí)進(jìn)行保存,或延遲保存直到局部變量空間被分配。在棧幀中并未為被調(diào)函數(shù)保存寄存器的空間指定標(biāo)準(zhǔn)的存儲(chǔ)位置。包含寄存器和臨時(shí)變量的函數(shù)調(diào)用棧布局可能如下圖所示:
圖3 函數(shù)調(diào)用棧的可能內(nèi)存布局
在多線程(任務(wù))環(huán)境,棧頂指針指向的存儲(chǔ)器區(qū)域就是當(dāng)前使用的堆棧。切換線程的一個(gè)重要工作,就是將棧頂指針設(shè)為當(dāng)前線程的堆棧棧頂?shù)刂贰?div style="height:15px;">
以下代碼用于函數(shù)棧布局示例:
//StackFrame.c#include #include struct Strt{ int member1; int member2; int member3;};#define PRINT_ADDR(x) printf("&"#x" = %p\n", &x)int StackFrameContent(int para1, int para2, int para3){ int locVar1 = 1; int locVar2 = 2; int locVar3 = 3; int arr[] = {0x11,0x22,0x33}; struct Strt tStrt = {0}; PRINT_ADDR(para1); //若para1為char或short型,則打印para1所對(duì)應(yīng)的棧上整型臨時(shí)變量地址! PRINT_ADDR(para2); PRINT_ADDR(para3); PRINT_ADDR(locVar1); PRINT_ADDR(locVar2); PRINT_ADDR(locVar3); PRINT_ADDR(arr); PRINT_ADDR(arr[0]); PRINT_ADDR(arr[1]); PRINT_ADDR(arr[2]); PRINT_ADDR(tStrt); PRINT_ADDR(tStrt.member1); PRINT_ADDR(tStrt.member2); PRINT_ADDR(tStrt.member3); return 0;}int main(void){ int locMain1 = 1, locMain2 = 2, locMain3 = 3; PRINT_ADDR(locMain1); PRINT_ADDR(locMain2); PRINT_ADDR(locMain3); StackFrameContent(locMain1, locMain2, locMain3); printf("[locMain1,2,3] = [%d, %d, %d]\n", locMain1, locMain2, locMain3); memset(&locMain2, 0, 2*sizeof(int)); printf("[locMain1,2,3] = [%d, %d, %d]\n", locMain1, locMain2, locMain3); return 0;}StackFrame
編譯鏈接并執(zhí)行后,輸出打印如下:
圖4 StackFrame輸出
函數(shù)棧布局示例如下圖所示。為直觀起見(jiàn),低于起始高地址0xbfc75a58的其他地址采用點(diǎn)記法,如0x.54表示0xbfc75a54,以此類推。
圖5 StackFrame棧幀
內(nèi)存地址從棧底到棧頂遞減,壓棧就是把ESP指針逐漸往地低址移動(dòng)的過(guò)程。而結(jié)構(gòu)體tStrt中的成員變量memberX地址=tStrt首地址+(memberX偏移量),即越靠近tStrt首地址的成員變量其內(nèi)存地址越小。因此,結(jié)構(gòu)體成員變量的入棧順序與其在結(jié)構(gòu)體中聲明的順序相反。
函數(shù)調(diào)用以值傳遞時(shí),傳入的實(shí)參(locMain1~3)與被調(diào)函數(shù)內(nèi)操作的形參(para1~3)兩者存儲(chǔ)地址不同,因此被調(diào)函數(shù)無(wú)法直接修改主調(diào)函數(shù)實(shí)參值(對(duì)形參的操作相當(dāng)于修改實(shí)參的副本)。為達(dá)到修改目的,需要向被調(diào)函數(shù)傳遞實(shí)參變量的指針(即變量的地址)。
此外,"[locMain1,2,3] = [0, 0, 3]"是因?yàn)閷?duì)四字節(jié)參數(shù)locMain2調(diào)用memset函數(shù)時(shí),會(huì)從低地址向高地址連續(xù)清零8個(gè)字節(jié),從而誤將位于高地址locMain1清零。
注意,局部變量的布局依賴于編譯器實(shí)現(xiàn)等因素。因此,當(dāng)StackFrameContent函數(shù)中刪除打印語(yǔ)句時(shí),變量locVar3、locVar2和locVar1可能按照從高到低的順序依次存儲(chǔ)!而且,局部變量并不總在棧中,有時(shí)出于性能(速度)考慮會(huì)存放在寄存器中。數(shù)組/結(jié)構(gòu)體型的局部變量通常分配在棧內(nèi)存中。
【擴(kuò)展閱讀】函數(shù)局部變量布局方式
與函數(shù)調(diào)用約定規(guī)定參數(shù)如何傳入不同,局部變量以何種方式布局并未規(guī)定。編譯器計(jì)算函數(shù)局部變量所需要的空間總數(shù),并確定這些變量存儲(chǔ)在寄存器上還是分配在程序棧上(甚至被優(yōu)化掉)——某些處理器并沒(méi)有堆棧。局部變量的空間分配與主調(diào)函數(shù)和被調(diào)函數(shù)無(wú)關(guān),僅僅從函數(shù)源代碼上無(wú)法確定該函數(shù)的局部變量分布情況。
基于不同的編譯器版本(gcc3.4中局部變量按照定義順序依次入棧,gcc4及以上版本則不定)、優(yōu)化級(jí)別、目標(biāo)處理器架構(gòu)、棧安全性等,相鄰定義的兩個(gè)變量在內(nèi)存位置上可能相鄰,也可能不相鄰,前后關(guān)系也不固定。若要確保兩個(gè)對(duì)象在內(nèi)存上相鄰且前后關(guān)系固定,可使用結(jié)構(gòu)體或數(shù)組定義。
4 堆棧操作
函數(shù)調(diào)用時(shí)的具體步驟如下:
1) 主調(diào)函數(shù)將被調(diào)函數(shù)所要求的參數(shù),根據(jù)相應(yīng)的函數(shù)調(diào)用約定,保存在運(yùn)行時(shí)棧中。該操作會(huì)改變程序的棧指針。
注:x86平臺(tái)將參數(shù)壓入調(diào)用棧中。而x86_64平臺(tái)具有16個(gè)通用64位寄存器,故調(diào)用函數(shù)時(shí)前6個(gè)參數(shù)通常由寄存器傳遞,其余參數(shù)才通過(guò)棧傳遞。
2) 主調(diào)函數(shù)將控制權(quán)移交給被調(diào)函數(shù)(使用call指令)。函數(shù)的返回地址(待執(zhí)行的下條指令地址)保存在程序棧中(壓棧操作隱含在call指令中)。
3) 若有必要,被調(diào)函數(shù)會(huì)設(shè)置幀基指針,并保存被調(diào)函數(shù)希望保持不變的寄存器值。
4) 被調(diào)函數(shù)通過(guò)修改棧頂指針的值,為自己的局部變量在運(yùn)行時(shí)棧中分配內(nèi)存空間,并從幀基指針的位置處向低地址方向存放被調(diào)函數(shù)的局部變量和臨時(shí)變量。
5) 被調(diào)函數(shù)執(zhí)行自己任務(wù),此時(shí)可能需要訪問(wèn)由主調(diào)函數(shù)傳入的參數(shù)。若被調(diào)函數(shù)返回一個(gè)值,該值通常保存在一個(gè)指定寄存器中(如EAX)。
6) 一旦被調(diào)函數(shù)完成操作,為該函數(shù)局部變量分配的??臻g將被釋放。這通常是步驟4的逆向執(zhí)行。
7) 恢復(fù)步驟3中保存的寄存器值,包含主調(diào)函數(shù)的幀基指針寄存器。
8) 被調(diào)函數(shù)將控制權(quán)交還主調(diào)函數(shù)(使用ret指令)。根據(jù)使用的函數(shù)調(diào)用約定,該操作也可能從程序棧上清除先前傳入的參數(shù)。
9) 主調(diào)函數(shù)再次獲得控制權(quán)后,可能需要將先前的參數(shù)從棧上清除。在這種情況下,對(duì)棧的修改需要將幀基指針值恢復(fù)到步驟1之前的值。
步驟3與步驟4在函數(shù)調(diào)用之初常一同出現(xiàn),統(tǒng)稱為函數(shù)序(prologue);步驟6到步驟8在函數(shù)調(diào)用的最后常一同出現(xiàn),統(tǒng)稱為函數(shù)跋(epilogue)。函數(shù)序和函數(shù)跋是編譯器自動(dòng)添加的開(kāi)始和結(jié)束匯編代碼,其實(shí)現(xiàn)與CPU架構(gòu)和編譯器相關(guān)。除步驟5代表函數(shù)實(shí)體外,其它所有操作組成函數(shù)調(diào)用。
以下介紹函數(shù)調(diào)用過(guò)程中的主要指令。
壓棧(push):棧頂指針ESP減小4個(gè)字節(jié);以字節(jié)為單位將寄存器數(shù)據(jù)(四字節(jié),不足補(bǔ)零)壓入堆棧,從高到低按字節(jié)依次將數(shù)據(jù)存入ESP-1、ESP-2、ESP-3、ESP-4指向的地址單元。
出棧(pop):棧頂指針ESP指向的棧中數(shù)據(jù)被取回到寄存器;棧頂指針ESP增加4個(gè)字節(jié)。
圖6 出棧入棧操作示意
可見(jiàn),壓棧操作將寄存器內(nèi)容存入棧內(nèi)存中(寄存器原內(nèi)容不變),棧頂?shù)刂窚p??;出棧操作從棧內(nèi)存中取回寄存器內(nèi)容(棧內(nèi)已存數(shù)據(jù)不會(huì)自動(dòng)清零),棧頂?shù)刂吩龃蟆m斨羔楨SP總是指向棧中下一個(gè)可用數(shù)據(jù)。
調(diào)用(call):將當(dāng)前的指令指針EIP(該指針指向緊接在call指令后的下條指令)壓入堆棧,以備返回時(shí)能恢復(fù)執(zhí)行下條指令;然后設(shè)置EIP指向被調(diào)函數(shù)代碼開(kāi)始處,以跳轉(zhuǎn)到被調(diào)函數(shù)的入口地址執(zhí)行。
離開(kāi)(leave): 恢復(fù)主調(diào)函數(shù)的棧幀以準(zhǔn)備返回。等價(jià)于指令序列movl %ebp, %esp(恢復(fù)原ESP值,指向被調(diào)函數(shù)棧幀開(kāi)始處)和popl %ebp(恢復(fù)原ebp的值,即主調(diào)函數(shù)幀基指針)。
返回(ret):與call指令配合,用于從函數(shù)或過(guò)程返回。從棧頂彈出返回地址(之前call指令保存的下條指令地址)到EIP寄存器中,程序轉(zhuǎn)到該地址處繼續(xù)執(zhí)行(此時(shí)ESP指向進(jìn)入函數(shù)時(shí)的第一個(gè)參數(shù))。若帶立即數(shù),ESP再加立即數(shù)(丟棄一些在執(zhí)行call前入棧的參數(shù))。使用該指令前,應(yīng)使當(dāng)前棧頂指針?biāo)赶蛭恢玫膬?nèi)容正好是先前call指令保存的返回地址。
基于以上指令,使用C調(diào)用約定的被調(diào)函數(shù)典型的函數(shù)序和函數(shù)跋實(shí)現(xiàn)如下:
若主調(diào)函數(shù)和調(diào)函數(shù)均未使用局部變量寄存器EDI、ESI和EBX,則編譯器無(wú)須在函數(shù)序中對(duì)其壓棧,以便提高程序的執(zhí)行效率。
參數(shù)壓棧指令因編譯器而異,如下兩種壓棧方式基本等效:
兩種壓棧方式均遵循C調(diào)用約定,但方式二中主調(diào)函數(shù)在調(diào)用返回后并未顯式清理堆??臻g。因?yàn)樵诒徽{(diào)函數(shù)序階段,編譯器在棧頂為函數(shù)參數(shù)預(yù)先分配內(nèi)存空間(sub指令)。函數(shù)參數(shù)被復(fù)制到棧中(而非壓入棧中),并未修改棧頂指針,故調(diào)用返回時(shí)主調(diào)函數(shù)也無(wú)需修改棧頂指針。gcc3.4(或更高版本)編譯器采用該技術(shù)將函數(shù)參數(shù)傳遞至棧上,相比棧頂指針隨每次參數(shù)壓棧而多次下移,一次性設(shè)置好棧頂指針更為高效。設(shè)想連續(xù)調(diào)用多個(gè)函數(shù)時(shí),方式二僅需預(yù)先分配一次參數(shù)內(nèi)存(大小足夠容納參數(shù)尺寸和最大的函數(shù)即可),后續(xù)調(diào)用無(wú)需每次都恢復(fù)棧頂指針。注意,函數(shù)被調(diào)用時(shí),兩種方式均使棧頂指針指向函數(shù)最左邊的參數(shù)。本文不再區(qū)分兩種壓棧方式,"壓棧"或"入棧"所提之處均按相應(yīng)匯編代碼理解,若無(wú)匯編則指方式二。
某些情況下,編譯器生成的函數(shù)調(diào)用進(jìn)入/退出指令序列并不按照以上方式進(jìn)行。例如,若C函數(shù)聲明為static(只在本編譯單元內(nèi)可見(jiàn))且函數(shù)在編譯單元內(nèi)被直接調(diào)用,未被顯示或隱式取地址(即沒(méi)有任何函數(shù)指針指向該函數(shù)),此時(shí)編譯器確信該函數(shù)不會(huì)被其它編譯單元調(diào)用,因此可隨意修改其進(jìn)/出指令序列以達(dá)到優(yōu)化目的。
盡管使用的寄存器名字和指令在不同處理器架構(gòu)上有所不同,但創(chuàng)建棧幀的基本過(guò)程一致。
注意,棧幀是運(yùn)行時(shí)概念,若程序不運(yùn)行,就不存在棧和棧幀。但通過(guò)分析目標(biāo)文件中建立函數(shù)棧幀的匯編代碼(尤其是函數(shù)序和函數(shù)跋過(guò)程),即使函數(shù)沒(méi)有運(yùn)行,也能了解函數(shù)的棧幀結(jié)構(gòu)。通過(guò)分析可確定分配在函數(shù)棧幀上的局部變量空間準(zhǔn)確值,函數(shù)中是否使用幀基指針,以及識(shí)別函數(shù)棧幀中對(duì)變量的所有內(nèi)存引用。
本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開(kāi)APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
[C/C++] 函數(shù)調(diào)用的棧分配
通過(guò)一段匯編,加深對(duì)寄存器ESP和EBP的理解
利用反匯編手段解析C語(yǔ)言函數(shù)
你了解你所運(yùn)行的程序的楨棧結(jié)構(gòu)以及什么是緩沖區(qū)嗎? - C/C++ - 課堂 - 話題 -...
C語(yǔ)言過(guò)程(函數(shù))的機(jī)器級(jí)表示
棧幀&溢出
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服