匯編實驗2 多個邏輯段的匯編源程序編寫與調試


目錄

一、實驗內容和實驗任務

1. 實驗任務1

任務1-1

使用debug調試程序,首先使用-u進行反匯編,可以看到第19行的地址是000D,因此使用-g D進行斷點調試。

問題①

執行完成后可以看到:

DS = 076A

SS = 076B

CS = 076C

問題②

code段地址為X,則data段地址為X-2h,stack段地址為X-1h

由於data段、stack段剛好各分配了16B的單位,而系統為段內存分配都是以16B為單位分配的,由於物理地址 = 段地址 × 16 + 偏移地址,因此相隔16B正好段地址相差1h

任務1-2

問題①

執行完成后可以看到:

DS = 076A

SS = 076B

CS = 076C

問題②

code段地址為X,則data段地址為X-2h,stack段地址為X-1h

由於data段、stack段剛好各分配了16B的單位,而系統為段內存分配都是以16B為單位分配的,由於物理地址 = 段地址 × 16 + 偏移地址,因此相隔16B正好段地址相差1h

任務1-3

問題①

執行完成后可以看到:

DS = 076A

SS = 076C

CS = 076E

問題②

code段地址為X,則data段地址為X-4h,stack段地址為X-2h

任務1-4

問題①

執行完成后可以看到:

DS = 076C

SS = 076E

CS = 076A

問題②

code段地址為X,則data段地址為X+2h,stack段地址為X+4h

任務1-5

問題①

對如下定義段

xxx segment
    db N dup(0)
xxx ends

程序加載后,實際分配給該段的內存空間大小是 16 × [數據長度 / 16] Byte[ ]為向上取整)。

問題②

task1_4.asm仍可以正常運行。

原因:

書上說(王爽《匯編語言》第2版 P126-127):

end除了通知編譯器程序結束外,還可以通知編譯器程序的入口在什么地方。在程序6.2中我們用end指令指明了程序的入口在標號start處。

偽指令end 描述了程序的結束和程序的入口。在編譯、連接后,"end start" 指明的程序入口,被轉化為一個入口地址,存儲在可執行文件的描述信息中。

由實驗1可知,程序默認DSCS之間間隔一個PSP區,該區有256Byte,也就是說DSCS地址默認相差10h。如果沒有指出程序開始位置,程序默認從DS+10h處開始運行。上圖的task111.exeend start改成了end,使用-r查看寄存器,可以看到CS的值為076A,而非task1_1.exe中的076C

使用反匯編進行查看,可以發現CS開始的區域全部為0,沒有指令可供執行

任務1總結

  1. 系統按程序段定義順序依次分配內存單元
  2. 程序段分配的內存空間按16Byte的倍數進行分配,如果不滿16Byte則分配16Byte,如果超出16Byte則按16 × [數據長度 / 16] Byte來分配。([ ]為取整)
  3. end start除了通知編譯器程序結束,也是指出程序的入口地址

如果段中數據位 N 個字節,程序加載后,該段實際占據空間為:(N/16的取整數+1)16個字節,如果 N小於16,那么實際占用16個字節(理解這個小問題);如果N大於16,那么實際占用(N/16的取整數+1)16個字節。

引用:https://blog.csdn.net/freeking101/article/details/99694092

2. 實驗任務2

代碼

assume cs:code

code segment
start:
    mov ax, 0b800h
    mov ds, ax
    mov bx, 0f00h
    mov cx, 50h				; 50h,即80次,每次1個字,160字節
    mov ax, 0403h			; 按字寫入
s:  mov ds:[bx], ax
    add bx, 2					; bx+2才是下一個字的地址
    loop s

    mov ah, 4ch
    int 21h
code ends
end start

分析說明

Line 5Lline 6:0b800h不能直接傳入DS,必須通過AX進行中轉

Line8和Line 9:由於一共寫入160字節,這里按字寫入,所以只需要寫80次,也就是50h

