這節講述IA32分段機制中的特權級。包括CPL、DPL、RPL的介紹以及代碼實現不同特權級之間的轉換。
IA32的分段機制有四種特權級別,從高到低分別是0、1、2、3。數字越小表示的特權級越大。
處理器引入特權級的目的是為了保護核心代碼和數據。核心的代碼和數據會被放在較高的層級中。從而避免低特權級(外層)的任務在不被允許的情況下訪問位於高特權級(內層)的段。
在開始之前,首先介紹一下一致代碼段的概念。
關於一致代碼段中一致的理解:程序經常會通過call和jmp實現直接轉移操作。當轉移的目標是一個特權級更高的一致代碼段,當前的特權級會被延續下去,而向特權級更高的非一致代碼段轉移將會引起常規保護錯誤(general-protection exception, #GP)。你看完下面CPL的介紹會對這句話有更深刻的理解。
當然是有辦法訪問特權級更高的非一致代碼段的:使用調用門或者任務門。
如何去劃分一致代碼段與非一致代碼段呢?從上面的介紹可以知道,一致代碼段的保護較弱,能夠被低特權級的代碼通過call和jmp訪問到。所以,如果系統代碼不訪問受保護的資源和某些類型的異常處理(比如,除法錯誤或溢出錯誤),那麼此系統代碼可被放在一致代碼段中。對於那些為了避免被低特權級的程序訪問而保護起來的系統代碼應該放到非一致代碼段中。
另外,如果目標代碼的特權級低,無論它是不是一致代碼段,都不能通過call或者jmp轉移進去,嘗試這樣的轉移將會導致常規保護性錯誤。
所有的數據段都是非一致的,這意味著不可能被低特權級的代碼訪問到。然而,與代碼段不同的是,數據段可以被更高特權級的代碼訪問到,而不需要使用特定的門。
綜上,通過call和jmp的轉移遵從下表的規則:
特權級`低->高` 特權級`高->低` 相同特權級之間 適用於何種代碼 一致代碼段 Yes No Yes 不訪問受保護的資源和某些
舉個例子,假設有代碼段A和B,數據段D。那麼,
當A向跳轉到B,有下面兩種情況:
當B是一致代碼段時,A的特權級低於或等於B的時候才有可能通過call和jmp從A轉移到B。
當B是非一致代碼段時,A的特權級必須等於B才有可能通過call和jmp從A轉移到B。
如果A想要訪問數據段D,那麼A的特權級必須高於或等於D。
上面講的所有東西都在這個例子中。從這兒可以看出,引入了特權級指令之後,call和jmp指令並不能滿足所有的轉移情況。比如想轉移到高特權級的非一致代碼段,call和jmp就無法實現了,但下面將要講的調用門能夠實現。
不過在講調用門之前,首先需要了解CPL、DPL、RPL。
1 CPL(Current Privilege Level)
CPL代表的是當前執行的程序或任務的特權級。它被存儲在cs和ss的第0位和第1位上。
通常情況下,CPL等於代碼所在的段的特權級。當程序轉移到不同的特權級的代碼段時,處理器將改變CPL。
上面說到是通常情況,那麼就有一個特例:轉移的目標是一致代碼段。因為一致代碼段可以被相同或者更低特權級的代碼訪問。所以當處理器訪問一個於CPL特權級不同的一致代碼段時,CPL不會被改變。
2 DPL(Descriptor Privilege Level)
DPL表示段或者門的特權級。它被存儲在段描述符或者門描述符的DPL字段中。正如我們先前所看到的那樣。當當前代碼段試圖訪問一個段或者門時,DPL將會和CPL以及段或門選擇子的RPL相比較,根據段或者門類型的不同,DPL將會被區別對待,下面介紹一下各種類型的段或者門的情況。
3 RPL(Requested Privilege Level)
RPL是選擇子的特權級,它是通過選擇子的第0位和第1位表現出來的。
處理器通過檢查RPL和CPL來確認一個訪問請求是否合法。即便提出訪問請求的段有足夠的特權級,如果RPL不夠也是不行的。也就是說,如果RPL的數字比CPL大(數字越大特權級越低),那麼RPL將會起決定性作用,反之亦然。
操作系統過程往往用RPL來避免低特權級應用程序訪問高特權級段內的數據。當操作系統過程(被調用過程)從一個應用程序(調用過程)接收到一個選擇子時,將會把選擇子的RPL設成調用者的特權級。於是,當操作系統用這個選擇子去訪問相應的段時,處理器將會用調用過程的特權級(已經被存到RPL中),而不是更高的操作系統過程的特權級(CPL)進行特權檢驗。這樣,RPL就保證了操作系統不會越俎代庖地代表一個程序去訪問一個段,除非這個程序本身是有權限的。什麼意思
上面的內容完全出自書本上
介紹完了CPL、DPL、RPL。接下來看看不同特權級代碼之間的轉移。
轉移過程:程序從一個代碼段轉移到另一個代碼段之前,目標代碼段的選擇子會被加載到cs中。在加載之前,處理器會檢驗描述符的界限、類型、特權級等內容。如果檢驗成功,cs將會被加載,程序控制將轉到新的代碼段中,從eip指示的位置開始執行。
程序控制轉移的發生引起原因如下:
指令引起,包括jmp、call、ret、sysenter、sysexit、int n、iret等指令。
中斷和異常引起。
使用jmp和call能夠實現的四種轉移如下:
這四種轉移可以看作兩大類,一類是通過jmp和call的直接轉移(上述第1種),另一類是通過某個描述符的間接轉移(上述第2、3、4種)。下面就來分別看一下。
上面介紹了很多通過jmp和call的直接轉移。這裡總結一下。
目標是非一致代碼段的轉移條件:CPL==DPL且RPL<=DPL
目標是一致代碼段的轉移條件:CPL>=DPL、RPL此時不做檢查
上面已經說過jmp和call進行直接轉移的限制條件太多。如果向自由地進行不同特權級之間的轉移,需要通過門描述符或者TSS。
選擇子:目標代碼的選擇子,用來初始化cs。指明轉移處的目標代碼段。
偏移地址:是用來初始化eip,指明轉移到目標代碼段的某個偏移處執行。
屬性:
BYTE5:與其他描述符完全相同,此時S位為0(代表門描述符)。
BYTE4:轉移過程需要從調用者堆棧中將參數復制到被調用者堆棧(新堆棧)中,Param Count指明復制參數的數目。Param Count為0將不會復制參數。
從上面門描述符的結構可以看出,一個門描述了由一個選擇子和一個偏移所指定的線性地址(關於虛擬地址,線性地址,物理地址的概念會在後面討論)。程序就是通過這個地址進行轉移的。
門描述符有四種:
下面用代碼實現調用門的使用。在下面這個例子中,先不涉及任何特權級的變換,只是實現通過調用門轉移代碼。
相對於上節的代碼,增加如下部分:
通過調用門轉移的目標段
265 [SECTION .sdest]; 調用門目標段 266 [BITS 32] 267 268 LABEL_SEG_CODE_DEST: 269 ;jmp $ 270 mov ax, SelectorVideo 271 mov gs, ax ; 視頻段選擇子(目的) 272 273 mov edi, (80 * 12 + 0) * 2 ; 屏幕第 12 行, 第 0 列。 274 mov ah, 0Ch ; 0000: 黑底 1100: 紅字 275 mov al, 'C' 276 mov [gs:edi], ax 277 278 retf 279 280 SegCodeDestLen equ $ - LABEL_SEG_CODE_DEST 281 ; END of [SECTION .sdest]
從這裡看目標段也比較簡單。在屏幕第12行第0列打印一個黑底紅色的字符C。
因為這裡打算用call指令調用將要建立的調用門,所以,在這段代碼的結尾處調用了一個retf指令。retf表示段間返回。
上述代碼段的描述符、選擇子、初始化描述符的代碼
18 LABEL_DESC_CODE_DEST: Descriptor 0,SegCodeDestLen-1, DA_C+DA_32; 非一致代碼段,32 ... 36 SelectorCodeDest equ LABEL_DESC_CODE_DEST - LABEL_GDT ... 103 ; 初始化測試調用門的代碼段描述符 104 xor eax, eax 105 mov ax, cs 106 shl eax, 4 107 add eax, LABEL_SEG_CODE_DEST 108 mov word [LABEL_DESC_CODE_DEST + 2], ax 109 shr eax, 16 110 mov byte [LABEL_DESC_CODE_DEST + 4], al 111 mov byte [LABEL_DESC_CODE_DEST + 7], ah
調用門
24 ; 門 目標選擇子,偏移,DCount, 屬性 25 LABEL_CALL_GATE_TEST: Gate SelectorCodeDest, 0, 0, DA_386CGate+DA_DPL0 26 ; GDT 結束
上面可以看到,門描述符的屬性是DA_386CGate+DA_DPL0。DA_386CGate表明他是一個調用門;DPL0指定門描述符的DPL為0。上面指定的選擇子是SelectorCodeDest,表明目標代碼是剛剛新添加的代碼段。偏移地址是0,表明將要跳轉到目標代碼段的開頭處執行。DCount代表的是Param Count,這裡表明轉移時不復制參數到被調用者的堆棧。
這裡用一個宏Gate來初始化描述符,Gate的定義在pm.inc中,如下:
264 ; 門 265 ; usage: Gate Selector, Offset, DCount, Attr 266 ; Selector: dw 267 ; Offset: dd 268 ; DCount: db 269 ; Attr: db 270 %macro Gate 4 271 dw (%2 & 0FFFFh) ; 偏移1 272 dw %1 ; 選擇子 273 dw (%3 & 1Fh) | ((%4 << 8) & 0FF00h) ; 屬性 274 dw ((%2 >> 16) & 0FFFFh) ; 偏移2 275 %endmacro ; 共 8 字節
在認識保護模式那一節我還不確定%1、%2、%3…這些符號是什麼意思。當時的猜測是傳遞進去的參數,按照位置分別是1,2,3。與shell中的位置變量類似。最近仔細看了上面的定義。現在可以肯定我的猜測是對的了。如果下次再有這種疑問,可以先猜測,也許在接下來的某天重新看會豁然開朗。
調用門對應的選擇子
42 SelectorCallGateTest equ LABEL_CALL_GATE_TEST - LABEL_GDT
好了,現在調用門准備就緒,它指向的位置是SelectorCodeDest:0,即標號LABEL_SEG_DESC_DEST處的代碼。
下面,使用call指令來使用調用門。
使用調用門
233 ; 測試調用門(無特權級變換),將打印字母 'C' 234 call SelectorCallGateTest:0 ... 241 jmp SelectorLDTCodeA:0 ; 跳入局部任務,將打印字母 'L'
call指令放在jmp之前。因為目標代碼以retf結尾,所以call調用結束之後會返回到call下面的那條代碼繼續執行。因此此段代碼最終的結果是在上一節的基礎上多了一個字母C。
其實調用門這種聽起來很可怕的東西本質上只不過是個入口地址,只是增加了若干的屬性而已。在我們的例子中所用到的調用門完全等同於一個地址,我們甚至可以把使用調用門進行跳轉的指令修改為跳轉到調用門內指定的地址的指令:
call SelectorCodeDest:0
運行一下,效果是完全相同的。
看起來引入調用門有一點多此一舉,但事實上並不是。下面將用他來實現不同特權級的代碼之間的轉移。不過首先你需要知道使用調用門進行轉移時的特權級檢驗規則。
call jmp 目標是一致代碼段 CPL <= DPL_G
從上表我們能夠看出,通過調用門和call指令,可以實現從低特權級到高特權級的轉移,無論目標代碼段是一致的還是非一致的。
說到這裡,你一定又躍躍欲試了,寫一個程序實現一個特權級變換應該是件有趣的事情。可是你可能突然發現,調用門只能實現特權級由低到高的轉移,而我們的程序一直是在最高的特權級下的。也就是說,我們需要先到相對低一點的特權級下,才可能有機會對調用門親自實踐一番。那麼,如何才能到低一點的特權級下呢?先不要慌,調用門的故事還沒有講完。
有特權級變換的轉移的復雜之處,不但在於嚴格的特權級檢驗,還在於特權級變化的時候,堆棧也要發生變化。處理器的這種機制避免了高特權級的過程由於棧空間不足而崩潰。而且,如果不同特權級共享同一個堆棧的話,高特權級的程序可能因此受到有意或無意的干擾。
首先回憶一下8086匯編語言的長跳轉和短跳轉。
長跳轉相當於:
push CS push IP jmp far ptr 標號
短跳轉相當於:
push IP jmp near ptr 標號
call的返回過程彈出IP(或IP與CS)
從上面可以看出,call指令是影響堆棧的。
我們的調用門轉移是通過長調用(長跳轉)call指令來實現的。上面已經知道,call指令會壓棧與出棧。但是在第7小結說過,特權級變化的時候,堆棧也要變化。因此call指令執行前後的堆棧已經不是同一個了。這樣一來問題出現了,我們在堆棧A中壓入參數和返回地址,等到需要使用他們的時候堆棧已經變成B了,如何解決這個問題呢?
Intel提供了這樣一種機制:將堆棧A的諸多內容復制到堆棧B中。
由於每一個任務最多都可能在4個特權級間轉移,所以,每個任務實際上需要4個堆棧。可是,我們只有一個ss和一個esp,那麼當發生堆棧切換,我們該從哪裡獲得其余堆棧的ss和esp呢?實際上,這裡涉及一樣新事物TSS(Task-State Stack),它是一個數據結構,裡面包含多個字段,32位TSS如下圖所示:
解釋一下TSS的4-27字段的使用方法:比如,我們當前所在的是ring3,當轉移至ring1時,堆棧將被自動切換到由ss1和esp1指定的位置。由於只是在由外層到內層(低特權級到高特權級)切換時新堆棧才會從TSS中取得,所以TSS中沒有位於最外層的ring3的堆棧信息。
到這裡堆棧會變化的問題也解決了,接下來讓我們看一下整個的轉移過程是怎樣的。下面就是CPU的整個過程所做的工作:
上面就是CPU在整個過程中所做的工作。調用過程結束後會通過ret(retf)返回,那麼返回過程中CPU做了哪些工作呢?看下一小節:
通過以上兩小節可以看出,使用調用門的過程分為兩個部分:
一部分是從低特權級到高特權級,通過調用門和call指令來實現
另一部分是從高特權級到低特權級,通過ret指令來實現。
接下來我們就用ret指令實現由高特權級到低特權級的轉移:
通過上面的分析我們知道,在ret指令執行前,堆棧中應該已經准備好了目標代碼的cs、eip、ss、esp,另外還可能有參數。這些可以是處理器壓入棧的,當然,也可以由我們自己壓棧。在接下來的例子中,ret前的堆棧如下圖所示:
這樣,ret執行之後就可以轉移到低特權級代碼中了。接下來用代碼實現如下:
在原來的代碼上添加如下內容:
ring3的代碼段、ring3的堆棧段
19 LABEL_DESC_CODE_RING3: Descriptor 0,SegCodeRing3Len-1, DA_C+DA_32+DA_DPL3 ... 22 LABEL_DESC_STACK3: Descriptor 0, TopOfStack3, DA_DRWA+DA_32+DA_DPL3 ... 25 LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW+DA_DPL3 ... 40 SelectorCodeRing3 equ LABEL_DESC_CODE_RING3 - LABEL_GDT + SA_RPL3 ... 43 SelectorStack3 equ LABEL_DESC_STACK3 - LABEL_GDT + SA_RPL3 ... 75 ; 堆棧段ring3 76 [SECTION .s3] 77 ALIGN 32 78 [BITS 32] 79 LABEL_STACK3: 80 times 512 db 0 81 TopOfStack3 equ $ - LABEL_STACK3 - 1 82 ; END of [SECTION .s3] ... 379 ; CodeRing3 380 [SECTION .ring3] 381 ALIGN 32 382 [BITS 32] 383 LABEL_CODE_RING3: 384 mov ax, SelectorVideo 385 mov gs, ax 386 387 mov edi, (80 * 14 + 0) * 2 388 mov ah, 0Ch 389 mov al, '3' 390 mov [gs:edi], ax 391 392 jmp $ 393 SegCodeRing3Len equ $ - LABEL_CODE_RING3 394 ; END of [SECTION .ring3]
ring3堆棧段與ring3代碼段的描述符的初始化代碼
146 ; 初始化堆棧段描述符(Ring3) 147 xor eax, eax 148 mov ax, ds 149 shl eax, 4 150 add eax, LABEL_STACK3 151 mov word [LABEL_DESC_STACK3 + 2], ax 152 shr eax, 16 153 mov byte [LABEL_DESC_STACK3 + 4], al 154 mov byte [LABEL_DESC_STACK3 + 7], ah ... 176 ; 初始化Ring3描述符 177 xor eax, eax 178 mov ax, ds 179 shl eax, 4 180 add eax, LABEL_CODE_RING3 181 mov word [LABEL_DESC_CODE_RING3 + 2], ax 182 shr eax, 16 183 mov byte [LABEL_DESC_CODE_RING3 + 4], al 184 mov byte [LABEL_DESC_CODE_RING3 + 7], ah
由於這段代碼運行在ring3,而在其中由於要寫顯存而訪問到了VIDEO段,為了不會產生錯誤,我們把VIDEO段的DPL修改為3(第25行)。依據上面所說的規則,RPL不需要修改。
上面代碼段和數據段都已經初始化好了。接下來將ss、esp、cs、eip依次壓棧,並且執行retf指令。
266 push SelectorStack3 267 push TopOfStack3 268 push SelectorCodeRing3 269 push 0 270 retf
查看結果,如果出現了紅色的3並且不返回到DOS(因為新添加的代碼段最後是jmp $),說明我們已經成功進入ring3。
上面就是從ring0到ring3的過程。接下來開始使用調用門實現ring3到ring0的轉移
上面已經進入ring3了,接下來通過調用門重新進入ring0。將上面ring3的代碼修改如下:
28 LABEL_CALL_GATE_TEST: Gate SelectorCodeDest, 0, 0, DA_386CGate+DA_DPL3 ... 47 SelectorCallGateTest equ LABEL_CALL_GATE_TEST - LABEL_GDT + SA_RPL3 ... 379 ; CodeRing3 380 [SECTION .ring3] 381 ALIGN 32 382 [BITS 32] 383 LABEL_CODE_RING3: 384 mov ax, SelectorVideo 385 mov gs, ax 386 mov edi, (80 * 14 + 0) * 2 387 mov ah, 0Ch 388 mov al, '3' 389 mov [gs:edi], ax 390 391 call SelectorCallGateTest:0 392 393 jmp $ 394 SegCodeRing3Len equ $ - LABEL_CODE_RING3 395 ; END of [SECTION .ring3]
在jmp $之前,增加了使用調用門的指令,這個調用門是之前已經定義好了的。修改描述符和選擇子是為了滿足CPL和RPL都小於等於調用門DPL的條件。
不要忘記,從低特權級到高特權級轉移的時候,需要用到TSS。因此接下來需要人工准備一個TSS:
24 LABEL_DESC_TSS: Descriptor 0, TSSLen-1, DA_386TSS ... 45 SelectorTSS equ LABEL_DESC_TSS - LABEL_GDT ... 85 ; TSS 86 [SECTION .tss] 87 ALIGN 32 88 [BITS 32] 89 LABEL_TSS: 90 DD 0 ; Back 91 DD TopOfStack ; 0 級堆棧 92 DD SelectorStack ; 93 DD 0 ; 1 級堆棧 94 DD 0 ; 95 DD 0 ; 2 級堆棧 96 DD 0 ; 97 DD 0 ; CR3 98 DD 0 ; EIP 99 DD 0 ; EFLAGS 100 DD 0 ; EAX 101 DD 0 ; ECX 102 DD 0 ; EDX 103 DD 0 ; EBX 104 DD 0 ; ESP 105 DD 0 ; EBP 106 DD 0 ; ESI 107 DD 0 ; EDI 108 DD 0 ; ES 109 DD 0 ; CS 110 DD 0 ; SS 111 DD 0 ; DS 112 DD 0 ; FS 113 DD 0 ; GS 114 DD 0 ; LDT 115 DW 0 ; 調試陷阱標志 116 DW $ - LABEL_TSS + 2 ; I/O位圖基址 117 DB 0ffh ; I/O位圖結束標志 118 TSSLen equ $ - LABEL_TSS
因為這裡只進入ring0,所以在這裡先只初始化0級堆棧。
接下來初始化TSS描述符:
223 ; 初始化 TSS 描述符 224 xor eax, eax 225 mov ax, ds 226 shl eax, 4 227 add eax, LABEL_TSS 228 mov word [LABEL_DESC_TSS + 2], ax 229 shr eax, 16 230 mov byte [LABEL_DESC_TSS + 4], al 231 mov byte [LABEL_DESC_TSS + 7], ah
最後需要在特權級變換之前加載TSS:
313 mov ax, SelectorTSS 314 ltr ax
接下來開始運行,運行結果如下:
初始的32位代碼段(ring0)打印一串字符串,ring3代碼段打印數字3,調用門的目標代碼段(ring0)打印字母C.因此到這裡,我們實現了從rong0到ring3,然後再返回ring0的整個過程。接下來做最後一步,就是使程序順利返回實模式,只需要將調用局部任務的代碼加入到調用門的目標代碼([SECTION .sdest])。最後,程序將由這裡進入局部任務,然後由原路返回實模式。代碼如下:
346 [SECTION .sdest]; 調用門目標段 347 [BITS 32] 348 349 LABEL_SEG_CODE_DEST: 350 mov ax, SelectorVideo 351 mov gs, ax ; 視頻段選擇子(目的) 352 353 mov edi, (80 * 12 + 0) * 2 ; 屏幕第 12 行, 第 0 列。 354 mov ah, 0Ch ; 0000: 黑底 1100: 紅字 355 mov al, 'C' 356 mov [gs:edi], ax 357 358 ; Load LDT 359 mov ax, SelectorLDT 360 lldt ax 361 362 jmp SelectorLDTCodeA:0 ; 跳入局部任務,將打印字母 'L'。 363 364 ;retf 365 366 SegCodeDestLen equ $ - LABEL_SEG_CODE_DEST 367 ; END of [SECTION .sdest]
從上面的運行結果我們能夠知道。到這裡就實現了DOS->ring0->ring3->ring0->DOS的整個過程。
; ========================================== ; pmtest5.asm ; 編譯方法:nasm pmtest5.asm -o pmtest5.com ; ========================================== %include "pm.inc" ; 常量, 宏, 以及一些說明 org 0100h jmp LABEL_BEGIN [SECTION .gdt] ; GDT ; 段基址, 段界限 , 屬性 LABEL_GDT: Descriptor 0, 0, 0 ;空描述符 LABEL_DESC_NORMAL: Descriptor 0, 0ffffh, DA_DRW ;Normal描述符 LABEL_DESC_CODE32: Descriptor 0, SegCode32Len-1, DA_C+DA_32 ;非一致,32 LABEL_DESC_CODE16: Descriptor 0, 0ffffh, DA_C ;非一致,16 LABEL_DESC_CODE_DEST: Descriptor 0, SegCodeDestLen-1, DA_C+DA_32 ;非一致,32 LABEL_DESC_CODE_RING3: Descriptor 0, SegCodeRing3Len-1, DA_C+DA_32+DA_DPL3 LABEL_DESC_DATA: Descriptor 0, DataLen-1, DA_DRW ;Data LABEL_DESC_STACK: Descriptor 0, TopOfStack, DA_DRWA+DA_32 ;Stack,32 LABEL_DESC_STACK3: Descriptor 0, TopOfStack3, DA_DRWA+DA_32+DA_DPL3 LABEL_DESC_LDT: Descriptor 0, LDTLen-1, DA_LDT ;LDT LABEL_DESC_TSS: Descriptor 0, TSSLen-1, DA_386TSS ;TSS LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW+DA_DPL3 ; 門 目標選擇子, 偏移, DCount, 屬性 LABEL_CALL_GATE_TEST: Gate SelectorCodeDest, 0, 0, DA_386CGate + DA_DPL3 ; GDT 結束 GdtLen equ $ - LABEL_GDT ; GDT長度 GdtPtr dw GdtLen - 1 ; GDT界限 dd 0 ; GDT基地址 ; GDT 選擇子 SelectorNormal equ LABEL_DESC_NORMAL - LABEL_GDT SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT SelectorCode16 equ LABEL_DESC_CODE16 - LABEL_GDT SelectorCodeDest equ LABEL_DESC_CODE_DEST - LABEL_GDT SelectorCodeRing3 equ LABEL_DESC_CODE_RING3 - LABEL_GDT + SA_RPL3 SelectorData equ LABEL_DESC_DATA - LABEL_GDT SelectorStack equ LABEL_DESC_STACK - LABEL_GDT SelectorStack3 equ LABEL_DESC_STACK3 - LABEL_GDT + SA_RPL3 SelectorLDT equ LABEL_DESC_LDT - LABEL_GDT SelectorTSS equ LABEL_DESC_TSS - LABEL_GDT SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT SelectorCallGateTest equ LABEL_CALL_GATE_TEST - LABEL_GDT + SA_RPL3 ; END of [SECTION .gdt] [SECTION .data1] ; 數據段 ALIGN 32 [BITS 32] LABEL_DATA: SPValueInRealMode dw 0 ; 字符串 PMMessage: db "In Protect Mode now. ^-^", 0 ; 進入保護模式後顯示此字符串 OffsetPMMessage equ PMMessage - $$ StrTest: db "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0 OffsetStrTest equ StrTest - $$ DataLen equ $ - LABEL_DATA ; END of [SECTION .data1] ; 全局堆棧段 [SECTION .gs] ALIGN 32 [BITS 32] LABEL_STACK: times 512 db 0 TopOfStack equ $ - LABEL_STACK - 1 ; END of [SECTION .gs] ; 堆棧段ring3 [SECTION .s3] ALIGN 32 [BITS 32] LABEL_STACK3: times 512 db 0 TopOfStack3 equ $ - LABEL_STACK3 - 1 ; END of [SECTION .s3] ; TSS --------------------------------------------------------------------------------------------- [SECTION .tss] ALIGN 32 [BITS 32] LABEL_TSS: DD 0 ; Back DD TopOfStack ; 0 級堆棧 DD SelectorStack ; DD 0 ; 1 級堆棧 DD 0 ; DD 0 ; 2 級堆棧 DD 0 ; DD 0 ; CR3 DD 0 ; EIP DD 0 ; EFLAGS DD 0 ; EAX DD 0 ; ECX DD 0 ; EDX DD 0 ; EBX DD 0 ; ESP DD 0 ; EBP DD 0 ; ESI DD 0 ; EDI DD 0 ; ES DD 0 ; CS DD 0 ; SS DD 0 ; DS DD 0 ; FS DD 0 ; GS DD 0 ; LDT DW 0 ; 調試陷阱標志 DW $ - LABEL_TSS + 2 ; I/O位圖基址 DB 0ffh ; I/O位圖結束標志 TSSLen equ $ - LABEL_TSS ; TSS ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [SECTION .s16] [BITS 16] LABEL_BEGIN: mov ax, cs mov ds, ax mov es, ax mov ss, ax mov sp, 0100h mov [LABEL_GO_BACK_TO_REAL+3], ax mov [SPValueInRealMode], sp ; 初始化 16 位代碼段描述符 mov ax, cs movzx eax, ax shl eax, 4 add eax, LABEL_SEG_CODE16 mov word [LABEL_DESC_CODE16 + 2], ax shr eax, 16 mov byte [LABEL_DESC_CODE16 + 4], al mov byte [LABEL_DESC_CODE16 + 7], ah ; 初始化 32 位代碼段描述符 xor eax, eax mov ax, cs shl eax, 4 add eax, LABEL_SEG_CODE32 mov word [LABEL_DESC_CODE32 + 2], ax shr eax, 16 mov byte [LABEL_DESC_CODE32 + 4], al mov byte [LABEL_DESC_CODE32 + 7], ah ; 初始化測試調用門的代碼段描述符 xor eax, eax mov ax, cs shl eax, 4 add eax, LABEL_SEG_CODE_DEST mov word [LABEL_DESC_CODE_DEST + 2], ax shr eax, 16 mov byte [LABEL_DESC_CODE_DEST + 4], al mov byte [LABEL_DESC_CODE_DEST + 7], ah ; 初始化數據段描述符 xor eax, eax mov ax, ds shl eax, 4 add eax, LABEL_DATA mov word [LABEL_DESC_DATA + 2], ax shr eax, 16 mov byte [LABEL_DESC_DATA + 4], al mov byte [LABEL_DESC_DATA + 7], ah ; 初始化堆棧段描述符 xor eax, eax mov ax, ds shl eax, 4 add eax, LABEL_STACK mov word [LABEL_DESC_STACK + 2], ax shr eax, 16 mov byte [LABEL_DESC_STACK + 4], al mov byte [LABEL_DESC_STACK + 7], ah ; 初始化堆棧段描述符(ring3) xor eax, eax mov ax, ds shl eax, 4 add eax, LABEL_STACK3 mov word [LABEL_DESC_STACK3 + 2], ax shr eax, 16 mov byte [LABEL_DESC_STACK3 + 4], al mov byte [LABEL_DESC_STACK3 + 7], ah ; 初始化 LDT 在 GDT 中的描述符 xor eax, eax mov ax, ds shl eax, 4 add eax, LABEL_LDT mov word [LABEL_DESC_LDT + 2], ax shr eax, 16 mov byte [LABEL_DESC_LDT + 4], al mov byte [LABEL_DESC_LDT + 7], ah ; 初始化 LDT 中的描述符 xor eax, eax mov ax, ds shl eax, 4 add eax, LABEL_CODE_A mov word [LABEL_LDT_DESC_CODEA + 2], ax shr eax, 16 mov byte [LABEL_LDT_DESC_CODEA + 4], al mov byte [LABEL_LDT_DESC_CODEA + 7], ah ; 初始化Ring3描述符 xor eax, eax mov ax, ds shl eax, 4 add eax, LABEL_CODE_RING3 mov word [LABEL_DESC_CODE_RING3 + 2], ax shr eax, 16 mov byte [LABEL_DESC_CODE_RING3 + 4], al mov byte [LABEL_DESC_CODE_RING3 + 7], ah ; 初始化 TSS 描述符 xor eax, eax mov ax, ds shl eax, 4 add eax, LABEL_TSS mov word [LABEL_DESC_TSS + 2], ax shr eax, 16 mov byte [LABEL_DESC_TSS + 4], al mov byte [LABEL_DESC_TSS + 7], ah ; 為加載 GDTR 作准備 xor eax, eax mov ax, ds shl eax, 4 add eax, LABEL_GDT ; eax <- gdt 基地址 mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址 ; 加載 GDTR lgdt [GdtPtr] ; 關中斷 cli ; 打開地址線A20 in al, 92h or al, 00000010b out 92h, al ; 准備切換到保護模式 mov eax, cr0 or eax, 1 mov cr0, eax ; 真正進入保護模式 jmp dword SelectorCode32:0 ; 執行這一句會把 SelectorCode32 裝入 cs, 並跳轉到 Code32Selector:0 處 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; LABEL_REAL_ENTRY: ; 從保護模式跳回到實模式就到了這裡 mov ax, cs mov ds, ax mov es, ax mov ss, ax mov sp, [SPValueInRealMode] in al, 92h ; ┓ and al, 11111101b ; ┣ 關閉 A20 地址線 out 92h, al ; ┛ sti ; 開中斷 mov ax, 4c00h ; ┓ int 21h ; ┛回到 DOS ; END of [SECTION .s16] [SECTION .s32]; 32 位代碼段. 由實模式跳入. [BITS 32] LABEL_SEG_CODE32: mov ax, SelectorData mov ds, ax ; 數據段選擇子 mov ax, SelectorVideo mov gs, ax ; 視頻段選擇子 mov ax, SelectorStack mov ss, ax ; 堆棧段選擇子 mov esp, TopOfStack ; 下面顯示一個字符串 mov ah, 0Ch ; 0000: 黑底 1100: 紅字 xor esi, esi xor edi, edi mov esi, OffsetPMMessage ; 源數據偏移 mov edi, (80 * 10 + 0) * 2 ; 目的數據偏移。屏幕第 10 行, 第 0 列。 cld .1: lodsb test al, al jz .2 mov [gs:edi], ax add edi, 2 jmp .1 .2: ; 顯示完畢 call DispReturn ; Load TSS mov ax, SelectorTSS ltr ax ;在任務內發生特權級變換時要切換堆棧,而內層堆棧的指針存放在當前任務的TSS中,所以要設置任務狀態段寄存器TR。 push SelectorStack3 push TopOfStack3 push SelectorCodeRing3 push 0 retf ; Ring0 -> Ring3,歷史性轉移!將打印數字 '3'。 ; ------------------------------------------------------------------------ DispReturn: push eax push ebx mov eax, edi mov bl, 160 div bl and eax, 0FFh inc eax mov bl, 160 mul bl mov edi, eax pop ebx pop eax ret ; DispReturn 結束--------------------------------------------------------- SegCode32Len equ $ - LABEL_SEG_CODE32 ; END of [SECTION .s32] [SECTION .sdest]; 調用門目標段 [BITS 32] LABEL_SEG_CODE_DEST: mov ax, SelectorVideo mov gs, ax ; 視頻段選擇子(目的) mov edi, (80 * 12 + 0) * 2 ; 屏幕第 12 行, 第 0 列。 mov ah, 0Ch ; 0000: 黑底 1100: 紅字 mov al, 'C' mov [gs:edi], ax ; Load LDT mov ax, SelectorLDT lldt ax jmp SelectorLDTCodeA:0 ; 跳入局部任務,將打印字母 'L'。 ;retf SegCodeDestLen equ $ - LABEL_SEG_CODE_DEST ; END of [SECTION .sdest] ; 16 位代碼段. 由 32 位代碼段跳入, 跳出後到實模式 [SECTION .s16code] ALIGN 32 [BITS 16] LABEL_SEG_CODE16: ; 跳回實模式: mov ax, SelectorNormal mov ds, ax mov es, ax mov fs, ax mov gs, ax mov ss, ax mov eax, cr0 and al, 11111110b mov cr0, eax LABEL_GO_BACK_TO_REAL: jmp 0:LABEL_REAL_ENTRY ; 段地址會在程序開始處被設置成正確的值 Code16Len equ $ - LABEL_SEG_CODE16 ; END of [SECTION .s16code] ; LDT [SECTION .ldt] ALIGN 32 LABEL_LDT: ; 段基址 段界限 , 屬性 LABEL_LDT_DESC_CODEA: Descriptor 0, CodeALen - 1, DA_C + DA_32 ; Code, 32 位 LDTLen equ $ - LABEL_LDT ; LDT 選擇子 SelectorLDTCodeA equ LABEL_LDT_DESC_CODEA - LABEL_LDT + SA_TIL ; END of [SECTION .ldt] ; CodeA (LDT, 32 位代碼段) [SECTION .la] ALIGN 32 [BITS 32] LABEL_CODE_A: mov ax, SelectorVideo mov gs, ax ; 視頻段選擇子(目的) mov edi, (80 * 13 + 0) * 2 ; 屏幕第 13 行, 第 0 列。 mov ah, 0Ch ; 0000: 黑底 1100: 紅字 mov al, 'L' mov [gs:edi], ax ; 准備經由16位代碼段跳回實模式 jmp SelectorCode16:0 CodeALen equ $ - LABEL_CODE_A ; END of [SECTION .la] ; CodeRing3 [SECTION .ring3] ALIGN 32 [BITS 32] LABEL_CODE_RING3: mov ax, SelectorVideo mov gs, ax ; 視頻段選擇子(目的) mov edi, (80 * 14 + 0) * 2 ; 屏幕第 14 行, 第 0 列。 mov ah, 0Ch ; 0000: 黑底 1100: 紅字 mov al, '3' mov [gs:edi], ax call SelectorCallGateTest:0 ; 測試調用門(有特權級變換),將打印字母 'C'。 jmp $ SegCodeRing3Len equ $ - LABEL_CODE_RING3 ; END of [SECTION .ring3]