1. 程式人生 > 其它 >王爽《組合語言》筆記(詳細)

王爽《組合語言》筆記(詳細)

王爽《組合語言》筆記(詳細)

轉自:https://blog.csdn.net/qq_39654127/article/details/88698911 文章目錄

 

一、基礎知識


1、指令

機器指令:CPU能直接識別並執行的二進位制編碼

彙編指令:彙編指令是機器指令的助記符,同機器指令一一對應。

指令:指令通常由操作碼和地址碼(運算元)兩部分組成

指令集:每種CPU都有自己的彙編指令集。

組合語言由3類指令組成。

  • 彙編指令
  • 偽指令:沒有對應的機器碼,由編譯器執行,計算機並不執行
  • 其他符號:如+、-、*、/等,由編譯器識別,沒有對應的機器碼。

編譯器:夠將彙編指令轉換成機器指令的翻譯程式每一種CPU都有自己的彙編指令集。

在記憶體或磁碟上,指令和資料沒有任何區別,都是二進位制資訊

2、儲存器

隨機儲存器(RAM)在程式的執行過程中可讀可寫,必須帶電儲存

只讀儲存器(ROM)在程式的執行過程中只讀,關機資料不丟失



(以上3張圖片來自王道考研 - 計算機組成原理課件)

3、匯流排


1、匯流排

匯流排是連線各個部件的資訊傳輸線,是各個部件共享的傳輸介質

主機板上有核心器件和一些主要器件,這些器件通過匯流排(地址匯流排、資料匯流排、控制匯流排)相連。這些器件有CPU、儲存器、外圍晶片組、擴充套件插槽等。擴充套件插槽上一般插有RAM記憶體條和各類介面卡。

匯流排根據位置分類:

  • 片內匯流排(晶片內部匯流排)

  • 系統匯流排(計算機各部件之間的資訊傳輸線)

    根據傳送資訊的不同,系統匯流排從邏輯上又分為3類,地址匯流排、控制匯流排和資料匯流排。

CPU要想進行資料的讀寫,必須和外部器件(標準的說法是晶片)進行以下3類資訊的互動。

  1. 地址匯流排:CPU通過地址匯流排來指定儲存單元

    1根導線可以傳送的穩定狀態只有兩種,高電平或是低電平。用二進位制表示就是1或0

圖示有10根地址線即一次可以傳輸10位,訪問儲存單元地址為1011,定址範圍為0 ~ (210 - 1)

  1. 資料匯流排:CPU與記憶體或其他器件之間的資料傳送是通過資料匯流排來進行的

    8根資料線一次可傳送一個8位二進位制資料(即一個位元組),傳送2個位元組需要兩次;16根資料線一次可傳送2個位元組(記憶體對齊核心原理)

  2. 控制匯流排:CPU對外部器件的控制是通過控制匯流排來進行的。

有多少根控制匯流排,就意味著CPU提供了對外部器件的多少種控制。
所以,控制匯流排的寬度決定了CPU對外部器件的控制能力。

2、CPU對儲存器的讀寫


1、 CPU通過地址線將地址資訊3發出。
2、 CPU通過控制線發出記憶體讀命令,選中儲存器晶片,並通知它,將要從中讀取資料。
3、 儲存器將3號單元中的資料8通過資料線送入CPU。寫操作與讀操作的步驟相似。
聯想:在組成原理中用微操作表示:(PC) → MAR; 1 → R; M(MAR) → MDR; …

3、CPU對外設的控制

CPU對外設都不能直接控制,如顯示器、音箱、印表機等。

直接控制這些裝置進行工作的是插在擴充套件插槽上的介面卡。

擴充套件插槽通過匯流排和CPU相連,所以介面卡也通過匯流排同CPU相連。CPU可以直接控制這些介面卡,從而實現CPU對外設的間接控制。

如:CPU無法直接控制顯示器,但CPU可以直接控制顯示卡,從而實現對顯示器的間接控制

4、記憶體地址空間

CPU將系統中各類儲存器看作一個邏輯儲存器,這個邏輯儲存器就是我們所說的記憶體地址空間。
對於CPU,所有儲存器中的儲存單元都處於一個統一的邏輯儲存器中,它的容量受CPU定址能力限制。(或許就是計組中學的統一編址吧)

每個物理儲存器在這個邏輯儲存器中佔有一個地址段,即一段地址空間。CPU在這段地址空間中讀寫資料,實際上就是在相對應的物理儲存器中讀寫資料(對ROM寫無效)。

二、暫存器


1、暫存器

CPU由運算器、控制器、暫存器等器件構成,這些器件靠片內匯流排相連。

運算器進行資訊處理;控制器控制各種器件進行工作;暫存器進行資訊儲存;

8086CPU有14個暫存器:AX、BX、CX、DX、SI、DI、SP、BP、IP、CS、SS、DS、ES、PSW都是16位

16位結構CPU具有下面幾方面的結構特性。

  • 運算器一次最多可以處理16位的資料;
  • 暫存器的最大寬度為16位;
  • 暫存器和運算器之間的通路為16位。

8086CPU可以一次性處理以下兩種尺寸的資料。

  • 位元組:記為byte,一個位元組由8個bit組成,可以存在8位暫存器中。
  • 字:記為word,一個字由兩個位元組組成,可以存在一個16位暫存器中(16位CPU)

    8086採用小端模式:高地址存放高位位元組,低地址存放低位位元組。

2、通用暫存器

通用暫存器:通常用來存放一般性的資料,有AX、BX、CX、DX,它們可分為兩個可獨立使用的8位暫存器,

16位 8高位 8低位
AX AH AL
BX BH BL
CX CH CL
DX DH DL

在進行資料傳送或運算時,要注意指令的兩個操作物件的位數應當是一致的

一個8位暫存器所能儲存的資料範圍是0 ~ 28-1。

3、8086CPU給出實體地址的方法

8086CPU有20位地址匯流排,可以傳送20位地址,達到1MB定址能力。
8086CPU又是16位結構,在內部一次性處理、傳輸、暫時儲存的地址為16位。
從8086CPU的內部結構來看,如果將地址從內部簡單地發出,那麼它只能送出16位的地址,表現出的定址能力只有64KB。
8086CPU採用一種在內部用兩個16位地址合成的方法來形成一個20位的實體地址。

當8086CPU要讀寫記憶體時:

  1. CPU中的相關部件提供兩個16位的地址,一個稱為段地址,另一個稱為偏移地址;
  2. 地址加法器將兩個16位地址合成為一個20位的實體地址;

地址加法器採用實體地址 = 段地址×16 + 偏移地址的方法用段地址和偏移地址合成物理地址。

例如,8086CPU要訪問地址為123C8H的記憶體單元,1230H左移一位(空出4位)加上00C8H合成123C8H

4、段暫存器

我們可以將一段記憶體定義為一個段,用一個段地址指示段,用偏移地址訪問段內的單元,可以用分段的方式來管理記憶體。

用一個段存放資料,將它定義為“資料段”;

用一個段存放程式碼,將它定義為“程式碼段”;

用一個段當作棧,將它定義為“棧段”。

注意:

  • 一個段的起始地址一定是16的倍數;
  • 偏移地址為16位,變化範圍為0-FFFFH,所以一個段的長度最大為64KB。
  • CPU可以用不同的段地址和偏移地址形成同一個實體地址。

段暫存器:8086CPU有4個段暫存器:CS、DS、SS、ES,提供記憶體單元的段地址。

1、CS和IP

CS為程式碼段暫存器,IP為指令指標暫存器,

CPU將CS、IP中的內容當作指令的段地址和偏移地址,用它們合成指令的實體地址,

CPU將CS:IP指向的內容當作指令執行。(即PC)

8086CPU的工作過程簡要描述

  1. 從CS:IP指向的記憶體單元讀取指令,讀取的指令進入指令緩衝器;
  2. IP=IP+所讀取指令的長度,從而指向下一條指令;
  3. 執行指令。轉到步驟1,重複這個過程。

在8086CPU加電啟動或復位後(即CPU剛開始工作時)CS和IP被設定為CS=FFFFH,IP=0000H,即在8086PC機剛啟動時,FFFF0H單元中的指令是8086PC機開機後執行的第一條指令。

8086CPU提供轉移指令修改CS、IP的內容。

  • jmp 段地址:偏移地址:用指令中給出的段地址修改CS,偏移地址修改IP。如:jmp 2AE3:3

  • jmp 某一合法暫存器:僅修改IP的內容。如:jmp ax。在含義上好似:mov IP,ax

8086CPU不支援將資料直接送入段暫存器的操作,這屬於8086CPU硬體設計

2、DS 和 [address]

DS暫存器:通常用來存放要訪問資料的段地址

[address]表示一個偏移地址為address的記憶體單元,段地址預設放在ds中

通過資料段段地址和偏移地址即可定位記憶體單元。

mov bx, 1000H ;8086CPU不支援將資料直接送入段暫存器的操作

mov ds, bx ;ds存放資料段地址

mov [0], al ;將al資料(1位元組)存到1000H段的0偏移地址處,即10000H

mov ax, [2] ;將資料段偏移地址2處的一個字(8086為2位元組)存放到ax暫存器

add cx, [4] ;將偏移地址4處的一個字資料加上cx暫存器資料放到cx暫存器

sub dx, [6] ;dx暫存器資料減去資料段偏移地址6處的字資料存到dx
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

3、SS 和 SP

在基於8086CPU程式設計的時候,可以將一段記憶體當作棧來使用。

棧段暫存器SS,存放段地址,SP暫存器存放偏移地址,任意時刻,SS:SP指向棧頂元素

8086CPU中,入棧時,棧頂從高地址向低地址方向增長。

push ax表示將暫存器ax中的資料送入棧中,由兩步完成。

  1. SP=SP-2,SS:SP指向當前棧頂前面的單元,以當前棧頂前面的單元為新的棧頂;
  2. 將ax中的內容送入SS:SP指向的記憶體單元處,SS:SP此時指向新棧頂。

pop ax表示從棧頂取出資料送入ax,由以下兩步完成。

  1. 將SS:SP指向的記憶體單元處的資料送入ax中;
  2. SP=SP+2,SS:SP指向當前棧頂下面的單元,以當前棧頂下面的單元為新的棧頂。

實驗

  1. 將10000H~1000FH這段空間當作棧,初始狀態棧是空的;
  2. 設定AX=001AH,BX=001BH;
  3. 將AX、BX中的資料入棧;
  4. 然後將AX、BX清零;
  5. 從棧中恢復AX、BX原來的內容。
mov ax, 1000H 
mov ss, ax 
mov sp, 0010H    ;初始化棧頂
mov ax, 001AH
mov bx, 001BH 

push ax 
push bx    ;ax、bx入棧

sub ax, ax   ;將ax清零,也可以用mov ax,0,
             ;sub ax,ax的機器碼為2個位元組,
             ;mov ax,0的機器碼為3個位元組。
        
sub bx, bx 

pop bx  ;從棧中恢復ax、bx原來的資料
pop ax  ;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

三、第一個程式

1、彙編程式從寫出到執行的過程


載入後,CPU的CS:IP指向程式的第一條指令(即程式的入口)

;1.asm
assume cs:codesg ;將用作程式碼段的段codesg和段暫存器cs聯絡起來。

codesg segment ;定義一個段,段的名稱為“codesg”,這個段從此開始
			   ;codesg是一個標號,作為一個段的名稱,最終被編譯連線成一個段的段地址

	mov ax, 0123H
	mov bx, 0456H 
	add ax, bx
	add ax, ax 
	
	mov ax, 4c00H 
	int 21H ;這兩條指令實現程式的返回
	
codesg ends ;名稱為“codesg”的段到此結束

end ;編譯器在編譯彙編程式的過程中,碰到了偽指令end,結束對源程式的編譯
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17


2、程式執行過程跟蹤

DOS系統中.EXE檔案中的程式的載入過程


四、[bx] 和 loop指令


1、[bx] 和 loop指令

[bx] 的含義:[bx]同樣表示一個記憶體單元,它的偏移地址在bx中,段地址預設在ds中

loop指令的格式是:loop 標號,CPU執行loop指令的時候,要進行兩步操作,

  1. (cx) = (cx) - 1;

  2. 判斷 cx 中的值,不為零則轉至標號處執行程式,如果為零則向下執行。

例如:計算212

assume cs:code 

code segment 
	mov ax, 2
	
	mov cx, 11 ;迴圈次數
s:  add ax, ax 
	loop s     ;在組合語言中,標號代表一個地址,標號s實際上標識了一個地址,
               ;這個地址處有一條指令:add ax,ax。
               ;執行loop s時,首先要將(cx)減1,然後若(cx)不為0,則向前
               ;轉至s處執行add ax,ax。所以,可以利用cx來控制add ax,ax的執行次數。
	
	mov ax,4c00h 
	int 21h 
code ends 
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

loop 和 [bx] 的聯合應用

計算ffff:0 ~ ffff:b單元中的資料的和,結果儲存在dx中

問題分析:

  1. 這些記憶體單元都是位元組型資料範圍0 ~ 255 ,12個位元組資料和不會超過65535,dx可以存下
  2. 對於8位資料不能直接加到 dx

解決方案:

用一個16位暫存器來做中介。將記憶體單元中的8位資料賦值到一個16位暫存器a中,再將ax中的資料加到dx

assume cs:code 

code segment 
	mov ax, 0ffffh ;在彙編源程式中,資料不能以字母開頭,所以要在前面加0。
	mov ds, ax 
	mov bx, 0   ;初始化ds:bx指向ffff:0
	mov dx, 0   ;初始化累加暫存器dx,(dx)= 0
	
	mov cx, 12  ;初始化迴圈計數暫存器cx,(cx)= 12
s:  mov al, [bx]
	mov ah, 0
	add dx, ax  ;間接向dx中加上((ds)* 16 +(bx))單元的數值
	inc bx      ;ds:bx指向下一個單元
	loop s 
	
	mov ax, 4c00h 
	int 21h 
code ends 
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

2、段字首

mov ax, ds:[bx]
mov ax, cs:[bx]
mov ax, ss:[bx]
mov ax, es:[bx]
mov ax, ss:[0]
mov ax, cs:[0]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

這些出現在訪問記憶體單元的指令中,用於顯式地指明記憶體單元的段地址
的“ds:”,“cs:”,“ss:”,“es:”,在組合語言中稱為段字首。

段字首的使用

將記憶體ffff:0 ~ ffff:b單元中的資料複製到0:200 ~ 0:20b單元中。

assume cs:code 

code segment 
	mov ax, 0ffffh 
	mov ds, ax   ;(ds)= 0ffffh 
	mov ax, 0020h
    mov es, ax   ;(es)= 0020h     0:200 等效於 0020:0
    mov bx, 0    ;(bx)= 0,此時ds:bx指向ffff:0,es:bx指向0020:0
    
	mov cx,12   ;(cx)=12,迴圈12次
s:  mov dl,[bx] ;(d1)=((ds)* 16+(bx)),將ffff:bx中的位元組資料送入dl 
	mov es:[bx],dl ;((es)*16+(bx))=(d1),將dl中的資料送入0020:bx 
	inc bx  ;(bx)=(bx)+1
	loop s 
	
	mov ax,4c00h 
	int 21h 
code ends 
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

五、包含多個段的程式

程式中對段名的引用,將被編譯器處理為一個表示段地址的數值。

mov ax, data 

mov ds, ax 

mov bx, ds:[6]
  • 1
  • 2
  • 3
  • 4
  • 5

在程式碼段中使用資料

;計算 8 個數據的和存到 ax 暫存器
assume cs:code 

code segment 

	dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h ;define word 定義8個字形資料

	start:	mov bx, 0  ;標號start
			mov ax, 0  
			
			mov cx, 8
	s:		add ax, cs:[bx]
			add bx, 2
			loop s 
			
			mov ax, 4c00h 
			int 21h 
code ends
end start    ;end除了通知編譯器程式結束外,還可以通知編譯器程式的入口在什麼地方
	     	 ;用end指令指明瞭程式的入口在標號start處,也就是說,“mov bx,0”是程式的第一條指令。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

在程式碼段中使用棧

;利用棧,將程式中定義的資料逆序存放。
assume cs:codesg 

codesg segment 
	dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h ; 0-15單元
	dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ; 16-47單元作為棧使用
			
	start:	mov ax, cs 
			mov ss, ax 
			mov sp, 30h ;將設定棧頂ss:sp指向棧底cs:30。   30h = 48d
			mov bx, 0
			
			mov cx, 8
	s:		push cs:[bx]
			add bx, 2
			loop s    ;以上將程式碼段0~15單元中的8個字型資料依次入棧
			
			mov bx, 0
			
			mov cx, 8
	s0:		pop cs:[bx]		
			add bx,2
			loop s0   ;以上依次出棧8個字型資料到程式碼段0~15單元中
			
			mov ax,4c00h 
			int 21h 
codesg ends 
end start	;指明程式的入口在start處
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

將資料、程式碼、棧放入不同的段

assume cs:code,ds:data,ss:stack 

data segment 
	dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h ;0-15單元
data ends 

stack segment 
	dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ;0-31單元
stack ends 

code segment 
	start:	mov ax, stack;將名稱為“stack”的段的段地址送入ax
			mov ss, ax
			mov sp, 20h  ;設定棧頂ss:sp指向stack:20。 20h = 32d
			
			mov ax, data ;將名稱為“data”的段的段地址送入ax
			mov ds, ax   ;ds指向data段
			
			mov bx, 0    ;ds:bx指向data段中的第一個單元
			
			mov cx, 8
	s:	    push [bx]
			add bx, 2
			loop s       ;以上將data段中的0~15單元中的8個字型資料依次入棧
			
			mov bx, 0
			
			mov cx, 8
	s0:		pop [bx]
			add bx, 2
			loop s0      ;以上依次出棧8個字型資料到data段的0~15單元中
			
			mov ax, 4c00h 
			int 21h 
code ends
end start
;“end start”說明了程式的入口,這個入口將被寫入可執行檔案的描述資訊,
;可執行檔案中的程式被載入入記憶體後,CPU的CS:IP被設定指向這個入口,從而開始執行程式中的第一條指令
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

關於可執行檔案結構與程式入口的詳細描述參考:PE檔案結構

六、更靈活的定位記憶體地址的方法


1、and 和 or

and指令:邏輯與指令,按位進行與運算。

mov al, 01100011B
and al, 00111011B

執行後:al=00100011B即都為1才為1

or指令:邏輯或指令,按位進行或運算。

mov al, 01100011B
or al, 00111011B
執行後:al=01111011B 即只要有一個為1就為1

關於ASCII碼
世界上有很多編碼方案,有一種方案叫做ASCII編碼,是在計算機系統中通常被採用的。簡單地說,所謂編碼方案,就是一套規則,它約定了用什麼樣的資訊來表示現實物件。比如說,在ASCII編碼方案中,用61H表示“a”,62H表示“b”。一種規則需要人們遵守才有意義。

在文字編輯過程中,我們按一下鍵盤的a鍵,就會在螢幕上看到“a”。我們按下鍵盤的a鍵,這個按鍵的資訊被送入計算機,計算機用ASCII碼的規則對其進行編碼,將其轉化為61H儲存在記憶體的指定空間中;文字編輯軟體從記憶體中取出61H,將其送到顯示卡上的視訊記憶體中;工作在文字模式下的顯示卡,用ASCII碼的規則解釋視訊記憶體中的內容,
61H被當作字元“a”,顯示卡驅動顯示器,將字元“a”的影象畫在螢幕上。我們可以看到,顯示卡在處理文字資訊的時候,是按照ASCII碼的規則進行的。這也就是說,如果我們要想在顯示器上看到“a”,就要給顯示卡提供“a”的ASCIⅡ碼,61H。如何提供?當然是寫入視訊記憶體中。

以字元形式給出的資料

assume cs:code,ds:data 

data segment 
	db 'unIx'   ;相當於“db 75H,6EH,49H,58H”
	db 'foRK'
data ends 

code segment
start:	mov al, 'a'  ;相當於“mov al, 61H”,“a”的ASCI碼為61H;
		mov b1, 'b'
		
		mov ax, 4c00h 
		int 21h 
code ends
end start
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

大小寫轉換的問題

小寫字母的ASCII碼值比大寫字母的ASCII碼值大20H

大寫字母ASCII碼的第5位為0,小寫字母的第5位為1(其他一致)

assume cs:codesg,ds:datasg 

datasg segment 
	db 'BaSiC'
	db 'iNfOrMaTion'
datasg end

codesg segment 
	start:	mov ax, datasg 
			mov ds, ax	;設定ds 指向 datasg段
		
			mov bx, 0	;設定(bx)=0,ds:bx指向’BaSic’的第一個字母
			
			mov cx, 5     	 ;設定迴圈次數5,因為’Basic'有5個字母
	s:		mov al, [bx]     ;將ASCII碼從ds:bx所指向的單元中取出
			and al, 11011111B;將al中的ASCII碼的第5位置為0,變為大寫字母
			mov [bx], al	 ;將轉變後的ASCII碼寫回原單元
			inc bx		     ;(bx)加1,ds:bx指向下一個字母
			loop s 
			
			mov bx, 5	;設定(bx)=5,ds:bx指向,iNfOrMaTion'的第一個字母
			
			mov cx, 11	;設定迴圈次數11,因為‘iNfOrMaTion'有11個字母
	s0:		mov al, [bx]
			or al, 00100000B;將a1中的ASCII碼的第5位置為1,變為小寫字母
			mov [bx], al 
			inc bx
			loop s0
			
			mov ax, 4c00h 
			int 21h 
