匯編基礎(chǔ)程序編寫教程示例

源程序
1.1 構(gòu)成
寄存器與段的關(guān)聯(lián)假設(shè)
assume:含義為“假設(shè)”。
它假設(shè)某一段寄存器和程序中的某一個(gè)用 segment … ends 定義的段相關(guān)聯(lián)。
通過(guò)assume說(shuō)明這種關(guān)聯(lián),在需要的情況下 ,編譯程序可以將段寄存器和某一個(gè)具體的段相聯(lián)系。
標(biāo)號(hào)
一個(gè)標(biāo)號(hào)指代了一個(gè)地址。
codesg:放在segment的前面,作為一個(gè)段的名稱,這個(gè)段的名稱最終將被編譯、連接程序處理為一個(gè)段的段地址。
定義一個(gè)段
segment和ends的功能是定義一個(gè)段,segment說(shuō)明一個(gè)段開始,ends 說(shuō)明一個(gè)段結(jié)束。
segment和ends是一對(duì)成對(duì)使用的偽指令
一個(gè)段必須有一個(gè)名稱來(lái)標(biāo)識(shí),使用格式為:
段名 segment
段名 ends
一個(gè)匯編程序是由多個(gè)段組成的,這些段被用來(lái)存放代碼、數(shù)據(jù)或當(dāng)作??臻g來(lái)使用。
一個(gè)有意義的匯編程序中至少要有一個(gè)段,這個(gè)段用來(lái)存放代碼。
程序結(jié)束標(biāo)記
End 是一個(gè)匯編程序的結(jié)束標(biāo)記,編譯器在編譯匯編程序的過(guò)程中,如果碰到了偽指令 end,就結(jié)束對(duì)源程序的編譯。
如果程序?qū)懲炅耍诮Y(jié)尾處加上偽指令end 。否則,編譯器在編譯程序時(shí),無(wú)法知道程序在何處結(jié)束。
注意:不要搞混了end和ends。
程序返回
一個(gè)程序結(jié)束后,將CPU的控制權(quán)交還給使它得以運(yùn)行的程序,我們稱這個(gè)過(guò)程為:程序返回。
如何返回
應(yīng)該在程序的末尾添加返回的程序段。
mov ax,4c00H
int 21H
程序運(yùn)行
DOS是一個(gè)單任務(wù)操作系統(tǒng)。
一個(gè)程序P2在可執(zhí)行文件中,則必須有一個(gè)正在運(yùn)行的程序P1,將P2從可執(zhí)行文件中加載入內(nèi)存后,將CPU的控制權(quán)交給P2,P2才能得以運(yùn)行。P2開始運(yùn)行后,P1暫停運(yùn)行。
而當(dāng)P2運(yùn)行完畢后,應(yīng)該將CPU的控制權(quán)交還給使它得以運(yùn)行的程序P1,此后,P1繼續(xù)運(yùn)行。
1.2 源程序中的“程序”
匯編源程序:
偽指令 (編譯器處理)
匯編指令(編譯為機(jī)器碼)
程序:源程序中最終由計(jì)算機(jī)執(zhí)行、處理的指令或數(shù)據(jù)。
注意
我們可以將源程序文件中的所有內(nèi)容稱為源程序,將源程序中最終由計(jì)算機(jī)執(zhí)行處理的指令或數(shù)據(jù) ,成為程序。
程序最先以匯編指令的形式存在源程序中,經(jīng)編譯、連接后轉(zhuǎn)變?yōu)闄C(jī)器碼,存儲(chǔ)在可執(zhí)行文件中,
1.3 段結(jié)束、程序結(jié)束、程序返回

1.4 語(yǔ)法錯(cuò)誤和邏輯錯(cuò)誤
語(yǔ)法錯(cuò)誤
程序在編譯時(shí)被編譯器發(fā)現(xiàn)的錯(cuò)誤
邏輯錯(cuò)誤
程序在編譯時(shí)不能表現(xiàn)出來(lái)的、在運(yùn)行時(shí)發(fā)生的錯(cuò)誤
2 程序執(zhí)行的過(guò)程
2.1 一個(gè)匯編語(yǔ)言程序從寫出到最終執(zhí)行的簡(jiǎn)要過(guò)程:

