目錄
一、實驗內容和實驗任務
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可知,程序默認DS
與CS
之間間隔一個PSP區,該區有256Byte,也就是說DS
與CS
地址默認相差10h
。如果沒有指出程序開始位置,程序默認從DS+10h
處開始運行。上圖的task111.exe
將end start
改成了end
,使用-r
查看寄存器,可以看到CS
的值為076A
,而非task1_1.exe
中的076C
。
使用反匯編進行查看,可以發現CS
開始的區域全部為0,沒有指令可供執行

任務1總結
- 系統按程序段定義順序依次分配內存單元
- 程序段分配的內存空間按16Byte的倍數進行分配,如果不滿16Byte則分配16Byte,如果超出16Byte則按
16 × [數據長度 / 16] Byte
來分配。([ ]
為取整) 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
代碼說明
數據段data1
:076A:0000
-076A:000F
數據段data2
:076A:0010
-076A:001F
數據段data3
:076A:0020
-076A:002F
根據上一個實驗任務,數據段的分配以16字節為單位,data1
和data2
雖然只有10字節,但仍會被分配16字節的空間。
對於Line21-22:
三個數據段的地址空間連續,將DS
設為data1
的起始地址,則data2
的起始地址為ds+10h
,data3
的起始地址為ds+20h
運行結果

反匯編:

相加前:

相加后:

可以看到data1
和data2
的數據的確相加存入了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
運行結果
- 原始代碼運行結果
可以看到屏幕左下角出現了彩色的NUIST字樣

-
修改line4里5個字節單元的值,重新匯編、鏈接、運行的結果
(1)將Line 4修改為:
db 5 dup(2)
可以看到左下角出現了綠色的”NUIST“字樣
(2)將Line 4改為:
db 5 dup(5)
可以看到左下角出現了紫色的”NUIST“字樣
問題&分析
該程序的功能是:將小寫字母轉換成大寫字母,並以不同的顏色打印到屏幕上
- 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的相與操作時,大寫字母沒有變化,而小寫字母會變成大寫字母。

- 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
內的值
可以看到第一個單詞全部變成了小寫,達到了預期結果

分析
這個程序使用了雙重循環,第一層循環控制行數,第二層循環控制每行內的字母
- 字母大小寫轉換
內循環(第二層)負責將每行前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
- 雙重循環代碼編寫
實現雙重循環,但是只有一個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
調試運行和運行結果
-
先單步執行Line 15 - Line 18,查看
data
和table
內的數據可以看到
ds
開始的部分載入了data段的數據,es
開始的部分前80字節全部為空格(20h
),說明data
段和table
段已經正確載入

-
在debug中進行反匯編,可以看出程序在
CS:0097
位置結束因此使用
-g 97
進行斷點調試

- 運行整個程序后再次查看
data
和table
內的數據(ds
和es
開始的內存空間)

仔細對比ds
內的數據,可以看到年份、收入和雇員數已經正確存入表中
- 計算人均收入
手動計算結果:
年份 | 收入 | 雇員數 | 人均收入(整數部分) | 人均收入(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 |
對比運行結果中的數據,可以發現人均收入計算正確。
至此可以確認,所有數據都已完整、准確存入表中。
二、實驗總結
-
重新復習了
mov
指令,在編寫的時候忘記mov
不能用於兩個內存數據移動,必須通過寄存器中轉 -
在程序中定義的邏輯段內存地址是連續的,在內存中的排列順序與邏輯段定義順序一致
如果沒有指定程序開始運行的位置,會從第一個邏輯段位置開始執行指令
-
系統按程序段定義順序依次分配內存單元,程序段分配的內存空間按16B的倍數進行分配,如果不滿16B則分配16B,如果超出16B則按
[數據長度 / 16] + 1
來分配。([ ]為取整) -
end start
除了通知編譯器程序結束,也是指出程序的入口地址 -
編寫多重循環時,只有
cx
可以用於控制循環次數,因此在從外循環進入內循環之前,需要先將cx
的值保存到其他寄存器,或是壓入棧中,內循環結束退出到外循環時再將cx
的值恢復回來 -
8086的顯存在存放在顯示內容時,以一個字為單位,低字節為打印的字符內容,高字節為8位色彩代碼
-
小寫字母轉大寫可以使用
and 16進制字母ASCII碼, 0DFh
來實現,大寫字母轉小寫可以使用or 16進制字母ASCII碼, 20h
來實現。 -
最后一個表格實驗任務實在是非常繁瑣,調試了很久,中間有些錯誤會導致無限循環,最后調試了半天才得出了正確結果。思路不難,難的是把思路轉換成匯編代碼。還得多寫寫,不太熟練,踩了不少坑。