Line11:每次bx需要+2,后移兩個字節繼續寫入

運行結果

第一張圖:-f b800:0f00 0f9f 03 04執行后的效果

第二張圖:task2.exe執行后的效果

可以看到兩個效果完全一致。

3. 實驗任務3

代碼

assume ds:data1, cs:code 
data1 segment
    db 50, 48, 50, 50, 0, 48, 49, 0, 48, 49 ; ten numbers
data1 ends

data2 segment
    db 0, 0, 0, 0, 47, 0, 0, 47, 0, 0       ; ten numbers
data2 ends

data3 segment
    db 16 dup(0)
data3 ends

code segment
start:
    mov ax, data1					; data1作為ds
    mov ds, ax
    mov bx, 0
    mov cx, 0ah						; 循環10次
s:  mov ax, ds:[bx]				; 把data1中的數據放進ax
    add ax, ds:[bx+10h]		; 把data2中的數據加到ax上
    mov ds:[bx+20h], ax		; 把ax數據存入data3
    inc bx
    loop s

   mov ah, 4ch
   int 21h
code ends
end start

代碼說明

數據段data1076A:0000-076A:000F

數據段data2076A:0010-076A:001F

數據段data3076A:0020-076A:002F

根據上一個實驗任務,數據段的分配以16字節為單位,data1data2雖然只有10字節,但仍會被分配16字節的空間。

對於Line21-22:

三個數據段的地址空間連續,將DS設為data1的起始地址,則data2的起始地址為ds+10hdata3的起始地址為ds+20h

運行結果

反匯編:

相加前:

相加后:

可以看到data1data2的數據的確相加存入了data3中。

該部分原理參考:https://www.cnblogs.com/danqing/archive/2011/12/01/2270429.html

4. 實驗任務4

代碼

assume cs:code, ss:stack

data1 segment
    dw 2, 0, 4, 9, 2, 0, 1, 9
data1 ends 

data2 segment
    dw 8 dup(0)
data2 ends

; 定義了一個棧段
stack segment
    dw 8 dup(0)
stack ends

code segment
start:
    mov ax, data1
    mov ds, ax			; 先把data1作為數據段ds
    mov sp, 9				; 設置棧頂
    mov bx, 0
    mov cx, 8				; 循環8次,將data1的數據依次進棧
s1: push ds:[bx]
    add bx, 2				; 由於操作的是字數據(dw),bx每次需要+2
    loop s1
	
    mov ax, data2		; 再把data2作為數據段ds
    mov ds, ax
    mov bx, 0
    mov cx, 8				; 循環8次,將棧中數據依次出棧,存儲data2中
s2: pop ds:[bx]
    add bx, 2
    loop s2

    mov ah, 4ch
    int 21h
code ends
end start

運行結果

執行前可以看到:

數據段data1起始地址為:076A:0000

數據段data2起始地址為:076A:0010

data2中全為0

執行后查看076A:0000處開始的內容:

可以看到數據被逆序存放在了076A:0010開始的位置,也就是data2所在位置

程序完成了題目的要求

思路說明

使用了棧段stack用來臨時存儲數據,將data1中的數據從頭到尾依次進棧,然后再將棧中數據彈出,依次存入data2中,完成數據逆序存儲

5. 實驗任務5

代碼

assume cs:code, ds:data
data segment
        db 'Nuist'
        db 2, 3, 4, 5, 6
data ends

code segment
start:
        mov ax, data
        mov ds, ax					; ds存放數據段地址

        mov ax, 0b800H	
        mov es, ax					; ex存放顯存段地址

        mov cx, 5						; 循環5次
        mov si, 0				
        mov di, 0f00h				
s:      mov al, [si]				; 把ds段的一個字母放進al
        and al, 0dfh				; 字母和0DFh相與
        mov es:[di], al 		; 把相與的結果放進es[di]
        mov al, [5+si]			; 把data段的一個數字移入al
        mov es:[di+1], al		; 把數字移到es[di+1]位置
        inc si							; si+1
        add di, 2						; di+2(往后移2字節)
        loop s

        mov ah, 4ch
        int 21h