2.2 連接
作用
當(dāng)源程序很大時(shí),可以將它分為多個(gè)源程序文件來(lái)編譯,每個(gè)源程序編譯成為目標(biāo)文件后,再用連接程序?qū)⑺鼈冞B接到一起,生成一個(gè)可執(zhí)行文件;
程序中調(diào)用了某個(gè)庫(kù)文件中的子程序,需要將這個(gè)庫(kù)文件和該程序生成的目標(biāo)文件連接到一起,生成一個(gè)可執(zhí)行文件;
一個(gè)源程序編譯后,得到了存有機(jī)器碼的目標(biāo)文件,目標(biāo)文件中的有些內(nèi)容還不能直接用來(lái)生成可執(zhí)行文件,連接程序?qū)⑦@此內(nèi)容處理為最終的可執(zhí)行信息。
所以,在只有一個(gè)源程序文件,而又不需要調(diào)用某個(gè)庫(kù)中的子程序的情況下,也必須用連接程序?qū)δ繕?biāo)文件進(jìn)行處理,生成可執(zhí)行文件。
注意,對(duì)于連接的過(guò)程,可執(zhí)行文件是我們要得到的最終結(jié)果。
使用匯編語(yǔ)言編譯程序?qū)υ闯绦蛭募械脑闯绦蜻M(jìn)行編譯,產(chǎn)生目標(biāo)文件;再用連接程序?qū)δ繕?biāo)文件進(jìn)行連接,生成可在操作系統(tǒng)中直接運(yùn)行的可執(zhí)行文件。
2.3 可執(zhí)行文件
可執(zhí)行文件中包含兩部分內(nèi)容:
- 程序(從原程序中的匯編指令翻譯過(guò)來(lái)的機(jī)器碼)和數(shù)據(jù)(源程序中定義的數(shù)據(jù))
- 相關(guān)的描述信息(比如:程序有多大、要占多少內(nèi)存空間等)
執(zhí)行可執(zhí)行文件中的程序
- 在操作系統(tǒng)中,執(zhí)行可執(zhí)行文件中的程序。
- 操作系統(tǒng)依照可執(zhí)行文件中的描述信息,將可執(zhí)行文件中的機(jī)器碼和數(shù)據(jù)加載入內(nèi)存,并進(jìn)行相關(guān)的初始化(比如:設(shè)置CS:IP指向第一條要執(zhí)行的指令),然后由CPU執(zhí)行程序。
可執(zhí)行文件中的程序裝入內(nèi)存并運(yùn)行的原理
- 在DOS中,可執(zhí)行文件中的程序P1若要運(yùn)行,必須有一個(gè)正在運(yùn)行的程序P2 ,將 P1 從可執(zhí)行文件中加載入內(nèi)存,將CPU的控制權(quán)交給它,P1才能得以運(yùn)行;
- 當(dāng)P1運(yùn)行完畢后,應(yīng)該將CPU的控制權(quán)交還給使它得以運(yùn)行的程序P2
exe的執(zhí)行過(guò)程
實(shí)際過(guò)程
(1)我們?cè)谔崾痉癈:\masm”后面輸入可執(zhí)行文件的名字“1”,按Enter鍵。
(2)1.exe中的程序運(yùn)行;
(3)運(yùn)行結(jié)束,返回,再次顯示提示符“C:\masm”。
操作過(guò)程
操作系統(tǒng)是由多個(gè)功能模塊組成的龐大 、復(fù)雜的軟件系統(tǒng)。任何通用的操作系統(tǒng) ,都要提供一個(gè)稱為shell(外殼)的程序 ,用戶(操作人員)使用這個(gè)程序來(lái)操作計(jì)算機(jī)系統(tǒng)工作。
DOS中有一個(gè)程序command.com ,這個(gè)程序在 DOS 中稱為命令解釋器,也就是DOS系統(tǒng)的shell。
(1)我們?cè)贒OS中直接執(zhí)行 1.exe 時(shí),是正在運(yùn)行的command將1.exe中的程序加載入內(nèi)存。
(2)command設(shè)置CPU的CS:IP指向程序的第一條指令(即程序的入口),從而使程序得以運(yùn)行。
(3)程序運(yùn)行結(jié)束后,返回到command中,CPU繼續(xù)運(yùn)行command。
2.4 程序執(zhí)行過(guò)程的跟蹤
Debug 可以將程序加載入內(nèi)存,設(shè)置CS:IP指向程序的入口,但Debug并不放棄對(duì)CPU 的控制,這樣,我們就可以使用Debug 的相關(guān)命令來(lái)單步執(zhí)行程序 ,查看每條指令指令的執(zhí)行結(jié)果。
我們?cè)?DOS中用 “Debug 1.exe” 運(yùn)行Debug對(duì)1.exe進(jìn)行跟蹤時(shí),程序加載的順序是:command加載Debug,Debug加載1.exe。
返回的順序是:從1.exe中的程序返回到Debug,從Debug返回到command。
EXE文件中的程序的加載過(guò)程
總結(jié)
程序加載后,ds中存放著程序所在內(nèi)存區(qū)的段地址,這個(gè)內(nèi)存區(qū)的偏移地址為 0 ,則程序所在的內(nèi)存區(qū)的地址為:ds:0;
這個(gè)內(nèi)存區(qū)的前256 個(gè)字節(jié)中存放的是PSP,dos用來(lái)和程序進(jìn)行通信。
從 256字節(jié)處向后的空間存放的是程序。
所以,我們從ds中可以得到PSP的段地址SA,PSP的偏移地址為 0,則物理地址為SA×16+0。
因?yàn)镻SP占256(100H)字節(jié),所以程序的物理地址是:
SA×16+0+256= SA×16+16×16=(SA+16)×16+0
可用段地址和偏移地址表示為:SA+10:0。
3 程序編寫
3.1 兩個(gè)基本的問(wèn)題
計(jì)算機(jī)是進(jìn)行數(shù)據(jù)處理、運(yùn)算的機(jī)器,那么有兩個(gè)基本的問(wèn)題就包含在其中:
(1)處理的數(shù)據(jù)在什么地方?
(2)要處理的數(shù)據(jù)有多長(zhǎng)?這兩個(gè)問(wèn)題,在機(jī)器指令中必須給以明確或隱含的說(shuō)明,否則計(jì)算機(jī)就無(wú)法工作。
為了描述上的簡(jiǎn)潔,在以后的課程中,我們將使用兩個(gè)描述性的符號(hào) reg來(lái)表示一個(gè)寄存器,用sreg表示一個(gè)段寄存器。
reg的集合包括:ax、bx、cx、dx、ah、al、bh、bl、ch、cl、dh、dl、sp、bp、si、di;
sreg的集合包括:ds、ss、cs、es。
3.2 數(shù)據(jù)在哪里
機(jī)器指令處理的數(shù)據(jù)所在位置
- 絕大部分機(jī)器指令都是進(jìn)行數(shù)據(jù)處理的指令,處理大致可分為三類:讀取、寫入、運(yùn)算
- 在機(jī)器指令這一層來(lái)講,并不關(guān)心數(shù)據(jù)的值是多少,而關(guān)心指令執(zhí)行前一刻,它將要處理的數(shù)據(jù)所在的位置。
- 指令在執(zhí)行前,所要處理的數(shù)據(jù)可以在三個(gè)地方:CPU內(nèi)部、內(nèi)存、端口
- 指令舉例
匯編語(yǔ)言中數(shù)據(jù)位置的表達(dá)
匯編語(yǔ)言中用三個(gè)概念來(lái)表達(dá)數(shù)據(jù)的位置。
立即數(shù)(idata)
對(duì)于直接包含在機(jī)器指令中的數(shù)據(jù)(執(zhí)行前在cpu 的指令緩沖器中),在匯編語(yǔ)言中稱為:立即數(shù)(idata ) ,在匯編指令中直接給出。例如:
mov ax,1
add bx,2000h
or bx,00010000b
mov al,'a'
寄存器
指令要處理的數(shù)據(jù)在寄存器中,在匯編指令中給出相應(yīng)的寄存器名。例如:
mov ax,bx
mov ds,ax
push bx
mov ds:[0],bx
push ds
mov ss,ax
mov sp,ax
mov ax,bx
對(duì)應(yīng)機(jī)器碼:89D8
執(zhí)行結(jié)果:(ax) = (bx)
段地址(SA)和偏移地址(EA)
指令要處理的數(shù)據(jù)在內(nèi)存中,在匯編指令中可用[X]的格式給出EA,SA在某個(gè)段寄存器中。
存放段地址的寄存器可以是默認(rèn)的。
mov ax,[0]
mov ax,[bx]
mov ax,[bx+8]
mov ax,[bx+si]
mov ax,[bx+si+8]
段地址默認(rèn)在ds中
存放段地址的寄存器也可以顯性的給出。
mov ax,[bp]
mov ax,[bp+8]
mov ax,[bp+si]
mov ax,[bp+si+8]
段地址默認(rèn)在ss中
顯性的給出存放段地址的寄存器

