call和ret指令
call和ret指令都是轉(zhuǎn)移指令,它們都修改IP,或同時(shí)修改CS和IP。
它們經(jīng)常被共同用來(lái)實(shí)現(xiàn)子程序的設(shè)計(jì)。
ret和retf
ret指令用棧中的數(shù)據(jù),修改IP的內(nèi)容,從而實(shí)現(xiàn)近轉(zhuǎn)移;
retf指令用棧中的數(shù)據(jù),修改CS和IP的內(nèi)容,從而實(shí)現(xiàn)遠(yuǎn)轉(zhuǎn)移。
CPU執(zhí)行ret指令時(shí),進(jìn)行下面的兩步操作:
(1)(IP) = ((ss)*16 +(sp))
(2)(sp) = (sp)+2
CPU執(zhí)行retf指令時(shí),進(jìn)行下面四步操作:
(1)(IP) = ((ss)*16) + (sp)
(2)(sp) = (sp) + 2
(3)(CS) = ((ss)*16) + (sp)
(4)(sp) = (sp) + 2
用匯編語(yǔ)法來(lái)解釋ret和retf指令,則:
CPU執(zhí)行ret指令時(shí),相當(dāng)于進(jìn)行:
pop IP
CPU執(zhí)行retf指令時(shí),相當(dāng)于進(jìn)行:
pop IP
pop CS
call指令
CPU執(zhí)行call指令時(shí),進(jìn)行兩步操作:
(1) 將當(dāng)前的IP或CS和IP壓入棧中;
(2) 轉(zhuǎn)移。
call指令不能實(shí)現(xiàn)短轉(zhuǎn)移,除此之外,call指令實(shí)現(xiàn)轉(zhuǎn)移的方法和jmp指令的原理相同。
依據(jù)位移進(jìn)行轉(zhuǎn)移的call指令
call 標(biāo)號(hào)(將當(dāng)前的IP壓棧后,轉(zhuǎn)到標(biāo)號(hào)處執(zhí)行指令)
CPU執(zhí)行此種格式的call指令時(shí),進(jìn)行如下的操作:
(1)(sp) = (sp)-2
((ss)*16 +(sp)) = (IP)
(2)(IP) = (IP)+16位位移。
16位位移=“標(biāo)號(hào)”處的地址-call指令后的第一個(gè)字節(jié)的地址;
16位位移的范圍為-32768~32767,用補(bǔ)碼表示;
16位位移由編譯程序在編譯時(shí)算出。
用匯編語(yǔ)法來(lái)解釋此種格式的call指令,則:
CPU執(zhí)行指令“call 標(biāo)號(hào)”時(shí),相當(dāng)于進(jìn)行:
push IP
jmp near ptr 標(biāo)號(hào)
轉(zhuǎn)移的目的地址在指令中的call指令
前面講的call指令,其對(duì)應(yīng)的機(jī)器指令中并沒(méi)有轉(zhuǎn)移的目的地址,而是相對(duì)于當(dāng)前IP的轉(zhuǎn)移位移。
指令“call far ptr 標(biāo)號(hào)”實(shí)現(xiàn)的是段間轉(zhuǎn)移。
CPU執(zhí)行此格式的call指令時(shí),進(jìn)行如下的操作:
(1)(sp)=(sp)-2
((ss)*16+(sp)) = (CS)
(sp)=(sp)-2
((ss)*16+(sp)) = (IP)
(2)(CS)=標(biāo)號(hào)所在段的段地址
(IP)=標(biāo)號(hào)在段中的偏移地址
用匯編語(yǔ)法來(lái)解釋此種格式的call指令,則:
CPU執(zhí)行指令“call far ptr 標(biāo)號(hào)”時(shí),相當(dāng)于進(jìn)行:
push CS
push IP
jmp far ptr 標(biāo)號(hào)
轉(zhuǎn)移地址在寄存器中的call指令
指令格式:call 16位寄存器
功能:
(sp) = (sp)-2
((ss)*16+(sp)) = (IP)
(IP) = (16位寄存器)
用匯編語(yǔ)法來(lái)解釋此種格式的call指令,CPU執(zhí)行call 16位reg時(shí),相當(dāng)于進(jìn)行:
push IP
jum 16位寄存器
轉(zhuǎn)移地址在內(nèi)存中的call指令
有兩種格式:
1) call word ptr 內(nèi)存單元地址
相當(dāng)于:
push IP
jum word ptr 內(nèi)存單元地址
2) call dword ptr 內(nèi)存單元地址
相當(dāng)于:
push CS
push IP
jmp dword ptr 內(nèi)存單元地址
call和ret的配合使用
如何將它們配合使用來(lái)實(shí)現(xiàn)子程序的機(jī)制。
子程序的框架如下:
標(biāo)號(hào):
指令
ret
具有子程序的源程序的框架如下:
assume cs:code
code segment
main: … ;主程序
…
call sub1 ;調(diào)用子程序sub1
…
mov ax,4c00h
int 21h
sub1: …. ;子程序sub1開(kāi)始
…
call sub2 ;調(diào)用子程序sub2
…
ret ;子程序返回
sub2: …. ;子程序sub2開(kāi)始
…
ret ;子程序返回
code ends
end maint
mul指令
mul是乘法指令。
使用mul做乘法的時(shí)候:
1) 兩個(gè)相乘的數(shù):兩個(gè)相乘的數(shù),要么都是8位,要么都是16位。
如果是8位,一個(gè)默認(rèn)放在AL中,別一個(gè)放在8位寄存器或內(nèi)存單元中;
如果是16位,一個(gè)默認(rèn)在AX中,另一個(gè)放在16位寄存器或內(nèi)存單元中。
2) 結(jié)果:如果是8位乘法,結(jié)果默認(rèn)放在AX中;如果是16位乘法,結(jié)果高位默認(rèn)在DX中存放,低位在AX中存放。
格式如下:
mul reg
mul 內(nèi)存單元
模塊化程序設(shè)計(jì)
call與ret指令共同支持了匯編語(yǔ)言編程中的模塊化設(shè)計(jì)。
在實(shí)際編程中,程序的模塊化是必不可少的。
因?yàn)閷?shí)現(xiàn)的問(wèn)題比較復(fù)雜,對(duì)現(xiàn)實(shí)問(wèn)題進(jìn)行分析時(shí),把它轉(zhuǎn)化成為相互聯(lián)系、不同層次的子問(wèn)題,是必須的解決方法。
而call與ret指令對(duì)這種分析方法提供了程序?qū)崿F(xiàn)上的支持。
利用call和ret指令,我們可以用簡(jiǎn)捷的方法,實(shí)現(xiàn)多個(gè)相互聯(lián)系、功能獨(dú)立的子程序來(lái)解決一個(gè)復(fù)雜的問(wèn)題。
參數(shù)和結(jié)果傳遞的問(wèn)題
子程序一般都要根據(jù)提供的參數(shù)處理一定的事務(wù),處理后,將結(jié)果(返回值)提供給調(diào)用者。
其實(shí),我們討論參數(shù)和返回值傳遞的問(wèn)題,實(shí)際上就是在探討,應(yīng)該如何存儲(chǔ)子程序需要的參數(shù)和產(chǎn)生的返回值。
;說(shuō)明:計(jì)算N的3次方
;參數(shù):(bx)=N
;結(jié)果:(dx:ax)=N^3
cube:mov ax,bx
mul bx
mul bx
ret
注意,編程時(shí)的良好風(fēng)格,應(yīng)有有詳細(xì)的注釋。包含對(duì)子程序的功能、參數(shù)和結(jié)果的說(shuō)明。
用寄存器來(lái)存儲(chǔ)參數(shù)和結(jié)果是最常使用的方法。對(duì)于存放參數(shù)的寄存器和存放結(jié)果的寄存器,調(diào)用者和子程序的讀寫(xiě)操作恰恰相反:調(diào)用者將參數(shù)送入?yún)?shù)寄存器,從結(jié)果寄存器中取到返回值;子程序從參數(shù)寄存器中取到參數(shù),將返回值送入結(jié)果寄存器。
批量數(shù)據(jù)的傳遞
寄存器的數(shù)量終究有限,我們不可能簡(jiǎn)單地用寄存器來(lái)存放多個(gè)需要傳遞的數(shù)據(jù)。對(duì)于返回值,也有同樣的問(wèn)題。
在這種時(shí)候,我們將批量數(shù)據(jù)放到內(nèi)存中,然后將它們所在內(nèi)存空間的首地址放在寄存器中,傳遞給需要的子程序。對(duì)于具有批量數(shù)據(jù)的返回結(jié)果,也可用同樣的方法。
除了用寄存器傳遞參數(shù)外,還有一種通用的方法是用棧來(lái)傳遞參數(shù)。
寄存器沖突的問(wèn)題
一個(gè)一般化的問(wèn)題,子程序中使用的寄存器,很可能在主程序中也要使用,造成了寄存器使用上的沖突。
那么我們?nèi)绾蝸?lái)避免這種沖突呢?粗略地看,可以有兩個(gè)方案:
1) 在編寫(xiě)調(diào)用子程序的程序時(shí),注意看看子程序中有沒(méi)有用到會(huì)產(chǎn)生沖突的寄存器,如果有,調(diào)用者使用別的寄存器;
2) 在編寫(xiě)子程序的時(shí)候,不要使用會(huì)產(chǎn)生沖突的寄存器。
以上兩個(gè)方案,不具可行性,第一種給調(diào)用子程序的程序的編寫(xiě)造成很大麻煩。第二種不可能實(shí)現(xiàn),子程序無(wú)法知道將來(lái)的調(diào)用情況。
我們希望:
1) 編寫(xiě)調(diào)用子程序的程序的時(shí)候不必關(guān)心子程序到底使用了哪些寄存器;
2) 編寫(xiě)子程序的時(shí)候不必關(guān)心調(diào)用者使用了哪些寄存器;
3) 不會(huì)發(fā)生寄存器沖突。
解決這個(gè)問(wèn)題的簡(jiǎn)捷方法是,在子程序的開(kāi)始將子程序中所有用到的寄存器中的內(nèi)容都保存起來(lái),在子程序返回前再恢復(fù)。我們可以用棧來(lái)保存寄存器中的內(nèi)容。
以后,我們編寫(xiě)子程序的標(biāo)準(zhǔn)框架如下:
子程序開(kāi)始:子程序中使用的寄存器入棧
子程序內(nèi)容
子程序中使用的寄存器出棧
返回(ret、retf)
要注意寄存器入棧和出棧的順序。
實(shí)驗(yàn)10 編寫(xiě)子程序
1、 顯示字符串
問(wèn)題:顯示字符串是現(xiàn)實(shí)工作中經(jīng)常要用到的功能,應(yīng)該編寫(xiě)一個(gè)通用的子程序來(lái)實(shí)現(xiàn)這個(gè)功能。我們應(yīng)該提供靈活的調(diào)用接口,使調(diào)用者可以決定顯示的位置(行、列)、內(nèi)容和顏色。
子程序描述
名稱(chēng):show_str
功能:在指定的位置,用指定的顏色,顯示一個(gè)用0結(jié)束的字符串。
參數(shù):(dh)=行號(hào)(取值范圍0~24),(dl)=列號(hào)(取值范圍0~79),
(cl)=顏色,ds:si指向字符串的首地址
返回:無(wú)
應(yīng)用舉例:在屏幕的8行3列,用綠色顯示data段中的字符串。
1) 子程序的入口參數(shù)是屏幕上的行號(hào)和列號(hào),注意在子程序內(nèi)部要將它們轉(zhuǎn)化為顯存中的地址,首先要分析一下屏幕上的行列位置和顯存地址的對(duì)應(yīng)關(guān)系。
2) 注意保存子程序中用到的相關(guān)寄存器。
3) 空上子程序的內(nèi)部處理和顯存的結(jié)構(gòu)密切相關(guān),但是向外提供了與顯存結(jié)構(gòu)無(wú)關(guān)的接口。通過(guò)調(diào)用這個(gè)子程序,進(jìn)行字符串的顯示時(shí)可以不必了解顯存的結(jié)構(gòu),為編程提供了方便。在實(shí)驗(yàn)中,注意體會(huì)這種設(shè)計(jì)思想。
2、 解決除法溢出的問(wèn)題
問(wèn)題:div指令可以做除法。當(dāng)進(jìn)行8位除法的時(shí)候,用al存儲(chǔ)結(jié)果的商,ah存儲(chǔ)結(jié)果的余數(shù);進(jìn)行16位除法的時(shí)候,用ax存儲(chǔ)結(jié)果的商,dx存儲(chǔ)結(jié)果的余數(shù)。可是,現(xiàn)在有一個(gè)問(wèn)題,如果結(jié)果的商大于ah或ax所能存儲(chǔ)的最大值,那么將如何?
當(dāng)CPU執(zhí)行div等除法指令的時(shí)候,如果發(fā)生結(jié)果數(shù)據(jù)超出了寄存器所能存儲(chǔ)的范圍,將引發(fā)CPU的一個(gè)內(nèi)部錯(cuò)誤,這個(gè)錯(cuò)誤被稱(chēng)為:除法溢出。
子程序描述
名稱(chēng):divdw
功能:進(jìn)行不會(huì)產(chǎn)生溢出的除法運(yùn)算,被除數(shù)為dword型,除數(shù)為word型,結(jié)果為dword型。
參數(shù):(ax)=dword型數(shù)據(jù)的低16位
(dx)=dword型數(shù)據(jù)的高16位
(cx)=除數(shù)
返回:(dx)=結(jié)果的高16位,(ax)=結(jié)果的低16位
(cx)=余數(shù)
應(yīng)用舉例:計(jì)算1000000/10(F4240H/0AH)
3、 數(shù)值顯示
問(wèn)題:編程:將data段中的數(shù)據(jù)以十進(jìn)制的形式顯示出來(lái)。
數(shù)據(jù)在內(nèi)存中都是二進(jìn)制信息,標(biāo)記了數(shù)值的大小。要把它們顯示到屏幕上,成為我們能夠讀懂的信息,需要進(jìn)行信息的轉(zhuǎn)化。
比如,數(shù)值12666,在機(jī)器中存儲(chǔ)為二進(jìn)制信息:11000101111010B(317AH),計(jì)算機(jī)可以理解它。而我們要在顯示器上讀到可以理解的數(shù)值12666,我們看到的應(yīng)該是一串字符:“12666”,由于顯卡遵循的是ASCII編碼,為了讓我們能在顯示器上看到這串字符,它在機(jī)器中應(yīng)以ASCII碼的形式存儲(chǔ)為:31H、32H、36H、36H、36H(字符“0”~“9”對(duì)應(yīng)的ASCII碼為30H~39H)。
通過(guò)上面的分析可以看到,在概念世界中,有一個(gè)抽象的數(shù)據(jù)12666,它表示了一個(gè)數(shù)值的大小。在現(xiàn)實(shí)世界中它可以有多種表示形式,可以在電子機(jī)器中以高低電平(二進(jìn)制)的形式存儲(chǔ),也可以在紙上、黑板上、屏幕上以人類(lèi)的語(yǔ)言“12666”來(lái)書(shū)寫(xiě)。現(xiàn)在,我們面臨的問(wèn)題就是,要將同一抽象的數(shù)據(jù),從一種表示形式轉(zhuǎn)化為另一種表示形式。
要將數(shù)據(jù)用十進(jìn)制形式顯示到屏幕上,要進(jìn)行兩步工作:
1) 將用二進(jìn)制信息存儲(chǔ)的數(shù)據(jù)轉(zhuǎn)變?yōu)槭M(jìn)制形式的字符串;
2) 顯示十進(jìn)制形式的字符串。
子程序描述
名稱(chēng):dtoc
功能:將word型數(shù)據(jù)轉(zhuǎn)變?yōu)楸硎臼M(jìn)制數(shù)的字符串,字符串以0為結(jié)尾符。
參數(shù):(ax)=word型數(shù)據(jù)
ds:si指向字符串的首地址
返回:無(wú)
應(yīng)用舉例:編程,將數(shù)據(jù)12666以十進(jìn)制的形式在屏幕的8行3列,用綠色顯示出來(lái)。
分析:要得到字符串“12666”,就是要得到一列表示該字符串的ASCII碼:31H、32H、36H、36H、36H。
十進(jìn)制數(shù)碼字符對(duì)應(yīng)的ASCII碼=十進(jìn)制數(shù)碼值+30H。
要得到表示十進(jìn)制數(shù)的字符串,先求十進(jìn)制數(shù)每位的值。
例如,對(duì)于12666,先求得每位的值:1、2、6、6、6。再將這些數(shù)分別加上30H,便得到了表示12666的ASCII碼串,31H、32H、36H、36H、36H。
那么,怎樣得到每位的值呢?采用下列方法(除10取余法):
12666/10=1266……6
1266/10=126……..6
126/10=12………6
12/10=1………..2
1/10=0………..1
可見(jiàn),用10除12666,共除5次,記下每次的余數(shù),就得到了每位的值。
綜合以上分析,可得出處理過(guò)程如下:
用12666除以10,循環(huán)5次,記下每次的余數(shù);將每次的余數(shù)分別加30H,使得到了表示十進(jìn)制數(shù)的ASCII碼串。
只要是除到商為0,各位的值就已經(jīng)全部求出。可以使用jcxz指令來(lái)實(shí)現(xiàn)相關(guān)的功能。