?? 001.txt
字號:
5. 從Win32匯編的角度看內存尋址
對初學者來說,DOS下的分段尋址方式就已經令人一頭霧水了,80386保護模式的內存管理就更麻煩。的確,如果在Win32匯編中訪問內存之前要先在描述符表中構造正確的描述符,然后再構造頁表把物理內存映射到要訪問的線性地址的話,那就簡直是一場噩夢,有90%的匯編程序員會因此改行去賣茶葉蛋!
但實際上這并沒有發生,因為Win32匯編中的內存訪問遠比DOS下的分段尋址方式簡單,這是為什么呢?
因為Windows是一個多任務的操作系統,最首要的宗旨就是“穩定壓倒一切”。但如果把描述符表以及頁表等內容交給用戶程序是很不安全的,不用說全局描述符表,就是為每個程序建立的局部描述符表也不應該讓用戶程序改寫,否則用戶可以通過構造自己的描述符來訪問操作系統不希望用戶訪問的東西。任何權限上開放引發的安全問題都是很嚴重的,如Windows 9x中的中斷描述符表是可寫的,CIH病毒可利用它將自己的權限提高到優先級0;而Windows NT下的中斷描述符表是不可寫的,CIH病毒在Windows NT下就無法進駐內存。
正因為如此,Windows操作系統干脆為用戶程序“安排好了一切”。具體表現在為用戶程序的代碼段、數據段和堆棧段全部預定義好了段描述符。這些段的起始地址為0,限長為ffffffff,所以用它們可以直接尋址全部的4 GB地址空間。程序開始執行的時候,CS,DS,ES和SS都已經指向了正確的描述符,在整個程序的生命周期內,程序員不必改動這些段寄存器,也不必關心它們的值究竟是多少(實際上,想改也改不了)。
所以對Win32匯編程序來說,整個源程序中竟然可以不用出現段寄存器的身影。這在DOS匯編編程中是不可想像的。回顧本節開頭提出的問題,答案是:并不是Win32匯編源代碼用不到段寄存器,而是用戶在使用中不必去關心段寄存器!
1.3.3 Windows的特權保護
Windows的特權保護和處理器硬件的支持是分不開的。優先級的劃分、指令的權限檢查和超出權限訪問的異常處理等是構成特權保護的基礎。這一節將簡單介紹這些課題,讀者可以考慮一下初學Win32匯編時遇到的疑問:
● Win32匯編中為什么找不到中斷指令的使用?
● Windows錯誤的“藍屏幕”是從哪里來的?
1. 80386的中斷和異常
中斷指當程序執行過程中有更重要的事情需要實時處理時(如串口中有數據到達,不及時處理數據會丟失,串行控制器就提交一個中斷信號給處理器要求處理),硬件通過中斷控制器通知處理器。處理器暫時掛起當前運行的程序,轉移到中斷處理程序中;當中斷處理程序處理完畢后,通過iret指令回到原先被打斷的程序中繼續執行。
異常指指令執行中發生不可忽略的錯誤時(如遇到無效的指令編碼,除法指令除零等),處理器用和中斷處理相同的操作方法掛起當前運行的程序轉移到異常處理程序中。異常處理程序決定在修正錯誤后是否回到原來的地方繼續執行。
更為DOS匯編程序員熟悉的“中斷”指的是用int n指令直接轉移到中斷向量n指定的中斷處理程序中執行。嚴格地講,int n指令應該算“自陷”而不是“中斷”。因為這時并不是程序被急需解決的事情打斷。而是自己要求停止執行并轉移到中斷處理程序中去。
不管中斷、異常還是自陷,雖然它們產生的原因不同,但處理過程是類似的,都通過中斷向量表里存放的入口地址轉移到服務程序,都由CPU自動在堆棧中保護斷點地址,最后也都可以用iret指令返回指令被中斷的地方。
先回顧一下8086或80386實模式下中斷和異常的處理過程。如圖1.7所示,實模式下的中斷和異常服務程序地址存放在中斷向量表中。中斷向量表位于物理內存00000h開始的400h字節中,共支持100h個中斷向量;每個中斷向量是一個xxxx:yyyy格式的地址,占用4字節。當發生n號異常或n號中斷,或者執行到int n指令的時候,CPU首先到內存n×4的地方取出服務程序的地址aaaa:bbbb(圖示步驟①);然后將標志寄存器、中斷時的CS和IP壓入堆棧,接著轉移到aaaa:bbbb處執行(步驟②);在服務程序最后遇到iret的時候,CPU從堆棧中恢復標志寄存器,然后取出CS和IP并返回。
圖1.7 實模式下中斷和異常的處理
在保護模式下,中斷或異常處理往往從用戶代碼切換到操作系統代碼中執行。由于保護模式下的代碼有優先級之分,因此出現了從優先級低的應用程序轉移到優先級高的系統代碼中的問題,如果優先級低的代碼能夠任意調用優先級高的代碼,就相當于擁有了高優先級代碼的權限。為了使高優先級的代碼能夠安全地被低優先級的代碼調用,保護模式下增加了“門”的概念。“門”指向某個優先級高的程序所規定的入口點,所有優先級低的程序調用優先級高的程序只能通過門重定向,進入門所規定的入口點。這樣可以避免低級別的程序代碼從任意位置進入優先級高的程序的問題。保護模式下的中斷和異常等服務程序也要從“門”進入,80386的門分為中斷門、自陷門和任務門幾種。
在保護模式下要表示一個中斷或異常服務程序的信息需要用8個字節,包括門的種類以及xxxx:yyyyyyyy格式的入口地址等。這組信息叫做“中斷描述符”。這樣,中斷向量表就無法采用和實模式下同樣的4字節一組的格式。保護模式下把所有的中斷描述符放在一起組成“中斷描述符表”IDT(Interrupt Descriptor Table)。IDT不再放在固定的地址00000h處,而是采用可編程設置的方式,支持的中斷數量也可以設置。為此80386處理器引入了一個新的48位寄存器IDTR。IDTR的高32位指定了IDT在內存中的基址(線性地址),低16位指定了IDT的長度,相當于指定了可以支持的中斷數量。
如圖1.8所示,保護模式下發生異常或中斷時,處理器先根據IDTR寄存器得到中斷描述符的地址,然后取出n號中斷/異常的門描述符,再從描述符中得到中斷服務程序的地址xxxx:yyyyyyyy,經過段地址轉換后得到服務程序的32位線性地址并轉移后執行。
圖1.8 保護模式下的中斷和異常處理
由于保護模式下用中斷門可以從低優先級的代碼調用高優先級的代碼,所以不能讓用戶程序寫中斷描述符表,否則會引發安全問題(又想到了CIH病毒)。這樣就如關了窗子擋住蒼蠅,也擋住了微風,用戶的系統擴展程序也就不能像在DOS中一樣再用中斷服務程序的方式提供服務了。因為用戶程序根本沒有權限將中斷地址指到自己的代碼中來。
在Windows中,操作系統使用動態鏈接庫來代替中斷服務程序提供系統功能,所以 Win32匯編中int指令也就失去了存在的意義。這就是在Win32匯編源代碼中看不到int指令的原因。其實那些調用API的指令原本是用int指令實現的。
2. 80386的保護機制
80286之前的處理器只支持單任務,操作系統并沒有什么安全性可言,計算機的全部資源包括操作系統的內部資源都可以任憑程序員調用。但對于多任務的操作系統,某個搗亂的程序為所欲為令使所有程序都無法運行。所以80286及以上的處理器引入了優先級的概念。80386處理器共設置4個優先級(0~3)。0級是最高級(特權級);3級是最低級(用戶級);1級和2級介于它們之間。特權級代碼一般是操作系統的代碼,可以訪問全部系統資源;其他級別的代碼一般是用戶程序,可以訪問的資源受到限制。
80386采用保護機制主要為了檢查和防止低級別代碼的越權操作,如訪問不該訪問的數據、端口以及調用高優先級的代碼等。保護機制主要由下列幾方面組成:
● 段的類型檢查——段的類型是由段描述符指定的,主要屬性有是否可執行,是否可讀和是否可寫等。而CS,DS和SS等段選擇器是否能裝入某種類型的段描述符是有限制的。如不可執行的段不能裝入CS;不可讀的段不能裝入DS與ES等數據段寄存器;不可寫的段不能裝入SS等。如果段類型檢查通不過,則處理器會產生一般性保護異常或堆棧異常。
● 頁的類型檢查——除了可以在段級別上指定整個段是否可讀寫外,在頁表中也可以為每個頁指定是否可寫。對于特權級下的執行代碼,所有的頁都是可寫的。但對于1,2和3級的代碼,還要根據頁表中的R/W項決定是否可寫,企圖對只讀的頁進行寫操作會產生頁異常。
● 訪問數據時的級別檢查——優先級低的代碼不能訪問優先級高的數據段。80386的段描述符中有一個DPL域(描述符優先級),表示這個段可以被訪問的最低優先級。而段選擇器中含有RPL域(請求優先級),表示當前執行代碼的優先級。只有DPL在數值上大于或等于RPL值的時候,該段才是可以訪問的,否則會產生一般性保護異常。
● 控制轉移的檢查——在處理器中,有很多指令可以實現控制轉移,如jmp,call,ret,int和iret等指令。但優先級低的代碼不能隨意轉移到優先級高的代碼中,所以遇到這些指令的時候,處理器要檢查轉移的目的位置是否合法。
● 指令集的檢查——有兩類指令可以影響保護機制。第一類是改變GDT,LDT,IDT以及控制寄存器等關鍵寄存器的指令,稱為特權指令;第二類是操作I/O端口的指令以及cli和sti等改變中斷允許的指令,稱為敏感指令。試想一下,如果用戶級程序可以用sti禁止一切中斷(包括時鐘中斷),那么整個系統就無法正常運行,所以這些指令的運行要受到限制。特權指令只能在優先級0上才能運行,而敏感指令取決于eflags寄存器中的IOPL位。只有IOPL位表示的優先級高于等于當前代碼段的優先級時,指令才能執行。
● I/O操作的保護——I/O地址也是受保護的對象。因為通過I/O操作可以繞過系統對很多硬件進行控制。80386可以單獨為I/O空間提供保護,每個任務有個TSS(任務狀態段)來記錄任務切換的信息。TSS中有個I/O允許位圖,用來表示對應的I/O端口是否可以操作。某個I/O地址在位圖中的對應數據位為0則表示可以操作;如果為1則還要看eflags中的IPOL位,這時只有IOPL位表示的優先級高于等于當前代碼段的優先級,才允許訪問該I/O端口。
3. Windows的保護機制
在Windows下,操作系統運行于0級,應用程序運行于3級。因為Alpha計算機只支持兩個優先級,為了便于將應用程序移植到Alpha計算機上,Windows操作系統不使用1和2級這兩個優先級。
Windows操作系統充分利用80386的保護機制,所有和操作系統密切相關的東西都是受保護的。運行于優先級3上的用戶程序有很多限制,只有在寫VxD等驅動程序的時候才可以使用全部資源。在Win32匯編編程中要注意避免以下的越權操作(當然寫驅動程序不在此列):
● 顯而易見,所有的特權指令都是不可執行的,如lgdt,lldt,lidt指令和對CRx與TRx等寄存器賦值。但是,讀取重要寄存器的指令是可以執行的,如sgdt,sldt和sidt等。
● Windows在頁表中把代碼段和數據段中的內存頁賦予不同的屬性。代碼段是不可寫的,數據段中也只有變量部分的頁面是可寫的。所以雖然可以尋址所有的4 GB空間,但訪問超出權限規定以外的東西還是會引發保護異常。
● 在Windows 98中,系統硬件用的I/O端口是受保護的,但其余的則可以操作。如果用戶在機器中插了一塊自己的卡,用的是300h等系統未定義的端口,那么在應用程序中就可以直接操作,但要操作3f8h(串口)和1f0h(硬盤端口)等系統已定義的端口就不行了。在Windows NT中,任何的端口操作都是不允許的。
如果違反了Windows規定的“保護條例”,那么會引發保護異常,處理器會毫不猶豫地把控制權轉移到對應的異常處理程序中去。Windows會在處理程序中用一個很酷的“非法操作”對話框把用戶的程序判死刑,沒有一點回旋的余地!在Windows 9x中,系統有時會用一個藍屏幕來通知用戶程序試圖訪問不存在的內存頁。
如果程序調用的DLL中有錯,那么錯誤還是會算在應用程序頭上,因為DLL的地址空間是被映射到應用程序的空間中去的。Windows 9x本身是32位和16位混合的操作系統,為了兼容DOS和Win16程序,很多的保護措施做起來力不從心。所以系統內部反而常常出現越權操作,以至于藍屏幕不斷,這些就不是用戶應用程序自己的問題了。
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -