匯編實驗4 8086標志寄存器及中斷


匯編實驗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         ; 打印字符串循環

總結:打印字符串,並且不會把#打出來

說明(實驗指導里給的)

  1. DOS系統功能調用int 21h的1號子功能:

功能:從鍵盤上輸入單個字符

入口參數:(ah) = 1

出口參數: (al)存放輸入字符的ASCII碼

即:

mov ah,1
int 21h ; (al) <-- 輸入字符的ascII碼
  1. 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.asmtask6_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號中斷程序。

所有說明都寫在代碼注釋中。

總結與思考

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM