Assembly 調(diào)用約定

2018-10-28 14:42 更新

當(dāng)調(diào)用了一個(gè)子程序,調(diào)用的代碼和子程序(被調(diào)用的代碼)必須協(xié)商好在它們之間如何傳遞數(shù)據(jù)。高級(jí)語(yǔ)言有標(biāo)準(zhǔn)傳遞數(shù)據(jù)的方法稱(chēng)為調(diào)用約定。要讓高級(jí)語(yǔ)言接口于匯編語(yǔ)言,匯編語(yǔ)言代碼就一定要使用與高級(jí)語(yǔ)言一樣的約定。不同的編譯器有不同的調(diào)用約定或者說(shuō)不同的約定可能取決于代碼如何被編譯。(例如:是否進(jìn)行了優(yōu)化)。一個(gè)廣泛的約定是:使用一條CALL指令來(lái)調(diào)用代碼再通過(guò)RET指令返回。


所有PC的C編譯器支持的調(diào)用約定將在本章的后續(xù)部分階段進(jìn)行描述。這些約定允許你創(chuàng)建可重入的子程序。一個(gè)可重入的子程序可以在程序中的任意一點(diǎn)被安全調(diào)用(甚至在子程序內(nèi)部)。


在堆棧上傳遞參數(shù)

給子程序的參數(shù)需要在堆棧中傳遞。它們?cè)贑ALL指令之前就已經(jīng)被壓入棧中了。和在C中是一樣的,如果參數(shù)被子程序改變了,那么需要傳遞的是數(shù)據(jù)的地址,而不是值。如果參數(shù)的大小小于雙字,它就需要在壓入棧之
前轉(zhuǎn)換成雙字。

在堆棧里的參數(shù)并沒(méi)有由子程序彈出,取而代之的是:它們自己從堆棧中訪問(wèn)本身。為什么?

1、因?yàn)樗鼈冊(cè)贑ALL指令之前被壓入棧中,所以返回時(shí)首先彈出的是返回地址(然后修改堆棧指針使其指向參數(shù)入棧以前的值)。

2、參數(shù)往往將會(huì)使用在子程序中幾個(gè)的地方。通常在整個(gè)程序中,它們不可以保存在一個(gè)寄存器中,而應(yīng)該儲(chǔ)存在內(nèi)存中。把它們留在堆棧里就相當(dāng)于把數(shù)據(jù)復(fù)制到了內(nèi)存中,這樣就可以在子程序的任意一點(diǎn)訪問(wèn)數(shù)據(jù)。

考慮通過(guò)堆棧傳遞了一個(gè)參數(shù)的子程序。當(dāng)子程序被調(diào)用了,堆棧狀態(tài)如圖4.1。這個(gè)參數(shù)可以通過(guò)間接尋址訪問(wèn)到。([ESP+4])。

堆棧傳遞

如果在子程序內(nèi)部使用了堆棧儲(chǔ)存數(shù)據(jù),那么與ESP相加的數(shù)將要改變。例如:圖4.2展示了如果一個(gè)雙字壓入棧中后堆棧的狀態(tài)?,F(xiàn)在參數(shù)在ESP + 8中,而不在ESP + 4中。因此,引用參數(shù)時(shí)若使用ESP就很容易犯錯(cuò)了。為了解決這個(gè)問(wèn)題,80386提供使用另外一個(gè)寄存器:EBP。這個(gè)寄存器的唯一目的就是引用堆棧中的數(shù)據(jù)。C調(diào)用約定要求子程序首先把EBP的值保存到堆棧中,然后再使EBP的值等于ESP。當(dāng)數(shù)據(jù)壓入或彈出堆棧時(shí),這就允許ESP值被改變的同時(shí)EBP不會(huì)被改變。在子程序的結(jié)束處,EBP的原始值必須恢復(fù)出來(lái)(這就是為什么它在子程序的開(kāi)始處被保存的緣故。圖4.3展示了遵循這些約定的子程序的一般格式。

子程序的一般格式

圖4.3中的第2行和第3行組成了一個(gè)子程序的大體上的開(kāi)始部分。第5行和第6行組成了結(jié)束部分。圖4.4展示了剛執(zhí)行完開(kāi)始部分之后堆棧的狀態(tài)?,F(xiàn)在參數(shù)可以在子程序中的任何地方通過(guò)[EBP + 8]來(lái)訪問(wèn),而不用擔(dān)心在子程序中有什么數(shù)據(jù)壓入到堆棧中了。

執(zhí)行完子程序之后,壓入棧中的參數(shù)必須移除掉。C調(diào)用約定規(guī)定調(diào)用的代碼必須做這件事。其它約定可能不同。例如:Pascal 調(diào)用約定規(guī)定子程序必須移除參數(shù)。(RET指令的另一種格式可以很容易做這件事。)一些C編譯器同樣支持這種約定。關(guān)鍵字pascal用在函數(shù)的原型和定義中來(lái)告訴編譯器使用這種約定。事實(shí)上,MS Windows API的C函數(shù)使用的stdcall調(diào)用約定同樣以這種方式運(yùn)作。這種方式有什么優(yōu)點(diǎn)?它比C調(diào)用約定更有效一點(diǎn)。那為什么所有的C函數(shù)都使用C調(diào)用約定呢?一般說(shuō)來(lái),C允許一個(gè)函數(shù)的參數(shù)為變化的個(gè)數(shù)(例如:printf和scanf函數(shù))。對(duì)于這種類(lèi)型的函數(shù),將參數(shù)移除出棧的操作在這次函數(shù)調(diào)用中和下次函數(shù)調(diào)用中是不同的。C調(diào)用約定能使指令簡(jiǎn)單地執(zhí)行這種不同的操作。Pascal和stdcall調(diào)用約定執(zhí)行這種操作是非常困難的。因此,Pascal調(diào)用約定(和Pascal語(yǔ)言一樣)不允許有這種類(lèi)型的函數(shù)。MS Windows只有當(dāng)它的API函數(shù)不可以攜帶變化個(gè)數(shù)的參數(shù)時(shí)才可以使用這種約定。

子程序調(diào)用

圖4.5展示了一個(gè)將被調(diào)用的子程序如何使用C調(diào)用約定。第3行通過(guò)直接操作堆棧指針將參數(shù)移除出棧。同樣可以使用POP指令來(lái)做這件事,但是常常使用在要求將無(wú)用的結(jié)果儲(chǔ)存到一個(gè)寄存器的情況下。實(shí)際上,對(duì)于這種情況,許多編譯器常常使用一條POP ECX來(lái)移除參數(shù)。編譯器會(huì)使用POP指令來(lái)代替ADD指令,因?yàn)锳DD指令需要更多的字節(jié)。但是,POP會(huì)改變ECX的值。下面是一個(gè)有兩個(gè)子程序的例子,它們使用了上面討論的C調(diào)用約定。54行(和其它行)展示了多個(gè)數(shù)據(jù)和文本段可以在同一個(gè)源文件中聲明。進(jìn)行連接處理時(shí),它們將會(huì)組合成單一的數(shù)據(jù)段和文本段。把數(shù)據(jù)和文本段分成單獨(dú)的幾段就允許數(shù)據(jù)定義在子程序代碼附近,這也是子程序
經(jīng)常做的。

實(shí)例1
實(shí)例2
實(shí)例3

堆棧上的局部變量

堆??梢苑奖愕赜脕?lái)儲(chǔ)存局部變量。這實(shí)際上也是C儲(chǔ)存普通變量(或C lingo中的自動(dòng)變量)的地方。如果你希望子程序是可重入的,那么使用堆棧存儲(chǔ)變量是非常重要的。一個(gè)可重入的子程序不管在任何地方被調(diào)用都能正常運(yùn)行,包括子程序本身。換句話(huà)說(shuō),可重入子程序可以嵌套調(diào)用。儲(chǔ)存變量的堆棧同樣在內(nèi)存中。不儲(chǔ)存在堆棧里的數(shù)據(jù)從程序開(kāi)始到程序結(jié)束都使用內(nèi)存(C稱(chēng)這種類(lèi)型的變量為全局變量或靜態(tài)變量)。儲(chǔ)存在堆棧里的數(shù)據(jù)只有當(dāng)定義它們的子程序是活動(dòng)的時(shí)候才使用內(nèi)存。

在堆棧中,局部變量恰好儲(chǔ)存在保存的EBP值之后。它們通過(guò)在子程序的開(kāi)始部分用ESP減去一定的字節(jié)數(shù)來(lái)分配存儲(chǔ)空間。圖4.6展示了子程序新的骨架。EBP用來(lái)訪問(wèn)局部變量??紤]圖4.7中的C函數(shù)。圖4.8 展示如何用匯編語(yǔ)言編寫(xiě)等價(jià)的子程序。

圖4.9展示了執(zhí)行完圖4.8中程序的開(kāi)始部分后的堆棧狀態(tài)。這一節(jié)的堆棧包含了參數(shù),返回信息和局部變量,這樣堆棧稱(chēng)為一個(gè)堆棧幀。C函數(shù)的每一次調(diào)用都會(huì)在堆棧上創(chuàng)建一個(gè)新的堆棧幀。

局部變量

可以使用兩條專(zhuān)門(mén)的指令來(lái)簡(jiǎn)化一個(gè)子程序的開(kāi)始部分和結(jié)束部分,它們是為這個(gè)目的而專(zhuān)門(mén)設(shè)計(jì)的。ENTER指令執(zhí)行開(kāi)始部分的代碼,而LEAVE指令執(zhí)行結(jié)束部分。ENTER指令攜帶兩個(gè)立即數(shù)。對(duì)于C調(diào)用約定, 第二個(gè)操作數(shù)總是為0。第一個(gè)操作數(shù)是局部變量所需要的字節(jié)數(shù)。LEAVE指令沒(méi)有操作數(shù)。圖4.10展示了如何使用這些指令。注意程序skeleton同樣使用了ENTER和LEAVE指令。

求總數(shù)

指令
以上內(nèi)容是否對(duì)您有幫助:
在線(xiàn)筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)