王爽匯編全書知識點大綱
第一章 基礎知識
-
機器語言
機器語言是機器指令的集合,電子計算機的機器指令是一系列二進制數字.計算機將之轉換為一系列高低電平脈沖信號來驅動硬件工作的.
-
匯編語言的產生
由于機器語言指令都是由01組成,難以編寫,記憶和維護程序.所以匯編語言為了解決這一問題產生.匯編語言又稱為機器語言的助記符
-
匯編語言的組成
匯編指令:有對應的機器碼.機器碼的助記符
偽指令:沒有對應的機器碼,由編譯器執行,計算機并不執行.
其他符號:如+,-,*,/等,由編譯器識別,沒有對應的機器碼
-
存儲器
相當于大腦,程序指令和數據在存儲器中都是以二進制數據形式存放,沒有什么區別.且內存是動態存儲數據的,程序或文件從磁盤加載到內存才能由cpu直接讀取.斷電后,內存中的數據就消失了.磁盤才是永久存儲數據的地方.
存儲單元從0開始編號.能存儲一個8位二進制數的單元,稱為存儲單元.為一個byte.大小,單位B. 位的單位為bit.向上為kb,mb,GB,TB,PB
-
cpu對存儲器的讀寫
必要的信息交互
(1). 存儲單元的地址(地址信息),地址總線(cpu要從存儲器中讀取數據,首先要要確認,某個存儲單元的地址.)
(2).器件的選擇,讀或寫的命令(控制信息)控制總線(另外計算器中不只有存儲器一種器件,其它器件,也有存儲器.要確定從什么器件讀取.)
(3).讀或寫的數據(數據信息),數據總線.
總線與信息傳送 : 電子計算機能處理,傳輸的信息都是電信號,電信號當然通過導線傳送,也就是總線(數根導線的集合).
讀取例子:Mov Ax,[3]
CPU通過地址線將地址信息3發出cpu通過控制總線發出讀命令,并選中存儲器芯片,并通知他,將要從中取數據,存儲器將3號單元中的數據8通過數據線送入cpu
-
三大總線
地址總線
地址總線:8根,表示能尋址0-1023的內存單元.能尋址2^8個內存單元,代表了cpu的尋址范圍
- 數據總線
假如數據總線有8根,那么每次只能傳送1B數據.每次只能傳送一個8位數.
- 控制總線
假如控制總線位有8根,那么有2^8種控制方式,控制總線的寬度決定了CPU對外部器件的控制能力.
-
內存地址空間
每種器件cpu都通過總線將其連接,cpu在操控它們的時候,都把它們當成是內存來對待.(正如linux把每個硬件都當成文件來對待),操作顯示器就是操作顯存,顯存就可以當文件一樣進行讀寫操作.cpu操作硬件實際上就是操作硬件的內存或端口
0-7fffh的32kb空間為主隨機存儲器的地址空間.地址8000h-9fffh的8kb空間為顯存地址空間A000h~ffffh的24kb空間為各個rom的地址空間.(寫入顯存地址空間,就會顯示在顯示器上,寫入只讀rom地址空間,那么沒有任何效果,我們基于一個計算機硬件系統編程的時候,必須知道這個系統中的內存地址空間分配情況).
BIOS有一個只讀room,是硬件和操作系統的接口.
第二章 寄存器
-
cpu的基本結構
運算器:運算器進行信息處理
寄存器:寄存器進行信息存儲
控制器:控制器控制各種器件進行工作
內部總線:用來連接cpu內部各種元器件的導線集合
寄存器:AX,BX,CX,DX, SI,DI,SP,BP,IP, CS,SS,DS,ES, PSW
-
通用寄存器
為了和上一代cpu兼容可以拆分16位通用寄存器,拆分為兩個8位寄存器.從右向左,以0開始編號.
AX可拆分為AH和AL
BX可拆分為BH和BL
CX可拆分為CH和CL
DX可拆分為DH和DL
-
字的存儲和數制表示
字在寄存器中的存儲:高8位放在寄存器的高8位中,低8位放在寄存器的低8位中
字在內存中的存儲:低位存儲在低地址中,高位存儲在高地址中.稱為小尾存儲.
數制的表示:源代碼中默認數制是十進制,在數字后面添加h表示16進制,如果16進制最高位為字符,那么必須在前面添0, 如果數字后面加b后綴表示二進制數字. 特別注意,在debug中數制默認是16進制.
-
幾條匯編指令
匯編指令默認不區分大小寫
假設ax和bx中的值為8226h,add ax,bx執行后,相加的結果為1044ch,但是ax存儲不下,最后ax中的值為044ch
假設ax中的值為00c5h,add al,85h執行結果為158h,al存儲不下會設置標志位.千萬不要認為高位進位會存儲在ah中,這是錯誤的想法,最后ax中的結果為0058h.
下面的指令是錯誤指令:
mov ax,bl //兩個寄存器尺寸不一致
mov bh,ax //兩個寄存器尺寸大小不一致
mov al 20000 //20000在al中不能存儲
add al 100H //100h超過al中存儲的最大范圍
- 計算2^4:
Mov ax,2
ADD AX,AX
ADD AX,AX
ADD AX,AX
-
8086物理地址
內存空間是一個線性空間.每一個內存單元在這個空間中都有唯一的地址,我們將這個唯一的地址稱為物理地址.
16位機的含義:
運算器一次最多處理 16 位的數據
寄存器最大寬度為 16 位
寄存器和運算器之間通路為 16 位,即數據總線 16 條
物理地址的計算:為了用16位地址尋址更多的內存,8086采取用連個16位寄存器合成一個20位地址,即
段寄存器中的地址*16+16位偏移地址
一個段的起始地址必然是16的倍數
因為偏移寄存器只有16位大小,所以給定一個段,它最大能在當前段尋址64kb
尋址過程:段地址和偏移地址通過內部總線送入一個稱為地址加法器的部件,地址加法器通過內部總線將20位物理地址送入輸入輸出控制電路.輸入輸出控制電路將20位物理地址送上地址總線.20位物理地址總線傳送到存儲器.
一個地址可以用不同的段形式表示.
-
CS和IP
段寄存器:CS,DS,ES,SS
CS位代碼段寄存器,IP位代碼偏移地址寄存器,cpu把任何時刻cs和ip指向的地址處的數據當做指令執行.
指令的讀取和執行:
假設地址2000:0~2000:2中存儲著指令mov ax,123h
初始狀態cs=2000h,ip=0,將從內存2000h:0中讀取指令執行
將CS和IP的值送上地址加法器合成一個20位的物理地址
地址加法器將物理地址送入輸入輸出電路
輸入輸出電路將物理地址20000h送上地址總線
從地址20000h中讀取數據通過數據總線送入cpu
輸入輸出電路將指令mov ax,123h送入指令緩沖器
讀取一條指令后ip的值自動增加,指向可以讀取的下一條指令
執行控制器執行指令mov ax,123h
然后重復上面的步驟
8086cpu加電啟動或復位后,cs和ip被設置為cs=ffffh,ip=0000h,cpu從內存FFFF0h單元中讀取指令并執行.
修改cs和ip的指令:
通過修改cs和ip的指向,起到控制cpu執行流程和跳轉等功能.在debug中可以通過mov指令修改cs的值,但是不能用mov修改ip的值
在cpu中程序員能夠用指令讀寫的部件只有寄存器
mov指令不能用于設置cs:ip的值.原因很簡單,因為沒有這個功能.但是debug中可以用mov cs,xxx
想同時修改 CS,IP
jmp 1000:0 則 CS=1000H,IP=0000H //這條指令只在debug中有效
僅修改 IP 的內容
jmp 某一合法寄存器 //在源代碼中也有效
-
debug調試指令
用 R(register) 命令查看或改變 cpu 寄存器內容
r ax/其它寄存器 功能:修改 ax 寄存器內容
直接r, 查看各個寄存器的值.
用 D(dump)命令查看內存中內容
d 1000:0 會顯示10000:0地址開始的128個內存單元的內容
D 段地址:開始偏移地址 結束偏移地址 可以查看指定內存單元的內容例子如下:
D 1000:0 f (1000:0 ~ 1000:f內存單元的數據) 再次輸入D會把后續的128個內存單元內容顯示出來.
用 E (enter)命令改寫內存中的內容
采用“e 起始段地址:偏移地址 數據 數據 數據..." e 1000:0 0 1 2 3 4 5 6 7 寫機器碼.這是一次性輸入
用一個一個的輸入方式,改寫內存的內容 (按空格鍵陸續輸入,敲回車結束輸入.) 例如 e 1000:0,以向內存中寫入字符 e 1000:0 1 'a' 2 'b' 3 'c' 寫機器碼和字符.
注:e命令寫進去的機器碼數值16進制形式,所以數字不能超過兩位十六位數字,eg:111這是錯誤的(因為debug中默認是16進制數制).
用 U(unassemble) 查看內存中機器碼含義,用 T 執行cs:ip指向的指令.
U 段地址:偏移地址
查看指定地址間的匯編指令:U 段地址:偏移地址 結束地址
用 A(assemble) 以匯編指令形式在內存中寫入機器指令. A 段地址:偏移地址
用g(go) 命令跳到某指令,g 地址
eg:g 0012(表示從當前cs:ip指向的指令執行,一直到(ip) = 0012為止.) 用 t(trace) 命令單步執行 用 p (proceed)命令跳躍循環
q退出debug
dosbox中可以按alt+enter鍵實現全屏.
debug界面認識

