匯編實驗4 8086標志寄存器及中斷
實驗任務1
源代碼
功能:對128位的兩個數字進行求和運算
點擊查看代碼
assume cs:code, ds:data
data segment
x dw 1020h, 2240h, 9522h, 5060h, 3359h, 6652h, 2530h, 7031h
y dw 3210h, 5510h, 6066h, 5121h, 8801h, 6210h, 7119h, 3912h
data ends
code segment
start:
mov ax, data
mov ds, ax
mov si, offset x ; x -> si
mov di, offset y ; y -> di
call add128
mov ah, 4ch
int 21h
add128:
push ax ; 依次保存寄存器的值
push cx
push si
push di
sub ax, ax ; 主要是把CF(進位)標志位置NC(0),否則第一次adc會出問題
; 由于8086是小端模式,根據邏輯來說,高高低低
; 左邊是低地址,應該是低位
; 從左加到右, 正好是從低位加到高位
mov cx, 8 ; 8組數字
s: mov ax, [si] ; x中的數
adc ax, [di] ; 帶進位的加法,加y中的數
mov [si], ax ; 結果送回x
inc si ; 如果換成add, 會影響CF的值,
inc si ; 本來應該有進位的, 如果用了add會導致CF = 0,
inc di ; 進位就失效了,
inc di ; 所以這里si和di只能用inc而不能用add.
loop s
pop di
pop si
pop cx
pop ax
ret
code ends
end start
實驗問題解答
① line34~line37的4條inc指令,能否替換成如下代碼?
add si,2
add di,2
答案:
這段代碼data段給的數據可以,但是如果換成其它數據就不一定可以。
原因:
這題中給的128位數據,每個16位相加都恰好都沒有產生進位,所以使用add即便修改了進位寄存器CF的值,也沒有影響。在這題中是可以替換的。
但如果換成其他數據,若相加過程中產生了進位,則使用add會導致進位寄存器CF的值發生變化。
如果本來應當是有進位的,CF的值為CY(1),但是做了add操作后CF會變成NC(0),會對后面的位數加法產生影響。所以不能使用add。
而inc指令并不影標志寄存器CF的值。
事實上,根據 intel 白皮書中對
inc的描述,可以很清楚的知道這一點(Intel? 64 and IA-32 Architectures Software Developer’s Manual,Vol. 2A3-493):(翻譯:給目標操作數加1,同時保留
CF標志的狀態。目標操作數可以是一個寄存器或一個內存位置。這條指令允許在不影響CF標志的情況下更新一個循環計數器。(使用即時操作數為1的ADD指令來執行更新CF標志的增量操作)。)
② 在debug中調試,觀察數據段中做128位加之前,和,加之后,數據段的值的變化。
可以觀察到數據被正確求和了。

觀察
① add指令對標志寄存器中的零標志位ZF(Zero Flag)、進位標志位CF(Carry Flag)是否有影響?
答案:有影響
根據 Intel 白皮書(Vol. 2A 3-31)對
ADD指令的描述:(翻譯:ADD指令執行整數加法。它對有符號和無符號整數操作的結果進行評估,并設置OF和CF標志,分別表示有符號或無符號結果中的進位(溢出)。SF標志表示有符號結果的符號。)
② inc指令對標志寄存器中的零標志位ZF(Zero Flag)、進位標志位CF(Carry Flag)是否有影響?
答案:對
ZF有影響,而對CF無影響根據 intel 白皮書中對
inc的描述,可以很清楚的知道這一點(Intel? 64 and IA-32 Architectures Software Developer’s Manual,Vol. 2A3-493):
實驗任務2
源代碼
點擊查看代碼
assume cs:code, ds:data
data segment
str db 80 dup(?)
data ends
code segment
start:
mov ax, data
mov ds, ax
mov si, 0
s1:
mov ah, 1 ; 調用int 21h的1號子程序
int 21h ; 從鍵盤接收輸入
mov [si], al ; 把輸入的字符放入 ds:[si]
cmp al, '#' ; 判斷接收到的字符是否是#
je next ; 如果是'#',執行next處操作
inc si ; 否則si + 1
jmp s1 ; 返回s1繼續讀入下一個字符
next:
mov ah, 2 ; 調用
mov dl, 0ah ; 0ah是換行符回車的ASCII碼
int 21h ; 打印一個換行符
mov cx, si ; 由于si從0開始,si = 讀入的字符個數-1,正好不會把'#'打印出來
mov si, 0 ; si回到字符串開始的位置
s2: mov ah, 2 ; 調用2號子程序
mov dl, [si] ; 打印字符
int 21h
inc si
loop s2 ; 打印字符串循環
mov ah, 4ch
int 21h
code ends
end start
實驗結果