codesg ends
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

2、[bx+idata]

[bx+idata]表示一個記憶體單元, 例如:mov ax, [bx+200]
該指令也可以寫成如下格式:

mov ax, [200+bx]

mov ax, 200[bx]

mov ax, [bx].200
  • 1
  • 2
  • 3
  • 4
  • 5

用[bx+idata]的方式進行陣列的處理

assume cs:codesg,ds:datasg 

datasg segment 
	db 'BaSiC';轉為大寫
	db 'MinIx';轉為小寫
datasg ends

codesg segment
	start:
		mov ax, datasg 
		mov ds, ax 
		mov bx, 0  ;初始ds:bx
	
		mov cx, 5
	s:	mov al, 0[bx]  
		and al, 11011111b ;轉為大寫字母
		mov 0[bx], al ;寫回
		mov al, 5[bx]  ;[5 + bx]
		or al, 00100000b ;轉為小寫字母
		mov 5[bx], al 
		inc bx
		loop s
		
		mov ax, 4c00h 
		int 21h
codesg ends
end start
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

C語言描述

int main()
{
	char a[] = "BaSic";
	char b[] = "MinIX";
	
	int i = 0;
	
	do
	{
		a[i] = a[i] & 0xDF;
		b[i] = b[i] | 0x20;
		i++;
	} while(i < 5);

	return 0;
 } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

3、SI 、DI 與 定址方式的靈活應用

1、si 、di

si和di是8086CPU中和bx功能相近的暫存器,si和di不能夠分成兩個8位暫存器來使用。

assume cs: codesg, ds: datasg 

datasg segment 
	db 'welcome to masm!';用si和di實現將字串‘welcome to masm!"複製到它後面的資料區中。
	db '................'
datasg ends

codesg segment 
	start:	mov ax, datasg 
			mov ds, ax 
			mov si, 0
			
			mov cx, 8
	s:		mov ax, 0[si] ;[0 + si]
			mov 16[si], ax ;[16 + si] 使用[bx +idata]方式代替di,使程式更簡潔
			add si, 2 
			loop s 
			
			mov ax, 4c00h 
			int 21h 
codesg ends 
end start
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

2、[bx + si] 和 [bx + di]

[bx+si]和[bx+di]的含義相似

[bx+si]表示一個記憶體單元,它的偏移地址為(bx)+(si)

指令mov ax, [bx + si]的含義:將一個記憶體單元字資料的內容送入ax,段地址在ds中

該指令也可以寫成如下格式:mov ax, [bx][si]

3、[bx+si+idata]和[bx+di+idata]
[bx+si+idata]表示一個記憶體單元,它的偏移地址為(bx)+(si)+idata

指令mov ax,[bx+si+idata]的含義:將一個記憶體單元字資料的內容送入ax,段地址在ds中

4、不同的定址方式的靈活應用
[idata]用一個常量來表示地址,可用於直接定位一個記憶體單元;
[bx]用一個變數來表示記憶體地址,可用於間接定位一個記憶體單元;
[bx+idata]用一個變數和常量表示地址,可在一個起始地址的基礎上用變數間接定位一個記憶體單元;
[bx+si]用兩個變量表示地址;
[bx+si+idata]用兩個變數和一個常量表示地址。


;將datasg段中每個單詞改為大寫字母
assume cs:codesg,ds:datasg,ss:stacksg 

datasg segment
	db 'ibm            ' ;16
	db 'dec            ' 
	db 'dos            '
	db 'vax            '  ;看成二維陣列
datasg ends 

stacksg segment ;定義一個段,用來做棧段,容量為16個位元組
	dw 0, 0, 0, 0, 0, 0, 0, 0
stacksg ends 

codesg segment 
	start:	mov ax, stacksg 
			mov ss, ax
			mov sp, 16 
			mov ax, datasg 
			mov ds, ax 
			mov bx, 0 ;初始ds:bx
			
			;cx為預設迴圈計數器,二重迴圈只有一個計數器,所以外層迴圈先儲存cx值,再恢復,我們採用棧儲存
			mov cx, 4
	s0:		push cx	;將外層迴圈的cx值入棧
			mov si, 0
			mov cx, 3	;cx設定為內層迴圈的次數
	s:		mov al, [bx+si]
			and al, 11011111b ;每個字元轉為大寫字母
			mov [bx+si], al 
			inc si
			loop s 
			
			add bx, 16 ;下一行
			pop cx	;恢復cx值
			loop s0 ;外層迴圈的loop指令將cx中的計數值減1
			
			mov ax,4c00H 
			int 21H 
codesg ends
end start
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

七、資料處理的兩個基本問題

1、 bx、si、di和bp

在8086CPU中,只有這4個暫存器可以用在“[…]”中來進行記憶體單元的定址。

在[ ]中,這4個暫存器可以單個出現,或只能以4種組合出現:bx和si、bx和dibp和si、bp和di

只要在[……]中使用暫存器bp,而指令中沒有顯性地給出段地址, 段地址就預設在ss中

2、機器指令處理的資料在什麼地方

資料處理大致可分為3類:讀取、寫入、運算。

在機器指令這一層來講,並不關心資料的值是多少,而關心指令執行前一刻,它將要處理的資料所在的位置。指令在執行前,所要處理的資料可以在3個地方:CPU內部、記憶體、埠

3、組合語言中資料位置的表達

組合語言中用3個概念來表達資料的位置

  • 立即數(idata)
mov ax, 1                 ;對於直接包含在機器指令中的資料(執行前在CPU的指令緩衝器中)
add bx, 2000h             ;在組合語言中稱為:立即數(idata)
or bx, 00010000b
mov al, 'a'
  • 1
  • 2
  • 3
  • 4
  • 暫存器
mov ax, bx     ;指令要處理的資料在暫存器中,在彙編指令中給出相應的暫存器名。
mov ds, ax 
push bx 
mov ds:[0], bx 
push ds 
mov ss, ax
mov sp, ax
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 段地址(SA)和偏移地址(EA)
;指令要處理的資料在記憶體中,在彙編指令中可用[X]的格式給出EA,SA在某個段暫存器中。
mov ax, [0]
mov ax, [di]
mov ax, [bx+8]
mov ax, [bx+si]
mov ax, [bx+si+8]   ;以上段地址預設在ds中

mov ax, [bp]
mov ax, [bp+8]
mov ax, [bp+si]
mov ax, [bp+si+8]   ;以上段地址預設在ss中

mov ax, ds:[bp]
mov ax, es:[bx]
mov ax, ss:[bx+si]
mov ax, cs:[bx+si+8] ;顯式給出存放段地址的暫存器
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

定址方式

4、指令要處理的資料有多長

8086CPU的指令,可以處理兩種尺寸的資料,byte和word

  1. 通過暫存器名指明要處理的資料的尺寸。
    例如: mov al, ds:[0] 暫存器al指明瞭資料為1位元組

  2. 在沒有暫存器名存在的情況下,用操作符X ptr指明記憶體單元的長度,X在彙編指令中可以為wordbyte
    例如:mov byte ptr ds:[0], 1 byte ptr 指明瞭指令訪問的記憶體單元是一個位元組單元

  3. 有些指令默認了訪問的是字單元還是位元組單元
    例如,push [1000H],push 指令只進行字操作。

5、定址方式的綜合應用

mov ax, seg 
mov ds, ax 
mov bx, 60h   ;確定記錄地址,ds:bx 

mov word ptr [bx+0ch], 38   ;排名欄位改為38  [bx].0ch
add word ptr [bx+0eh], 70   ;收入欄位增加70  [bx].0eh
mov si, 0   ;用si來定位產品字串中的字元
mov byte ptr [bx+10h+si], 'V'   ;[bx].10h[si]
inc si 
mov byte ptr [bx+10h+si], 'A'
inc si 
mov byte ptr [bx+10h+si], 'X'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

C語言描述

/*定義一個公司記錄的結構體*/
struct company
{
    char cn[3];/*公司名稱*/
    char hn[9];/*總裁姓名*/
    int pm;/*排名*/
    int sr;/*收入*/
    char cp[3];/*著名產品*/
};
//sizeof (struct company) == 24