- 習題講解:下面的代碼幾次修改IP的值
mov ax,bx
sub ax,ax
jmp ax
答:一共4次,第一次:讀取完mov ax,bx后.第二次:讀取完sub ax,bx后.第三次:讀取完jmp ax后.第四次:執行完jmp ax后.
第三章 寄存器內存訪問
-
字在內存中的存儲
低位字節存放在低地址單元中,高位字節存放在高地址單元中.
字單元:兩個字節聯合起來的16位.存儲單元.起始地址為N的字單元簡稱為N地址字單元
-
DS和[ADRESS]訪問內存單元
ds寄存器用來存放要訪問的內存單元的段地址
8086硬件設計的問題,不能將立即數直接用mov指令傳送到段寄存器中
在源代碼中使用[idata]時,[idata]會被當成idata.
調試的時候,[idata]才會被翻譯成內存單元.源代碼中需要使用[bx/si/di..]等
表示內存單元.也可以使用段前綴, 段寄存器 : [idata]
-
字的傳送
只要在mov指令中給出的寄存器是16位,就會對字進行操作.
也可以用尺寸指針指明要操作的內存單元大小,byte ptr [],word ptr [],dword ptr[]
-
mov/add/sub指令詳解
mov指令有以下幾種格式:
mov 寄存器,數據
mov 寄存器,寄存器
mov 寄存器,內存單元
mov 內存單元,寄存器
mov 段寄存器,寄存器
mov 寄存器,段寄存器 //這個是自己實驗添加的
mov 段寄存器,內存單元//這個是自己實驗添加的
mov 內存單元,段寄存器//這個是自己實驗添加的
注意事項:
- 從這幾種格式可以看出,操作數不能同時為段寄存器
- 也不能同時為內存操作數.
- 不能將立即數直接送入段寄存器.
- 立即數只能放在右邊,如果此時左邊操作數為內存單元,需要指明內存單元尺寸
- 操作數不能出現ip寄存器
- add指令有以下幾種格式:
add 寄存器,數據
add 寄存器,寄存器
add 寄存器,內存單元
add 內存單元,寄存器
add byte/word/... ptr 內存單元,數據
注意事項:
- add指令中不能出現段寄存器操作數
- add中操作數不能同時為內存單元
- 第一個操作數必須是寄存器或者內存單元
- add指令中也不能出現ip寄存器
sub指令,格式和使用方式同add指令
-
數據段
我們可以將一組已知起始地址的內存單元定義為數據段,該段<= 64kb
然后將ds設置為起始地址,然后該地址開始的段就可以當做數據段使用
同理可以設置ss:sp的指向,來構造棧段
代碼段是通過,
end 標號
指明起始地址(標號就是代碼段開始處)-
棧
后進先出數據結構,最初棧指針指向高地址,越壓棧,sp寄存器指針越往低地址移動.
通過ss:sp指定和初始化棧.
push和pop指令出棧和入棧
push 寄存器/內存單元/段寄存器 : sp=sp-2 mov (sp),(寄存器/內存單元/段寄存器)
pop 寄存器/內存單元/段寄存器: mov (寄存器/內存單元/段寄存器),(sp) sp=sp+2
注意事項:
操作數不能是ip寄存器,棧每次操作一個字
棧可以超界,且沒有任何保護機制
棧環繞問題
如果將10000H~1FFFFH這段空間當做棧段,初始狀態是空的,此時ss=1000h,sp=?
答:sp=0,因為此時會發生棧環繞問題,FFFF的底端無法尋址,所以就會循環跑到另一頭0上面去.,此時壓棧會發生,sp=0-2=0FFFEH
- 編程例子:
編程:將10000h~1000Fh這段空間當做棧,初始狀態是空的,設置ax=001ah,bx=001bh,將ax,bx入棧,然后將ax,bx清零,從棧中恢復ax,bx的值.
解答:
mov ax,1000h
mov ss,ax
mov sp,10h
mov ax,001ah
mov bx,001bh
push ax
push bx
sub ax,ax
sub bx,bx
pop bx
pop ax
利用棧交換ax,bx內容
push ax
push bx
pop ax
pop bx
....
-
實驗2
可以在調試命令中需要段地址的地方,使用段寄存器.例子如下:
d 1000:0 會將段地址1000送入ds中.
e ds:0 0 1 2 3 4
u cs:0 ;以匯編指令形式,顯示當前代碼
a ds:0
ss:sp的設置指令應該放在一起緊挨著,單步中斷并不會中斷,這兩條 指令會連續執行,因為中斷也設計壓棧操作,那么假設如果在sp還未設置時,發生了中斷,那么中斷的壓棧操作將會壓入不正確的棧頂,緊接著又設置了新的sp值,那么程序就不會正確運行.
第4章 第一個程序
-
源程序到可執行程序
源程序文本文件-->編譯鏈接--->生成可執行程序
可執行程序包含從源程序中翻譯過來的機器碼和數據,還有相關的描述信息(比如,程序有多大,要占用多少內存空間等)
操作系統根據可執行文件的描述信息,將可執行文件中的機器碼和數據加載入內存,并進行相關的初始化,并進行相關的初始化(比如設置CS:IP指向第一條指令),然后cpu執行程序.
如果不用end指明程序入口,那么編譯成程序時,cpu會自動將cs,ip指向改程序數據開頭.如果指定了標號,那么cpu會將cs和ip指向標號地址處.
-
源程序
assume cs:code
code segment
mov ax,0123h
mov bx,0456h
add ax,bx
add ax,ax
mov ax, 4c00h
int 21h
code ends
end
xxx segment ....xxx ends偽指令是定義一個段.標號就是偏移地址,不需要用offset獲取
end偽指令 : 表示結束一個匯編源程序的編譯,end 后面可以接一個標號來指明可執行代碼入口.
assume偽指令 : 它假設程序的某個段寄存器與某個段標號相關聯.通過這種關聯,在需要的情況下,編譯程序可以將段寄存器和某一個具體的段想聯系.
程序返回 : mov ax,4c00h int 21h程序返回.告訴運行本程序的程序,運行結束后的結果狀態.ah=4c,傳遞中斷子程序功能號,表示調用21中斷的4c號子程序. Al=0表示返回值為0.如果不正確返回
程序就會運行不正常.
-
鏈接
當源程序很大的時候,我們可以將源程序分成多個程序,然后編譯成多個obj文件.然后連接在一起,
程序中調用了某個庫文件中子程序,需要將這個庫文件和程序生成的目標文件連接到一起,生成一個可執行文件.
一個源程序編譯后,得到了存有機器碼的目標文件,目標文件中的有些內容還不能直接生成可執行文件.連接程序將這些內容處理為最終可執行信息.所在,在只有一個源程序文件,而又不需要調用某個庫中的子程序的情況下,也必須用連接程序對目標文件進行處理,生成可執行文件.
-
dos程序的加載流程
dos中直接運行exe文件時,首先加載入內存,設置cs:ip的入口(end指定的標號處).
找到一段起始地址為SA:0000(即起始地址的偏移地址為0)的容量足夠的空閑內存區
前256個字節中創建一個段前綴psp的數據區,dos利用psp來和被加載程序進行通信.ds首先初始化時,是指向psp數據區的.
程序放在SA+10H:0地址處,CS:IP指向這里
-
實驗3
雜項知識:將程序加載入內存執行,cx存儲的是程序的長度
第五章 [bx]和loop
-
[bx]
要完整描述一個內存單元需要兩種信息:@內存單元地址@內存單元長度
表示段地址在ds中,bx中存放的是該段的偏移地址.
-
Loop指令
(cx)=(cx) - 1
判斷cx中的值是否為零,非零繼續循環,零則退出循環.
循環例子程序:計算2^12次方
assume cs:code
code segment
mov ax,2
mov cx,11
s:
add ax,ax
loop s
mov ax, 4c00h
int 21h
code ends
end
例子2:計算123*236
assume cs:code
code segment
mov ax,0
mov cx,123
s:
add ax,236
loop s
mov ax, 4c00h
int 21h
code ends
end
inc/dec 寄存器或
inc/dec (byte /word/..) ptr [內存單元]
自增或自減指令
-
雜項
在匯編源程序中,數據不能以字母開頭需要在前面添加一個0.
在 dos 下,一般情況
0:200-0:2ff
空間中沒有系統或其他程序的數據或代碼遇到loop指令時,用p,可以跳過循環.遇到int 21是也可以用p命令結束程序
g 地址偏移 ,直接跳轉到該地址處執行
-
loop和bx 的聯合應用
計算ffff:0~ffff:b每個內存單元中的數據相加的和,結果存儲在dx中
assume cs:code
code segment
mov ax,0ffffh
mov ds,ax
mov bx,0
mov dx,0
mov cx,12
mov ah,0
s:
mov al,[bx]
add dx,ax
inc bx
loop s
mov ax, 4c00h
int 21h
code ends
-
段前綴
可以在[idata]或[bx]前面加上cs,ds,es,ss等段前綴來指明段地址
cs/ds/es/ss : []
-
段前綴的使用
編程:將內存ffff:0ffff:b單元的數據復制到0:200020b中
assume cs:code
code segment
mov ax,0ffffh
mov ds,ax
mov ax,0020h
mov es,ax
mov bx,0
mov cx,6
s:
mov ax,[bx]
mov es:[bx],ax
add bx,2
loop s
mov ax, 4c00h
int 21h
code ends
end
-
實驗4 bx和loop的使用
編程,向內存0:200~0:23f依次傳送數據0-63(3fh)
assume cs:code
code segment
mov ax,0020h
mov ds,ax
mov bx,0
mov cx,64
s:
mov [bx],bl
inc bx
loop s
mov ax, 4c00h
int 21h
code ends
end
第六章 包含多個段的程序
-
在代碼中使用數據
程序獲得所需空間的方法有兩種
是在程序加載的時候為程序分配
是在程序運行過程中向系統申請.我們只討論前者.因為后者屬于動態分配內存.這兩種方式都是由操作系統分配.
還有就是在dos時代,可以直接將數據送入內存.當數據有很多時,這種方法存儲明顯不現實
db定義字節, dw定義字數據,dd定義雙字,dq8字節,dt十字節
-
程序框架
assume cs:code
code segment
start:
程序代碼
mov ax, 4c00h
int 21h
code ends
end start
end代表程序源代碼結束,并指明可執行代碼入口地址.
如果指明入口地址,那么從程序的首地址開始執行,不管起始地址處是否是可執行代碼.
CPU根據什么設置CS:IP指向程序的第一條要執行的指令?是由可執行文件中的描述信息指明的.描述信息則主要是編譯,連接程序對源程序中相關偽指令進行處理所得到的信息.
即使我們用了類似Assume cs:code,ds:data這樣的偽指令,在執行的時候,Cpu也不會把他們的段寄存器指向相應的內容,cpu在加載程序運行把CS:IP指向我們Start入口處,我們在這里調整ss,ds等段寄存器的指向.
-
在代碼中使用棧
利用棧將程序中的數據逆序存放
assume cs:code
code segment
dw 0123h, 0456h, 0789h, 0abch, 0defh, 0fedh, 0cbah, 0987h
dw 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0,0,0,0,0
start:
mov ax,cs
mov ss,ax
mov sp,30h
mov bx,0
mov cx,8
s:
push cs:[bx]
add bx,2
loop s
mov bx,0
mov cx,8
s1:
pop cs:[bx]
add bx,2
loop s1
mov ax, 4c00h
int 21h
code ends
end start
- 棧使用技巧:在內存之間傳遞數據,連個變量之間的傳遞,可以先用棧做中轉,即先壓棧變量1
然后彈出來給變量2
-
將數據,代碼,棧放入不同的段
-
實驗5
非常重要:如果段中的數據占N個字節,則程序加載后,該段實際占有的空間為 ((N+15)/16)_16 。
解析:
N分為被16整除和不被16整除。
當N被16整除時: 占有的空間為(N/16)_16
當N不被16整除時: 占有的空間為(N/16+1)_16,N/16得出的是可以整除的部分,還有一個余數,余數肯定小于16,加上一個16。
程序加載后分配空間是以16個字節為單位的,也就是說如果不足16個字節的也分配16個字節。
兩種情況總結成一個通用的公式:((N+15)/16)_16
每個段(例如 數據段,代碼段)必須是 16字節的整數倍(系統規定)所以小于16個字節的會自動的變成16個字節
程序開始時ds指向psp比cs大10h,cx中存放著程序的長度
編寫代碼,將a段和b段的數據相加,保存在c段中.
assume cs:code
a segment
db 1, 2, 3, 4, 5, 6, 7, 8
a ends
b segment
db 1, 2, 3, 4, 5, 6, 7, 8
b ends
c segment
db 0, 0, 0, 0, 0, 0, 0, 0
c ends
code segment
start: mov ax, a
mov ds, ax
mov ax, b
mov es,ax
mov ax, c
mov ss,ax
mov ax,0
mov bx,0
mov cx,8
s: mov al,ds:[bx]
add al,es:[bx]
mov ss:[bx],al
inc bx
loop s
mov ax, 4c00h
int 21h
code ends
end start
第7章 更靈活的定位內存的方法
-
and和or 指令
and指令,按位進行與運算,or按位進行或運算.
第一個操作數必須寄存器或內存單元(內存單元必須指明尺寸)
操作數不能是段寄存器,或ip寄存器
兩個操作數的尺寸必須一致
操作數不能同時是兩個內存單元.
-
關于ascii碼
大寫字母在6590(41h5Ah)(0100000101011010)開始,小寫字母97122(61h-7Ah)(01100001~01111010)開始.
大寫字母第6位為0,小寫字母第6位為1(索引從1開始算)
重點是了解了字符的ascii碼的規律后,可以利用and和or指令進行大小寫轉換
數字的ascii碼4857(30h-49h)(00110000b00111001b).
數字字符,跟二進制數字的區別是數字字符第5位和第6位都為1而,數字值第7位和第8位都為0
有助于理解ascii碼壓縮和非壓縮指令
編碼方案:在顯示器上呈現的文字等都是文本字符串,在計算機內部是以二進制形式存儲,通過解碼,顯示器在屏幕上畫出文本,其中一種編碼方案是ascii碼.
文本處理過程例子:我們按下鍵盤a鍵,這個按鍵的信息被送入計算機,計算機用ascii嗎的規則對其進行編碼,將其轉化為61h存儲在內存的指定空間中,文本編輯軟件從內存中取出61h,將其送到顯卡上的顯存中;工作在文本模式下的顯卡,用ascii碼的規則解釋顯存中的內容.61h被當作字符"a".顯卡驅動顯示器,將字符"a"的圖像畫在屏幕上.
db 'unIX' <=> db 75h,6eh, 49h,58h.
mov al, 'a' <=> mov al,97
-
大小寫轉換問題例子代碼
寫一段程序將 'BaSic'字符串轉化為大寫,將'iNfOrMaTiOn'轉換為小寫
assume cs:code, ds:data
data segment
db 'BaSic'
db 'iNfOrMaTiOn'
data ends
code segment
start:
mov ax, data
mov ds, ax
mov bx,0
mov cx,5
s0:
mov al,[bx]
and al,11011111b
mov [bx],al
inc bx
loop s0
mov cx,11
mov bx,5
s1:
mov al,[bx]
or al,00100000b
mov [bx], al
inc bx
loop s1
mov ax, 4c00h
int 21h
code ends
end start
-
更靈活的尋址
[bx+idata] <=> idata[bx],[bx].idata
可以用于數組尋址,例子如下,將數據段中第一個字符串轉換為大寫,第二個轉換為小寫
assume cs:code, ds:data
data segment
db 'BaSic'
db 'MinIX'
data ends
code segment
start:
mov ax, data
mov ds, ax
mov bx,0
mov cx,5
s0:
mov al,[bx]
and al,11011111b
mov [bx],al
mov al,[bx+5]
or al,00100000b
mov [bx+5],al
inc bx
loop s0
mov ax, 4c00h
int 21h
code ends
end start
技巧:因為第一個字符串和第二個字符串一樣長,我們就可以用bx+idata尋址,且可以用一個循環就可以達到轉換目的,因為長度一樣長.
C語言:a[i]<=>idata[i]
[bx+si],[bx+di]
[bx+si+idata],[bx+di+idata]默認段地址在ds中.
[bp+si+idata],[bp+di+idata] 如果bp沒有給出段前綴,默認段地址在ss中.
注意事項 : bp和bx不能同時出現,di和si也不能同時出現.且di和si不能拆分成8位寄存器.
-
尋址綜合應用
將data段中的每個單詞的首字母大寫.每個字符串占16個字節.
assume cs:code, ds:data
data segment
db '1\. file '
db '2\. edit '
db '3\. search '
db '4\. view '
db '5\. options '
db '6\. help '
data ends
code segment
start:
mov ax, data
mov ds, ax
mov bx,0
mov cx, 6
s0:
mov al,[bx+3]
and al,11011111b
mov [bx+3],al
add bx,16
loop s0
mov ax, 4c00h
int 21h
code ends
end start
- 將data段中的每個單詞改為大寫字母,由于每個字符串一樣長,我們可以用嵌套循環
assume cs:codesg, ds:datasg
datasg segment
db 'ibm '
db 'dec '
db 'dos '
db 'vax '
datasg segment
codesg segment
start:mov ax,datasg
mov ds,ax
mov bx,0
mov cx,4
mov ax,0
s0: mov dx,cx
mov si,0
mov cx,3
s1:mov al,[bx+si]
and al,11011111b
mov [bx+si], al
inc si
loop s1
add bx,16
mov cx,dx
loop s0
codesg ends
end start
這里注意保存外層循環的cx值.可以用其它寄存器保存,也可以保存在內存單元中,也可以保存在棧中
- 將data段中的前四個字母改為大寫.
assume cs:codesg, ds:datasg
datasg segment
db '1\. display '
db '2\. brows '
db '3\. replace '
db '4\. modify '
datasg segment
codesg segment
start:
mov ax, datasg
mov ds,ax
mov bx,0
mov cx,4
s1:
push cx
mov cx,4
mov si,0
s2:
mov al,[bx+si+3]
and al,11011111b
mov [bx+si+3],al
inc si
loop s2
add bx, 16
pop cx
loop s1
mov ax, 4c00h
int 21h
codesg ends
end start
注意:si需要在循環中每次重置為0
-
實驗6
實踐本單元所有程序
第八章 數據處理的兩個基本問題
-
兩個基本問題
處理的數據在什么地方
cpu內部(寄存器,緩存)
端口
內存
數據的尺寸有多長
寄存器的大小指明尺寸
用byte/word/dword ptr [尋址寄存器或段前綴]來指明.
-
數據的位置表達
立即數:直接包含在指令中的立即數在執行前是在cpu內部的指令緩沖器中.
寄存器
內存單元:bx系列尋址組合默認段地址在ds中,bp系列組合默認地址在ss中
-
尋址的綜合應用
關于DEC公司的一條記錄(1982年)如下:
公司名稱:DEC
總裁姓名:Ken Olsen
排名:137
收入: 40(40億美元)
著名產品:PDP(小型機)

