2022-12-09

棧段

對(duì)于8086PC機(jī),在編程時(shí),可以根據(jù)需要,將組內(nèi)存單元定義為一個(gè)段。我們可以將長(zhǎng)度為N(N<=64KB)的一組地址連續(xù)、起始地址為16的倍數(shù)的內(nèi)存單元,當(dāng)作??臻g來用,從而定義了一個(gè)棧段。比如,我們將10010H~1001FH這段長(zhǎng)度為16 字節(jié)的內(nèi)存空間當(dāng)作棧來用,以棧的方式進(jìn)行訪問。這段空間就可以稱為一個(gè)棧段,段地址為1001H,大小為16字節(jié)。

將一段內(nèi)存當(dāng)作棧段,僅僅是我們?cè)诰幊虝r(shí)的一種安排, CPU并不會(huì)由于這種安排,就在執(zhí)行push pop 等棧操作指令時(shí)自動(dòng)地將我們定義的棧段當(dāng)作??臻g來訪問。如何使得如push pop 等棧操作指令訪問我們定義的棧段呢?就是要將SS:SP指向我們定義的棧段。

我們可以將一段內(nèi)存定義為一個(gè)段,用一個(gè)段地址指示段,用偏移地址訪問段內(nèi)的單元。這完全是我們自己的安排。

我們可以用一個(gè)段存放數(shù)據(jù), 將它定義為“數(shù)據(jù)段”;

我們可以用一個(gè)段存放代碼,將它定義為“代碼段”;

我們可以用一個(gè)段當(dāng)作棧,將它定義為“棧段”。

若要讓CPU按照我們的安排來訪問這些段,就要:

對(duì)于數(shù)據(jù)段,將它的段地址放在DS中,用mov、add、 sub等訪問內(nèi)存單元的指令時(shí),CPU就將我們定義的數(shù)據(jù)段中的內(nèi)容當(dāng)作數(shù)據(jù)來訪問;

對(duì)于代碼段,將它的段地址放在CS中,將段中第一條 指令的偏移地址放在IP中,這樣CPU就將執(zhí)行我們定義的代碼段中的指令;

對(duì)于棧段,將它的段地址放在SS中,將棧頂單元的偏移地址放在SP中,這樣CPU在需要進(jìn)行棧操作的時(shí)候,比如執(zhí)行push、pop指令等,就將我們定義的棧段當(dāng)作??臻g來用。

CPU將內(nèi)存中的某段內(nèi)容當(dāng)作代碼,是因CS:IP指向了那里: CPU 將某段內(nèi)存當(dāng)作棧,是因?yàn)镾S:SP指向了那里。我們一定要清楚,什么是我們的安排,以及如何讓CPU按我們的安排行事。要非常清楚CPU的工作機(jī)理,才能在控制CPU按照我們的安排運(yùn)行的時(shí)候做到游刃有余。

將10000H~1001FH安排為代碼段,并在里面存儲(chǔ)如下代碼:

mov ax ,1000H

mov SS, ax

mov sp, 0020H;初始化棧頂

mov ax, cs

mov ds, ax? ;設(shè)置數(shù)據(jù)段段地址

mov ax, [0 ]

add ax, [2]

mov bx, [4]

add bx, [6]

push ax

push bx

pop ax

pop bx

設(shè)置CS=1000H,IP=0, 這段代碼將得到執(zhí)行。可以看到,在這段代碼中,我們又將10000H~1001FH安排為棧段和數(shù)據(jù)段。10000H~1001FH這段內(nèi)存,既是代碼段,又是棧段和數(shù)據(jù)段。

一段內(nèi)存,可以既是代碼的存儲(chǔ)空間,又是數(shù)據(jù)的存儲(chǔ)空間,還可以是??臻g,也可以什么也不是。 關(guān)鍵在于 CPU中寄存器的設(shè)置,即CS、IP, SS、 SP, DS的指向。

用機(jī)器指令和匯編指令編程

Debug的使用

D命令是查看內(nèi)存單元的命令,可以用:

“d段地址:偏移地址”的格式查看指定的內(nèi)存單元的內(nèi)容

段地址是放在段寄存器中的,在D命令后面直接給出段地址,是Debug提供的一種直觀的操作方式。D命令是由Debug執(zhí)行的,Debug 在執(zhí)行“d1000:0”這樣的命令時(shí),也會(huì)先將段地址1000H送入段寄存器中。

Debug是靠一段程序來執(zhí)行D命令

CPU執(zhí)行這段程序

CPU在訪問內(nèi)存單元的時(shí)候從段寄存器中得到內(nèi)存單元的段地址。