尋址方式
當(dāng)數(shù)據(jù)存放在內(nèi)存中的時(shí)候,我們可以用多種方式來(lái)給定這個(gè)內(nèi)存單元的偏移地址,這種定位內(nèi)存單元的方法一般被稱為尋址方式。

3.3 指令處理的數(shù)據(jù)有多長(zhǎng)
8086CPU的指令,可以處理兩種尺寸的數(shù)據(jù),byte和word。所以在機(jī)器指令中要指明,指令進(jìn)行的是字操作還是字節(jié)操作
對(duì)于這個(gè)問(wèn)題,匯編語(yǔ)言中用以下方法處理。
(1)通過(guò)寄存器名指明要處理的數(shù)據(jù)的尺寸。
(2)在沒(méi)有寄存器名存在的情況下,用操作符X ptr指明內(nèi)存單元的長(zhǎng)度,X在匯編指令中可以為word或byte。
(3)其他方法
下面的指令中,寄存器指明了指令進(jìn)行的是字節(jié)操作:
mov al,1
mov al,bl
mov al,ds:[0]
mov ds:[0],al
inc al
add al,100
下面的指令中,寄存器指明了指令進(jìn)行的是字操作:
mov ax,1
mov bx,ds:[0]
mov ds,ax
mov ds:[0],ax
inc ax add ax,1000
在沒(méi)有寄存器參與的內(nèi)存單元訪問(wèn)指令中,用word ptr或byte ptr顯性地指明所要訪問(wèn)的內(nèi)存單元的長(zhǎng)度是很必要的。
否則,CPU無(wú)法得知所要訪問(wèn)的單元是字單元,還是字節(jié)單元
下面的指令中,用word ptr指明了指令訪問(wèn)的內(nèi)存單元是一個(gè)字單元:
mov word ptr ds:[0],1
inc word ptr [bx]
inc word ptr ds:[0]
add word ptr [bx],2
下面的指令中,用byte ptr指明了指令訪問(wèn)的內(nèi)存單元是一個(gè)字節(jié)單元:
mov byte ptr ds:[0],1
inc byte ptr [bx]
inc byte ptr ds:[0]
add byte ptr [bx],2
有些指令默認(rèn)了訪問(wèn)的是字單元還是字節(jié)單元,
比如:push [1000H]就不用指明訪問(wèn)的是字單元還是字節(jié)單元
因?yàn)閜ush指令只進(jìn)行字操作
3.4 數(shù)據(jù)處理
在代碼段中使用數(shù)據(jù)
考慮這樣一個(gè)問(wèn)題,編程計(jì)算以下8個(gè)數(shù)據(jù)的和,結(jié)果存在ax 寄存器中:
0123H,0456H,0789H,0abcH,0defH,0fedH,0cbaH,0987H。
在前面的課程中,我們都是累加某些內(nèi)存單元中的數(shù)據(jù),并不關(guān)心數(shù)據(jù)本身。
可現(xiàn)在我們要累加的就是已經(jīng)給定了數(shù)值的數(shù)據(jù)。
程序第一行中的 “dw”的含義是定義字型數(shù)據(jù)。dw即define word。
在這里,我們使用dw定義了8個(gè)字型數(shù)據(jù)(數(shù)據(jù)之間以逗號(hào)分隔),它們所占的內(nèi)存空間的大小為16個(gè)字節(jié)。
程序中的指令就要對(duì)這8個(gè)數(shù)據(jù)進(jìn)行累加,可這8個(gè)數(shù)據(jù)在哪里呢?
由于它們?cè)诖a段中,程序在運(yùn)行的時(shí)候CS中存放代碼段的段地址,所以我們可以從CS中得到它們的段地址
這8個(gè)數(shù)據(jù)的偏移地址是多少呢?
- 因?yàn)橛胐w定義的數(shù)據(jù)處于代碼段的最開始,所以偏移地址為0,這8 個(gè)數(shù)據(jù)就在代碼段的偏移0、2、4、6、8、A、C、E處。
- 程序運(yùn)行時(shí),它們的地址就是CS:0、CS:2、CS:4、CS:6、CS:8、CS:A、CS:C、CS:E。
程序中,我們用bx存放加2遞增的偏移地址,用循環(huán)來(lái)進(jìn)行累加。
在循環(huán)開始前,設(shè)置(bx)=0,cs:bx指向第一個(gè)數(shù)據(jù)所在的字單元。
每次循環(huán)中(bx)=(bx)+2,cs:bx指向下一個(gè)數(shù)據(jù)所在的字單元。
如何讓這個(gè)程序在編譯后可以存系統(tǒng)中直接運(yùn)行呢?我們可以在源程序中指明界序的入口所在