mov ax,seg
mov ds,ax
mov bx,60h
mov word ptr [bx+0ch],38
mov word ptr [bx+0eh],70
mov byte ptr [bx+10h],'V'
inc bx
mov byte ptr [bx+10h],'A'
inc bx
mov byte ptr [bx+10h],'X'
-
幾條指令
div指令
格式:
div reg
div 內存單元 //必須指明內存單元尺寸
指令說明
除數:有8位和16位兩種在reg或內存單元中.
被除數:放ax或ax和dx中,如果除數為8位,則被除數為16位,默認在ax中存放,如果除數為16位,則被除數則為32位.在dx和ax中存放,dx存放高16位,ax存放低16位
結果:如果除數為8位,al存放商,AH存放余數,如果除數為16位,則AX存放除法的商,dx存放除法操作的余數..
例子:利用除法計算1000001/100
因為100001大于65536,化成16進制為186A1
mov dx,1
mov ax,86A1h
mov bx,100
div bx
- 例子:編程,利用除法指令計算1001/100
mov ax,1001
mov bl,100
div bl
- 用div計算data段中的第一個數據除以第二個數據后的結果,商存在第三個數據的存儲單元中
assume cs:code, ds:data
data segment
dd 100001
dw 100
dw 0
data ends
code segment
start:
mov ax, data
mov ds,ax
mov bx,0
mov ax,[bx]
mov dx,[bx+2]
div word ptr [bx+4]
mov [bx+6],ax
mov ax, 4c00h
int 21h
code ends
end start
dd偽指令:定義雙字型數據. 還有db/dw/dq(八字節)/dt(十字節)
dup偽指令:db/dw/dd 重復次數 dup (需要重復的數據列表)
eg:db 3 dup (0,1,2) 一共占用3*3=9個字節
-
實驗7
代碼如下:
assume cs:code,ds:data,es:table
data segment
db '1975','1976','1977','1978','1979','1980','1981','1982','1983'
db '1984','1985','1986','1987','1988','1989','1990','1991','1992'
db '1993','1994','1995'
dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
dw 11452,14430,15257,17800
data ends
table segment
db 21 dup ('year summ ne ?? ')
table ends
code segment
start:
mov ax,data
mov ds,ax
mov ax,table
mov es,ax
mov bx,0
mov si,0
mov di,0
mov cx,21
s1:
mov ax,[bx]
mov es:[di],ax
mov ax,[bx+2]
mov es:[di+2],ax
mov byte ptr [di+4],' '
mov ax,84[bx]
mov es:[di+5],ax
mov ax,84[bx+2]
mov es:[di+7],ax
mov byte ptr [di+9],' '
mov ax,168[si]
mov es:[di+10],ax
mov byte ptr [di+12],' '
mov dx,86[bx]
mov ax,84[bx]
div word ptr 168[si]
mov es:[di+13],ax
mov es:[di+15],' '
add bx,4
add si,2
add di,16
loop s1
mov ax, 4c00h
int 21h
code ends
end start
第九章 轉移指令的原理
-
offset操作符
操作符offset在匯編語言中是由編譯器處理的符號.功能是獲取標號的偏移地址
段標號名稱就代表地址,所以不需要offset 操作符.
Nop指令占用一個字節
-
轉移指令分類
可以修改ip或同時修改CS或IP的指令統稱為轉移指令:
(1).只修改ip:段內轉移
8位數值范圍的修改IP,稱為短轉移
修改范圍為16位的稱為近轉移
(2).同時修改Cs和IP時,稱為段間轉移.
- 8086CPU的轉移指令分為以下幾類:
(1)無條件轉移指令(如:jmp)
(2)條件轉移指令
(3)循環指令
(4)過程
(5)中斷
-
無條件跳轉jmp指令
跳轉到標號:
短轉移:jmp sort 標號 (機器碼中包含的是8位位移)
近轉移:jmp near ptr 標號(機器碼中包含16位位移)
位移計算:要跳轉的標號的偏移地址 減掉 跳轉指令的下一條指令的偏移地址,位移在編譯時由匯編器算出.
遠轉移:jmp far ptr 標號,cs=標號段地址,ip=標號偏移地址,該指令機器碼中不是用的位移表示.而是包含了轉移地址4個字節.機器碼的高位字單元是段地址,低位時偏移地址
轉移地址在寄存器:jmp 16位寄存器
轉移地址在內存中的jmp指令
段內轉移:jmp word ptr 內存單元地址
段間轉移:jmp dword ptr 內存單元地址(高字單元是段地址,低地址字單元是偏移地址)
-
條件轉移指令jcxz
所有條件轉移都是短轉移.,在對應的機器碼中包含轉移的位移.
格式:jcxz 標號,當cx=0時就跳轉到標號處.
例子程序:補全程序,利用jcxz指令,實現在內存2000h段中,查找第一個值為0的字節,找到后,將它的偏移地址存儲在dx中
assume cs:code
code segment
start:
mov ax,2000h
mov ds,ax
mov bx,0
s:
mov cx, 0
mov cl,[bx]
jcxz ok
inc bx
jmp short s
ok:
mov dx, bx
mov ax, 4c00h
int 21h
code ends
end start
-
loop指令
所有的循環指令都是短轉移.對應的機器碼包含的是位移.
例子程序:例子程序:補全程序,利用loop指令,實現在內存2000h段中,查找第一個值為0的字節,找到后,將它的偏移地址存儲在dx中
assume cs:code
code segment
start: mov ax,2000h
mov ds,ax
mov bx,0
s:mov cl,[bx]
mov ch,0
inc cx
inc bx
loop s
ok:dec bx
mov dx,bx
mov ax,4c00h
int 21h
code ends
end start
inc cx是防止當cx為0時,loop指令會首先cx=cx-1會變成0ffffh,就會循環ffff次.
-
實驗
實驗8:
assume cs:codesg
codesg segment
mov ax,4c00h
int 21h
start: mov ax,0 ax=0
s: nop 占一字節,機器碼90
nop 占一字節,機器碼90
mov di,offset s (di)=s偏移地址
mov si,offset s2 (si)=s2偏移地址
mov ax,cs:[si] (ax)=jmp short s1指令對應的機器碼EBF6
mov cs:[di],ax jmp short s1覆蓋s處指令2條nop指令
s0: jmp short s 執行到這里,不會再繼續向下執行,直接跳回mov ax,4c00h了
s1: mov ax,0
int 21h
mov ax,0
s2: jmp short s1
nop
codesg ends
end start
因為jmp short s1<=>EB 位移.位移等于S1標號偏移地址減去nop指令的偏移地址大約為-10.當跳轉到s處執行jmp short s1,實際上是執行EB -10.會向前跳轉10個字節,也就是說會執行程序返回的代碼.
實驗9:根據材料編程:在屏幕中間分別顯示綠色,綠底紅色,白色藍底的字符串’welcome to masm!’
顯示字符相關知識: B8000h-bffffh共32kb的空間,為80*25彩色字符模式的顯示緩存區.一個字符占兩個字節,后面的字節是該字符的一些顏色屬性信息.所以一屏幕內容占緩沖區4000b約等于4kb