所以,Debug 在其處理D命令的程序段中,必須有將段地址送入段寄存器的代碼。段寄存器有4個(gè): CS、DS、SS、ES, 將段地址送入哪個(gè)段寄存器呢?

首先不能是CS,因?yàn)镃S:IP必須指向Debug處理D命令的代碼,也不能是ss,因?yàn)镾S:SP要指向棧頂。這樣只剩下了DS和ES可以選擇,訪問內(nèi)存的指令如“mov ax,[0]” 等一般都默認(rèn)段地址在ds中,所以Debug在執(zhí)行如“d段地址:偏移地址”這種D命令時(shí),將段地址送入ds中比較方便。

D命令也提供了一 種符合CPU機(jī)理的格式: “d段寄存器:偏移地址”,以段寄存器中的數(shù)據(jù)為段地址SA,列出從SA:偏移地址開始的內(nèi)存區(qū)間中的數(shù)據(jù)。

用編譯和連接程序?qū)ebug和匯編語言編譯連接成為可執(zhí)行文件(如*.exe文件),在操作系統(tǒng)中運(yùn)行。

一個(gè)源程序從寫出到執(zhí)行的過程

圖4.1描述了一個(gè)匯編語言程序從寫出到最終執(zhí)行的簡(jiǎn)要過程。

第一步:編寫匯編源程序。

使用文本編輯器(如Edit、 記事本等),用匯編語言編寫匯編源程序。

這一步工作的結(jié)果是產(chǎn)生了一個(gè)存儲(chǔ)源程序的文本文件。

第二步:對(duì)源程序進(jìn)行編譯連接。

使用匯編語言編譯程序?qū)υ闯绦蛭募械脑闯绦蜻M(jìn)行編譯,產(chǎn)生目標(biāo)文件;再用連接程序?qū)δ繕?biāo)文件進(jìn)行連接,生成可在操作系統(tǒng)中直接運(yùn)行的可執(zhí)行文件。

可執(zhí)行文件包含兩部分內(nèi)容。

程序(從源程序中的匯編指令翻譯過來的機(jī)器碼)和數(shù)據(jù)(源程序中定義的9數(shù)據(jù))

相關(guān)的描述信息(比如,程序有多大、要占用多少內(nèi)存空間等)

這一步工作的結(jié)果:產(chǎn)生了一個(gè)可在操作系統(tǒng)中運(yùn)行的可執(zhí)行文件。

第三步:執(zhí)行可執(zhí)行文件中的程序。

在操作系統(tǒng)中,執(zhí)行可執(zhí)行文件中的程序。

操作系統(tǒng)依照可執(zhí)行文件中的描述信息,將可執(zhí)行文件中的機(jī)器碼和數(shù)據(jù)加載入內(nèi)存,并進(jìn)行相關(guān)的初始化(比如設(shè)置CS:IP指向第一條要執(zhí)行的指令), 然后由CPU執(zhí)行程序

偽指令

在匯編語言源程序中,包含兩種指令,一種是匯編指令,一種是偽指令。 匯編指令是有對(duì)應(yīng)的機(jī)器碼的指令,可以被編譯為機(jī)器指令,最終為CPU所執(zhí)行。而偽指令沒有對(duì)應(yīng)的機(jī)器指令,最終不被CPU所執(zhí)行。那么誰來執(zhí)行偽指令呢?偽指令是由編譯器來執(zhí)行的指令,編譯器根據(jù)偽指令來進(jìn)行相關(guān)的編譯工作。

程序4.1中出現(xiàn)了3種偽指令。

XXX segment

XXX ends

segment和ends是對(duì)成對(duì)使用的偽指令,這是在寫可被編譯器編譯的匯編程序時(shí),必須要用到的一對(duì)偽指令。 segment 和ends的功能是定義一個(gè)段, segment 說明一個(gè)段開始,ends 說明一個(gè)段結(jié)束。一個(gè)段必須有一個(gè)名稱來標(biāo)識(shí),使用格式為:

段名segment

段名ends

比如,程序4.1中的:

codesg segment? ;定義一個(gè)段,段的名稱為“codesg",這個(gè)段從此開始

codesg ends? ;名稱為“codesg"的段到此結(jié)束

一個(gè)匯編程序是由多個(gè)段組成的,這些段被用來存放代碼、數(shù)據(jù)或當(dāng)作??臻g來使用。我們?cè)谇懊娴恼n程中所講解的段的概念,在匯編源程序中得到了應(yīng)用與體現(xiàn),一個(gè)源程序中所有將被計(jì)算機(jī)所處理的信息:指令、數(shù)據(jù)、棧, 被劃分到了不同的段中。