實驗分析
該程序的作用是:從鍵盤接收輸入一串以#為結尾的字符串,然后將其打印在屏幕上
① 匯編指令代碼line11-18,實現的功能是?
s1:
mov ah, 1 ; 調用int 21h的1號子程序
int 21h ; 從鍵盤接收輸入
mov [si], al ; 把輸入的字符放入 ds:[si]
cmp al, '#' ; 判斷接收到的字符是否是#
je next ; 如果是'#',執行next處操作
inc si ; 否則si + 1
jmp s1 ; 返回s1繼續讀入下一個字符
總結:從鍵盤上讀取輸入的字符,并保存到ds:[si],每讀入一個就判斷是否為#,如果是則不保存,轉跳至標號next處執行;如果不是則si + 1并繼續讀入下一個字符。
② 匯編指令代碼line20-22,實現的功能是?
mov ah, 2 ; 調用
mov dl, 0ah ; 0ah是換行符回車的ASCII碼
int 21h ; 打印一個換行符
總結:打印一個回車(換行符)
③ 匯編指令代碼line24-30,實現的功能是?
mov cx, si ; 由于si從0開始,si = 讀入的字符個數-1,正好不會把'#'打印出來
mov si, 0 ; si回到字符串開始的位置
s2: mov ah, 2 ; 調用int 21h的2號子程序
mov dl, [si] ; 打印字符
int 21h
inc si
loop s2 ; 打印字符串循環
總結:打印字符串,并且不會把#打出來
說明(實驗指導里給的)
- DOS系統功能調用int 21h的1號子功能:
功能:從鍵盤上輸入單個字符
入口參數:(ah) = 1
出口參數: (al)存放輸入字符的ASCII碼
即:
mov ah,1 int 21h ; (al) <-- 輸入字符的ascII碼
- DOS系統功能調用int 21h的2號子功能
功能:輸出單個字符到屏幕上
入口參數:(ah) = 2, (dl) = 待輸出的字符或其ascII碼
出口參數:無
即:
mov ah, 2 mov dl, ×× ; ××是待輸出的字符,或,其ascII碼 int 21h
實驗任務3
題目描述
注:該任務是對實驗3的任務3進行改進。
針對8086CPU,已知邏輯段定義如下:
data segment
x dw 91, 792, 8536, 65521, 2021
len equ $ - x
data ends
編寫8086匯編源程序task3.asm,在屏幕上以十進制形式輸出data段中這一組連續的數據,數據和數據之間以空格間隔。
要求:
-
編寫子程序printNumber
- 功能:以十進制形式輸出一個任意位數的整數(整數范圍0 ~ 65535)
- 入口參數:寄存器ax(待輸出的數據 --> ax)
- 出口參數:無
-
編寫子程序printSpace
- 功能:打印一個空格
- 入口參數:無
- 出口參數:無
-
在主體代碼中,綜合應用尋址方式和循環,調用printNumber和printSpace, 實現題目要求。
源代碼
點擊查看代碼
; 可以打印0~65535不定位數的數字
assume ds:data, cs:code, ss:stack
data segment
x dw 91, 792, 8536, 65535, 2021, 0
len equ $ - x
data ends
stack segment
dw 8 dup(?)
stack ends
code segment
start:
mov ax, data
mov ds, ax
mov ax, stack
mov ss, ax
mov sp, 16
mov cx, len/2 ; 由于數據都是word型,所以len/2才是數據個數
; print循環: 依次打印所有數字
print:
mov ax, word ptr ds:[di] ; 把數據放入al
add di, 2 ; di指針后移2字節
push cx ; 把cx保存起來, 子程序中會修改cx值
call printNumber ; 打印數字
call printSpace ; 打印空格
pop cx ; 恢復cx
loop print
mov ah, 4ch
int 21h
; 子程序: printNumber
; 功能: 打印數字
; 入口參數:
; 寄存器ax (待輸出的數據 --> ax)
; 局部變量說明:
; bx -> 存儲數字字符個數
printNumber:
mov bx, 0 ; 獲取之前位數為0
; 逐位獲取數字
; getEach循環: 獲取每一位,然后壓入棧中
getEach:
; 除數放在16位寄存器bp中
mov bp, 10 ; 除10運算
mov dx, 0 ; 由于除數是16位寄存器,dx也是被除數一部分,需要置零
div bp ; 數據除10
push dx ; 將數字壓入棧中(余數在dx里)
inc bx ; 位數+1
mov cx, ax ; 除法商賦給cx, 如果商為0則說明所有位數都獲取完了
inc cx ; 由于loop時會-1,這里先+1,防止出現負數
loop getEach
; 打印數字
mov cx, bx ; 先把bx存的數字位數賦給cx
; printEach循環: 依次從棧中取出數字,逐位打印
printEach:
pop dx ; 取出一位數
add dl, 30h ; dl是剛才除法的余數,也就是需要得到的位數,+30h是轉成對應字符
mov ah, 2 ; 調用int 21h的2號子程序打印
int 21h
loop printEach
ret
; 子程序: printSpace
; 功能: 打印空格
printSpace:
mov ah, 2
mov dl, 20h
int 21h
ret
code ends
end start
實驗結果
該程序可以成功打印0~65535之間的任意數字。

