第十章
call和ret都是轉移指令,都修改IP或同時修改CS和IP,用於子程序的設計
10.1 ret和retf
ret指令用棧中的數據,修改IP的內容,從而實現近轉移
retf指令用棧中的數據,修改CS和IP的內容,實現遠轉移
執行ret時:
1、(IP)=((ss)*16+(sp))
2、(sp)=(sp)+2
用匯編語法解釋,相當於pop IP
執行retf時:
1、(IP)=((ss)*16+(sp))
2、(sp)=(sp)+2
3、(CS)=((ss)*16+(sp))
2、(sp)=(sp)+2
用匯編語法解釋,相當於pop IP、pop CS
下面的程序中,ret指令執行後,(IP)=0,CS:IP指向代碼段的第一條指令
assume cs:code
stack segment
db 16 dup(0)
stack ends
code segment
mov ax,4c00h
int 21h
start: mov ax,stack
mov ss,ax
mov sp,16
mov ax,0
push ax
mov bx,0
ret
code ends
end start
下面的程序中,retf指令執行後,CS:IP指向代碼段的第一條
assume cs:code
stack segment
db 16 dup(0)
stack ends
code segment
mov ax,4c00h
int 21h
start: mov ax,stack
mov ss,ax
mov sp,16
mov ax,0
push cs
push ax
mov bx,0
retf
code ends
end start
10.2 call指令
CPU指向call指令時,分兩步
1、將當前的IP或CS和IP壓入棧中
2、轉移
call指令不能實現短轉移,除此之外,call指令轉移的方法與jmp指令原理相同
10.3 依據位移進行轉移的call指令
call 標號:將當前IP壓入棧後,轉移到標號處執行指令
執行過程:
1、(sp)=(sp)-2
((ss)*16+(sp))=(IP)
2、(IP)=(IP)+16位位移
16位位移由編譯器算出=標號處地址-call指令後的第一個字節的地址
範圍-32768~32767
用匯編語言解釋push IP
jmp near ptr 標號
下面程序執行後,ax中的數值爲多少?
內存地址 機器碼 彙編指令
1000:0 b8 00 00 mov ax,0
1000:3 e8 01 00 call s
1000:6 40 inc ax
1000:7 58 s:pop ax
CPU從內存中取第二條指令以後,IP指向下一條指令,(IP)=6,隨後執行call指令,IP的值入棧,IP執行標號處的指令,執行後(ax)=6,由於還沒遇到ret指令,所以ax中最終的數值是6
10.4 轉移的目的地址在指令中的call指令
call far ptr 標號 實現的是段間轉移
用匯編語言解釋push CS
push IP
jmp far ptr 標號
10.5 轉移地址在寄存器中的call指令
指令格式 call 16位reg
用匯編語法解釋push IP
jmp 16位reg
下面的程序執行後,ax中的數值爲多少
內存地址 機器碼 彙編指令
1000:0 b8 06 00 mov ax,6
1000:3 ff d0 call ax
1000:5 40 inc ax
1000:6 mov bp,sp
add ax,[bp]
CPU從內存中取完call指令後,IP指向下一條指令,(IP)=5,執行call指令,(IP)的值入棧,跳轉,在執行add指令時,因爲用bp表示偏移地址時,默認段寄存器爲ss,因此就是(ax)+之前壓入棧中IP的值,最終(ax)=0bH
10.6 轉移地址在內存中的call指令
有兩種格式
第一種:call word ptr 內存單元地址
用匯編語言解釋push IP
jmp word ptr 內存單元地址
執行下面指令後,(IP)=0123H,(sp)=0EH
mov sp,10h
mov ax,0123h
mov ds:[0],ax
call word ptr ds:[0]
第二種:call dword ptr 內存單元地址
用匯編語言解釋push CS
push IP
jmp dword ptr 內存單元地址
10.7 call和ret的配合使用
一個具有一定功能的程序段,我們稱爲子程序,在需要的時候,用call指令轉去執行,ret返回
框架
assume cs:code
code segment
main:
……
call sub1
……
mov ax,4c00h
int 21h
sub1:
……
call sub2
……
ret
sub2:
……
ret
code ends
end main
10.8 mul指令
mul是乘法指令,使用的注意點
1、兩個相乘的數,要麼都是8位,要麼都是16位。如果是8位,一個默認放在AL中,另一個放在8位reg或內存字節單元中;如果是16位,一個默認在AX中,另一個放在16位reg或內存字單元中
2、結果如果是8位乘法,結果默認放在AX中,如果是16位乘法,結果高位默認在DX中存放,低位在AX中放
格式:mul reg/內存單元
內存單元可以用不同的尋址方式給出,如
mul byte ptr ds:[0]
mul word ptr [bx+si+8]
計算100*10000,由於10000大於255,所以必須做16位乘法
mov ax,100
mov bx,10000
mul bx
10.9 模塊化程序設計
利用call和ret實現多個相互聯繫、功能獨立的子程序解決一個複雜的問題
10.10 參數和結果的傳遞問題
用寄存器來存儲參數和結構是最常用的方法
編程,計算data段中第一組數據的三次方,結果保留在後面一組dword單元中
assume cs:code
data segment
dw 1,2,3,4,5,6,7,8
dd 0,0,0,0,0,0,0,0
data ends
code segment
start: mov ax,data
mov ds,ax
mov si,0 ; ds:si指向第一組word單元
mov di,16 ; ds:di指向第二組dword單元
mov cx,8
s: mov bx,[si]
call cube
mov [di],ax ; 存儲低位數據
mov [di].2.dx ; 存儲高位數據
add si,2
add di,4
loop s
mov ax,4c00h
int 21h
cube: mov ax,bx
mul bx
mul bx
code ends
end start
10.11 批量數據的傳遞
如果要傳入多個參數,寄存器的數量是不足的,這種時候,我們將批量數據放到內存中,然後將它們所在內存空間的首地址放在寄存器中,傳遞給需要的子程序,對於批量返回的結果,用同樣的方法
編程,將data段中的字符串轉化爲大寫
assume cs:code
data segment
db 'conversation'
data ends
code segment
start: mov ax,data
mov ds,ax
mov si,0 ; ds:si指向批量數據所在空間的首地址
mov cx,12 ; cx存放字符串的長度
call captial
mov ax,4c00h
int 21h
capital: and byte ptr [si],11011111b ; 表明參與運算的數據大小
inc si
loop capital
ret
code ends
end start
注意,除了用寄存器傳遞參數外,還可以用棧來傳遞參數
10.12 寄存器衝突的問題
編程,將一個全是字母,以0結尾的字符串轉化爲大寫
因爲0標誌着字符串結束,所以子程序不需要字符串的長度作爲參數,用jcxz來檢測0
但是這會出現一個問題:因爲可能有不止一個字符串,因此需要循環將字符串變爲大寫,而在子程序中,jczx是根據cx的值進行跳轉,因此子程序會改變cx的值,影響程序 的正確運行
在編寫子程序時,注意子程序不要使用會和主程序產生衝突的寄存器,儘量使用其他寄存器代替
如果都必須用到同一個寄存器,則在子程序開始前將所用到的寄存器的內容先保存起來,可以用棧來保存,還要注意出棧的順序
assume cs:code
data segment
db 'word',0
db 'unix',0
db 'wind',0
db 'good',0
data ends
code segment
start: mov ax,data
mov ds,ax
mov bx,0
mov cx,4
s: mov si,bx
call capital
add bx,5
loop s
mov ax,4c00h
int 21h
capital: push cx
push si
change: mov cl,[si]
mov ch,0
jczx ok
and byte ptr [si],11011111b
inc si
jmp short change
ok: pop si
pop cx
ret
code ends
end start
編寫子程序的標準框架
子程序開始:子程序中使用的寄存器入棧
子程序內容
子程序中使用的寄存器出棧
返回