一個(gè)有意義的匯編程序中至少要有一個(gè)段,這個(gè)段用 來存放代碼。

程序4.1中,在codesg segment和codesg ends 之間寫的匯編指令是這個(gè)段中存放的內(nèi)容,這是一個(gè)代碼段

end

end是一個(gè)匯編程序的結(jié)束標(biāo)記,編譯器在編譯匯編程序的過程中,如果碰到了偽指令end,就結(jié)束對(duì)源程序的編譯。所以,在我們寫程序的時(shí)候,如果程序?qū)懲炅?,要在結(jié)尾處加上偽指令end.否則,編譯器在編譯程序時(shí),無法知道程序在何處結(jié)束。

ends 是和segment成對(duì)使用的,標(biāo)記一個(gè)段的結(jié)束,ends的含義可理解為“end segment"。我們這里講的end的作用是標(biāo)記整個(gè)程序的結(jié)束。

assume

這條偽指令的含義為“假設(shè)”。它假設(shè)某一段寄存器和程序中的某一個(gè)用segment...ends定義的段相關(guān)聯(lián)。通過assume說明這種關(guān)聯(lián),在需要的情況下,編譯程序可以將段寄存器和某一個(gè)具體的段相聯(lián)系。assume并不是一條非要深入理解不可的偽指令,以后我們編程時(shí),記著用assume將有特定用途的段和相關(guān)的段寄存器關(guān)聯(lián)起來

比如,在程序4.1中,我們用codesg segment ....codesg ends 定義了一個(gè)名為codseg的段,在這個(gè)段中存放代碼,所以這個(gè)段是- -個(gè)代碼段。在程序的開頭,用assumecs:codesg將用作代碼段的段codesg和CPU中的段寄存器cs聯(lián)系起來。

源程序中的“程序”

用匯編語言寫的源程序,包括偽指令和匯編指令,我們編程的最終目的是讓計(jì)算機(jī)完成一定的任務(wù)。源程序中的匯編指令組成了最終由計(jì)算機(jī)執(zhí)行的程序,而源程序中的偽指令是由編譯器來處理的,它們并不實(shí)現(xiàn)我們編程的最終目的。這里所說的程序就是指源程序中最終由計(jì)算機(jī)執(zhí)行、處理的指令或數(shù)據(jù)。

注意,以后可以將源程序文件中的所有內(nèi)容稱為源程序,將源程序中最終由計(jì)算機(jī)執(zhí)行、處理的指令或數(shù)據(jù),稱為程序。程序最先以匯編指令的形式存在源程序中,經(jīng)編譯、連接后轉(zhuǎn)變?yōu)闄C(jī)器碼,存儲(chǔ)在可執(zhí)行文件中。這個(gè)過程如圖4.2所示。

標(biāo)號(hào)

匯編源程序中,除了匯編指令和偽指令外,還有一些標(biāo)號(hào),比如“codesg” 。一個(gè)標(biāo)號(hào)指代了一個(gè)地址。比如codesg在segment的前面,作為一個(gè)段的名稱,這個(gè)段的名稱最終將被編譯、連接程序處理為一個(gè)段的段地址。

程序的結(jié)構(gòu)

通過直接在Debug 中寫入?yún)R編指令來寫匯編程序,對(duì)于十分簡(jiǎn)短的程序這樣做的確方便??蓪?duì)于大一些的程序,就不能如此了。我們需要寫出能讓編譯器進(jìn)行編譯的源程序,這樣的源程序應(yīng)該具備起碼的結(jié)構(gòu)。

源程序是由一些段構(gòu)成的。我們可以在這些段中存放代碼、數(shù)據(jù)、或?qū)⒛硞€(gè)段當(dāng)作??臻g。

程序返回

我們的程序最先以匯編指令的形式存在源程序中,經(jīng)編譯、連接后轉(zhuǎn)變?yōu)闄C(jī)器碼,存儲(chǔ)在可執(zhí)行文件中,那么,它怎樣得到運(yùn)行呢?

我們?cè)贒OS(一個(gè)單任務(wù)操作系統(tǒng))的基礎(chǔ)上,簡(jiǎn)單地討論一下這個(gè)問題。

一個(gè)程序P2在可執(zhí)行文件中,則必須有一個(gè)正在運(yùn)行的程序P1,將P2從可執(zhí)行文件中加載入內(nèi)存后,將CPU的控制權(quán)交給P2, P2才能得以運(yùn)行。P2開始運(yùn)行后,P1暫停運(yùn)行。

而當(dāng)P2運(yùn)行完畢后,應(yīng)該將CPU的控制權(quán)交還給使它得以運(yùn)行的程序P1,此后,P1繼續(xù)運(yùn)行。

