# 第10章 call和ret指令
ret指令用棧中的資料,修改ip的內容,從而實現近轉移。
retf指令用棧中的資料,修改cs和ip內容,從而實現遠轉移。
cpu執行ret指令時,進行下面兩步操作:
(ip)=((ss)*16+(sp))
(sp)=(sp)+2
這是將棧頂的資料存入ip,然後將該資料進行出棧操作。
cpu執行retf指令時,進行下面四步操作:
(ip)=((ss)*16+(sp))
(sp)=(sp)+2
(cs)=((ss)*16+(sp))
(sp)=(sp)+2
通過上面可以看出,段位址儲存在高位,故要先儲存cs,之後在儲存ip,這種操作不要忘記。例子:
push cs
push ax
retf
cpu執行call指令時,進行兩步操作:
將當前的ip或cs和ip壓入棧中;
轉移。
call指令不能實現短轉移,除此之外,和jmp指令的原理相同。
call 標號(將call指令的下一條指令壓入棧後,轉到標號處之行指令)
還記得cpu讀取指令的順序嗎?讀取一條指令進入指令緩衝區後,ip立刻指向下乙個指令。所以,在緩衝區的call指令執行儲存ip時,是指向下一條指令而不是本身指令,這個概念應該理解。cpu執行call指令時,進行如下的操作:另外,call指令就相當於完成上面所講的入棧操作。
(sp)=(sp)-2 |((ss)*16+(sp))=(ip)
(ip)=(ip)+16位位移
16位位移由編譯程式算出。
相當於:
push ip
jmp near ptr 標號
call far ptr 標號
相當於:
push cs
push ip
jmp far ptr 標號
call 16位reg
注意,乙個暫存器大小為16位,乙個字,僅能代表乙個位址,所以,這裡自然就是偏移位址了。
push ip
jmp16位 reg
call word ptr 記憶體單元位址
call dword ptr 記憶體單元位址
這個的區別一目了然。
前者針對偏移位址,而後者針對目的位址和偏移位址。
後者相當於:
push cs
push ip
jmp dword ptr 記憶體單元
其中根據棧的規則,必須先存放cs,這個概念是你要理解的。
舉個例子:
mov sp,0h
mov ax,0123h
mov ds:[0],ax ;低位,這個是ip位址。
mov word ptr ds:[2],0 ;高位,這個是段位址。
call dword ptr ds:[0]
我們應該著重去理解其記憶體中有如何向記憶體中寫入位址,很簡單,就是手動寫入,然後宣告位址的開頭,程式就會自動讀取,就是這個樣子。
我們可以通過call和ret指令,來實現子程式的機制
舉個例子:
start:
mov ax,1
mov cx,3
call s
mov bx,ax
mov ax,4c00h
int 21h
s: add ax,ax
loop s
ret
通過這個程式,可以看出,這裡使用call 標號
的指令,這裡的標號可以直接使用段標記。
這裡進入子程式s,然後算其三次方,結果儲存在ax中,最後ret返回到call下一條指令。
這些邏輯順序其實是很好理解的。
介紹一下mul指令,mul是乘法指令,使用mul指令時,注意以下兩點:
兩個相乘的數,要麼都是8位,要麼都是16位。如果時8位,乙個預設放在al中,另乙個放在8位reg或記憶體位元組單元中;如果是16位,乙個預設在ax中,另乙個放在16位reg或記憶體字單元中。
結果:如果是8位乘法,結果預設放在ax中;如果是16位乘法,結果高位預設放在dx中,低位放在ax中。
格式如下:
mul reg
mul 記憶體單元
記憶體單元可以用不同的定址方式給出,比如:
mul byte ptr ds:[0]
從上面我們看到,call和ret指令共同支援了組合語言程式設計中的模組化設計。在實際程式設計中,程式的模組化是比不可少的。因為實現的問題比較複雜,對現實問題進行分析時,把它轉化為相互聯絡,不同層次的子問題,是必須解決的辦法,而利用call和ret指令則是很好的解決方法
這裡設計子程式傳遞引數時存在的兩個問題:
將引數n儲存在什麼地方?
計算得到的值,儲存在什麼地方?
用暫存器來儲存引數和結果是最常使用的方法。
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
mov di,16
mov cx,8
s: mov bx,[si]
call cube
mov [di],ax
mov [di].2,dx
add si,2
add di,4
loop s
cube: mov
往記憶體中寫,其實就是用mov指令,這裡還有點「恐懼」,其實就是這點樣子,你隨便指明個記憶體單元,然後下標的形式來表示出相對位址,後面是要儲存的資料。
其中,si有資料,所以指向棧界。而di無資料,指向棧頂。
在建立相關記憶體空間時,利用 關鍵字 dd,dw之類的就能很好的建立出你所想要建立的記憶體。
而往記憶體中寫入結果,則利用[di]就行,[di].2這種相對位址的給出辦法,你不用在去改變di的值,在最後位址改變兩個字,即4位,這種程式的執行方式其實很好理解的。
暫存器的數量終究是有限的,當資料量過大,顯然不能放在暫存器中訪問,這是,你可以利用[di],[si]這種來表示記憶體位址,直接在記憶體中進行修改。
對於記憶體位址的修改,我們似乎不再那麼恐懼,我們可以使用[di]的形式來指向記憶體,然後對di加法運算之類的來更好的。我們需要明確的就是開始讓(di)==0,這種思路就會很好理解的。
舉個例子:將data中的字串轉化為大寫
assume cs:code
data segment
db'conversation'
data ends
code segment
start: mov ax,data
mob ds,ax
mov si,0
mov cx,12
call capital
mov ax,4c00h
int 21h
capital:and
byte ptr [si],11011111b
inc si
loop capital
retcode ends
endstart
」運算;增加位址;迴圈到運算「,這種步驟是你需要明確的。
如jcxz和loop指令都會用到暫存器cx,這樣如果在一起使用,很可能會發生衝突,這是你需要明確的。
我們乙個常用的解決辦法就是將你的資料壓入棧中,等之後再推出來,這種操作是需要你明確的。
舉個例子:要程式處理的字串以0位結尾符,小寫換大寫,這個字串可以如下定義:
data segment
db 'conversation',0
db 'conversation',0
db 'conversation',0
segment ends
code segment
strat: 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
int21hcapital: push cx
push si
change: mov cl,[si]
mov ch,0
jcxz ok
and byte ptr [si],11011111b
inc si
jmp short change
ok: pop si
pop cx
ret
其中哪些地方是你需要處理的,一定要注意好程式的入棧和出棧順序
還有要確保所有內容都出棧時才使用ret,否則可能導致資料提取的不對。
該程式是雙迴圈的架構,裡面處理的是一行中的內容,這個邏輯是你要明確的!
編寫子程式的標準框架:
子程式開始: 子程式中使用的暫存器入棧
子程式內容
子程式中使用的暫存器出棧
返回(ret,retf)
組合語言第十章call和ret指令
ret 指令用棧中的資料,替換掉ip裡的內容,從而實現近轉移。retf 指令則是修改cs和ip的內容實現遠轉移 return far ret指令 ip ss 16 sp sp sp 2 retf指令 ip ss 16 sp call指令兩個步驟 將當前的ip或ip和cs壓入棧中 sp sp 2 ss...
第十章 call和ret指令
目錄mul指令 課後檢測點 這章主要學習call和ret兩個指令,前者的作用相當於現在高階語言的方法呼叫,後者相當於高階語言的方法返回 call指令分為了根據偏移量轉移和根據目的地轉移兩種格式 call 16位暫存器 call 標號 call word ptr 記憶體單元位址 call far pt...
彙編學習筆記 第十章 CALL和RET指令
call和ret指令都是轉移指令,它們都修改cs和ip。經常被共同用於實現子程式的設計。這一章,我們講解call和ret指令的原理 retf指令用棧中的資料,修改cs和ip的內容,從而實現遠轉移 cpu執行call指令時,進行兩步操作 call指令不能實現短轉移,除此之外,call與jmp類似。接下...