在一個(gè)月前我打算學(xué)Win32匯編,發(fā)現(xiàn)匯編的基礎(chǔ)不是太好,便花了近一個(gè)月的時(shí)間把王爽老師的《匯編語言》看了一遍。現(xiàn)在再來看Win32匯編,比一個(gè)月前輕松了不少。真是本看書,對于我個(gè)人來說。
接下來,我將繼續(xù)Win32匯編的學(xué)習(xí)。堅(jiān)持、堅(jiān)持!
Let's go!
Background
Windows環(huán)境下32位匯編語言是一種全新的編程語言。它使用與C++語言相同的API接口,不僅可以用來開發(fā)出大型的軟件,而且是了解操作系統(tǒng)運(yùn)行細(xì)節(jié)的最佳方式。
Win32指的是32位的Windows系列操作系統(tǒng)。
1990年5月份微軟推出了Windows 3.0,可以支持Intel 80286/386/486微處理器的保護(hù)模式,并可以訪問達(dá)16MB的內(nèi)存。Windows 3.0一面世便在商業(yè)上取得了驚人的成功,從而一舉奠定了Microsoft在操作系統(tǒng)上的壟斷地位。
1992年4月,Microsoft推出了更穩(wěn)定的Windows 3.1,可以支持True Type字體。Windows 3.1是16位Windows中最流行的版本。
1993年5月,Microsoft發(fā)布了具備安全性和穩(wěn)定性特征的32位操作系統(tǒng)Windows NT 3.11,主要針對網(wǎng)絡(luò)和服務(wù)器市場。NT代表新技術(shù)(New Technology)。NT 3.11是Windows系列中使用32位編程模式的第一個(gè)版本。它充分利用80386及以上處理器的平面地址空間和保護(hù)模式等新技術(shù)。
1995年8月,Microsoft推出新一代操作系統(tǒng)Windows 95。Windows 95實(shí)現(xiàn)了很友好的用戶界面,支持即插即用功能,支持主流多媒體設(shè)備和DirectX編程接口,成為Microsoft發(fā)展史上的一個(gè)里程碑,也是操作系統(tǒng)發(fā)展史上的一個(gè)里程碑。從此,Windows 9x便取代了Windows 3.x和MS-DOS操作系統(tǒng),成為個(gè)人計(jì)算機(jī)平臺(tái)的主流操作系統(tǒng)。
1998年Microsoft又發(fā)布了使用更方便的Windows 98。
在操作系統(tǒng)的分類上,Microsoft根據(jù)家庭個(gè)人用戶和商業(yè)辦公用戶的不同需求,分別提供Windows 9x和Windows NT系列,Windows 9x注重用戶界面及其他易用性特征,而NT系列則在純32位內(nèi)核的穩(wěn)定性和可靠性等企業(yè)級(jí)特征上下功夫。
2000年,微軟發(fā)布采用純32位內(nèi)核并照顧了家庭消費(fèi)類應(yīng)用軟件的Windows NT 5.0,即Windows 2000。至此Micosoft的兩個(gè)系列操作系統(tǒng)(9x和NT)終于開始統(tǒng)一。
為了利用MS-DOS時(shí)代大量的應(yīng)用程序,保持向下的兼容性,Windows 9x的內(nèi)核模塊還有許多地址使用16位程序,但在編程上,支持32位的編程模式。
Windows NT系列和Windows 9x系列操作系統(tǒng)都支持Win32 API(Application Programming Interface),即Windows 32位應(yīng)用程序編程接口,Win32 API為應(yīng)用程序提供了大量的系統(tǒng)功能調(diào)用,通過Win32 API調(diào)用Windows系統(tǒng)相當(dāng)于在MS-DOS中通過中斷方式調(diào)用系統(tǒng)功能。就像DOS匯編程序中隨處可見的 INT 21h指令一樣,Windows應(yīng)用程序中Win32 API也隨處可見。
Windows的特色
對于使用者來說:
1)圖形用戶界面;(GUI,Graphic User Interface) Windows最重要的特色。
2)一致的用戶界面; 便于使用。
3)多任務(wù)。 非常重要的特色。用戶可以同時(shí)運(yùn)行多個(gè)程序,可以在不同的程序之間傳送數(shù)據(jù)。
對于程序員來說,更關(guān)心隱藏在底下的東西:
1)大量的函數(shù)調(diào)用;Win32支持上千種函數(shù)的調(diào)用,幾乎涉及所有的方面,程序員可以把更多的時(shí)間放在程序的邏輯結(jié)構(gòu)和用戶界面上。
2)和設(shè)備的無關(guān)性;Win32程序并不直接訪問屏幕、打印機(jī)和鍵盤等硬件設(shè)備,Windows虛擬了所有的硬件。只要有硬件的設(shè)備驅(qū)動(dòng)程序,這個(gè)硬件就可以使用,應(yīng)用程序并不需要關(guān)心硬件的具體型號(hào)。與DOS編程中需要針對不同的顯示卡和打印機(jī)等編寫很多的驅(qū)動(dòng)程序來比,這個(gè)特性對程序員的幫助是巨大的。
3)內(nèi)存管理。由于內(nèi)存分頁和虛擬內(nèi)存的使用,每個(gè)程序都可以使用4GB的地址空間,DOS編程時(shí)必須考慮的640KB的內(nèi)存問題已經(jīng)成為歷史。
必須了解的東西
80x86處理器的工作模式
80386處理器有3種工作模式:實(shí)模式、保護(hù)模式和虛擬86模式。
實(shí)模式和虛擬86模式是為了和8086處理器兼容而設(shè)置的。在實(shí)模式下,80386處理器就相當(dāng)于一個(gè)快速的8086處理器。
保護(hù)模式是80386處理器的主要工作模式。在此方式下,80386可以尋址4GB的地址空間,同時(shí),保護(hù)模式提供了80386先進(jìn)的多任務(wù)、內(nèi)存分頁管理和優(yōu)先級(jí)保護(hù)等機(jī)制。
為了在保護(hù)模式下繼續(xù)提供和8086處理器的兼容,80386又設(shè)計(jì)了一種虛擬86模式,以便可以在保護(hù)模式的多任務(wù)條件下,有的任務(wù)運(yùn)行32位程序,有的任務(wù)運(yùn)行MS-DOS程序。
在虛擬86模式下,同樣支持任務(wù)切換、內(nèi)存分頁管理和優(yōu)先級(jí),但內(nèi)存的尋地址方式和8086相同,也是可以尋址1MB的空間。
實(shí)模式是80386處理器工作的基礎(chǔ),這時(shí)80386當(dāng)做一個(gè)快速的8086處理器工作。在實(shí)模式下可以通過指令切換到保護(hù)模式,也可以從保護(hù)模式退回實(shí)模式。
虛擬86模式則以保護(hù)模式為基礎(chǔ),在保護(hù)模式和虛擬86模式之間可以互相切換,但不能從實(shí)模式直接進(jìn)入虛擬86模式或從虛擬86模式直接退到實(shí)模式。
實(shí)模式
80386處理器被復(fù)位或加電的時(shí)候以實(shí)模式啟動(dòng)。這時(shí)候處理器中的各寄存器以實(shí)模式的初始化值工作。80386處理器在實(shí)模式下的存儲(chǔ)器尋址方式和8086是一樣的,由段寄存器的內(nèi)容乘以16當(dāng)做基地址,加上段內(nèi)的偏移地址形成最終的物理地址,這時(shí)候它的32位地址線只使用了低20位。在實(shí)模式下,80386處理器不能對內(nèi)存進(jìn)行分頁管理,所以指令尋址的地址就是內(nèi)存中實(shí)際的物理地址。在實(shí)模式下,所有的段都是可以讀、寫和執(zhí)行的。
實(shí)模式下80386不支持優(yōu)先級(jí),所有的指令相當(dāng)于在工作在特權(quán)級(jí)(優(yōu)先級(jí)0),所以它可以執(zhí)行所有特權(quán)指令,包括讀寫控制寄存器CR0等。實(shí)際上,80386就是通過在實(shí)模式下初始化控制寄存器,GDTR,LDTR,IDTR與TR等管理寄存器以及頁表,然后再通過加載CR0使其中的保護(hù)模式使能位置位而進(jìn)入保護(hù)模式的。實(shí)模式下不支持硬件上的多任務(wù)切換。
實(shí)模式下的中斷處理方式和8086處理器相同,也用中斷向量表來定位中斷服務(wù)程序地址。中斷向量表的結(jié)構(gòu)也和8086處理器一樣,每4個(gè)字節(jié)組成一個(gè)中斷向量,其中包括兩個(gè)字節(jié)的段地址和兩個(gè)字節(jié)的偏移地址。
從編程的角度看,除了可以訪問80386新增的一些寄存器外,實(shí)模式的80386處理器相比8086,其最大的好處是可以使用80386的32位寄存器,用32位寄存器進(jìn)行編程可以使計(jì)算程序更加簡捷,加快了執(zhí)行速度。其次,80386中增加的兩個(gè)輔助段寄存器FS和GS在實(shí)模式下也可以使用,這樣,同時(shí)可以訪問的段達(dá)到了6個(gè)而不必考慮重新裝入的問題;最后,很多80386的新增指令也使一些原來不很方便的操作得以簡化,如80386中可以使用下述指令進(jìn)行數(shù)組訪問:
mov cx,[eax + ebx * 2 + 數(shù)組基地址]
這相當(dāng)于把數(shù)組下標(biāo)為eax和ebx的項(xiàng)目放入cx中;ebx*2中的2可以是1,2,4或8,這樣就可以支持8位到64位的數(shù)組。
另外,pushad和popad指令可以一次把所有8個(gè)通用寄存器的值壓入或從堆棧中彈出,比起用下面的指令分別將8個(gè)寄存器入棧要快了很多:
push eax
push ebx
…
pop ebx
pop eax
當(dāng)然,使用了這些新指令的程序是無法拿回到8086處理器上去執(zhí)行的,因?yàn)檫@些指令的編碼在8086處理器上是未定義的。
保護(hù)模式
當(dāng)80386工作在保護(hù)模式下的時(shí)候,它的所有功能都是可用的。這時(shí)80386所有的32根地址線都可供尋址,物理尋地址空間高達(dá)4GB。在保護(hù)模式下,支持內(nèi)存分頁機(jī)制,提供了對虛擬內(nèi)存的良好支持。雖然與8086可尋址的1MB物理地址空間相比,80386可尋地址的物理地址空間可謂很大,但實(shí)際的微機(jī)系統(tǒng)不可能安裝如此大的物理內(nèi)存。所以,為了運(yùn)行大型程序和真正實(shí)現(xiàn)多任務(wù),虛擬內(nèi)存是一種必需的技術(shù)。
保護(hù)模式下80386支持多任務(wù),可以依靠硬件僅在一條指令中實(shí)現(xiàn)任務(wù)切換。任務(wù)環(huán)境的保護(hù)工作是由處理器自動(dòng)完成的。在保護(hù)模式下,80386處理器還支持優(yōu)先級(jí)機(jī)制,不同的程序可以運(yùn)行在不同的優(yōu)先級(jí)上。優(yōu)先級(jí)一共分0~3 四個(gè)級(jí)別,操作系統(tǒng)運(yùn)行在最高的優(yōu)先級(jí)0上,應(yīng)用程序則運(yùn)行在比較低的級(jí)別上;配合良好的檢查機(jī)制后,既可以在任務(wù)間實(shí)現(xiàn)數(shù)據(jù)的安全共享也可以很好地隔離各個(gè)任務(wù)。從實(shí)模式切換到保護(hù)模式是通過修改控制寄存器CR0的控制位PE(位0)來實(shí)現(xiàn)的。在這之前還需要建立保護(hù)模式必需的一些數(shù)據(jù)表,如全局描述符表GDT和中斷描述符表IDT等。
DOS操作系統(tǒng)運(yùn)行于實(shí)模式下,而Windows操作系統(tǒng)運(yùn)行于保護(hù)模式下。
虛擬86模式
虛擬86模式是為了在保護(hù)模式下執(zhí)行8086程序而設(shè)置的。雖然80386處理器已經(jīng)提供了實(shí)模式來兼容8086程序,但這時(shí)8086程序?qū)嶋H上只是運(yùn)行得快了一點(diǎn),對CPU的資源還是獨(dú)占的。在保護(hù)模式的多任務(wù)環(huán)境下運(yùn)行這些程序時(shí),它們中的很多指令和保護(hù)模式環(huán)境格格不入,如段尋址方式、對中斷的處理和I/O操作的特權(quán)問題等。為了在保護(hù)模式下工作而丟棄這些程序的代價(jià)是巨大的。設(shè)想一下,如果Windows或80386處理器推出的時(shí)候宣布不能運(yùn)行以前的MS-DOS程序,那么就等于放棄了一個(gè)巨大的軟件庫,Windows以及80386處理器可能就會(huì)落得和蘋果機(jī)一樣的下場,這是Microsoft和Intel都不愿意看到的。所以,80386處理器又設(shè)計(jì)了一個(gè)虛擬86模式。
虛擬86模式是以任務(wù)形式在保護(hù)模式上執(zhí)行的,在80386上可以同時(shí)支持由多個(gè)真正的80386任務(wù)和虛擬86模式構(gòu)成的任務(wù)。在虛擬86模式下,80386支持任務(wù)切換和內(nèi)存分頁。在Windows操作系統(tǒng)中,有一部分程序?qū)iT用來管理虛擬86模式的任務(wù),稱為虛擬86管理程序。
既然虛擬86模式以保護(hù)模式為基礎(chǔ),它的工作方式實(shí)際上是實(shí)模式和保護(hù)模式的混合。為了和8086程序的尋地址方式兼容,虛擬86模式采用和8086一樣的尋址方式,即用段寄存器乘以16當(dāng)作基址再配合偏移地址形成線性地址,尋址空間為1MB。但顯然多個(gè)虛擬86任務(wù)不能同時(shí)使用同一位置的1MB地址空間,否則會(huì)引起沖突。操作系統(tǒng)利用分頁機(jī)制將不同虛擬86任務(wù)的地址空間映射到不同的物理地址上去,這樣每個(gè)虛擬86任務(wù)看起來都認(rèn)為自己在使用0~1MB的地址空間。
8086代碼中有相當(dāng)一部分指令在保護(hù)模式下屬于特權(quán)指令,如屏幕中斷的cli和中斷返回指令iret等。這些指令在8086程序中是合法的。如果不讓這些指令執(zhí)行,8086代碼就無法工作。為了解決這個(gè)問題,虛擬86管理程序采用模擬的方法完成這些指令。這些特權(quán)指令執(zhí)行的時(shí)候引起了保護(hù)異常。虛擬86管理程序在異常處理程序中檢查產(chǎn)生異常的指令,如果是中斷指令,則從虛擬86任務(wù)的中斷向量表中取出中斷處理程序的入口地址,并將控制轉(zhuǎn)移過去;如果是危及操作系統(tǒng)的指令,如cli等,則簡單地忽略這些指令,在異常處理程序返回的時(shí)候直接返回到下一條指令。通過這些措施,8086程序既可以正常地運(yùn)行下去,在執(zhí)行這些指令的時(shí)候又覺察不到已經(jīng)被虛擬86管理程序做了手腳。MS-DOS應(yīng)用程序在Windows操作系統(tǒng)中就是這樣工作的。
Windows的內(nèi)存管理
Win32匯編中,每個(gè)程序都可以用4GB的內(nèi)存嗎?
Win32匯編源代碼中為什么看不到CS、DS、ES和SS等段寄存器的使用?
DOS操作系統(tǒng)的內(nèi)存安排
Win32編程相對于DOS編程最大的區(qū)別之一就是內(nèi)存的使用。
00000h
|
中斷向量表
|
00400h
|
BIOS數(shù)據(jù)區(qū)
|
00500h
|
DOS數(shù)據(jù)區(qū)
|
|
系統(tǒng)程序
(DOS的駐留部分、驅(qū)動(dòng)程序等)
|
|
可用空間
|
A0000h
|
圖形模式視頻緩沖區(qū)
|
B0000h
|
單色字符模式視頻緩沖區(qū)
|
B8000h
|
彩色字符模式視頻緩沖區(qū)
|
C0000h
|
VGA BIOS地址
|
C8000h
|
ROM擴(kuò)展、系統(tǒng)BIOS地址
|
FFFFFh
|
64KB高端內(nèi)存
|
在實(shí)模式下,一個(gè)內(nèi)存尋址方式是,一個(gè)完整的地址由段地址和偏移地址兩部分組成。段地址放在16位的段寄存器中,然后在指令中用16位的偏移地址尋址。處理器換算時(shí)先將段地址乘以10h(即16),得到段在物理內(nèi)存中的起始地址;然后 加上16位的偏移地址得到實(shí)際的物理地址。
當(dāng)80386處理器工作在保護(hù)模式和虛擬8086模式的時(shí)候,可以使用全部32根地址線訪問內(nèi)存4GB大的內(nèi)存。段地址加偏移地址的計(jì)算方法顯然無法覆蓋這么大的范圍。但計(jì)算一下就可以發(fā)現(xiàn),實(shí)際上和8086同樣的限制已經(jīng)不復(fù)存在,因?yàn)?/span>80386所有的通用寄存器都是32位的,2的32次方相當(dāng)于4G,所以用任何一個(gè)通用寄存器來間接尋址,不必分段就已經(jīng)可以訪問到所有的內(nèi)存地址。
這是不是說,在保護(hù)模式下,段寄存器就不再有用了呢?答案是否定的。實(shí)際上段寄存器更有用了,雖然在尋址上不再有分段的限制問題,但在保護(hù)模式下,一個(gè)地址空間是否可以被寫入,可以被多少優(yōu)先級(jí)的代碼寫入,是不是允許執(zhí)行等涉及保護(hù)的問題就出來了。要解決這些問題,必須對一個(gè)地址空間定義一些安全上的屬性。段寄存器這時(shí)就派上了用途。但是涉及屬性和保護(hù)模式下段的其他參數(shù),要表示的信息太多了,要用64位長的數(shù)據(jù)才能表示。我們把這64位的屬性數(shù)據(jù)叫做段描述符(Segment Desciptor)。
80386的段寄存是16位的,無法放下保護(hù)模式下64位的段描述符。解決辦法是把所有段的段描述符順序放在內(nèi)存中的指定位置,組成一個(gè)段描述符表(Descriptor Table);而段寄存器中的16位用來做索引信息,指定這個(gè)段的屬性用段描述符表中的第幾個(gè)描述符來表示。這時(shí),段寄存器中的信息不再是段地址了,而是段選擇器(Segment Selector)。可以通過它在段描述符表中,選擇一個(gè)項(xiàng)目以得到段的全部信息。
既然這樣,段描述符表放在哪里呢?80386中引入了兩個(gè)新的寄存器來管理段描述符表。一個(gè)是48位的全局描述符表寄存器GDTR(Global Descriptor Table Register),一個(gè)是16位的局部描述符表寄存器LDTR(Local Descriptor Table Register)。那么,為什么有兩個(gè)描述符表寄存器呢?
GDTR指向的描述符表為全局描述符表GDT(Global Descriptor Table)。它包含系統(tǒng)中所有任務(wù)都可用的段描述符,通常包含描述操作系統(tǒng)所使用的代碼段、數(shù)據(jù)段和堆棧段的描述符及各任務(wù)的LDT段等;全局描述符表只有一個(gè)。
LDTR則指向局部描述符表LDT(Local Descriptor Table)。80386處理器設(shè)計(jì)成每個(gè)任務(wù)都有一個(gè)獨(dú)立的LDT。它包含有每個(gè)任務(wù)私有的代碼段、數(shù)據(jù)段和堆棧段的描述符,也包含該任務(wù)所使用的一些門描述符,如任務(wù)門和調(diào)用門描述符等。
不同任務(wù)的局部描述符表分別組成不同的內(nèi)存段,描述這些內(nèi)存段的描述符當(dāng)做系統(tǒng)描述符放在全局描述符表中。和GDTR直接指向內(nèi)存地址不同,LDTR和CS,DS等段選擇器一樣只存放索引值,指向局部描述符表內(nèi)存段對應(yīng)的描述符在全局描述符表中的位置。隨著任務(wù)的切換,只要改變LDTR的值,系統(tǒng)當(dāng)前的局部描述符表LDT也隨之切換,這樣便于各任務(wù)之間數(shù)據(jù)的隔離。但GDT并不隨著任務(wù)的切換而切換。
看到這里,讀者可能會(huì)提出一個(gè)問題,既然有全局描述符表和局部描述符表兩個(gè)表,那么段選擇器中的索引值對應(yīng)哪個(gè)表中的描述符呢?實(shí)際上,16位的段選擇器中只有高13位表示索引值。剩下的3個(gè)數(shù)據(jù)位中,第0,1位表示程序的當(dāng)前優(yōu)先RPL;第2位TI位用來表示在段描述符的位置;TI=0表示在GDT中,TI=1表示在LDT中。
80386的內(nèi)存分頁機(jī)制
在實(shí)模式下尋址的時(shí)候,段寄存器+偏移地址經(jīng)過轉(zhuǎn)換計(jì)算以后得到的地址是物理地址,也就是在物理內(nèi)存中的實(shí)際地址。
而保護(hù)模式下,段選擇器+偏移地址轉(zhuǎn)換后的地址被稱為線性地址,而不是物理地址。
線性地址是物理地址嗎?
可能是,也可能不是,這取決于80386的內(nèi)存分頁機(jī)制是否被使用。
在單任務(wù)的DOS系統(tǒng)中,一個(gè)應(yīng)用程序可以使用所有的空閑內(nèi)存。程序退出后,操作系統(tǒng)回收所有的碎片內(nèi)存并且合并成一個(gè)大塊內(nèi)存繼續(xù)供下一個(gè)程序使用。內(nèi)存合并過程中的一個(gè)極端情況匯報(bào)系統(tǒng)中有多個(gè)TSR卸載后,后裝入的TSR會(huì)留存內(nèi)存的中間部位,把空閑內(nèi)存隔成兩個(gè)區(qū)域。這時(shí)應(yīng)用程序使用的最大內(nèi)存塊只能是這兩塊內(nèi)存中較大的一塊,無法將它們合并使用。
對于一個(gè)多任務(wù)的操作系統(tǒng),內(nèi)存的碎片化是不能容忍的。否則,經(jīng)過一段時(shí)間后,即使空閑內(nèi)存的總和很大,也可能出現(xiàn)任何一處內(nèi)存都小到無法裝入執(zhí)行程序的地步。所以多任務(wù)操作系統(tǒng)中碎片內(nèi)存的合并是個(gè)很重要的問題。
80386處理器的分頁機(jī)制可以很好地解決這個(gè)問題。80386處理器把4KB大小的一塊內(nèi)存當(dāng)做一頁內(nèi)存,每頁物理內(nèi)存可以根據(jù)頁目錄和頁表,隨意映射到不同的線性地址上。這樣,就可以將物理地址不連續(xù)的內(nèi)存的映射連到一起,在線性地址上視為連續(xù)。在80386處理器中,除了和CR3寄存器(指定當(dāng)前頁目錄的地址)相關(guān)的指令使用的是物理地址外,其他所有指令都是用線性地址尋址的。
是否啟用內(nèi)存分頁機(jī)制是由80386處理器新增的CR0寄存器中的位31(PG位)決定的。如果PG=0,則分頁機(jī)制不啟用,這時(shí)所有指令尋址的地址(線性地址)就是系統(tǒng)中實(shí)際的物理地址;當(dāng)PG=1的時(shí)候,80386處理器進(jìn)入內(nèi)存分頁管理模式,所有的線性地址要經(jīng)過頁表的映射才得到最后的物理地址。
內(nèi)存分頁管理只能在保護(hù)模式下才可以實(shí)現(xiàn),實(shí)模式不支持分頁機(jī)制。但不管在哪種模式下,所有尋址指令使用的都是線性地址,程序不用關(guān)心數(shù)據(jù)最后究竟存放在物理內(nèi)存的哪個(gè)地方。
頁表規(guī)定的不僅是地址的映射,同時(shí)還規(guī)定了頁的訪問屬性,如是否可寫、可讀和可執(zhí)行等。比如把代碼所在的內(nèi)存頁設(shè)置為可讀與可執(zhí)行,那么權(quán)限不夠的代碼向它寫數(shù)據(jù)就會(huì)引發(fā)保護(hù)異常。利用這個(gè)機(jī)制可以在硬件層次上支持虛擬內(nèi)存的實(shí)現(xiàn)。
頁表可以指定一個(gè)頁面并不真正映射到物理內(nèi)存中,這樣,訪問這個(gè)頁的指令會(huì)引發(fā)頁異常錯(cuò)誤。這時(shí),處理器會(huì)自動(dòng)轉(zhuǎn)移到頁異常處理程序中去,操作系統(tǒng)可以在異常處理程序中將硬盤上的虛擬內(nèi)存讀到內(nèi)存中并修改頁表重新映射,然后重新執(zhí)行引發(fā)異常的指令。這樣指令可以正常執(zhí)行下去。
Windows的內(nèi)存安排
Windows系統(tǒng)一般在硬盤上建立大小為物理內(nèi)存兩倍左右的交換文件(文件名在Windows 9x下為Win386.swp,Windows NT下為PageFile.sys)用做虛擬內(nèi)存。
利用80386處理器的內(nèi)存分頁機(jī)制,交換文件在尋地址上可以很方便地作為物理內(nèi)存使用。只需在真正訪問到的時(shí)候?qū)⒂脖P文件的內(nèi)容讀入物理內(nèi)存,然后重新將線性地址映射到這塊物理內(nèi)存就可以了。同樣道理,被執(zhí)行的可執(zhí)行文件也不必真正裝入內(nèi)存,只要在頁表中建立映射關(guān)系,以后到真正訪問到的時(shí)候再調(diào)入物理內(nèi)存。
如果把虛擬內(nèi)存暫時(shí)先視為物理內(nèi)存的一部分,從物理內(nèi)存中的層次看,Windows操作系統(tǒng)和DOS一樣,也是所有的內(nèi)容共享內(nèi)存,如操作系統(tǒng)使用的代碼和數(shù)據(jù)(GDT,LDT與頁表等),當(dāng)前執(zhí)行中的所有程序的代碼和數(shù)據(jù)以及這些程序調(diào)用的DLL的代碼和數(shù)據(jù)等。
但是從應(yīng)用程序代碼的層次看,也就是說從分頁映射后線性地址層次看,內(nèi)存的安排卻不是這個(gè)樣子。因?yàn)?/span>Windows是一個(gè)分時(shí)的多任務(wù)操作系統(tǒng),CPU時(shí)間被分成一個(gè)個(gè)的時(shí)間片后分配給不同程序輪流使用,在一個(gè)程序的時(shí)間片中,和這個(gè)程序執(zhí)行無關(guān)的東西(如其他程序的代碼和數(shù)據(jù))并不需要映射到線地址中去。
Windows操作系統(tǒng)通過切換不同的頁表內(nèi)容讓線性地址在不同的時(shí)間片中映射不同的內(nèi)容。在物理內(nèi)存中,操作系統(tǒng)和系統(tǒng)DLL的代碼需要供每個(gè)應(yīng)用程序調(diào)用,所以在所有的時(shí)間片中都必須被映射;用戶程序只在自己所屬的時(shí)間片內(nèi)被映射;而用戶DLL則有選擇地被映射。
Win32編程中幾個(gè)很重要的概念:
每個(gè)應(yīng)用程序都有自己的4GB的尋址空間。該空間可存放操作系統(tǒng)、系統(tǒng)DLL和用戶DLL的代碼,它們之中有各種函數(shù)供應(yīng)用程序調(diào)用。再除去其他的一些空間,余下的是應(yīng)用程序的代碼、數(shù)據(jù)和可以分配的地址空間。
不同應(yīng)用程序的線性地址空間是隔離的。雖然它們在物理內(nèi)存中同時(shí)存在,但在某個(gè)程序所屬的時(shí)間片中,其他應(yīng)用程序程序的代碼和數(shù)據(jù)沒有被映射到可尋地址的線性地址中,所以是不可訪問的。從編程的角度看,程序可以使用4GB的尋址空間,而這個(gè)空間是私有的。
DLL程序沒有自己的私有的空間。它們總是被映射到其他應(yīng)用程序的地址空間中,當(dāng)做和其他應(yīng)用程序的一部分運(yùn)行。原因很簡單,如果它不和其他程序同屬一個(gè)地址空間,應(yīng)用程序該如何調(diào)用它呢?
從Win32匯編的角度看內(nèi)存尋址
DOS下的分段尋地址方式令人一頭霧水,80386保護(hù)模式的內(nèi)存管理就更復(fù)雜。
Windows是一個(gè)多任務(wù)操作系統(tǒng),最首要的宗旨就是穩(wěn)定壓倒一切,如果描述符表以及頁表等內(nèi)容交給用戶程序是很不安全的,不用說全局描述符表,就是為每個(gè)程序建立的局部描述符表也不應(yīng)該讓用戶程序改寫,否則用戶可以通過構(gòu)造自己的描述符來訪問操作系統(tǒng)不希望用戶訪問的東西。任何權(quán)限上開放引發(fā)的安全問題都是很嚴(yán)重的。
病毒程序就是利用這些系統(tǒng)漏洞,提升自身的權(quán)限,做一些破壞動(dòng)作的。
所以,Windows操作系統(tǒng)干脆為用戶程序安排好了一切。具體表現(xiàn)在為用戶程序的代碼段、數(shù)據(jù)段和堆棧段全部預(yù)定義好了段描述符。這些段的起始地址為0,限長為ffffffff,所以用它們可以直接尋址全部的4GB地址空間。程序開始執(zhí)行的時(shí)候,CS,DS,ES和SS都已經(jīng)指向了正確的描述符,在整個(gè)程序的生命周期內(nèi),程序員不必改動(dòng)這些段寄存器,也不必關(guān)心它們的值究竟是多少(實(shí)際上,想改也改不了)。
所以對Win32匯編程序來說,整個(gè)源程序中竟然可以不用出現(xiàn)段寄存器的身影。這在DOS匯編編程中是不可想象的。
并不是Win32匯編源代碼用不到段寄存器,而是用戶在使用中不必去關(guān)心段寄存器。
Windows的特權(quán)保護(hù)
Windows的特權(quán)保護(hù)和處理器硬件的支持是分不開的。
優(yōu)先級(jí)的劃分、指令的權(quán)限檢查和超出權(quán)限訪問的異常處理等是構(gòu)成特權(quán)保護(hù)的基礎(chǔ)。
1、Win32匯編中為什么找不到中斷指令的使用?
2、Windows錯(cuò)誤的藍(lán)屏屏幕是從哪里來的?
80386的中斷和異常
中斷指當(dāng)程序執(zhí)行過程中有更重要的事情需要實(shí)時(shí)處理時(shí)(如串口中有數(shù)據(jù)到達(dá),不及時(shí)處理數(shù)據(jù)會(huì)丟失,串行控制器就提交一個(gè)中斷信號(hào)給處理器要求處理),硬件通過中斷控制器通知處理器。處理器暫時(shí)掛起當(dāng)前運(yùn)行的程序,轉(zhuǎn)移到中斷處理程序中;當(dāng)中斷處理程序處理完畢后,通過iret指令回到原先被打斷的程序中繼續(xù)執(zhí)行。
異常指指令執(zhí)行中發(fā)生不可忽略的錯(cuò)誤時(shí)(如遇到無效的指令編碼,除法指令除零等),處理器用和中斷處理相同的操作方法掛起當(dāng)前運(yùn)行的程序轉(zhuǎn)移到異常處理程序中。異常處理程序決定在修正錯(cuò)誤后是否回到原來的地方繼續(xù)執(zhí)行。
更為DOS匯編程序員熟悉的中斷,指的是用int n指令直接轉(zhuǎn)移到中斷向量n指定的中斷處理程序中執(zhí)行。嚴(yán)格地講,int n指令應(yīng)該算自陷而不是中斷。因?yàn)檫@時(shí)并不是程序被急需解決的事情打斷。而是自己要求停止執(zhí)行并轉(zhuǎn)移到中斷處理程序中去。
不管中斷、異常還是自陷,雖然它們產(chǎn)生的原因不同,但處理過程是類似的,都通過中斷向量表里存放的入口地址轉(zhuǎn)移到服務(wù)程序,都由CPU自動(dòng)在堆棧中保護(hù)斷點(diǎn)地址,最后也都可以用iret指令返回指令被中斷的地方。
8086或80386實(shí)模式下中斷和異常的處理過程。
中斷和異常服務(wù)程序地址存放在中斷向量表中。中斷向量表位于物理內(nèi)存00000h開始的400h字節(jié)中,共支持100h個(gè)中斷向量;每個(gè)中斷向量是一個(gè)xxxx:yyyy格式的地址,占用4字節(jié)。當(dāng)發(fā)生n號(hào)異常或n號(hào)中斷,或者執(zhí)行到int n指令的時(shí)候,CPU首先到內(nèi)存n 4的地方取出服務(wù)程序的地址aaaa:bbbb;然后將標(biāo)志寄存器、中斷時(shí)的CS和IP壓入堆棧,接著轉(zhuǎn)移到aaaa:bbbb處執(zhí)行;在服務(wù)程序最后遇到iret的時(shí)候,CPU從堆棧中標(biāo)志寄存器,然后取出CS和IP并返回。
在保護(hù)模式下,中斷或異常處理往往從用戶代碼切換到操作系統(tǒng)代碼中執(zhí)行。由于保護(hù)模式下的代碼有優(yōu)先級(jí)之分,因此出現(xiàn)了從優(yōu)先級(jí)低的應(yīng)用程序轉(zhuǎn)移到優(yōu)先級(jí)高的系統(tǒng)代碼中的問題,如果優(yōu)先級(jí)低的代碼能夠任意調(diào)用優(yōu)先級(jí)高的代碼,就相當(dāng)于擁有了高優(yōu)先級(jí)代碼的權(quán)限。為了使高優(yōu)先級(jí)的代碼能夠安全地被低優(yōu)先級(jí)的代碼調(diào)用,保護(hù)模式下增加了門的概念。
門,指向某個(gè)優(yōu)先級(jí)高的程序所規(guī)定的入口點(diǎn),所有優(yōu)先級(jí)低的程序調(diào)用優(yōu)先級(jí)高的程序只能通過門重定向,進(jìn)入門的規(guī)定的入口點(diǎn)。這樣可以避免低級(jí)別的程序代碼從任意位置進(jìn)入優(yōu)先級(jí)高的程序的問題。
保護(hù)模式下的中斷和異常等服務(wù)程序也要從門進(jìn)入,80386的門分為中斷門、自陷門和任務(wù)門幾種。
在保護(hù)模式下要表示一個(gè)中斷或異常服務(wù)程序的信息需要用8個(gè)字節(jié),包括門的種類以及xxxx:yyyyyyyy格式的入口地址等。這組信息叫做中斷描述符。這樣,中斷向量表就無法采用和實(shí)模式下同樣的4字節(jié)一組的格式。
保護(hù)模式下把所有的中斷描述符放在一起組成“中斷描述符表”IDT(Interrupt Descriptor Table)。IDT不再放在固定的地址00000h處,而是采用可編程設(shè)置的方式,支持的中斷數(shù)量也可以設(shè)置。為此80386處理器引入了一個(gè)新的48位寄存器IDTR。IDTR的高32位指定了IDT在內(nèi)存中的基址(線性地址),低16位指定了IDT的長度,相當(dāng)于指定了可以支持的中斷數(shù)量。
保護(hù)模式下發(fā)生異常或中斷時(shí),處理器先根據(jù)IDTR寄存器得到中斷描述符的地址,然后取出n號(hào)中斷/異常的門描述符,再從描述符中得到中斷服務(wù)程序的地址xxxx:yyyyyyyy,經(jīng)過段地址轉(zhuǎn)換后得到服務(wù)程序的32位線性地址并轉(zhuǎn)移后執(zhí)行。
由于保護(hù)模式下用中斷門可以從低優(yōu)先級(jí)的代碼調(diào)用高優(yōu)先級(jí)的代碼,所以不能讓用戶程序?qū)懼袛嗝枋龇恚駝t會(huì)引發(fā)安全問題(CIH病毒)。這樣就如同關(guān)了窗子擋住蒼蠅,也擋住了微風(fēng),用戶的系統(tǒng)擴(kuò)展程序也就不能像在DOS中一樣再用中斷服務(wù)程序的方式提供服務(wù)了。因?yàn)橛脩舫绦蚋緵]有權(quán)限將中斷地址指到自己的代碼中來。
在Windows中,操作系統(tǒng)使用動(dòng)態(tài)鏈接庫來代替中斷服務(wù)程序提供系統(tǒng)功能,所以Win32匯編中int指令也就失去了存在的意義。這就是在Win32匯編源代碼中看不到int指令的原因。其實(shí)那些調(diào)用API的指令原本是用int指令實(shí)現(xiàn)的。
80386的保護(hù)機(jī)制
80286之前的處理器只支持單任務(wù),操作系統(tǒng)并沒有什么安全性可言,計(jì)算機(jī)的全部資源包括操作系統(tǒng)的內(nèi)部都可以任憑程序員調(diào)用。
但對于多任務(wù)的操作系統(tǒng),某個(gè)搗亂的程序的為所欲為令即可使所有程序都無法運(yùn)行。
所以80286及以上的處理器引入了優(yōu)先級(jí)的概念。80386處理器共設(shè)置4個(gè)優(yōu)先級(jí)(0~3)。0級(jí)是最高級(jí)(特權(quán)級(jí));3級(jí)是最低級(jí)(用戶級(jí));1級(jí)和2級(jí)介于它們之間。特權(quán)級(jí)代碼一般是操作系統(tǒng)的代碼,可以訪問全部系統(tǒng)資源;其他級(jí)別的代碼一般都是用戶程序,可以訪問的資源受到限制。
80386采用保護(hù)機(jī)制主要為了檢查和防止低級(jí)別代碼的越權(quán)操作,如訪問不該訪問的數(shù)據(jù)、端口以及調(diào)用高優(yōu)先級(jí)的代碼等。保護(hù)機(jī)制主要由下列幾方面組成:
1)段的類型檢查——段的類型是由段描述符指定的,主要屬性有是否可執(zhí)行,是否可讀和是否可寫等。而CS、DS和SS等段選擇器是否能裝入某種類型的段描述符是有限制的。如不可執(zhí)行的段不能裝入CS;不可讀的段不能裝入DS與ES等數(shù)據(jù)段寄存器;不可寫的段不能裝入SS等。如果段類型檢查通不過,則處理器會(huì)產(chǎn)生一般性保護(hù)異常或堆棧異常。
2)頁的類型檢查——除了可以在段級(jí)別上指定整個(gè)段是否可以讀寫外,在頁表中也可以為每個(gè)頁指定是否可寫。對特權(quán)級(jí)下的執(zhí)行代碼,所有的頁都是可寫的。但對1,2和3級(jí)的代碼,還要根據(jù)頁表中的R/W項(xiàng)決定是否可寫,企圖對只讀的頁進(jìn)行寫操作會(huì)產(chǎn)生頁異常。
3)訪問數(shù)據(jù)時(shí)的級(jí)別檢查——優(yōu)先級(jí)低的代碼不能訪問優(yōu)先級(jí)高的數(shù)據(jù)段。
80386的段描述符中有一個(gè)DPL域(描述符優(yōu)先級(jí)),表示這個(gè)段可以被訪問的最低優(yōu)先級(jí)。而段選擇器中含有RPL域(請求優(yōu)先級(jí)),表示當(dāng)前執(zhí)行代碼的優(yōu)先級(jí)。只有DPL在數(shù)值上大于或等RPL值的時(shí)候,該段才是可以訪問的,否則會(huì)產(chǎn)生一般性保護(hù)異常。
4)控制轉(zhuǎn)移的檢查——在處理器中,有很多指令可以實(shí)現(xiàn)控制轉(zhuǎn)移,如jmp,call,ret,int和iret等指令。但優(yōu)先級(jí)低的代碼不能隨意轉(zhuǎn)移到優(yōu)先級(jí)高的代碼中,所以遇到這些指令的時(shí)候,處理器要檢查轉(zhuǎn)移的目的位置是否合法。
5)指令集的檢查——有兩類指令可以影響保護(hù)機(jī)制。
第一類是改變GDT,LDT,IDT以及控制寄存器等關(guān)鍵寄存器的指令,稱為特權(quán)指令; 第二類是操作I/O端口的指令以及cli和sti等改變中斷允許的指令,稱為敏感指令。試想一下,如果用戶程序可以用sti禁止一切中斷(包括時(shí)鐘中斷),那么整個(gè)系統(tǒng)就無法正常運(yùn)行,所以這些指令的運(yùn)行要受到限制。特權(quán)指令只能在優(yōu)先級(jí)0上才能運(yùn)行,而敏感指令取決于eflags寄存器中的IOPL位。只有IOPL位表示的優(yōu)先級(jí)高于當(dāng)前代碼段的優(yōu)先級(jí)時(shí),指令才能執(zhí)行。
6)I/O操作的保護(hù)——I/O地址也是受保護(hù)的對象。因?yàn)橥ㄟ^I/O操作可以繞過系統(tǒng)對很多硬件進(jìn)行控制。80386可以單獨(dú)為I/O空間提供保護(hù),每個(gè)任務(wù)有個(gè)TSS(任務(wù)狀態(tài)段)來記錄任務(wù)切換的信息。TSS中有個(gè)I/O允許位圖,用來表示對應(yīng)的I/O端口是否可以操作。某個(gè)I/O地址在位圖中的對應(yīng)數(shù)據(jù)位為0則表示可以操作;如果為1則還要看eflags中的IPOL位,這時(shí)只有IOPL位表示的優(yōu)先級(jí)高于等于當(dāng)前代碼段的優(yōu)先級(jí),才允許訪問該I/O端口。
Windows的保護(hù)機(jī)制
在Windows下,操作系統(tǒng)運(yùn)行于0行,應(yīng)用程序運(yùn)行于3級(jí)。因?yàn)?/span>Alpha計(jì)算機(jī)只支持兩個(gè)優(yōu)先級(jí),為了便于將應(yīng)用程序移植到Alpha計(jì)算機(jī)上,Windows操作系統(tǒng)不使用1和2級(jí)這兩個(gè)優(yōu)先級(jí)。
Windows操作系統(tǒng)充分利用80386的保護(hù)機(jī)制,所有和操作系統(tǒng)密切相關(guān)的東西都是受保護(hù)的。運(yùn)行于優(yōu)先級(jí)3上的用戶程序有很多限制,只有在寫VxD等驅(qū)動(dòng)程序的時(shí)候才可以使用全部資源。在Win32匯編編程中要注意避免以下的越權(quán)操作(當(dāng)然寫驅(qū)動(dòng)程序不在此列):
1)顯而易見,所有的特權(quán)指令都是不可執(zhí)行的,如lgdt,lldt,lidt指令和對CRx與TRx等寄存器賦值。但是,讀取重要寄存器的指令是可以執(zhí)行的,如sgdt,sldt和sidt等。
2)Windows在頁表中把代碼段和數(shù)據(jù)段中的內(nèi)存賦予不同的屬性。代碼段是不可寫的,數(shù)據(jù)段中也只有變量部分的頁面是可寫的。所以雖然可以尋址所有的4GB空間,但訪問越出權(quán)限規(guī)定以外的東西還是會(huì)引發(fā)保護(hù)異常。
3)在Windows 98中,系統(tǒng)硬件用的I/O端口是受保護(hù)的,但其余的則可以操作。如果用戶在機(jī)器中插入一塊自己的卡,用的是300h系統(tǒng)未定義的端口,那么在應(yīng)用程序中就可以直接操作,但要操作3f8h(串口)和1f0h(硬盤端口)等系統(tǒng)已定義的端口就不行了。在Windows NT中,任何的端口操作都是不允許的。
如果違反了Windows規(guī)定的“保護(hù)條例”,那么會(huì)引發(fā)保護(hù)異常,處理器會(huì)毫不猶豫地把控制權(quán)轉(zhuǎn)移到對應(yīng)的異常處理程序中去。Windows會(huì)在處理程序中用一個(gè)很酷的“非法操作”對話框把用戶的程序判死刑,沒有一點(diǎn)回旋的余地!在Windows 9x中,系統(tǒng)有時(shí)會(huì)用一個(gè)藍(lán)屏幕來通知用戶程序試圖訪問不存在的內(nèi)存頁。
如果程序調(diào)用的DLL中有錯(cuò),那么錯(cuò)誤還是會(huì)算在應(yīng)用程序頭上,因?yàn)?/span>DLL的地址空間是被映射到應(yīng)用程序的空間中去的。Windows 9x本身是32位和16位混合的操作系統(tǒng),為了兼容DOS和Win16程序,很多的保護(hù)措施做起來力不從心。所以系統(tǒng)內(nèi)部反而常常出現(xiàn)越權(quán)操作,以至于藍(lán)屏幕不斷,這些就不是用戶應(yīng)用程序自己的問題了。