探討end的作用:
end 除了通知編譯器程序結(jié)束外,還可以通知編譯器程序的入口在什么地方。
有了這種方法,我們就可以這樣來(lái)安排程序的框架:

在代碼段中使用棧
完成下面的程序,利用棧,將程序中定義的數(shù)據(jù)逆序存放
assume cs:codesg
codesgsegment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
?
code ends end
程序的思路大致如下:
程序運(yùn)行時(shí),定義的數(shù)據(jù)存放在cs:0~cs:15單元中,共8個(gè)字單元。依次將這8個(gè)字單元中的數(shù)據(jù)入棧,然后再依次出棧到這 8 個(gè)字單元中,從而實(shí)現(xiàn)數(shù)據(jù)的逆序存放。
問(wèn)題是,我們首先要有一段可當(dāng)作棧的內(nèi)存空間。如前所述,這段空間應(yīng)該由系統(tǒng)來(lái)分配。我們可以在程序中通過(guò)定義數(shù)據(jù)來(lái)取得一段空間,然后將這段空間當(dāng)作棧空間來(lái)用
mov ax,cs
mov ss,ax
mov sp,32
我們要講 cs:16 ~ cs:31 的內(nèi)存空間當(dāng)作棧來(lái)用,初始狀態(tài)下棧為空,所以 ss:sp要指向棧底,則設(shè)置ss:sp指向cs:32。
比如對(duì)于:
dw 0123H,0456H,0789H,0abcH,0defH,0fedH,0cbaH,0987H
我們可以說(shuō),定義了8個(gè)字型數(shù)據(jù),也可以說(shuō),開辟了8個(gè)字的內(nèi)存空間,這段空間中每個(gè)字單元中的數(shù)據(jù)依次是:
0123H,0456H,0789H,0abcH,0defH,0fedH,0cbaH,0987H。
因?yàn)樗鼈冏罱K的效果是一樣的
將數(shù)據(jù)、代碼、棧放入不同的段
在前面的內(nèi)容中,我們?cè)诔绦蛑杏玫搅藬?shù)據(jù)和棧,我們將數(shù)據(jù)、棧和代碼都放到了一個(gè)段里面。我們?cè)诰幊痰臅r(shí)候要注意何處是數(shù)據(jù),何處是棧,何處是代碼。這樣做顯然有兩個(gè)問(wèn)題:
(1)把它們放到一個(gè)段中使程序顯得混亂;
(2)前面程序中處理的數(shù)據(jù)很少,用到的棧空間也小,加上沒(méi)有多長(zhǎng)的代碼,放到一個(gè)段里面沒(méi)有問(wèn)題。
但如果數(shù)據(jù)、棧和代碼需要的空間超過(guò)64KB,就不能放在一個(gè)段中(一個(gè)段的容量不能大于64 KB,是我們?cè)趯W(xué)習(xí)中所用的8086模式的限制,并不是所有的處理器都這樣)。
所以,我們應(yīng)該考慮用多個(gè)段來(lái)存放數(shù)據(jù)、代碼和棧。
我們用和定義代碼段一樣的方法來(lái)定義多個(gè)段,然后在這些段里面定義需要的數(shù)據(jù),或通過(guò)定義數(shù)據(jù)來(lái)取得棧空間。
程序中“data”段中的數(shù)據(jù)“0abch”的地址就是:data:6。
我們要將它送入bx中,就要用如下的代碼:
mov ax,data
mov ds,ax
mov bx,ds:[6]
我們不能用下面的指令:
mov ds,data
mov ax,ds:[6]
其中指令“mov ds,data” 是錯(cuò)誤的,因?yàn)?086CPU不允許將一個(gè)數(shù)值直接送入段寄存器中。
程序中對(duì)段名的引用,如指令“mov ds,data”中的“data”,將被編譯器處理為一個(gè)表示段地址的數(shù)值。
“代碼段”、“數(shù)據(jù)段”、“棧段”完全是我們的安排
我們?cè)谠闯绦蛑杏脗沃噶?/p>
“assume cs:code,ds:data,ss:stack”將cs、ds和ss分別和code、data、stack段相連。
這樣做了之后,CPU是否就會(huì)將 cs指向 code,ds 指向 data,ss 指向stack,從而按照我們的意圖來(lái)處理這些段呢?
當(dāng)然也不是,要知道 assume 是偽指令,是由編譯器執(zhí)行的,也是僅在源程序中存在的信息,CPU并不知道它們。
若要CPU按照我們的安排行事,就要用機(jī)器指令控制它,源程序中的匯編指令是CPU要執(zhí)行的內(nèi)容
CPU如何知道去執(zhí)行它們?
我們?cè)谠闯绦虻淖詈笥谩癳nd start”說(shuō)明了程序的入口,這個(gè)入口將被寫入可執(zhí)行文件的描述信息,可執(zhí)行文件中的程序被加載入內(nèi)存后,CPU的CS:IP被設(shè)置指向這個(gè)入口,從而開始執(zhí)行程序中的第一條指令。
標(biāo)號(hào)“start”在“code”段中,這樣CPU就將code段中的內(nèi)容當(dāng)作指令來(lái)執(zhí)行了。
我們?cè)赾ode段中,使用指令:
mov ax,stack
mov ss,ax
mov sp,16 設(shè)置ss指向stack,設(shè)置ss:sp指向stack:16, CPU 執(zhí)行這些指令后,將把stack段當(dāng)做??臻g來(lái)用。 CPU若要訪問(wèn)data段中的數(shù)據(jù),則可用 ds 指向 data 段,用其他的寄存器(如:bx)來(lái)存放 data段中數(shù)據(jù)的偏移地址
總之,CPU到底如何處理我們定義的段中的內(nèi)容,是當(dāng)作指令執(zhí)行,當(dāng)作數(shù)據(jù)訪問(wèn),還是當(dāng)作??臻g,完全是靠程序中具體的匯編指令,和匯編指令對(duì)CS:IP、SS:SP、DS等寄存器的設(shè)置來(lái)決定的。
3.5 模塊化實(shí)現(xiàn):call 和 ret 指令
功能:call和ret 指令都是轉(zhuǎn)移指令,它們都修改IP,或同時(shí)修改CS和IP。
ret
ret指令用棧中的數(shù)據(jù),修改IP的內(nèi)容,從而實(shí)現(xiàn)近轉(zhuǎn)移;
CPU執(zhí)行ret指令時(shí),進(jìn)行下面兩步操作:
(1)(IP)=((ss)*16+(sp))
(2)(sp)=(sp)+2
retf
retf指令用棧中的數(shù)據(jù),修改CS和IP的內(nèi)容,從而實(shí)現(xiàn)遠(yuǎn)轉(zhuǎn)移;
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
示例
ret指令
程序中ret指令執(zhí)行后,(IP)=0,CS:IP指向代碼段的第一條指令。
retf指令
程序中retf指令執(zhí)行后,CS:IP指向代碼段的第一條指令。
call 指令
CPU執(zhí)行call指令,進(jìn)行兩步操作:
(1)將當(dāng)前的 IP 或 CS和IP 壓入棧中
(2)轉(zhuǎn)移
主要應(yīng)用格式
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位位移
call 標(biāo)號(hào)
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 far ptr 標(biāo)號(hào)”這種格式的call指令時(shí)的操作:
(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ǔ)法解釋此種格式的 call 指令,CPU執(zhí)行call 16位reg時(shí),相當(dāng)于進(jìn)行: push IP jmp 16位寄存器
轉(zhuǎn)移地址在內(nèi)存中的call指令
轉(zhuǎn)移地址在內(nèi)存中的call指令有兩種格式:
(1) call word ptr 內(nèi)存單元地址
匯編語(yǔ)法解釋: push IP jmp word ptr 內(nèi)存單元地址 比如下面的指令: mov sp,10h mov ax,0123h mov ds:[0],ax call word ptr ds:[0] 執(zhí)行后,(IP)=0123H,(sp)=0EH
(2) call dword ptr 內(nèi)存單元地址
匯編語(yǔ)法解釋: push CS push IP jmp dword ptr 內(nèi)存單元地址 比如,下面的指令: mov sp,10h mov ax,0123h mov ds:[0],ax mov word ptr ds:[2],0 call dword ptr ds:[0] 執(zhí)行后,(CS)=0,(IP)=0123H,(sp)=0CH
call 和 ret 的配合使用
我們看一下程序的主要執(zhí)行過(guò)程:

