《組合語言》第十章 CALL和RET指令

2021-08-17 04:06:54 字數 4803 閱讀 4836

# 第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時,是指向下一條指令而不是本身指令,這個概念應該理解。

另外,call指令就相當於完成上面所講的入棧操作。

cpu執行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類似。接下...