以簡化的方式進(jìn)行編譯和鏈接

1.exe的執(zhí)行

程序運(yùn)行,只是從屏幕上不可能看到任何運(yùn)行結(jié)果,因?yàn)?,我們的程序根本沒有向顯示器輸出任何信息。程序只是做了一些將數(shù)據(jù)送入寄存器和加法的操作,而這些事情,我們不可能從顯示屏上看出來。程序執(zhí)行完成后,返回,屏幕上再次出現(xiàn)操作系統(tǒng)的提示符
操作系統(tǒng)的外殼
操作系統(tǒng)是由多個(gè)功能模塊組成的龐大、復(fù)雜的軟件系統(tǒng)。任何通用的操作系統(tǒng),都要提供一一個(gè)稱為sel(外殼)的程序,用戶(操作人員)使用這個(gè)程序來操作計(jì)算機(jī)系統(tǒng)進(jìn)行工作。
DOS中有一個(gè) 程序command.com,這個(gè)程序在DOS中稱為命令解釋器,也就是DOS系統(tǒng)的shell.
DOS啟動(dòng)時(shí),先完成其他重要的初始化工作,然后運(yùn)行command com, command.com 運(yùn)行后,執(zhí)行完其他的相關(guān)任務(wù)后,在屏幕上顯示出由當(dāng)前盤符和當(dāng)前路徑組成的提示符,比如:“c:\” 或“c:windows"等,然后等待用戶的輸入。
用戶可以輸入所要執(zhí)行的命令,比如,cd、 dir. type 等,這些命令由command執(zhí)行,command 執(zhí)行完這些命令后,再次顯示由當(dāng)前盤符和當(dāng)前路徑組成的提示符,等待用戶的輸入。
如果用戶要執(zhí)行一個(gè)程序, 則輸入該程序的可執(zhí)行文件的名稱,command 首先根據(jù)文件名找到可執(zhí)行文件,然后將這個(gè)可執(zhí)行文件中的程序加載入內(nèi)存,設(shè)置CS:IP指向程序的入口。此后,command 暫停運(yùn)行,CPU運(yùn)行程序。程序運(yùn)行結(jié)束后,返回到command中,command再次顯示由當(dāng)前盤符和當(dāng)前路徑組成的提示符,等待用戶的輸入。
在DOS中,command處理各種輸入:命令或要執(zhí)行的程序的文件名。我們就是通過command來進(jìn)行工作的。
匯編程序從寫出到執(zhí)行的過程
到此,完成了一個(gè)匯編程序從寫出到執(zhí)行的全部過程。我們經(jīng)歷了這樣一個(gè)歷程:
編程一1.asm→編譯→1.obj-連接→1.exe→加載→內(nèi)存中的程序一運(yùn)行
程序過程的跟蹤
現(xiàn)在我們知道,在DOS中運(yùn)行一個(gè)程序的時(shí)候,是由command將程序從可執(zhí)行文件中加載入內(nèi)存,并使其得以執(zhí)行。但是,這樣我們不能逐條指令地看到程序的執(zhí)行過程,因?yàn)閏ommand的程序加載,設(shè)置CS:IP指向程序的入口的操作是連續(xù)完成的,而當(dāng)CS:IP指向程序的入口, command就放棄了CPU 的控制權(quán), CPU立即開始運(yùn)行程序, 直至程序結(jié)束。
為了觀察程序的運(yùn)行過程,可以使用Dcbug. Debug 可以將程序加載入內(nèi)存,設(shè)置CS:IP指向程序的入口,但Debug 并不放棄對CPU的控制,這樣,我們就可以使用Debug的相關(guān)命令來單步執(zhí)行程序,查看每一條指令的執(zhí)行結(jié)果。



