第四講 匯編程序
4.1 匯編程序框架
data SEGMENT '數(shù)據(jù)段,編程者可以把數(shù)據(jù)都放到這個段里
....數(shù)據(jù)部分
'數(shù)據(jù)格式是: 標識符 db/dw 數(shù)據(jù)。
data ENDS'數(shù)據(jù)段結(jié)束處。
edata SEGMENT '附加數(shù)據(jù)段,編程者可以把數(shù)據(jù)都放到這個段里
....附加數(shù)據(jù)部分
edata ENDS'附加數(shù)據(jù)段結(jié)束處。
code SEGMENT'代碼段,實際的程序都是放這個段里。
ASSUME CS:code,DS:data,ES:edata '告訴編譯程序,data段是數(shù)據(jù)段DS,code段是代碼段CS
start:MOV AX,data '前面的start表示一個標識位,后面用到該位,如果用不到,就可以不加
MOV DS,AX '這一句與上一行共同組成把data賦值給DS。段寄存器.
MOV AX,edata
MOV ES,AX '與前一句共同組成edata->ES
.......程序部分
MOV AX,4C00h'程序退出,該句內(nèi)存由下一行決定。退出時,要求ah必須是4c。
INT 21h
code ENDS'代碼段結(jié)束。
END start'整個程序結(jié)束,并且程序執(zhí)行時由start那個位置開始執(zhí)行。
上面就是一個程序的框架結(jié)構。在這個結(jié)構中,有三個段,DS,ES,CS。這三個段分別存數(shù)據(jù),附加數(shù)據(jù),代碼段。
4.2 編寫我們的Hello,world思路。
開始編寫我們的第一個程序。
程序要求:顯示一個“Hello,Mr.286.”怎么樣?
思路:
1 要顯示一個字符串,根據(jù)前面我讓你們記的七八個指令夠嗎?答案是:不僅夠,而且還用不完。
首先定義一下總可以吧。
hellostr db 'Hello,Mr.286.$'
最后的$不要忘了。
2 首先要考慮的問題就是找中斷,找到合適的中斷,該中斷就能幫我們完成這個顯示任務。我找到(在哪找到的,怎么找到的,別問我,到網(wǎng)上或書上都能找到):
-------------------------------------------
中斷INT 21H功能09H
功能描述: 輸出一個字符串到標準輸出設備上。如果輸出操作被重定向,那么,將無法判斷磁盤已滿
入口參數(shù): AH=09H
DS:DX=待輸出字符的地址
說明:待顯示的字符串以’$’作為其結(jié)束標志
出口參數(shù): 無
-------------------------------------------
由上面看到,我們所需要作的就是把DS指向數(shù)據(jù)段,DX指向字符串的地址,AH等于9H,調(diào)用21h中斷。
mov ds,數(shù)據(jù)段地址
lea dx,hellostr 'hellostr已在前面1中定義了。
mov ah,9h
int 21h。
由于只要在調(diào)用int 21h之前把準備的東西準備齊就行了,所以int 21h前面三行的順序并不重要。
3 退出程序,運行完總要退出呀。再查中斷手冊
--------------------------------------------
中斷INT 21H功能4CH
功能描述: 終止程序的執(zhí)行,并可返回一個代碼
入口參數(shù): AH=4CH
AL=返回的代碼
出口參數(shù): 無
--------------------------------------------
mov ah,4Ch
mov al,0
int 21h
或
mov ax,4c00h
int 21h
這里需要說明的是返回代碼有什么用,返回給誰?返回給操作系統(tǒng),因為是操作系統(tǒng)DOS調(diào)用的這個程序,這個返回值可以通過批處理中的errorlevel得到,這里不多說明,實際上操作系統(tǒng)很少處理這一值,因此al你隨便寫什么值影響都不大。
4.3 程序?qū)崿F(xiàn)
data SEGMENT
msg DB 'Hello, Mr.286.$'
data ENDS
code SEGMENT
ASSUME CS:code,DS:data
start:MOV AX,data
MOV DS,AX
lea dx,msg
mov ah,9h
int 21h
MOV AX,4C00h
INT 21h
code ENDS
END start
4.4 編譯運行。
把上面程序保存成hello286.asm后,就可以編譯運行了。進入DOS,進入?yún)R編目錄,如果還沒下載,到前面找下載地址。
=================================================
E:\Download\Masm>masm hello286.asm
Microsoft (R) Macro Assembler Version 5.00
Copyright (C) Microsoft Corp 1981-1985, 1987. All rights reserved.
Object filename [hello286.OBJ]:
Source listing [NUL.LST]:
Cross-reference [NUL.CRF]:
50408 + 415320 Bytes symbol space free
0 Warning Errors
0 Severe Errors
說明:上面連續(xù)三個回車,表示我要的都是默認值。下面是零個警告,零個嚴重錯誤,(當然了,我的程序還敢錯嗎?)
E:\Download\Masm>link hello286
Microsoft (R) Overlay Linker Version 3.60
Copyright (C) Microsoft Corp 1983-1987. All rights reserved.
Run File [HELLO286.EXE]:
List File [NUL.MAP]:
Libraries [.LIB]:
LINK : warning L4021: no stack segment
說明:三個回車仍要默認,后面有個警告,沒有棧段,這個沒關系,沒有的話系統(tǒng)會自動給一個。
E:\Download\Masm>hello286
Hello, Mr.286.
說明:運行成功。
E:\Download\Masm>
4.4 深度思考
4.4.1 是不是數(shù)據(jù)必須放數(shù)據(jù)段,代碼必段放代碼段呢?
答,代碼必段放代碼段,否則你怎么執(zhí)行呀?但數(shù)據(jù)也可以放到代碼段,只是程序要作修改。
code SEGMENT
ASSUME CS:code,DS:data
msg DB 'Hello, Mr.286.$'
start:MOV AX,data
MOV DS,AX
lea dx,msg
mov ah,9h
int 21h
MOV AX,4C00h
INT 21h
code ENDS
END start
編譯后仍然可以。
4.4.2 我編的程序在內(nèi)存中是什么樣子的呢?
------------------------------------------------------------------------
E:\Download\Masm>debug hello286.exe
-u
1420:0000 B81F14 MOV AX,141F
1420:0003 8ED8 MOV DS,AX
1420:0005 8D160000 LEA DX,[0000]
1420:0009 B409 MOV AH,09
1420:000B CD21 INT 21
1420:000D B8004C MOV AX,4C00
1420:0010 CD21 INT 21
1420:0012 FF362421 PUSH [2124]
1420:0016 E87763 CALL 6390
1420:0019 83C406 ADD SP,+06
1420:001C FF362421 PUSH [2124]
-d 141f:0000 L20
141F:0000 48 65 6C 6C 6F 2C 20 4D-72 2E 32 38 36 2E 24 00 Hello, Mr.286.$.
141F:0010 B8 1F 14 8E D8 8D 16 00-00 B4 09 CD 21 B8 00 4C ............!..L
-q
E:\Download\Masm>
------------------------------------------------------------------------------
上面是什么呀?還記得前面說的嗎?
1420:0000 B81F14 MOV AX,141F
| | | | |
段址:偏址 機器語言 mov指令 把段地址的地址(141f)賦值給AX寄存器。
1420:0012后面的是垃圾數(shù)據(jù),不用管它,把上面程序與源程序作一個比較,看有什么不用,差別在于把標號語言轉(zhuǎn)成實際地址了。
程序前兩行一執(zhí)行,數(shù)據(jù)段地址就變成了141f,而那個字符串偏移地址在0000,由(LEA DX,[0000]看出),所以我用-d 141f:0000 L20(后面L20表示只顯示20個字節(jié)),就能把段地址顯示出來了。
所以剛才的程序在內(nèi)存中就變成了:
141f:0000 Hello, Mr.286.$ ----->這是段地址里的內(nèi)存
1420:0000 B81F14 MOV AX,141F ------>這是代碼段里的內(nèi)存。data變成了實際地址
1420:0003 8ED8 MOV DS,AX
1420:0005 8D160000 LEA DX,[0000] ------>偏址變成了0000,因為實際上msg也就是從頭開始的。當然是0了。
1420:0009 B409 MOV AH,09 ------->注意Debug里,默認的是十六進制
1420:000B CD21 INT 21
1420:000D B8004C MOV AX,4C00
1420:0010 CD21 INT 21
剛剛學習了8086/8088匯編語言,發(fā)現(xiàn)尋址方式非常重要,于是做了一個小總結(jié),請各位笑納。
概念:
1.指令集:cpu能夠執(zhí)行的指令的集合。
2.指令:cpu所能夠執(zhí)行的操作。
3.操作數(shù):參加指令運算的數(shù)據(jù)。
4.尋址方式:在指令中得到操作數(shù)的方式。
現(xiàn)在就重點討論尋址方式,說白了也就是cpu怎么樣從指令中得到操作數(shù)的問題。另外再強調(diào)一點操作數(shù)還分種類:
1)數(shù)據(jù)操作數(shù):全都是在指令當中參加操作的數(shù)據(jù)。
1.立即操作數(shù):它在指令中直接給出。
2.寄存器操作數(shù):它被放到寄存器中。
3.存儲器操作數(shù):當然在存儲器也就是內(nèi)存中。
4.i/o操作數(shù):它在你給出的i/o端口中。
2)轉(zhuǎn)移地址操作數(shù):在指令當中不是參加運算或被處理的數(shù)據(jù)了,而是轉(zhuǎn)移地址。
還可以按照下面分類方式:
1)源操作數(shù)src
2)目的操作數(shù)dst
源操作數(shù)都是指令當中的第2個操作數(shù),在執(zhí)行完指令后操作數(shù)不變。而目的操作數(shù)是指令當中的第1個操作數(shù),在執(zhí)行完操作指令后被新的數(shù)據(jù)替代。
我們就圍繞這幾種操作數(shù),也就是操作數(shù)所在的位置展開討論。
先說數(shù)據(jù)操作數(shù),它分3大類共7種。
1)立即數(shù)尋址方式:是針對立即操作數(shù)的尋址方式。在指令當中直接給出,它根本就不用尋址。
例1:mov ax,1234h
mov [bx],5678h
在這里1234h和5678h都是立即操作數(shù),在指令當中直接給出。
2)寄存器尋址方式:是針對寄存器操作數(shù)的尋址方式,它在寄存器中我們就用這中方式來找到它。
例2:mov bx,ax
mov bp,[si]
在這里ax,bx,ds都算是寄存器尋址,例1中的ax也是寄存器尋址方式。
3)存儲器尋址方式:針對在內(nèi)存中的數(shù)據(jù)(存儲器操作數(shù))都用這種方式來尋找,一共有5種(這是我自己的說法,便于記憶)。
不得不提及以下的概念:由于8086/8088的字長是16bit,能夠直接尋址2的16次方也就是64kb,而地址總線是20bit,能夠直接尋址2的20次方也就是1M空間,所以把內(nèi)存分為若干個段,每個段最小16byte(被稱為小節(jié)),最大64kb,它們之間可以相互重疊,這樣一來內(nèi)存就被分成以16byte為單元的64k小節(jié),cpu就以1小節(jié)為單位尋址:在段寄存器中給出段地址(16bit),在指令當中給出段內(nèi)偏移地址(16bit),然后把段地址左移4bit再與偏移地址求和就得到數(shù)據(jù)在內(nèi)存當中的實際物理地址了,因而可以找到數(shù)據(jù)。
1.存儲器直接尋址方式:在指令當中以 [地址] 的方式直接給出數(shù)據(jù)所在內(nèi)存段的偏移地址。
例3:mov ax,es:[1234h]
mov dx,VALUE
mov dx,[VALUE]
在這里[1234h]和VALUE就是在指令中直接給出的數(shù)據(jù)所在內(nèi)存段的偏移地址(16bit)。
VALUE是符號地址,是用偽指令來定義的,它代表一個在內(nèi)存中的數(shù)據(jù)(也就是它的名字)。es:是段前綴符,用來指出段地址,在這之前應該將段地址添入段中,本例中是es,默認是ds,也就是不需給出。應該注意 [地址] 與立即尋址的區(qū)別,在直接給出的數(shù)據(jù)兩邊加 [] 表示存儲器直接尋址,以區(qū)別立即尋址。另外 VALUE=[VALUE]。
2.寄存器間接尋址:不是在指令中直接給出數(shù)據(jù)在內(nèi)存中的偏移地址,而是把偏移地址放到了寄存器中。
例4:mov ax,[bx]
這里[bx]就是寄存器間接尋址,bx中應方入段內(nèi)偏移地址。其中:若使用bx,si,di默認段地址為ds,若使用bp則默認段地址為ss,并且允許段跨越,也就是加段前綴符。注意:在寄存器兩邊加 [] 以與寄存器尋址區(qū)別。
3.寄存器間接相對尋址:偏移地址是bx,bp,si,di中的內(nèi)容再與一個8bit或16bit 的位移量之和。
例5:mov ax,[bx]+12h
mov ax,[si]+5678h
mov ax,[bp]+1234h
在這里[bx]+12h,[si]+5678h,[bp]+1234h都是寄存器間接相對尋址。12h是8bit位移量,1234h和5678h是16bit位移量。若使用bx,si,di則默認段寄存器是ds,若使用bp則默認段寄存器是ss,并且允許段跨越。
4.基址變址尋址:偏移地址是一個基址寄存器和一個變址寄存器內(nèi)容的和,既:bx或bp中的一個與si或di中的一個求和而得到。
例6:mov ax,[bx+si]
mov ax,[bp+di]
上面[bx+si]和[bp+di]都是基址變址尋址。若使用bx做基址寄存器則默認段地址為ds,若使用bp為基址寄存器則默認段為ss,允許段跨越。
5.基址變址相對尋址:偏移量是一個基址寄存器一個變址寄存器只和再與一個8bit或一個16bit位移量只和得到。
例7:mov ax,[bx+si]+12h
mov ax,[bp+di]+1234h
[bx+si]+12h和[bp+di]+1234h就是基址變址相對尋址。若使用bx做基址寄存器則默認段是ds,若使用bp做基址寄存器則默認段為ss。允許段跨越。
下面是轉(zhuǎn)移地址操作數(shù)的尋址方式:
1)段內(nèi)直接轉(zhuǎn)移
1.段內(nèi)直接短轉(zhuǎn)移:cs(代碼段)內(nèi)容不變,而ip(指令指針寄存器)內(nèi)容由當前ip內(nèi)容+(-127~127),在指令中直接給出。
例8:jmp short SHORT_NEW_ADDR
其中,short是段內(nèi)短轉(zhuǎn)移的操作符,用以指出是轉(zhuǎn)移到當前位置前后不超過±127字節(jié)的地方。而NEW_ADDR是要轉(zhuǎn)移到的符號地址,它的位置應該在當前ip指針所在偏移地址不超過
±127的地方。否則語法出錯。
2.段內(nèi)直接近轉(zhuǎn)移:cs內(nèi)容不變,而ip內(nèi)容由當前ip內(nèi)容+(-32767~32767),在指令中直接給出。
例9:jmp near ptr NEAR_NEW_ADDR
其中near ptr是段內(nèi)近轉(zhuǎn)移的操作符,用以指出轉(zhuǎn)移到當前位置前后不超過±32767的地方。NEAR_NEW_ADDR是要轉(zhuǎn)移到的符號地址。
2)段內(nèi)間接轉(zhuǎn)移:cs的內(nèi)容不變,而ip的內(nèi)容放在寄存器中或者存儲器中給出。
例10:jmp bx
jmp word ptr [bx]+1234h
這種尋址方式是在寄存器或存儲器中找到要轉(zhuǎn)移到的地址,而地址是16bit的,因而寄存器必須為16bit,如:bx,我們用word ptr來指定存儲器單元也是16bit的。注意:它是間接的給出,只能使用類似于數(shù)據(jù)操作數(shù)中的除立即尋址以外的6種尋址方式(就在上面)。
3)段間直接尋址:cs和ip的內(nèi)容全都變化,由指令當中直接給出要轉(zhuǎn)移到的某一個段內(nèi)的某一個偏移地址處。
例11:jmp 1234h:5678h
jmp far ptr NEW_ADDR
1234h送入cs中作為新的段地址,5678h送入ip中作為新的偏移地址。far ptr是段間直接轉(zhuǎn)移操作符,NEW_ADDR是另外一個段內(nèi)的偏移地址,在這個指令中把NEW_ADDR的段地址送入cs(不用你給出),把它的段內(nèi)偏移地址送入ip中作為新的偏移地址。
4)段間間接尋址:cs和ip的內(nèi)容全變化,由指令當中給出的一個4字節(jié)連續(xù)存儲單元,其中低2字節(jié)送入ip作為偏移地址,高2字節(jié)送入cs作為段地址。
例12:jmp dword ptr [bx][si]+1234h
jmp dword ptr [1234h]
jmp dword ptr [si]
dword ptr是雙字(4個字節(jié)連續(xù)存儲單元)操作符,用來指出下面的存儲單元是4個字節(jié)的。由于它是4個字節(jié)的,所以只能使用類似于數(shù)據(jù)操作數(shù)中的存儲器尋址方式(共5種,還記得嗎?)。
另外作為特殊的尋址方式還有三種:I/O尋址,串尋址,隱含尋址。它們都分別針對I/O指令,串操作指令以及無操作數(shù)的指令,而且都比較簡單,讀者自行總結(jié)。
到此為止說明了8086/8088cpu中的所有尋址方式,我這里只是個總結(jié),具體的細節(jié)還要大家自己鉆研課本,才能理解。
寫的有些倉促可能有些遺漏或錯誤,還請諒解。
-------------------------------------------------------------------------------------
一定要記住尋址方式特別重要,這是我“畢生” 的總結(jié),請笑納,笑納。