一個(gè)程序結(jié)束后,將CPU的控制權(quán)交還給使它得以運(yùn)行的程序,我們稱這個(gè)過程為:程序返回。那么,如何返回呢?應(yīng)該在程序的末尾添加返回的程序段。

我們已經(jīng)遇到了幾個(gè)和結(jié)束相關(guān)的內(nèi)容:段結(jié)束、程序結(jié)束、程序返回。表4.1 展示了它們的區(qū)別。

語法錯(cuò)誤和邏輯錯(cuò)誤

一般說來, 程序在編譯時(shí)被編譯器發(fā)現(xiàn)的錯(cuò)誤是語法錯(cuò)誤,

在源程序編譯后,在運(yùn)行時(shí)發(fā)生的錯(cuò)誤是邏輯錯(cuò)誤。語法錯(cuò)誤容易發(fā)現(xiàn),也容易解決。而邏輯錯(cuò)誤通常不容易被發(fā)現(xiàn)。

編輯源程序

編譯

運(yùn)行masm后,首先顯示出些版本信息,然后提示輸入將要被編譯的源程序文件的名稱。注意,“[.ASM]” 提示我們,默認(rèn)的文件擴(kuò)展名是asm, 比如,要編譯的源程序文件名是“p1.asm”,只要在這里輸入“p1"即可??扇绻闯绦蛭募皇且詀sm為擴(kuò)展名的話,就要輸入它的全名。比如源程序文件名為“p1.txt",就要輸入全名。

在輸入源程序文件名的時(shí)候一定要指明它所在的路徑。如果文件就在當(dāng)前路徑下,只輸入文件名就可以,可如果文件在其他的目錄中,則要輸入路徑,比如,要編譯的文件p1.txt在“c:\windows\desktop"下, 則要輸入“c:\windows\desktop\p1.txt"。

輸入要編譯的源程序文件名后,按Enter鍵,屏幕顯示如圖

在輸入源程序文件名后,程序繼續(xù)提示我們輸入要編譯出的目標(biāo)文件的名稱,目標(biāo)文件是我們對(duì)一個(gè)源程序進(jìn)行編譯要得到的最終結(jié)果。注意屏幕上的顯示:“[1.OBJ]”,因?yàn)槲覀円呀?jīng)輸入了源程序文件名為1.asm, 則編譯程序默認(rèn)要輸出的目標(biāo)文件名為l.obj, 所以可以不必再另行指定文件名。直接按Enter 鍵,編譯程序?qū)⒃诋?dāng)前的目錄下,生成1.obj 文件。

如果編譯的過程中出現(xiàn)錯(cuò)誤,那么將得不到目標(biāo)文件。一般來 說,有兩類錯(cuò)誤使我們得不到所期望的目標(biāo)文件:

程序中有“Severe Errors”;

找不到所給出的源程序文件。

注意,在編譯的過程中,我們提供了一個(gè)輸入,即源程序文件。最多可以得到3個(gè)輸出:目標(biāo)文件(obj)列表文件(lst)、交叉引用文件(.crf), 這3個(gè)輸出文件中,目標(biāo)文件是我們最終要得到的結(jié)果,而另外兩個(gè)只是中間結(jié)果,可以讓編譯器 忽略對(duì)它們的生成。

連接

在對(duì)源程序進(jìn)行編譯得到目標(biāo)文件后,我們需要對(duì)目標(biāo)文件進(jìn)行連接,從而得到可執(zhí)行文件。

連接的作用有以下幾個(gè)。

(1)當(dāng)源程序很大時(shí), 可以將它分為多個(gè)源程序文件來編譯,每個(gè)源程序編譯成為目標(biāo)文件后,再用連接程序?qū)⑺鼈冞B接到一起,生成一個(gè)可執(zhí)行文件;

(2)程序中調(diào)用了某個(gè)庫文件中的子程序,需要將這個(gè)庫文件和該程序生成的目標(biāo)文件連接到一起,生成一個(gè)可執(zhí)行文件;

(3)一個(gè)源程序編譯后,得到了存有機(jī)器碼的目標(biāo)文件,目標(biāo)文件中的有些內(nèi)容還不能直接用來生成可執(zhí)行文件,連接程序?qū)⑦@些內(nèi)容處理為最終的可執(zhí)行信息。所以,在只有一個(gè)源程序文件,而又不需要調(diào)用某個(gè)庫中的子程序的情況下,也必須用連接程序?qū)δ繕?biāo)文件進(jìn)行處理,生成可執(zhí)行文件。

注意,對(duì)于連接的過程,可執(zhí)行文件是我們要得到的最終結(jié)果。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容