(1)前三條指令執(zhí)行后,棧的情況如下:

(2)call 指令讀入后,(IP) =000EH,CPU指令緩沖器中的代碼為 B8 05 00; CPU執(zhí)行B8 05 00,首先,棧中的情況變?yōu)椋?/p>
然后,(IP)=(IP)+0005=0013H。
(3)CPU從cs:0013H處(即標(biāo)號(hào)s處)開始執(zhí)行。
(4)ret指令讀入后:(IP)=0016H,CPU指令緩沖器中的代碼為 C3;CPU執(zhí)行C3,相當(dāng)于進(jìn)行pop IP,執(zhí)行后,棧中的情況為:
(IP)=000EH;
(5)CPU回到 cs:000EH處(即call指令后面的指令處)繼續(xù)執(zhí)行。
我們發(fā)現(xiàn),可以寫一個(gè)具有一定功能的程序段,我們稱其為子程序,在需要的時(shí)候,用call指令轉(zhuǎn)去執(zhí)行
call指令轉(zhuǎn)去執(zhí)行子程序之前,call指令后面的指令的地址將存儲(chǔ)在棧中,所以可以在子程序的后面使用 ret 指令,用棧中的數(shù)據(jù)設(shè)置IP的值,從而轉(zhuǎn)到 call 指令后面的代碼處繼續(xù)執(zhí)行。
這樣,我們可以利用call和ret來(lái)實(shí)現(xiàn)子程序的機(jī)制。
子程序的框架
標(biāo)號(hào): 指令 ret 具有子程序的源程序的框架:

參數(shù)和結(jié)果傳遞的問(wèn)題
子程序一般都要根據(jù)提供的參數(shù)處理一定的事務(wù),處理后,將結(jié)果(返回值)提供給調(diào)用者。
其實(shí),我們討論參數(shù)和返回值傳遞的問(wèn)題,實(shí)際上就是在探討,應(yīng)該如何存儲(chǔ)子程序需要的參數(shù)和產(chǎn)生的返回值。
我們?cè)O(shè)計(jì)一個(gè)子程序,可以根據(jù)提供的N,來(lái)計(jì)算N的3次方。
這里有兩個(gè)問(wèn)題:
(1)我們將參數(shù)N存儲(chǔ)在什么地方?
(2)計(jì)算得到的數(shù)值,我們存儲(chǔ)在什么地方?
很顯然,我們可以用寄存器來(lái)存儲(chǔ),可以將參數(shù)放到 bx 中 ;
因?yàn)樽映绦蛑幸?jì)算 N×N×N ,可以使用多個(gè) mul 指令,為了方便,可將結(jié)果放到 dx 和 ax中。
子程序
說(shuō)明:計(jì)算N的3次方
參數(shù): (bx)=N
結(jié)果: (dx:ax)=N∧3
cube:mov ax,bx
mul bx ;用ax與bx相乘
mul bx
ret
用寄存器來(lái)存儲(chǔ)參數(shù)和結(jié)果是最常使用的方法。對(duì)于存放參數(shù)的寄存器和存放結(jié)果的寄存器,調(diào)用者和子程序的讀寫操作恰恰相反:
調(diào)用者將參數(shù)送入?yún)?shù)寄存器,從結(jié)果寄存器中取到返回值;
子程序從參數(shù)寄存器中取到參數(shù),將返回值送入結(jié)果寄存器。
以上就是匯編基礎(chǔ)程序編寫教程示例的詳細(xì)內(nèi)容,更多關(guān)于匯編語(yǔ)言基礎(chǔ)程序編寫的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
最好懂的匯編語(yǔ)言教程,解釋CPU如何執(zhí)行代碼
這篇文章主要介紹了最好懂的匯編語(yǔ)言教程,解釋CPU如何執(zhí)行代碼。計(jì)算機(jī)真正能夠理解的是低級(jí)語(yǔ)言,它專門用來(lái)控制硬件。匯編語(yǔ)言就是低級(jí)語(yǔ)言,直接描述/控制?CPU?的運(yùn)行。如果你想了解?CPU?到底干了些什么,以及代碼的運(yùn)行步驟,就一定要學(xué)習(xí)匯編語(yǔ)言。2022-12-12
一位數(shù)乘法的匯編語(yǔ)言實(shí)現(xiàn)方法
這篇文章主要介紹了一位數(shù)乘法的匯編語(yǔ)言實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02
匯編語(yǔ)言 輸入10個(gè)數(shù)排序并輸出的實(shí)現(xiàn)
這篇文章主要介紹了匯編語(yǔ)言 輸入10個(gè)數(shù)排序并輸出的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01
匯編語(yǔ)言功能字符串大小寫轉(zhuǎn)換實(shí)現(xiàn)實(shí)例詳解
這篇文章主要為大家介紹了匯編語(yǔ)言功能大小寫轉(zhuǎn)換實(shí)現(xiàn)的實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2021-11-11
匯編語(yǔ)言XOR指令:對(duì)兩個(gè)操作數(shù)進(jìn)行邏輯(按位)異或操作(推薦)
匯編語(yǔ)言(assembly language)是一種用于電子計(jì)算機(jī)、微處理器、微控制器或其他可編程器件的低級(jí)語(yǔ)言,亦稱為符號(hào)語(yǔ)言。這篇文章主要介紹了匯編語(yǔ)言XOR指令:對(duì)兩個(gè)操作數(shù)進(jìn)行邏輯(按位)異或操作,需要的朋友可以參考下2020-01-01
匯編語(yǔ)言指令集學(xué)習(xí)CMPXCHG比較并交換操作指令詳解
這篇文章主要為大家介紹了匯編語(yǔ)言指令集學(xué)習(xí)CMPXCHG比較并交換操作的指令詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2021-11-11