code ends
end start

運行結果

  1. 原始代碼運行結果

可以看到屏幕左下角出現了彩色的NUIST字樣

  1. 修改line4里5個字節單元的值,重新匯編、鏈接、運行的結果

    (1)將Line 4修改為:db 5 dup(2)

    可以看到左下角出現了綠色的”NUIST“字樣

    (2)將Line 4改為:db 5 dup(5)

    可以看到左下角出現了紫色的”NUIST“字樣

問題&分析

該程序的功能是:將小寫字母轉換成大寫字母,並以不同的顏色打印到屏幕上

  1. Line 19 的作用:將小寫字母轉換成大寫字母

通過分析可知,0DFh的二進制是:1101 1111

0010 0000

任何數與其相與,從高到低第3位始終為0

由於低4位沒有變化,簡單起見取高4位進行分析,可以發現,每間隔兩個數,會有兩個數發生改變,其值會減2,如下表所示。

原數字(16進制) 原數字(2進制) 新數字(16進制) 是否改變(減2)
0 0000 0 -
1 0001 1 -
2 0010 0
3 0011 1
4 0100 4 -
5 0101 5 -
6 0110 4
7 0111 5
8 1000 8 -
9 1001 9 -
A 1010 8
B 1011 9
C 1100 C -
D 1101 D -
E 1110 C
F 1111 D

觀察ASCII表可知,大寫字母的ASCII碼二進制表示的高4位為4或5,查閱上表可知4和5在Line 19相與時不會發生改變。

而小寫字母的高4位二進制表示為6或7,通過上表可知在Line 19相與時,其高4位的值會減2,變為4或5。對比下表分析可以發現,同一個字母大小寫的ASCII碼低4位二進制表示是一樣的,而高4位的十六進制表示相差2。因此在執行Line 19的相與操作時,大寫字母沒有變化,而小寫字母會變成大寫字母。

  1. Line 4的內容的用途:8位色彩代碼

由於這邊定義的是db也就是字節,1個字節為8位,所以這里的5個數是8位色彩代碼,不同的數字對應了不同的色彩編號。但是由於現在的計算機使用的為24位真彩色,其十六進制代碼為8位,找不到8位色彩對應的色彩表,所以暫時無法根據代碼確定顏色,只能隨機嘗試。


2021.12.14更新:

當時寫的時候沒看書,網上也沒搜到詳細資料,大意了。

Line 4的內容其實包含了四個內容:閃爍(第1位)、背景顏色(第2-4位)、高亮(第5位)、前景顏色(第6-8位)。

詳細分析請看《匯編實驗3 轉移指令跳轉原理及其簡單應用編程 - 實驗任務4》

6. 實驗任務6

代碼

assume cs:code, ds:data

data segment
    db 'Pink Floyd      '
    db 'JOAN Baez       '
    db 'NEIL Young      '
    db 'Joan Lennon     '
data ends

code segment
start:
    mov ax, data
    mov ds, ax

    mov ax, data    
    mov es, ax      ; 使用es控制段地址

    mov bx, 0       ; 當前字母偏移
    mov cx, 4       ; 執行4行
s1: ; 進入外層循環
    mov si, cx      ; 備份外層循環次數
    mov cx, 4       ; 給內層循環次數賦值
    
s2: ; 進入內層循環
    mov al, es:[bx]     ; 先把字母放入ax
    or al, 20h          ; ds:[bx]相與, 大寫變小寫, 原理參考task5
    mov es:[bx], al     ; 將轉換好的字母放回去
    inc bx              ; 字母指針后移
    loop s2             
    ; 內層循環結束
    
    ; 回到外層循環
    mov cx, si      ; 恢復外層循環的次數
    mov bx, 0       ; bx置0, 為下一次循環做准備

    mov ax, es      ; es不能直接修改, 需要通過ax中轉
    inc ax          ; 移到下一行字符串開頭(段地址+1, 偏移16字節)
    mov es, ax      ; 修改es

    loop s1         ; 外層循環結束

    mov ah, 4ch
    int 21h