[BX]和loop指令
?[bx]和內(nèi)存單元的描述
[bx]是什么呢?和10有些類似,[01表示內(nèi)存單元,它的偏移地址是0. 比如在下面的指令中(在Debug中使用):
mov ax, [0]
將一個(gè)內(nèi)存單元的內(nèi)容送入ax,這個(gè)內(nèi)存單元的長度為2字節(jié)(字單元),存放一個(gè)字,偏移地址為0,段地址在ds中。
mov al, [0]
將一個(gè)內(nèi)存單元的內(nèi)容送入al, 這個(gè)內(nèi)存單元的長度為1字節(jié)(字節(jié)單元),存放一個(gè)字節(jié),偏移地址為0,段地址在ds中。
要完整地描述一個(gè)內(nèi)存單元,需要兩種信息:1內(nèi)存單元的地址:2內(nèi)存單元的長度(類型)。
用[0]表示一個(gè)內(nèi)存單元時(shí),0表示單元的偏移地址,段地址默認(rèn)在ds中,單元的長度(類型)可以由具體指令中的其他操作對象(比如說寄存器)指出。
[bx]同樣也表示一個(gè)內(nèi)存單元,它的偏移地址在bx中,比如下面的指令:
mov ax, [bx]
將一個(gè)內(nèi)存單元的內(nèi)容送入ax, 這個(gè)內(nèi)存單元的長度為2字節(jié)(字單元),存放一個(gè)字,偏移地址在bx中,段地址在ds中。
mov al, [bx]
將一個(gè)內(nèi)存單元的內(nèi)容送入a1, 這個(gè)內(nèi)存單元的長度為1字節(jié)(字節(jié)單元),存放一個(gè)字節(jié),偏移地址在bx中,段地址在ds中。
loop英文單詞“l(fā)oop”有循環(huán)的含義,顯然這個(gè)指令和循環(huán)有關(guān)
我們定義的描述性的符號:“()”
(ax)表示ax中的內(nèi)容、(al)表示 al中的內(nèi)容:
(20000H)表示內(nèi)存20000H單元的內(nèi)容(O中的內(nèi)存單元的地址為物理地址);((ds)*16+(bx))表示:
ds中的內(nèi)容為ADR1, bx中的內(nèi)容為ADR2,內(nèi)存ADR1X16+ADR2單元的內(nèi)容。也可以理解為: ds中的ADRI作為段地址,bx中的ADR2作為偏移地址,內(nèi)存ADR1:ADR2單元的內(nèi)容。
注意,“()” 中的元素可以有3種類型:寄存器名:段寄存器名:內(nèi)存單元的物理地址(一個(gè) 20位數(shù)據(jù))。比如:
(ax)、(ds)、 (al)、 (ex)、 (20000H)、((ds)*16+(bx))等是正確的用法:
(2000:0)、((ds): 1000H)等是不正確的用法。
我們看一下(X)的應(yīng)用,比如,
(1) ax 中的內(nèi)容為0010H,可以這樣來描述: (ax)= 0010H;
(2) 2000:1000 處的內(nèi)容為0010H,可以這樣來描述: (21000H)= 0010H;
(3)對于mov ax,[2]的功能,可以這樣來描述: (ax)= ((ds)*16+2);
(4)對于mov [2],ax的功能,可以這樣來描述: ((ds)*16+2)=(ax);
(5) 對于add ax,2的功能,可以這樣來描述: (ax)=(ax)+2;
(6)對于add ax,bx的功能,可以這樣來描述: (ax)=(ax)+(bx);
(7)對于push ax的功能,可以這樣來描述:
(sp)=(sp)-2
((ss)*16+(sp))=(ax)
(8)對于pop ax的功能,可以這樣來描述:
(ax)=((ss)*16+(sp))
(sp)=(sp)+2
“(X)”所表示的數(shù)據(jù)有兩種類型:1字節(jié);2字。是哪種類型由寄存器名或具體的運(yùn)算決定,比如:
(a1)、(b1)、 (c1)等得到的數(shù)據(jù)為字節(jié)型; (ds)、 (ax)、 (bx)等 得到的數(shù)據(jù)為字型。
(a1)=(20000H),則(20000H)得 到的數(shù)據(jù)為字節(jié)型; (ax)=(20000H),則20000HD)得 到的數(shù)據(jù)為字型
約定符號idata表示常量
我們在Debug中寫過類似的指令: mov ax,[0], 表示將ds:0 處的數(shù)據(jù)送入ax中。指令中,在“[...]”里用一個(gè)常量0表示內(nèi)存單元的偏移地址。以后,我們用idata 表示常量。比如:
mov ax,[idata]就代表mov ax,[1]、mov ax,[2]、mov ax,[3]等。mov bx,idata就代表mov bx,1、mov bx,2、mov bx,3等。
mov ds,idata就代表movds,1、movds,2等,它們都是非法指令。