實驗說明
說明全部寫在注釋中。
實驗任務4
題目描述
針對8086CPU,已知邏輯段定義如下:
data segment
str db "assembly language, it's not difficult but tedious"
len equ $ - str
data ends
編寫8086匯編源程序task4.asm,將data段中字符串里的小寫字符轉換成大寫。
要求:
- 編寫子程序
strupr- 功能:將包含任意字符的字符串中的小寫字母變成大寫
- 入口參數
- (
ds:si) 字符串首地址的段地址和偏移地址分別送至ds和si - (
cx) 字符串的長度
- (
- 出口參數:無
- 在主體代碼中,設置入口參數,調用strupr, 實現題目要求。
源代碼
點擊查看代碼
assume cs:code,ds:data
data segment
str db "assembly language, it's not difficult but tedious"
len equ $ - str
data ends
stack segment
db 8 dup(?)
stack ends
code segment
start:
mov ax, data
mov ds, ax
mov si, 0
mov cx, len ; 參數:字符串長度存在cx中
call strupr ; 調用轉換子程序
call printStr ; 調用打印子程序
mov ax, 4c00h
int 21h
; 子程序 strupr
; 功能: 將包含任意字符的字符串中的小寫字母變成大寫
; 入口參數
; (ds:si) 字符串首地址的段地址和偏移地址分別送至ds和si
; (cx) 字符串的長度
strupr:
push cx
push si ; 先保存兩個寄存器的值
transform:
mov al, ds:[si] ; 取出一個字符
cmp al, 97 ; 判斷ASCII碼是否 >= 97
jl continue ; 小于97直接進入下次循環
cmp al, 122 ; 判斷ASCII碼是否 <= 122
jg continue ; 大于122直接進入下次循環
and al, 0dfh ; 小寫轉成大寫,原理詳見實驗2
mov ds:[si], al ; 把轉換后的字符送回data段
continue:
inc si ; 指針后移
loop transform ; 循環
pop si
pop cx ; 恢復寄存器的值
ret ; 返回
; 子程序 printStr
; 功能: 打印字符串
; 入口參數
; (ds:si) 字符串首地址的段地址和偏移地址分別送至ds和si
; (cx) 字符串的長度
printStr:
push cx ; 保存寄存器的值
push si
print: ; 打印字符循環
mov ah, 2
mov dl, ds:[si]
int 21h
inc si
loop print
pop si ; 恢復寄存器的值
pop cx
ret ; 返回
code ends
end start
實驗結果
可以看到,該程序成功將小寫字母轉成了大寫。

實驗任務5
源代碼
點擊查看代碼
assume cs:code, ds:data
data segment
str1 db "yes", '$'
str2 db "no", '$'
data ends
code segment
start:
mov ax, data
mov ds, ax
mov ah, 1 ; 調用int 21h的1號子程序進行單字符輸入
int 21h
mov ah, 2 ; 調用BIOS中斷例程int 10h的2號子功能
mov bh, 0 ; 設置顯示頁為第0頁
mov dh, 24 ; 設置光標位置在第24行
mov dl, 70 ; 設置光標位置在第70列
int 10h ; 設置光標位置
cmp al, '7' ; 把輸入的字符和字符'7'進行比較
je s1 ; 如果輸入的字符是'7',就跳轉s1處
mov ah, 9 ; 調用int 21h的9號子程序顯示str2
mov dx, offset str2 ; 把標號str2的偏移量放入dx
int 21h ; 顯示標號str2處的字符串(no)
jmp over ; 無條件跳轉到over處
s1: mov ah, 9 ; 調用int 21h的9號子程序顯示str1
mov dx, offset str1
int 21h ; 顯示標號str1處的字符串(yes)
over:
mov ah, 4ch ; 程序結束
int 21h
code ends
end start
運行結果
輸入7后,屏幕上倒數第2行右下角顯示yes

輸入其它字符,屏幕上倒數第2行右下角顯示no

總結和理解
該程序的解釋已經全部寫在源代碼的注釋中。
總結一下,這個程序實現的功能是:
從鍵盤輸入一個字符,判斷輸入的字符是否為
7如果為
7,則從屏幕的第24行第70列開始顯示yes如果不為
7,則從屏幕的第24行第70列開始顯示no
實驗任務6
運行42號中斷程序
通過編譯連接運行task6_1.asm和task6_2.asm后可以看到,屏幕底部出現了綠色的"welcome to 2049!",說明42號中斷程序被成功調用。

自定義中斷:36號中斷程序
源代碼
點擊查看代碼
assume cs:code
code segment
start:
; 36號中斷
mov ax, cs
mov ds, ax
mov si, offset int36 ; set ds:si
mov ax, 0
mov es, ax
mov di, 200h ; set es:di
; 復制中斷程序到 es:[di]
mov cx, offset int36_end - offset int36
cld
rep movsb
; 設置中斷向量表
mov ax, 0
mov es, ax
mov word ptr es:[36*4], 200h
mov word ptr es:[36*4+2], 0
; 調用36號中斷程序
int 36
; 程序結束
mov ah, 4ch
int 21h
; 36號中斷程序
int36:
jmp short int36_start
stu_num db "201983290048 Li Qingyun"
len1 equ $ - stu_num
task db "Assembly Experiment 4 Interrupt No.36"
len2 equ $ - task
; 中斷程序指令部分
int36_start:
mov ax, cs
mov ds, ax
mov di, 202h
call printString
iret
; 打印子程序:
; 參數說明:
; 學號字符串存儲在 -> ds:[di]
printString:
mov ax, 0b800h
mov es, ax ; 控制顯存區域段指針
mov si, 0 ; 顯存區域列指針從0開始
; 先把屏幕前11行置位藍色(第一行會被吃掉)
mov al, 11 ; 全25行
mov dl, 80 ; 每行160個字節文字和顏色都需要修改
mul dl ; 25*160, 獲得需要修改的字節數
mov cx, ax
printBlue1:
mov byte ptr es:[si], 20h ; 用一個空格填充文字位置
inc si ; 后移指針
mov byte ptr es:[si], 17h ; 填充顏色: 藍底+白字:0 001 0 111 -> 17h
inc si ; 后移指針
loop printBlue1
; 把屏幕中間5行置為白底藍字
mov cx, 5*80
printWhite:
mov byte ptr es:[si], 20h ; 用一個空格填充文字位置
inc si ; 后移指針
mov byte ptr es:[si], 71h ; 填充顏色: 白底+藍字:0 111 0 001 -> 71h
inc si ; 后移指針
loop printWhite
; 把屏幕最后10行置為藍色
mov cx, 10*80
printBlue2:
mov byte ptr es:[si], 20h ; 用一個空格填充文字位置
inc si ; 后移指針
mov byte ptr es:[si], 17h ; 填充顏色: 藍底+白字:0 001 0 111 -> 17h
inc si ; 后移指針
loop printBlue2
; 打印學號
mov si, 12*160 ; 從第12行開始打印
mov dx, (80-len1)/2 ; 計算左右兩邊空格數
; 調用打印空格的子程序, 打印學號左側的空格
mov cx, dx
call printSpace
; 打印學號字符串
mov cx, len1
printNumber:
mov al, ds:[di] ; 低位是字符
mov ah, 71h ; 高位是顏色
mov word ptr es:[si], ax ; 按字放入
inc di
add si, 2
loop printNumber
; 再次調用打印空格的子程序, 打印學號右側的空格
mov cx, dx
call printSpace
; 打印下面一行文字
mov si, 14*160 ; 從第14行開始打印
mov dx, (80-len2)/2 ; 計算左右兩邊空格數
; 調用打印空格的子程序, 打印文字左側的空格
mov cx, dx
call printSpace
; 打印文字字符串
mov cx, len2
printTask:
mov al, ds:[di] ; 低位是字符
mov ah, 71h ; 高位是顏色
mov word ptr es:[si], ax ; 按字放入
inc di
add si, 2
loop printTask
; 再次調用打印空格的子程序, 打印文字右側的空格
mov cx, dx
call printSpace
ret
; 子程序: 打印分隔符空格
; 參數: 長度 -> cx
; 位置 -> es:[si]
printSpace:
mov al, 20h ; 一個空格
mov ah, 71h
mov word ptr es:[si], ax
add si, 2
loop printSpace
ret
int36_end:
nop
code ends
end start
運行結果
36號中斷的運行效果是:
將屏幕背景變為藍色,中間5行背景變為白色;
并居中打印"
201983290048 Li Qingyun"和"Assembly Experiment 4 Interrupt No.36",表明這是36號中斷程序。
所有說明都寫在代碼注釋中。

總結與思考
- 標志寄存器會被多數指令修改,如
add、sub、jmp等,而一些指令執行時也會使用標志寄存器的值來確定執行的方式,這可能會對操作結果造成影響。比如實驗一中,inc不會對CF造成影響,但add卻會,如果使用add再使用adc會對結果造成影響。 movsb和movsw指令會使用方向寄存器的值,確定指針移動方向。cmp指令會對兩個操作數做減法,然后將結果存在標志寄存器中,有條件跳轉指令則會根據標志寄存器的值進行判斷并跳轉。- 使用
pushf和popf可以間接實現對標志寄存器的修改,但通常不需要這樣操作。 - 內中斷程序可以使CPU在執行到特殊情況時調用,中斷處理程序可以讓CPU從錯誤中恢復,比如除法中除0的錯誤。
- 中斷向量表存儲在
0000:0000開始空間中,可以存一個字的中斷程序地址。




浙公網安備 33010602011771號