assume cs:code, ds:data
data segment
db 'welcome to masm!'
data ends
code segment
start:
mov ax, data
mov ds, ax
mov bx,0
mov ax,0b800h
mov es, ax
mov di,11*160+72
mov cx,16
s:
mov al,[bx]
mov es:[di], al
mov byte ptr es:[di+1],00000010b
mov es:160[di],al
mov byte ptr es:161[di],00100100b
mov es:320[di],al
mov byte ptr es:321[di],01110001b
add di,2
inc bx
loop s
mov ax, 4c00h
int 21h
code ends
end start
第十章 call和ret指令
-
ret和retf指令
ret<=>pop ip<=>(ip)=((ss)*16+(sp)),(sp)=(sp)+2
retf<=>pop ip,pop cs <=>(ip)=((ss)_16+(sp)),(sp)=(sp)+2
(cs)=((ss)_16+(sp)),(sp)=(sp)+2
-
call指令
call指令執行的步驟
將當前call指令后的下一條指令的ip,或cs和ip壓入棧中
跳轉到目標地址執行
(sp)=(sp) - 2
((ss)*16+(sp)) = (ip)
(ip)=(ip)+16位位移
call指令不能實現短轉移,此外轉移的方法同jmp指令一樣.
call 標號:16位位移=標號處的地址-call指令后的第一個字節的地址, 位移范圍為-32768~32767
有等同于 push ip,jmp near ptr 標號
- call far ptr 標號:實現的是段間轉移.
push cs,push ip, jmp far ptr 標號
- 轉移地址在寄存器中的call指令:call 16位reg
push ip, jmp reg
轉移地址在內存中的call指令:
call word ptr 內存單元地址
push ip, jmp word ptr 內存單元地址
- call dword ptr 內存單元地址
push cs, push ip,jmp dword ptr 內存單元地址
-
call和ret的配合使用
子程序框架:
assume cs:code
code segment
main:
call sub1
:
:
call sub2
:
:
mov ax, 4c00h
int 21h
sub1:
:
:
sub2:
:
code ends
所以c語言中的函數名其實就是一個標識子程序的地址
-
mul指令
要求:
兩個相乘的數,要么都是8位,要么都是16位,如果是8位,那么一個乘數默認放在AL中,另一個放在8位reg或內存單元中,如果是16位,一個默認在ax中,另一個放在16位reg或內存單元中
結果:如果是8位乘法,結果默認放在AX中,如果是16位乘法,結果高位默認放在dx中,低位放在ax中.
格式:mul reg/內存單元(必須指明內存單元尺寸)
例子:計算100*10,因為兩個乘數都是8位,所以用8位乘法
mov al,100
mov bl,10
mul bl
計算 100 * 10000,必須用16位乘法
mov ax,100
mov bx,10000
mul bx
-
模塊化程序設計
參數和結果傳遞問題:
用寄存器傳遞參數和用寄存器存儲函數結果值
例子:計算n的立方,用bx傳遞參數dx和ax返回結果值
cube:
mov ax,bx
mul bx
mul bx
ret
參數的批量傳遞:可以將參數放在一組內存單元中,然后用si等間接寄存器指向它.返回值也可以這樣做.
例子:將一個si指向的全是字母的字符串轉換為大寫
capital:
and byte ptr [si], 11011111b
inc si
loop capital
ret
用棧來傳遞參數
寄存器沖突問題:
例子程序:將一個全是字母,以0結尾的字符串,轉化為大寫si指向該字符串
capital:mov cl, [si]
mov ch,0
jcxz ok
and byte ptr [si],11011111b
inc si
jmp short capital
ok : ret
如果調用該子程序中的代碼中使用了cx寄存器,將產生錯誤.解決辦法是在該子程序開頭壓棧保存需要使用的寄存器,在返回前彈出該寄存器的值.改正如下
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
ret
- 所以子程序框架如下:
子程序標號:
子程序中使用的寄存器入棧保存
子程序指令標號:
....
子程序返回標號:
子程序中保存的寄存器出棧恢復
子程序返回(ret,retf)
-
實驗10 編寫子程序
顯示字符串子程序:show_str
功能:在指定的位置,用指定的顏色,顯示一個用0結束的字符串
參數:(dh)=行號(取值范圍024),(dl)=列號(取值范圍是079).(cl)=顏色,ds:si指向字符串的首地址
返回值:無
show_str:
push ax
push bx
push cx
push dx
push es
push si
push di
begn:
mov ax,0b800h
mov es,ax
mov al,160
mul dh
mov bx,ax
mov al,2
mul dl
add bx,ax
mov di,bx
mov al,cl
mov ch,0
s:
mov cl,[si]
jcxz retn
mov es:[di],cl
mov es:[di+1],al
inc si
add di,2
jmp short s
retn:
pop di
pop si
pop es
pop dx
pop cx
pop bx
pop ax
ret
解決觸發溢出問題子程序:
名稱:divdw
功能:進行不會產生溢出的除法運算,被除數位dword型,除數位word型.結果為dword型
參數:(ax)=dword型數據的低16位,(dx)=dword型數據的高16位,(cx)=除數
返回:(dx)=結果的高16位,(ax)=結果的低16位,(cx)=余數
divdw:
push ax
push bx
push cx
push dx
begn:
mov bx,ax
mov ax,dx
mov dx,0
div cx
push ax
mov ax,bx
div cx
mov cx,dx
pop dx
retn:
pop dx
pop cx
pop bx
pop ax
ret
將數值以十進制形式顯示:
名稱:dtoc
功能:將word型數據轉變為表示十進制數的字符串,字符串以0結尾
參數:(ax)=word型數據,ds:si指向字符串首地址
返回值:無
dtoc:
push ax
push bx
push cx
push dx
push ds
push si
push 0
begn:
mov dx,0
mov bx,10
div bx
mov cx,ax
jcxz sw1
add dx,30h
push dx
jmp short begn
sw1:add dx,30h
push dx
sw2:
pop cx
jcxz retn
mov [si],cl
inc si
jmp short sw2
retn:
mov [si+1],0
pop si
pop ds
pop dx
pop cx
pop bx
pop ax
ret