code ends
end start

調試和運行結果

通過-u cs:0 2c進行反匯編可以發現,CS:002C是退出語句,因此使用-g 2c進行斷點調試

先單步調試到將data地址載入ds,使用-d ds:0查看初始值,可以看到第一個單詞中都夾雜着大寫字母

使用-g 2c斷點調試運行后,使用-d ds:0查看ds內的值

可以看到第一個單詞全部變成了小寫,達到了預期結果

分析

這個程序使用了雙重循環,第一層循環控制行數,第二層循環控制每行內的字母

  1. 字母大小寫轉換

內循環(第二層)負責將每行前4個字母依次變成小寫,原理是參考task5中大小寫轉換的思路,這里是大寫轉小寫,需要將大寫字母十六進制ASCII碼的高4位從4或5變成6或7

task5中是將字母與0DFh相與,將從高到低第3位全部變0,因此本題中大寫轉小寫需要將第3位全部變1。將某一位全部變1的方法是用1去進行或運算。因此本題中,使用字母的ASCII碼和20h進行或運算(20h的二進制表示:0010 0000),對應代碼中Line 26:or al, 20h

  1. 雙重循環代碼編寫

實現雙重循環,但是只有一個cx用於控制循環次數,因此在外循環進入內循環前需要將外循環的cx值保存起來(對應代碼Line 21:mov si, cx),然后在內循環結束后將cx的值恢復回去(對應代碼Line 33:mov cx, si

7. 實驗任務7

思路

由於data段中的數據連續,采取從ds開始從頭讀到尾的按行讀入思路進行,es則是按列寫入
先把年份依次取出,放在每行首列,然后依次取出5年的收入、5年雇員數,放在每行對應列上,最后對table中每行分別計算人均收入

代碼

assume cs:code, ds:data, es:table

data segment
    db '1975', '1976', '1977', '1978', '1979'   ; 20字節
    dd  16, 22, 382, 1356, 2390                 ; 5個雙字, 20字節
    dw  3, 7, 9, 13, 28                         ; 5個字, 10字節
data ends

table segment
    db 5 dup( 16 dup(' ') ) 	; 5行,每行16字節
table ends

code segment
start:
    mov ax, data
    mov ds, ax
    mov ax, table
    mov es, ax

; 1.把年份放入每行開頭
    mov bx, 0   ; table行指針(每行開始地址)
    mov si, 0   ; table中的字節指針
    mov di, 0   ; data中的字節指針
    mov cx, 5   ; 5個年份
year:  
    ; 外循環, 5個年份
    mov dx, cx  ; 保存cx
    mov cx, 4   ; 每個年份4個數字
yearnum:  
    ; 內循環, 每個年份的4個數字
    mov al, byte ptr ds:[di]     ; 內存之間不能直接轉移,需要寄存器中轉
    mov byte ptr es:[bx][si], al
    inc si          ; table指針后移
    inc di          ; data中指針后移
    loop yearnum 
    ; 內循環結束, 一個年份放到位

    ; 繼續外循環
    mov cx, dx      ; 恢復cx
    add bx, 10h     ; bx移到table下一行開始的位置
    mov si, 0       ; si指向table下一行的第一個位置
    loop year
    ; 外循環結束, 5個年份放入表中

; 2.把收入放入每行中
    mov bx, 0       ; table第1行
    mov si, 5       ; table第6列(收入開始的那一列)
    mov cx, 5       ; 5個收入
income:
    ; 收入是dword, 一次放不了雙字, 只能分兩個字放過去
    mov ax, word ptr ds:[di]
    mov word ptr es:[bx][si], ax
    add si, 2
    add di, 2
    mov ax, word ptr ds:[di]
    mov word ptr es:[bx][si], ax
    add si, 2
    add di, 2               

    add bx, 10h     ; table下一行
    mov si, 5       ; table下一行的第6列
    
    loop income

; 3.把雇員放入每行
    mov bx, 0       ; table第1行
    mov si, 0Ah     ; table第11列
    mov cx, 5       ; 雇員放5次
employee: 
    ; 雇員是單字, 直接放就行
    mov ax, word ptr ds:[di]
    mov word ptr es:[bx][si], ax
    add si, 2
    add di, 2
    
    add bx, 10h     ; table下一行
    mov si, 0Ah     ; table下一行第10列

    loop employee

; 4.計算人均收入
    mov bx, 0       ; table第1行
    mov si, 5       ; table第6列,收入那一列開始
    mov cx, 5       ; 計算5年的
average:
    ; 這里注意: 是從低地址依次取出兩個字
    ; 32位被除數低16位放ax, 因此第一次取出的一個字要放在ax
    mov ax, word ptr es:[bx][si]
    add si, 2
    ; 32位被除數高16位放dx
    mov dx, word ptr es:[bx][si]
    add si, 3       ; 需要跳過一個空格
    ; 除法運算
    div word ptr es:[bx][si]
    add si, 3       ; 需要跳過一個空格
    ; 把ax中的商取出放在表中
    mov word ptr es:[bx][si], ax
    
    add bx, 10h     ; table下一行
    mov si, 5       ; table下一行第5列
    loop average


    mov ah, 4ch
    int 21h
code ends
end start

調試運行和運行結果

  1. 先單步執行Line 15 - Line 18,查看datatable內的數據

    可以看到ds開始的部分載入了data段的數據,es開始的部分前80字節全部為空格(20h),說明data段和table 段已經正確載入

  1. 在debug中進行反匯編,可以看出程序在CS:0097位置結束

    因此使用-g 97進行斷點調試

  1. 運行整個程序后再次查看datatable內的數據(dses開始的內存空間)

仔細對比ds內的數據,可以看到年份、收入和雇員數已經正確存入表中

  1. 計算人均收入

手動計算結果:

年份 收入 雇員數 人均收入(整數部分) 人均收入(16進制)
1975 16 3 5 05
1976 22 7 3 03
1977 382 9 42 2A
1978 1356 13 104 68
1979 2390 28 85 55

對比運行結果中的數據,可以發現人均收入計算正確。

至此可以確認,所有數據都已完整、准確存入表中。

二、實驗總結

  1. 重新復習了mov指令,在編寫的時候忘記mov不能用於兩個內存數據移動,必須通過寄存器中轉

  2. 在程序中定義的邏輯段內存地址是連續的,在內存中的排列順序與邏輯段定義順序一致

    如果沒有指定程序開始運行的位置,會從第一個邏輯段位置開始執行指令

  3. 系統按程序段定義順序依次分配內存單元,程序段分配的內存空間按16B的倍數進行分配,如果不滿16B則分配16B,如果超出16B則按[數據長度 / 16] + 1來分配。([ ]為取整)

  4. end start除了通知編譯器程序結束,也是指出程序的入口地址

  5. 編寫多重循環時,只有cx可以用於控制循環次數,因此在從外循環進入內循環之前,需要先將cx的值保存到其他寄存器,或是壓入棧中,內循環結束退出到外循環時再將cx的值恢復回來

  6. 8086的顯存在存放在顯示內容時,以一個字為單位,低字節為打印的字符內容,高字節為8位色彩代碼

  7. 小寫字母轉大寫可以使用and 16進制字母ASCII碼, 0DFh來實現,大寫字母轉小寫可以使用or 16進制字母ASCII碼, 20h來實現。

  8. 最后一個表格實驗任務實在是非常繁瑣,調試了很久,中間有些錯誤會導致無限循環,最后調試了半天才得出了正確結果。思路不難,難的是把思路轉換成匯編代碼。還得多寫寫,不太熟練,踩了不少坑。


免責聲明!

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



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