int main()
{
    /*定義一個公司記錄的變數,記憶體中將存有一條公司的記錄*/
    struct company dec = {"DEC", "Ken Olsen", 137, 40, "PDP"};

    int i;

    dec.pm = 38;
    dec.sr = dec.sr + 70;

    i = 0;
    dec.cp[i] = 'V'; //mov byte ptr [bx].10h[si], 'V'
    i++;
    dec.cp[i] = 'A';
    i++;
    dec.cp[i] = 'X';

    return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

6、div指令、dd、dup、mul指令

div是除法指令

  1. 除數:有8位和16位兩種,在一個暫存器記憶體單元中。

  2. 被除數:預設放在AXDX和AX中,
    如果除數為8位,被除數則為16位,預設在AX中存放;
    如果除數為16位,被除數則為32位,在DX和AX中存放,DX存放高16位,AX存放低16位。

  3. 結果:
    如果除數為8位,則AL儲存除法操作的商AH儲存除法操作的餘數
    如果除數為16位,則AX儲存除法操作的商,DX儲存除法操作的餘數。

;利用除法指令計算100001/100。
;100001D = 186A1H
mov dx, 1
mov ax, 86A1H ;(dx)*10000H+(ax)=100001
mov bx, 100
div bx

;利用除法指令計算1001/100
mov ax, 1001
mov bl, 100
div b1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

偽指令dd

db和dw定義位元組型資料和字型資料。

dd是用來定義dword(double word,雙字)型資料的偽指令

操作符dup

dup在組合語言中同db、dw、dd等一樣,也是由編譯器識別處理的符號。
它和db、dw、dd等資料定義偽指令配合使用,用來進行資料的重複

db 3 dup (0)       ;定義了3個位元組,它們的值都是0,相當於db 0,0,0。
db 3 dup (0, 1, 2) ;定義了9個位元組,它們是0、1、2、0、1、2、0、1、2,相當於db 0,1,2,0,1,2,0,1,2。
db 3 dup ('abc', 'ABC') ;定義了18個位元組,它們是abcABCabcABCabcABCC,相當於db 'abc', 'ABC' ,'abc' , 'ABC, 'abc', 'ABC'。
  • 1
  • 2
  • 3

mul 指令

mul是乘法指令,使用 mul 做乘法的時候:相乘的兩個數:要麼都是8位,要麼都是16位。

  • 8 位: AL中和 8位暫存器記憶體位元組單元中;

  • 16 位: AX中和 16 位暫存器記憶體字單元中。

結果

  • 8位:AX中;

  • 16位:DX(高位)和 AX(低位)中。

格式:mul 暫存器 或 mul 記憶體單元

;計算100*10
;100和10小於255,可以做8位乘法
mov al,100
mov bl,10
mul bl

;結果: (ax)=1000(03E8H) 

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
;計算100*10000
;100小於255,可10000大於255,所以必須做16位乘法,程式如下:
mov ax,100
mov bx,10000
mul bx

;結果: (ax)=4240H,(dx)=000FH     (F4240H=1000000)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

八、轉移指令的原理


可以修改IP,或同時修改CS和IP的指令統稱為轉移指令。概括地講,轉移指令就是可以控制CPU執行記憶體中某處程式碼的指令。

8086CPU的轉移行為有以下幾類。

  • 只修改IP時,稱為段內轉移,比如:jmp ax
  • 同時修改CS和IP時,稱為段間轉移,比如:jmp 1000:0

由於轉移指令對IP的修改範圍不同,段內轉移又分為:短轉移和近轉移

  • 短轉移IP的修改範圍為-128 ~ 127
  • 近轉移IP的修改範圍為-32768 ~ 32767

8086CPU的轉移指令分為以下幾類。

  • 無條件轉移指令(如:jmp)
  • 條件轉移指令
  • 迴圈指令(如:loop)
  • 過程
  • 中斷

1、操作符offset

操作符offset在組合語言中是由編譯器處理的符號,它的功能是取得標號的偏移地址。

;將s處的一條指令複製到s0處
assume cs:codesg
codesg segment
 s:   mov ax, bx           ;(mov ax,bx 的機器碼佔兩個位元組)
      mov si, offset s     ;獲得標號s的偏移地址
      mov di, offset s0    ;獲得標號s0的偏移地址
      
      mov ax, cs:[si]
      mov cs:[di], ax
 s0:  nop                     ;(nop的機器碼佔一個位元組)
      nop
 codesg ends
 ends
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

2、jmp指令

jmp為無條件轉移,轉到標號處執行指令可以只修改IP,也可以同時修改CS和IP;

jmp指令要給出兩種資訊:

  • 轉移的目的地址
  • 轉移的距離(段間轉移、段內短轉移,段內近轉移)

​ jmp short 標號 jmp near ptr 標號 jcxz 標號 loop 標號 等幾種彙編指令,它們對 IP的修改

是根據轉移目的地址和轉移起始地址之間的位移來進行的。在它們對應的機器碼中不包含轉移的目的地址,而包含的是到目的地址的位移距離。

1、依據位移進行轉移的jmp指令

jmp short 標號(段內短轉移)

指令“jmp short 標號”的功能為(IP)=(IP)+8位位移,轉到標號處執行指令

(1)8位位移 = “標號”處的地址 - jmp指令後的第一個位元組的地址;

(2)short指明此處的位移為8位位移;

(3)8位位移的範圍為-128~127,用補碼錶示

(4)8位位移由編譯程式在編譯時算出。

assume cs:codesg
codesg segment
  start:mov ax,0
        jmp short s ;s不是被翻譯成目的地址
        add ax, 1
      s:inc ax ;程式執行後, ax中的值為 1 
codesg ends
end start
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

CPU不需要這個目的地址就可以實現對IP的修改。這裡是依據位移進行轉移

jmp short s指令的讀取和執行過程:

  1. (CS)=0BBDH,(IP)=0006,上一條指令執行結束後CS:IP指向EB 03(jmp short s的機器碼);
  2. 讀取指令碼EB 03進入指令緩衝器;
  3. (IP) = (IP) + 所讀取指令的長度 = (IP) + 2 = 0008,CS:IP指向add ax,1;
  4. CPU指行指令緩衝器中的指令EB 03;
  5. 指令EB 03執行後,(IP)=000BH,CS:IP指向inc ax

jmp near ptr 標號 (段內近轉移)

指令“jmp near ptr 標號”的功能為:(IP) = (IP) + 16位位移

2、轉移的目的地址在指令中的jmp指令

jmp far ptr 標號(段間轉移或遠轉移)

指令 “jmp far ptr 標號” 功能如下:

  • (CS) = 標號所在段的段地址;
  • (IP) = 標號所在段中的偏移地址。
  • far ptr指明瞭指令用標號的段地址和偏移地址修改CS和IP。
assume cs:codesg
codesg segment
   start: mov ax, 0
		  mov bx, 0
          jmp far ptr  s ;s被翻譯成轉移的目的地址0B01 BD0B
          db 256 dup (0) ;轉移的段地址:0BBDH,偏移地址:010BH
    s:    add ax,1
          inc ax
codesg ends
end start
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

3、轉移地址在暫存器或記憶體中的jmp指令

jmp 16位暫存器 功能:IP =(16位暫存器)

轉移地址在記憶體中的jmp指令有兩種格式:

  • jmp word ptr 記憶體單元地址(段內轉移)

功能:從記憶體單元地址處開始存放著一個字,是轉移的目的偏移地址。

mov ax, 0123H
mov ds:[0], ax
jmp word ptr ds:[0]
;執行後,(IP)=0123H
  • 1
  • 2
  • 3
  • 4
  • jmp dword ptr 記憶體單元地址(段間轉移)

功能:從記憶體單元地址處開始存放著兩個字,高地址處的字是轉移的目的段地址,低地址處是轉移的目的偏移地址。

  1. (CS)=(記憶體單元地址+2)
  2. (IP)=(記憶體單元地址)
mov ax, 0123H
mov ds:[0], ax;偏移地址
mov word ptr ds:[2], 0;段地址
jmp dword ptr ds:[0]
;執行後,
;(CS)=0
;(IP)=0123H
;CS:IP 指向 0000:0123。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

4、jcxz指令和loop指令

jcxz指令

jcxz指令為有條件轉移指令,所有的有條件轉移指令都是短轉移,

在對應的機器碼中包含轉移的位移,而不是目的地址。對IP的修改範圍都為-128~127。

指令格式:jcxz 標號(如果(cx)=0,則轉移到標號處執行。)

當(cx) = 0時,(IP) = (IP) + 8位位移

  • 8位位移 = “標號”處的地址 - jcxz指令後的第一個位元組的地址;
  • 8位位移的範圍為-128~127,用補碼錶示;
  • 8位位移由編譯程式在編譯時算出。

當(cx)!=0時,什麼也不做(程式向下執行)

loop指令

loop指令為迴圈指令,所有的迴圈指令都是短轉移,在對應的機器碼中包含轉移的位移,而不是目的地址。

對IP的修改範圍都為-128~127。

指令格式:loop 標號 ((cx) = (cx) - 1,如果(cx) ≠ 0,轉移到標號處執行)。

(cx) = (cx) - 1;如果 (cx) != 0,(IP) = (IP) + 8位位移。

  • 8位位移 = 標號處的地址 - loop指令後的第一個位元組的地址;
  • 8位位移的範圍為-128~127,用補碼錶示;
  • 8位位移由編譯程式在編譯時算出。

如果(cx)= 0,什麼也不做(程式向下執行)。

九、call和ret指令


call和ret指令都是轉移指令,它們都修改IP,或同時修改CS和IP。

1、ret 和 retf

  • ret指令用棧中的資料,修改IP的內容,從而實現近轉移;

  • retf指令用棧中的資料,修改CS和IP的內容,從而實現遠轉移。

CPU執行ret指令時,相當於進行: pop IP

(1)(IP) = ( (ss) * 16 + (sp) )

(2)(sp) = (sp) + 2

CPU執行retf指令時,相當於進行:pop IP, pop CS

(1)(IP) = ( (ss) * 16 + (sp) )

(2)(sp) = (sp) + 2

(3)(CS) = ( (ss) * 16 + (sp) )

(4)(sp) = (sp) + 2

assume cs:code 
stack seqment
	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 ;ax入棧
		mov bx, 0
		ret ;ret指令執行後,(IP)=0,CS:IP指向程式碼段的第一條指令。可以push cs  push ax  retf
code ends
end start
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

2、call 指令

call指令經常跟ret指令配合使用,因此CPU執行call指令,進行兩步操作:

(1)將當前的 IP 或 CS和IP 壓入棧中;

(2)轉移(jmp)。

call指令不能實現短轉移,除此之外,call指令實現轉移的方法和 jmp 指令的原理相同。

call 標號(近轉移)

CPU執行此種格式的call指令時,相當於進行 push IP jmp near ptr 標號

call far ptr 標號(段間轉移)

CPU執行此種格式的call指令時,相當於進行:push CS,push IP jmp far ptr 標號

call 16位暫存器

CPU執行此種格式的call指令時,相當於進行: push IP jmp 16位暫存器

call word ptr 記憶體單元地址

CPU執行此種格式的call指令時,相當於進行:push IP jmp word ptr 記憶體單元地址

mov sp, 10h
mov ax, 0123h
mov ds:[0], ax
call word ptr ds:[0]
;執行後,(IP)=0123H,(sp)=0EH
  • 1
  • 2
  • 3
  • 4
  • 5

call dword ptr 記憶體單元地址

CPU執行此種格式的call指令時,相當於進行:push CS push IP jmp dword ptr 記憶體單元地址

mov sp, 10h
mov ax, 0123h
mov ds:[0], ax
mov word ptr ds:[2], 0
call dword ptr ds:[0]
;執行後,(CS)=0,(IP)=0123H,(sp)=0CH
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

3、call 和 ret 的配合使用

分析下面程式

assume cs:code
code segment
start:	mov ax,1
	    mov cx,3
     	call s ;(1)CPU指令緩衝器存放call指令,IP指向下一條指令(mov bx, ax),執行call指令,IP入棧,jmp
     	
	    mov bx,ax	;(4)IP重新指向這裡  bx = 8
     	mov ax,4c00h
     	int 21h
     s: add ax,ax
     	loop s;(2)迴圈3次ax = 8
	    ret;(3)return : pop IP
code ends
end start
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

call 與 ret 指令共同支援了組合語言程式設計中的模組化設計

編寫子程式

十、標誌暫存器


1、標誌暫存器

CPU內部的暫存器中,有一種特殊的暫存器(對於不同的處理機,個數和結構都可能不同)具有以下3種作用。

(1)用來儲存相關指令的某些執行結果;

(2)用來為CPU執行相關指令提供行為依據;

(3)用來控制CPU的相關工作方式。

這種特殊的暫存器在8086CPU中,被稱為標誌暫存器(flag)。

8086CPU的標誌暫存器有16位,其中儲存的資訊通常被稱為程式狀態字(PSW-Program Status Word)

flag暫存器是按位起作用的,它的每一位都有專門的含義,記錄特定的資訊。


在8086CPU的指令集中,有的指令的執行是影響標誌暫存器的,比如,add、sub、mul、div、inc、or、and等,它們大都是運算指令(進行邏輯或算術運算);有的指令的執行對標誌暫存器沒有影響,比如,mov、push、pop等,它們大都是傳送指令

1、零標誌位 (ZF)

零標誌位(Zero Flag)。它記錄相關指令執行後,其結果是否為0。

如果結果為0,那麼zf = 1(表示結果是0);如果結果不為0,那麼zf = 0。

mov ax, 1
sub ax, 1 ;執行後,結果為0,則zf = 1

mov ax, 2
sub ax, 1 ;執行後,結果不為0,則zf = 0
  • 1
  • 2
  • 3
  • 4
  • 5

2、奇偶標誌位 (PF)

奇偶標誌位(Parity Flag)。它記錄相關指令執行後,其結果的所有bit位中1的個數是否為偶數。

如果1的個數為偶數,pf = 1,如果為奇數,那麼pf = 0。

mov al, 1
add al, 10 ;執行後,結果為00001011B,其中有3(奇數)個1,則pf = 0;

mov al, 1
or al, 2  ;執行後,結果為00000011B,其中有2(偶數)個1,則pf = 1;
  • 1
  • 2
  • 3
  • 4
  • 5

3、符號標誌位(SF)

符號標誌位(Symbol Flag)。它記錄相關指令執行後,其結果是否為負。

如果結果為負,sf = 1;如果非負,sf = 0。

計算機中通常用補碼來表示有符號資料。計算機中的一個數據可以看作是有符號數,也可以看成是無符號數。

00000001B,可以看作為無符號數1,或有符號數+1;
10000001B,可以看作為無符號數129,也可以看作有符號數-127。

對於同一個二進位制資料,計算機可以將它當作無符號資料來運算,也可以當作有符號資料來運算

CPU在執行add等指令的時候,就包含了兩種含義:可以將add指令進行的運算當作無符號數的運算,也可以將add指令進行的運算當作有符號數的運算

SF標誌,就是CPU對有符號數運算結果的一種記錄,它記錄資料的正負。在我們將資料當作有符號數來運算的時候,可以通過它來得知結果的正負。如果我們將資料當作無符號數來運算,SF的值則沒有意義,雖然相關的指令影響了它的值

mov al, 10000001B 
add al, 1   ;執行後,結果為10000010B,sf = 1,表示:如果指令進行的是有符號數運算,那麼結果為負;
  • 1
  • 2
mov al, 10000001B
add al, 01111111B   ;執行後,結果為0,sf = 0,表示:如果指令進行的是有符號數運算,那麼結果為非負
  • 1
  • 2

3、進位標誌位(CF)

進位標誌位(Carry Flag)。一般情況下,在進行無符號數運算的時候,它記錄了運算結果的最高有效位向更高位的進位值,或從更高位的借位值

97H - 98H 產生借位CF = 1 ==》 (al) = 197H - 98H = FFH

4、溢位標誌位(OF)

溢位標誌位(Overflow Flag)。一般情況下,OF記錄了有符號數運算的結果是否發生了溢位。

如果發生溢位,OF = 1;如果沒有,OF = 0。

CF和OF的區別:CF是對無符號數運算有意義的標誌位,而OF是對有符號數運算有意義的標誌位

CPU在執行add等指令的時候,就包含了兩種含義:無符號數運算和有符號數運算。

  • 對於無符號數運算,CPU用CF位來記錄是否產生了進位;
  • 對於有符號數運算,CPU用OF位來記錄是否產生了溢位,當然,還要用SF位來記錄結果的符號。
mov al, 98
add al, 99   ;執行後將產生溢位。因為進行的"有符號數"運算是:(al)=(al)+ 99 = 98 + 99=197 = C5H 為-59的補碼
             ;而結果197超出了機器所能表示的8位有符號數的範圍:-128-127。
             ;add 指令執行後:無符號運算沒有進位CF=0,有符號運算溢位OF=1
             ;當取出的資料C5H按無符號解析C5H = 197, 當按有符號解析通過SP得知資料為負,即C5H為-59補碼儲存,
             
mov al,0F0H  ;F0H,為有符號數-16的補碼   -Not(F0 - 1)
add al,088H  ;88H,為有符號數-120的補碼   -Not(88- 1)
              ;執行後,將產生溢位。因為add al, 088H進行的有符號數運算結果是:(al)= -136 
              ;而結果-136超出了機器所能表示的8位有符號數的範圍:-128-127。
              ;add 指令執行後:無符號運算有進位CF=1,有符號運算溢位OF=1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

2、adc指令和sbb指令

adc是帶進位加法指令,它利用了CF位上記錄的進位值。

指令格式:adc 操作物件1, 操作物件2

功能:操作物件1 = 操作物件1 + 操作物件2 + CF

mov ax, 2
mov bx, 1
sub bx, ax  ;無符號運算借位CF=1,有符號運算OF = 0
adc ax, 1   ;執行後,(ax)= 4。adc執行時,相當於計算:(ax)+1+CF = 2+1+1 = 4。
  • 1
  • 2
  • 3
  • 4
;計算1EF000H+201000H,結果放在ax(高16位)和bx(低16位)中。
;將計算分兩步進行,先將低16位相加,然後將高16位和進位值相加。
mov ax, 001EH 
mov bx, 0F000H 
add bx, 1000H
adc ax, 0020H
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

sbb指令

sbb是帶借位減法指令,它利用了CF位上記錄的借位值。

指令格式:sbb 操作物件1, 操作物件2

功能:操作物件1 = 操作物件1 - 操作物件2 - CF

;計算 003E1000H - 00202000H,結果放在ax,bx中,程式如下:
mov bx, 1000H
mov ax, 003EH
sub bx, 2000H
sbb ax, 0020H
  • 1
  • 2
  • 3
  • 4
  • 5

3、cmp指令

cmp是比較指令,cmp的功能相當於減法指令,只是不儲存結果。cmp指令執行後,將對標誌暫存器產生影響。

其他相關指令通過識別這些被影響的標誌暫存器位來得知比較結果。

cmp指令格式:cmp 操作物件1,操作物件2

例如:
指令cmp ax, ax,做(ax)-(ax)的運算,結果為0,但並不在ax中儲存,僅影響flag的相關各位。
指令執行後:zf=1,pf=1,sf=0,cf=0,of=0。

CPU在執行cmp指令的時候,也包含兩種含義:進行無符號數運算和進行有符號數運算。

cmp ax, bx 無符號比較時
(ax) = (bx) zf = 1
(ax) ≠ (bx) zf = 0
(ax) < (bx) cf = 1
(ax) ≥ (bx) cf = 0
(ax) > (bx) cf = 0 且 zf = 0
(ax) ≤ (bx) cf = 1 且 zf = 1

上面的表格可以正推也可以逆推

如果用cmp來進行有符號數比較時
SF只能記錄實際結果的正負,發生溢位的時候,實際結果的正負不能說明邏輯上真正結果的正負。
但是邏輯上的結果的正負,才是cmp指令所求的真正結果,所以我們在考察SF的同時考察OF,就可以得知邏輯上真正結果的正負,同時就知道比較的結果。

mov ah, 08AH  ; -Not(8A-1) = -118  即當成有符號數時為-118
mov bh, 070H  ; 有符號數時最高位為0為正數, 70H = 112
cmp ah, bh    ;(ah)-(bh)實際得到的結果是1AH 
		      ; 在邏輯上,運算所應該得到的結果是:(-118)- 112 = -230
		      ; sf記錄實際結果的正負,所以sf=0
  • 1
  • 2
  • 3
  • 4
  • 5

cmp ah, bh
(1)如果sf=1,而of=0 。 of=0說明沒有溢位,邏輯上真正結果的正負=實際結果的正負; sf=1,實際結果為負,所以邏輯上真正的結果為負,所以(ah)<(bh)

(2)如果sf=1,而of=1: of=1,說明有溢位,邏輯上真正結果的正負≠實際結果的正負; sf=1,實際結果為負。
實際結果為負,而又有溢位,這說明是由於溢位導致了實際結果為負,,如果因為溢位導致了實際結果為負,那麼邏輯上真正的結果必然為正。 這樣,sf=1,of=1,說明了(ah)>(bh)。

(3)如果sf=0,而of=1。of=1,說明有溢位,邏輯上真正結果的正負≠實際結果的正負;sf=0,實際結果非負。而of=1說明有溢位,則結果非0,所以,實際結果為正。
實際結果為正,而又有溢位,這說明是由於溢位導致了實際結果非負,如果因為溢位導致了實際結果為正,那麼邏輯上真正的結果必然為負。這樣,sf=0,of=1,說明了(ah)<(bh)。
(4)如果sf=0,而of=0
of=0,說明沒有溢位,邏輯上真正結果的正負=實際結果的正負;sf=0,實際結果非負,所以邏輯上真正的結果非負,所以(ah)≥(bh)。

4、檢測比較結果的條件轉移指令

可以根據某種條件,決定是否修改IP的指令

jcxz它可以檢測cx中的數值,如果(cx)=0,就修改IP,否則什麼也不做。

所有條件轉移指令的轉移位移都是[-128,127]。

多數條件轉移指令都檢測標誌暫存器的相關標誌位,根據檢測的結果來決定是否修改IP

這些條件轉移指令通常都和cmp相配合使用,它們所檢測的標誌位,都是cmp指令進行無符號數比較的時記錄比較結果的標誌位

根據無符號數的比較結果進行轉移的條件轉移指令(它們檢測zf、cf的值)

指令 含義 檢測的相關標誌位
je 等於則轉移 zf = 1
jne 不等於則轉移 zf = 0
jb 低於則轉移 cf = 1
jnb 不低於則轉移 cf = 0
ja 高於則轉移 cf = 0 且 zf = 0
jna 不高於則轉移 cf = 1 且 zf = 1

j:jump,e:equal,b:below,a:above,n:not

;程式設計,統計data段中數值為8的位元組的個數,用ax儲存統計結果。
mov ax, data 
mov ds, ax 
mov bx, 0   ;ds:bx指向第一個位元組
mov ax, 0   ;初始化累加器mov cx,8

s:
	cmp byte ptr [bx], 8   ;和8進行比較
	jne next  ;如果不相等轉到next,繼續迴圈
	inc ax  ;如果相等就將計數值加1
next:
	inc bx
	loop s ;程式執行後:(ax)=3
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

5、DF標誌和串傳送指令

方向標誌位。在串處理指令中,控制每次操作後si、di的增減。

  • df = 0每次操作後si、di遞增;
  • df = 1每次操作後si、di遞減。

格式:movsb
功能:將ds:si指向的記憶體單元中的位元組送入es:di中,然後根據標誌暫存器df位的值,將si和di遞增或遞減

格式:movsw
功能:將ds:si指向的記憶體字單元中的字送入es:di中,然後根據標誌暫存器df位的值,將si和di遞增2或遞減2。

格式:rep movsb
movsb和movsw進行的是串傳送操作中的一個步驟,一般來說,movsb和movsw都和rep配合使用,
功能:rep的作用是根據cx的值,重複執行後面的串傳送指令

8086CPU提供下面兩條指令對df位進行設定。

  • cld指令:將標誌暫存器的df位置0
  • std指令:將標誌暫存器的df位置1
;將data段中的第一個字串複製到它後面的空間中。
data segment 
	db 'Welcome to masm!'
	db 16 dup (0)
data ends

mov ax, data 
mov ds, ax 
mov si, 0   ;ds:si 指向data:0
mov es, ax 
mov di, 16  ;es:di指向data:0010

mov cx, 16  ;(cx)=16,rep迴圈16次
cld  ;設定df=0,正向傳送
rep movsb
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

6、pushf和popf

pushf的功能是將標誌暫存器的值壓棧,而popf是從棧中彈出資料,送入標誌暫存器中

pushf和popf,為直接訪問標誌暫存器提供了一種方法。

十一、內中斷

1、內中斷的產生

任何一個通用的CPU,都具備一種能力,可以在執行完當前正在執行的指令之後,檢測到從CPU外部發送過來的或內部產生的一種特殊資訊,並且可以立即對所接收到的資訊進行處理。這種特殊的資訊,我們可以稱其為:中斷資訊。中斷的意思是指,CPU不再接著(剛執行完的指令)向下執行,而是轉去處理這個特殊資訊。

中斷資訊可以來自CPU的內部和外部(內中斷,外中斷)

內中斷:當CPU的內部有需要處理的事情發生的時候,將產生中斷資訊,引發中斷過程。這種中斷資訊來自CPU的內部

8086CPU的內中斷(下面四種情況將產生中斷資訊)

  • 除法錯誤,比如,執行div指令產生的除法溢位;
  • 單步執行;
  • 執行 into指令;
  • 執行 int指令。

中斷資訊中包含中斷型別碼,中斷型別碼為一個位元組型資料,可以表示256種中斷資訊的來源(中斷源)

上述的4種中斷源,在8086CPU中的中斷型別碼如下。

  • 除法錯誤:0
  • 單步執行:1
  • 執行into指令:4
  • 執行int指令,該指令的格式為int n,指令中的n為位元組型立即數,是提供給CPU的中斷型別碼。

2、中斷處理程式、中斷向量表、中斷過程

中斷處理程式

用來處理中斷資訊的程式被稱為中斷處理程式。

根據CPU的設計,中斷型別碼的作用就是用來定位中斷處理程式。比如CPU根據中斷型別碼4,就可以找到4號中斷的處理程式

中斷向量表

中斷向量就是中斷處理程式的入口地址。中斷向量表就是中斷處理程式入口地址的列表

CPU用8位的中斷型別碼通過中斷向量表找到相應的中斷處理程式的入口地址

中斷過程

中斷過程的主要任務就是用中斷型別碼在中斷向量表中找到中斷處理程式的入口地址,設定CS和IP

簡要描述如下

  1. 取得中斷型別碼N;
  2. pushf
  3. TF=0,IF=0 (為什麼這樣參考單步中斷)
  4. push CS , push IP
  5. (IP)=(N * 4),(CS)=(N * 4 + 2)

硬體在完成中斷過程後,CS:IP將指向中斷處理程式的入口,CPU開始執行中斷處理程式。

3、iret指令

CPU隨時都可能執行中斷處理程式,中斷處理程式必須一直儲存在記憶體某段空間之中
而中斷處理程式的入口地址,即中斷向量,必須儲存在對應的中斷向量表表項中。

中斷處理程式的常規編寫步驟:

  1. 儲存用到的暫存器;
  2. 處理中斷;
  3. 恢復用到的暫存器;
  4. iret指令返回。

iret 指令描述為:pop IP pop CS popf

iret指令執行後,CPU回到執行中斷處理程式前的執行點繼續執行程式

4、除法錯誤中斷的處理

mov ax, 1000h 
mov bh, 1
div bh ;除法溢位錯誤
  • 1
  • 2
  • 3

1、當CPU執行div bh時,發生了除法溢位錯誤,產生0號中斷資訊,從而引發中斷過程,

2、CPU執行0號中斷處理程式

3、系統中的0號中斷處理程式的功能:顯示提示資訊“Divide overflow”後,返回到作業系統中。

程式設計實驗

程式設計:編寫0號中斷處理程式do0,當發生除法溢位時,在螢幕中間顯示“overflow!”,返回DOS。

1、0000:0200至0000:02FF的256個位元組的空間所對應的中斷向量表項都是空的,可以將中斷處理程式do0傳送到記憶體0000:0200處。

2、中斷處理程式do0放到0000:0200,再將其地址登記在中斷向量表對應表項

  • 0號表項的地址0:00:0字單元存放偏移地址,0:2字單元存放段地址
  • 將do0的段地址0存放在0000:0002字單元中,將偏移地址200H存放在0000:0000字單元
assume cs:code

code segment
start:	
		mov ax, cs
		mov ds, ax
		mov si, offset do0		;設定ds:si指向源地址
		mov ax, 0
		mov es, ax
		mov di, 200h			;設定es:di指向目的地址0000:0200
		mov cx, offset do0end - offset do0		;設定cx為傳輸長度 編譯時給出do0部分程式碼長度
		cld				        ;設定傳輸方向為正
		rep movsb ;將do0的程式碼送入0:200處
		
		mov ax, 0               ;設定中斷向量表
		mov es, ax
		mov word ptr es:[0*4], 200h
		mov word ptr es:[0*4+2], 0

      	mov ax,4c00h
      	int 21h

;do0程式的主要任務是顯示字串
do0:	jmp short do0 start 
      	db "overflow!"

do0start:
      	mov ax, cs
      	mov ds, ax
      	mov si, 202h			;設定ds:si指向字串

      	mov ax, 0b800h
      	mov es, ax
		mov di, 12*160+36*2		;設定es:di指向視訊記憶體空間的中間位置

        mov cx, 9				;設定cx為字串長度
	s:	mov al, [si]
      	mov es:[di], al
      	inc si
      	add di, 1
		mov al, 02h             ;設定顏色
		mov es:[di], al        
		add di, 1
      	loop s

      	mov ax, 4c00h
      	int 21h
do0end:	nop

code ends
end start

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

5、單步中斷

CPU在執行完一條指令之後,如果檢測到標誌暫存器的TF位為1,則產生單步中斷,引發中斷過程。單步中斷的中斷型別碼為1

Debug是如何利用CPU所提供的單步中斷的功能進行除錯?如使用t命令檢視暫存器狀態

Debug提供了單步中斷的中斷處理程式,功能為顯示所有暫存器中的內容後等待輸入命令

在使用t命令執行指令時,Debug將TF設定為1,在CPU執行完這條指令後就引發單步中斷,執行單步中斷的中斷處理程式,所有暫存器中的內容被顯示在螢幕上,並且等待輸入命令。

在進入中斷處理程式之前,設定TF=0。從而避免CPU在執行中斷處理程式的時候發生單步中斷

6、int指令

int指令的格式為:int n ,n為中斷型別碼,它的功能是引發中斷過程。

CPU執行int n指令,相當於引發一個n號中斷的中斷過程

在程式中使用int指令呼叫任何一箇中斷的中斷處理程式(中斷例程)

編寫供應用程式呼叫的中斷例程

實驗1

;求2 * 3456^2
assume cs:code

code segment

start: 
     mov ax, 3456 ;(ax)=3456
​     int 7ch  ; 呼叫中斷7ch的中斷例程,計算ax中的資料的平方
​     add ax, ax  
​     adc dx, dx  ;存放結果,將結果乘以2

​     mov ax,4c00h
​     int 21h
code ends
end start 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
;程式設計:安裝中斷7ch的中斷例程
;功能:求一word型資料的平方。
;引數:(ax) = 要計算的資料。
;返回值:dx、ax中存放結果的高16位和低16位。

assume cs:code

code segment
start:
		mov ax,cs
		mov ds,ax
		mov si,offset sqr					;設定ds:si指向源地址
		mov ax,0
		mov es,ax
		mov di,200h							;設定es:di指向目的地址
		mov cx,offset sqrend - offset sqr	;設定cx為傳輸長度
		cld									;設定傳輸方向為正
		rep movsb

		mov ax,0
		mov es,ax
		mov word ptr es:[7ch*4], 200h
		mov word ptr es:[7ch*4+2], 0

		mov ax,4c00h
		int 21h

  sqr:  
		mul ax
		iret  ;CPU執行int 7ch指令進入中斷例程之前,標誌暫存器、當前的CS和IP被壓入棧
		      ;在執行完中斷例程後,應該用iret 指令恢復int 7ch執行前的標誌暫存器和CS、IP的
sqrend:	nop

code ends
end start
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

實驗2

;功能:將一個全是字母,以0結尾的字串,轉化為大寫。
;引數:ds:si指向字串的首地址。
;應用舉例:將data段中的字串轉化為大寫。
assume cs:code

data segment
	db 'conversation',0
data ends

code segment
start:  mov ax, data
		mov ds, ax
		mov si, 0
		int 7ch
		
		mov ax,4c00h
		int 21h
code ends
end start   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

assume cs:code
code segment

start:
		mov ax,cs
		mov ds,ax
		mov si,offset capital
		mov ax,0
		mov es,ax
		mov di,200h
		mov cx,offset capitalend - offset capital
		cld
		rep movsb

		mov ax,0
		mov es,ax
		mov word ptr es:[7ch*4],200h
		mov word ptr es:[7ch*4+2],0

		mov ax,4c00h
		int 21h

capital:
		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
		iret
		
capitalend:nop

code ends

end start

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

7、BIOS和DOS所提供的中斷例程

在系統板的ROM中存放著一套程式,稱為BIOS(基本輸入輸出系統)

BIOS中主要包含以下幾部分內容

  • 硬體系統的檢測和初始化程式;
  • 外部中斷和內部中斷的中斷例程;
  • 用於對硬體裝置進行I/O操作的中斷例程;
  • 其他和硬體系統相關的中斷例程。

程式設計師在程式設計的時候,可以用int 指令直接呼叫BIOS和DOS系統提供的中斷例程,來完成某些工作。
和硬體裝置相關的DOS中斷例程中,一般都呼叫了BIOS的中斷例程。

BIOS和DOS中斷例程的安裝過程

BIOS和DOS提供的中斷例程是如何安裝到記憶體中的呢?

1、開機後,CPU一加電,初始化(CS)= 0FFFFH,(IP)= 0,自動從FFFF:0單元開始執行程式。FFFF:0處有一條轉跳指令,CPU執行該指令後,轉去執行BIOS中的硬體系統檢測和初始化程式。

2、初始化程式將建立BIOS所支援的中斷向量,即將BIOS提供的中斷例程的入口地址登記在中斷向量表中。
注意,對於BIOS所提供的中斷例程,只需將入口地址登記在中斷向量表中即可,因為它們是固化到ROM中的程式,一直在記憶體中存在。

3、硬體系統檢測和初始化完成後,呼叫int 19h進行作業系統的引導。從此將計算機交由作業系統控制。

4、DOS啟動後,除完成其他工作外,還將它所提供的中斷例程裝入記憶體,並建立相應的中斷向量。

BIOS中斷例程應用

一般來說,一個供程式設計師呼叫的中斷例程中往往包括多個子程式,中斷例程內部用傳遞進來的引數來決定執行哪一個子程式。

BIOS和DOS提供的中斷例程,都用 ah 來傳遞內部子程式的編號。

程式設計:在螢幕的5行12列顯示3個紅底高亮閃爍綠色的“al。

assume cs:code 

code segment
;int 10h中斷例程的"設定游標位置"功能
mov ah, 2;設定游標呼叫第10h號中斷例程的2號子程式,功能為設定游標位置(可以提供游標所在的行號、列號和頁號作為引數)

;設定游標到第0頁,第5行,第12列
mov bh, 0;第0頁
mov dh, 5;dh中放行號
mov dl, 12;dl中放列號
int 10h

;int10h中斷例程的"在游標位置顯示字元"功能。
mov ah,9 ;呼叫第10h號中斷例程的9號子程式,功能為在游標位置顯示字元
;提供要顯示的字元、顏色屬性、頁號、字元重複個數作為引數
mov al,'a'  ;字元
mov b1,11001010b  ;顏色屬性
mov bh,0  ;第0頁
mov cx,3  ;字元重複個數
int 10h

code ends 
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

bh中頁號的含義:記憶體地址空間中,B8000H~BFFFFH共32kB的空間,為80*25彩色字元模式的顯示緩衝區。
一屏的內容在顯示緩衝區中共佔4000個位元組。顯示緩衝區分為8頁,每頁4KB(約4000B),顯示器可以顯示任意一頁的內容。一般情況下,顯示第0頁的內容。也就是說,通常情況下,B8000H~B8F9FH中的4000個位元組的內容將出現在顯示器上。

DOS中斷例程應用
int 21h中斷例程是DOS提供的中斷例程,4ch號功能,即程式返回功能

mov ah, 4ch ;呼叫第21h號中斷例程的4ch號子程式,功能為程式返回,可以提供返回值作為引數
mov al, 0 ;返回值
int 21h
  • 1
  • 2
  • 3

程式設計:在螢幕的5行12列顯示字串“Welcome to masm!”。

assume cs:code 
 
data segment 
	db	'Welcome to masm',  '$'     ;“$”本身並不顯示,只起到邊界的作用
data ends 

code segment
start:	mov ah, 2 ;10號中斷設定游標位置功能
		mov bh, 0 ;第0頁
		mov dh, 5;dh中放行號
		mov dl, 12 ;dl中放列號
		int 10h 
		
		mov ax, data 
		mov ds, ax 
		mov dx, 0 ;ds:dx指向字串的首地址data:0  (引數)
		mov ah, 9 ;呼叫第21h號中斷例程的9號子程式,功能為在游標位置顯示字串,可以提供要顯示字串的地址作為引數
		int 21h 
		
		mov ax, 4c00h ;21號中斷程式返回功能
		int 21h 
code ends
end start
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

十二、埠

在PC機系統中,和CPU通過匯流排相連的晶片除各種儲存器外,還有以下3種晶片。

  • 各種介面卡(比如,網絡卡、顯示卡)上的介面晶片,它們控制介面卡進行工作;
  • 主機板上的介面晶片,CPU通過它們對部分外設進行訪問;
  • 其他晶片,用來儲存相關的系統資訊,或進行相關的輸入輸出處理。

在這些晶片中,都有一組可以由CPU讀寫的暫存器。這些暫存器,它們在物理上可能處於不同的晶片中,
但是它們在以下兩點上相同。

  • 都和CPU的匯流排相連,這種連線是通過它們所在的晶片進行的;
  • CPU對它們進行讀或寫的時候都通過控制線向它們所在的晶片發出埠讀寫命令。

從CPU的角度,將這些暫存器都當作埠,對它們進行統一編址,從而建立了一個統一的埠地址空間。
每一個埠在地址空間中都有一個地址。在訪問埠的時候,CPU通過埠地址來定位埠。因為埠所在的晶片和CPU通過匯流排相連,

CPU可以直接讀寫以下3個地方的資料。

  • CPU內部的暫存器;
  • 記憶體單元;
  • 埠。

1、埠的讀寫

埠地址和記憶體地址一樣,通過地址匯流排來傳送。在PC系統中,CPU最多可以定位64KB個不同的埠。則埠地址的範圍為0-65535

埠的讀寫指令只有兩條:inout,分別用於從埠讀取資料和往埠寫入資料。

在in和out指令中,只能使用ax或al來存放從埠中讀入的資料或要傳送到埠中的資料。

;對0~255以內的埠進行讀寫時:
in al, 20h  ;從20h埠讀入一個位元組
out 20h, al  ;往20h埠寫入一個位元組

;對256~65535的埠進行讀寫時,埠號放在dx中:
mov dx, 3f8h  ;將埠號3f8h送入dx
in al, dx  ;從3f8h埠讀入一個位元組
out dx, al ;向3f8h埠寫入一個位元組
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

2、CMOS RAM晶片

PC機中,有一個CMOS RAM晶片,一般簡稱為CMOS。此晶片的特徵如下

  • 包含一個實時鐘和一個有128個儲存單元的RAM儲存器
  • 該晶片靠電池供電。關機後內部的實時鐘正常工作,RAM中的資訊不丟失
  • 128個位元組的RAM中,內部實時鐘佔用0~0dh單元來儲存時間資訊,其餘大部分單元用於儲存系統配置資訊,供系統啟動時BIOS程式讀取。BIOS也提供了相關的程式,使我們可以在開機的時候配置CMOS RAM中的系統資訊。
  • 該晶片內部有兩個埠,埠地址為70h和71h。CPU通過這兩個埠來讀寫CMOS RAM
  • 70h為地址埠,存放要訪問的CMOS RAM單元的地址;71h為資料埠,存放從選定的CMOS RAM單元中讀取的資料,或要寫入到其中的資料。
    可見,CPU對CMOS RAM的讀寫分兩步進行,比如,讀CMOS RAM的2號單元:
    ①將2送入埠70h;
    ②從埠71h讀出2號單元的內容。

CMOS RAM中儲存的時間資訊

在CMOS RAM中,存放著當前的時間:年、月、日、時、分、秒。長度都為1個位元組,
存放單元為:

9 8 7 6 5 4 3 2 1 0
       

BCD碼是以4位二進位制數表示十進位制數碼的編碼方法 4 == 0100B

一個位元組可表示兩個BCD碼。則CMOS RAM儲存時間資訊的單元中,儲存了用兩個BCD碼錶示的兩位十進位制數,高4位的BCD碼錶示十位,低4位的BCD碼錶示個位。比如,00010100b表示14。

;程式設計,在螢幕中間顯示當前的月份。
assume cs:code
code segment 
start:	mov al,8 ;從CMOS RAM的8號單元讀出當前月份的BCD碼。
		out 70h,al 
		in al, 71h ;從資料埠71h中取得指定單元中的資料:
		
		mov ah, al ;al中為從CMOS RAM的8號單元中讀出的資料
		mov cl, 4
		shr ah, cl ;ah中為月份的十位數碼值,左移四位空出四位
		and al, 00001111b ;al中為月份的個位數碼值
		
		add ah, 30h ;BCD碼值+30h=十進位制數對應的ASCII
		add al, 30h 
		
		mov bx, 0b800h 
		mov es, bx 
		mov byte ptr es:[160*12+40*2], ah ;顯示月份的十位數碼
		mov byte ptr es:[160*12+40*2+2], al ;接著顯示月份的個位數碼
		
		mov ax,4c00h
		int 21h
code ends
end start
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

3、shl和shr指令

shl和shr是邏輯移位指令

shl是邏輯左移指令,它的功能為:

  1. 將一個暫存器或記憶體單元中的資料向左移位;
  2. 將最後移出的一位寫入CF中;
  3. 最低位用0補充。

shr是邏輯右移指令,同理

mov al, 01001000b 
shl al, 1 ;將a1中的資料左移一位執行後(al)=10010000b,CF=0。

mov al, 01010001b 
mov cl, 3 ;如果移動位數大於1時,必須將移動位數放在cl中
shl al, c1

mov al, 10000001b 
shr al, 1  ;將al中的資料右移一位執行後(al)=01000000b,CF=1。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

將X邏輯左移一位,相當於執行X=X*2。
將X邏輯右移一位,相當於執行X=X/2

十三、外中斷

1、外中斷

CPU在計算機系統中,除了能夠執行指令,進行運算以外,還應該能夠對外部裝置進行控制,接收它們的輸入,向它們進行輸出(I/O能力)

PC系統的介面卡和主機板上,裝有各種介面晶片。這些外設介面晶片的內部有若干暫存器,CPU將這些暫存器當作埠來訪問

外設的輸入不直接送入記憶體和CPU,而是送入相關的介面晶片的埠中;
CPU向外設的輸出也不是直接送入外設,而是先送入埠中,再由相關的晶片送到外設。
CPU還可以向外設輸出控制命令,而這些控制命令也是先送到相關晶片的埠中,然後再由相關的晶片根據命令對外設實施控制。

即:CPU通過埠和外部裝置進行聯絡

當CPU外部有需要處理的事情發生的時候,比如說,外設的輸入到達,相關晶片將向CPU發出相應的中斷資訊。CPU在執行完當前指令後,可以檢測到傳送過來的中斷資訊,引發中斷過程,處理外設的輸入。

PC系統中,外中斷源有兩類

1、可遮蔽中斷

可遮蔽中斷是CPU可以不響應的外中斷。CPU是否響應可遮蔽中斷,要看標誌暫存器的IF位的設定。
當CPU檢測到可遮蔽中斷資訊時,如果IF=1,則CPU在執行完當前指令後響應中斷,引發中斷過程;如果IF=0,則不響應可遮蔽中斷。

可遮蔽中斷資訊來自於CPU外部,中斷型別碼是通過資料匯流排送入CPU的;而內中斷的中斷型別碼是在CPU內部產生的。

中斷過程中將IF置0的原因就是,在進入中斷處理程式後,禁止其他的可遮蔽中斷。
如果在中斷處理程式中需要處理可遮蔽中斷,可以用指令將IF置1。

8086CPU提供的設定IF的指令:sti,設定IF=1;cli,設定IF=0。

2、不可遮蔽中斷

不可遮蔽中斷是CPU必須響應的外中斷。當CPU檢測到不可遮蔽中斷資訊時,則在執行完當前指令後,立即響應,引發中斷過程。

對於8086CPU,不可遮蔽中斷的中斷型別碼固定為2,所以中斷過程中,不需要取中斷型別碼。則不可遮蔽中斷的中斷過程為:①標誌暫存器入棧,IF=0,TF=0;②CS、IP入棧;③(IP)=(8),(CS)=(0AH)。

幾乎所有由外設引發的外中斷,都是可遮蔽中斷。當外設有需要處理的事件(比如說鍵盤輸入)發生時,相關晶片向CPU發出可遮蔽中斷資訊。不可遮蔽中斷是在系統中有必須處理的緊急情況發生時用來通知CPU的中斷資訊。

2、PC機鍵盤的處理過程

鍵盤中有一個晶片對鍵盤上的每一個鍵的開關狀態進行掃描。按下一個鍵時,開關接通,該晶片就產生一個掃描碼,掃描碼說明了按下的鍵在鍵盤上的位置。掃描碼被送入主機板上的相關介面晶片的暫存器中,該暫存器的埠地址為60h。鬆開按下的鍵時,也產生一個掃描碼,掃描碼說明了鬆開的鍵在鍵盤上的位置。鬆開按鍵時產生的掃描碼也被送入60h埠中。

一般將按下一個鍵時產生的掃描碼稱為通碼,鬆開一個鍵產生的掃描碼稱為斷碼。

掃描碼長度為一個位元組,通碼的第7位為0,斷碼的第7位為1
即:斷碼 = 通碼 + 80h。比如,g鍵的通碼為22h,斷碼為a2h

鍵盤的輸入到達60h埠時,相關的晶片就會向CPU發出中斷型別碼為9的可遮蔽中斷資訊。CPU檢測到該中斷資訊後,如果IF=1,則響應中斷,引發中斷過程,轉去執行int 9中斷例程。

BIOS提供了int 9中斷例程,用來進行基本的鍵盤輸入處理,主要的工作如下:
(1)讀出60h埠中的掃描碼;
(2)如果是字元鍵的掃描碼,將該掃描碼和它所對應的字元碼(即ASCII碼)送入記憶體中的BIOS鍵盤緩衝區; 如果是控制鍵(比如Ctrl)和切換鍵(比如CapsLock)的掃描碼,則將其轉變為狀態位元組寫入記憶體中儲存狀態位元組的單元;
(3)對鍵盤系統進行相關的控制,比如說,向相關晶片發出應答資訊。

BIOS鍵盤緩衝區可以儲存15個鍵盤輸入,一個鍵盤輸入用一個字單元存放,高位位元組存放掃描碼,低位位元組存放字元碼。

0040:17單元儲存鍵盤狀態位元組,該位元組記錄了控制鍵和切換鍵的狀態。鍵盤狀態位元組各位記錄的資訊如下。

0 右shift狀態 置1表示按下右shift鍵
1 左shift狀態 置1表示按下左shift鍵
2 Ctrl狀態 置1表示按下Ctrl鍵
3 Alt狀態 置1表示按下Alt鍵
4 ScrollLock狀態 置1表示Scroll指示燈亮
5 NumLock狀態 置1表示小鍵盤輸入的是數字
6 CapsLock狀態 置1表示輸入大寫字母
7 Insert狀態 置1表示處於刪除態

編寫int 9中斷例程

;程式設計:在螢幕中間依次顯示“a”~“z”,並可以讓人看清。在顯示的過程中,按下'Esc'鍵後,改變顯示的顏色。

;完整功能程式碼:

assume cs:code

stack segment
	db 128 dup (0)
stack ends

data segment
	dw 0,0
data ends

code segment
start:	
	mov ax,stack
	mov ss,ax
	mov sp,128
	mov ax,data
	mov ds,ax
	mov ax,0
	mov es,ax

	push es:[9*4]
	pop ds:[0]
	push es:[9*4+2]
	pop ds:[2]		;將原來的int 9中斷例程的入口地址儲存在ds:0、ds:2單元中

	mov word ptr es:[9*4], offset int9
	mov es:[9*4+2], cs	;在中斷向量表中設定新的int 9中斷例程的入口地址

;顯示字串
	mov ax, 0b800h
	mov es, ax
	mov ah, 'a'
s:	
	mov  es:[160*12+40*2], ah
	call delay
	inc ah
	cmp ah, 'z'
	jna s
	mov ax,0
	mov es,ax

	push ds:[0]
	pop es:[9*4]
	push ds;[2]
	pop es;[9*4+2]   	;將中斷向量表中int 9中斷例程的入口恢復為原來的地址

	mov ax,4c00h
	int 21h

;將迴圈延時的程式段寫為一個子程式
delay:	
	push ax 
	push dx
	mov dx, 2000h  ;用兩個16位暫存器來存放32位的迴圈次數
	mov ax, 0
s1: 	
	sub ax, 1
	sbb dx, 0
	cmp ax, 0
	jne s1
	cmp dx, 0
	jne s1
	pop dx
	pop ax
	ret

;------以下為新的int 9中斷例程--------------------

int9:	
	push ax
	push bx
	push es

	in al, 60h;從埠60h讀出鍵盤的輸入

	pushf ;標誌暫存器入棧

	pushf   
	pop bx
	and bh,11111100b
	push bx
	popf	;TF=0,IF=0
	
	call dword ptr ds:[0] 	;對int指令進行模擬,呼叫原來的int 9中斷例程

	cmp al,1
	jne int9ret

	mov ax,0b800h
	mov es,ax
	inc byte ptr es:[160*12+40*2+1]  ;屬性增加1,改變顏色

int9ret:
	pop es
	pop bx
	pop ax
	iret

code ends

end start

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106

CPU對外設輸入的通常處理方法
(1)外設的輸入送入埠;
(2)向CPU發出外中斷(可遮蔽中斷)資訊;
(3)CPU檢測到可遮蔽中斷資訊,如果IF=1,CPU在執行完當前指令後響應中斷,執行相應的中斷例程;
(4)可在中斷例程中實現對外設輸入的處理。

埠和中斷機制,是CPU進行I/O的基礎。

十四、直接定址表

assume cs:code
code segment
         a : db 1,2,3,4,5,6,7,8  ;在後面加有“:”的地址標號,只能在程式碼段中使用,不能在其他段中使用。
         b : dw 0
start :mov si,offset a
         mov bx,offset b
         mov cx,8
    s : mov al,cs:[si]
         mov ah,0
         add cs:[bx],ax
         inc si
         loop s
         mov ax,4c00h
         int 21h
code ends
end start

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

程式中,code、a、b、start、s都是標號。這些標號僅僅表示了記憶體單元的地址

描述了單位長度的標號

assume cs:code
code segment
          a db 1,2,3,4,5,6,7,8 ;標號a、b後面沒有":",因此它們是可以同時描述記憶體地址和單元長度的標號。
                               ;標號a,描述了地址code:0,和從這個地址開始,以後的記憶體單元都是位元組單元
          b dw 0               ;標號b描述了地址code:8,和從這個地址開始,以後的記憶體單元都是字單元。
start :  mov si,0
          mov cx,8
    s :   mov al,a[si]
          mov ah,0
          add b,ax
          inc si
          loop s
          mov ax,4c00h
          int 21h
code ends
end start

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

使用資料標號來描述儲存資料的單元的地址和長度。

assume cs:code,ds:data ;用偽指令assume將標號所在的段和一個段暫存器聯絡起來(編譯器需要)
data segment          
          a db 1,2,3,4,5,6,7,8
          b dw 0
data ends
code segment
start:  mov ax,data
          mov ds,ax ;真正確定ds暫存器
          mov si,0
          mov cx,8
s:       mov al,a[si] ;編譯為:mov al,[si+0] 預設所訪問單元的段地址在ds
          mov ah,0
          add b,ax ;編譯為:add [8],ax
          inc si
          loop s
          mov ax,4c00h
          int 21h
code ends
end start
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
data segment
	a db 1,2,3,4,5,6,7,8
	b dw 0
	c dw a, b ;等價於c dw offset a, offset b
	;資料標號c處儲存的兩個字型資料為標號a、b 的偏移地址
data ends

data segment
	a db 1,2,3,4,5,6,7,8
	b dw 0
	c dd a,b ;等價於c dw offset a, seg a, offset b, seg b
	;資料標號c處儲存的兩個雙字型資料為標號a的偏移地址和段地址、標號b 的偏移地址和段地址
data ends

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

seg操作符,功能為取得某一標號的段地址

建立一張表,表中依次儲存字元“0”~“F”,我們可以通過數值0 ~ 15直接查詢到對應的字元

assume cs:code

code segment
start:  
		mov al,0eh

        call showbyte

        mov ax,4c00h
        int 21h

;子程式:
;用al傳送要顯示的資料

showbyte:
        jmp short show

        table db '0123456789ABCDEF'	;字元表

show:   push bx
        push es

        mov ah,al
        shr ah,1           
        shr ah,1
        shr ah,1
        shr ah,1			    ;右移4位,ah中得到高4位的值
        and al,00001111b		;al中為低4位的值

        mov bl,ah
        mov bh,0
        mov ah,table[bx]		;用高4位的值作為相對於table的偏移,取得對應的字元

        mov bx,0b800h
        mov es,bx
        mov es:[160*12+40*2],ah

        mov bl,al
        mov bh,0
        mov al,table[bx]		;用低4位的值作為相對於table的偏移,取得對應的字元
        
        mov es:[160*12+40*2+2],al

        pop es
        pop bx
        ret

code ends
end start

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

十五、 指令系統總結

我們對8086CPU的指令系統進行一下總結。讀者若要詳細瞭解8086指令系統中的各個指令的用,可以檢視有關的指令手冊。

8086CPU提供以下幾大類指令。

  1. 資料傳送指令
    mov、push、pop、pushf、popf、xchg 等都是資料傳送指令,這些指令實現暫存器和記憶體、寄器和暫存器之間的單個數據傳送。
  2. 算術運算指令
    add、sub、adc、sbb、inc、dec、cmp、imul、idiv、aaa等都是算術運算指令,這些指令實現存器和記憶體中的資料的算數運算。它們的執行結果影響標誌暫存器的sf、zf、of、cf、pf、af位。
  3. 邏輯指令
    and、or、not、xor、test、shl、shr、sal、sar、rol、ror、rcl、rcr等都是邏輯指令。除了not指外,它們的執行結果都影響標誌暫存器的相關標誌位。
  4. 轉移指令
    可以修改IP,或同時修改CS和IP的指令統稱為轉移指令。轉移指令分為以下幾類。
    (1)無條件轉移指令,比如,jmp
    (2)條件轉移指令,比如,jcxz、je、jb、ja、jnb、jna等;
    (3)迴圈指令,比如,loop
    (4)過程,比如,call、ret、retf
    (5)中斷,比如,int、iret
  5. 處理機控制指令
    對標誌暫存器或其他處理機狀態進行設定,cld、std、cli、sti、nop、clc、cmc、stc、hlt、wait、esc、lock等都是處理機控制指令。
  6. 串處理指令
    對記憶體中的批量資料進行處理,movsb、movsw、cmps、scas、lods、stos等。若要使用這些指令方便地進行批量資料的處理,則需要和rep、repe、repne 等字首指令配合使用。

文中大部分的圖片來自王爽《組合語言》有些圖片來自劉巨集偉·計算機組成原理課件和王道考研計算機組成原理
博主靠這本書入門彙編,只是匆匆看了一遍,很多地方理解片面甚至錯誤,將來發現一定修正