歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> 一個操作系統的實現(5)-關於特權級

一個操作系統的實現(5)-關於特權級

日期:2017/3/1 11:49:23   编辑:關於Linux

這節講述IA32分段機制中的特權級。包括CPL、DPL、RPL的介紹以及代碼實現不同特權級之間的轉換。

IA32的分段機制有四種特權級別,從高到低分別是0、1、2、3。數字越小表示的特權級越大。

處理器引入特權級的目的是為了保護核心代碼和數據。核心的代碼和數據會被放在較高的層級中。從而避免低特權級(外層)的任務在不被允許的情況下訪問位於高特權級(內層)的段。

在開始之前,首先介紹一下一致代碼段的概念。

一致代碼段

關於一致代碼段中一致的理解:程序經常會通過call和jmp實現直接轉移操作。當轉移的目標是一個特權級更高的一致代碼段,當前的特權級會被延續下去,而向特權級更高的非一致代碼段轉移將會引起常規保護錯誤(general-protection exception, #GP)。你看完下面CPL的介紹會對這句話有更深刻的理解。

當然是有辦法訪問特權級更高的非一致代碼段的:使用調用門或者任務門。

如何去劃分一致代碼段與非一致代碼段呢?從上面的介紹可以知道,一致代碼段的保護較弱,能夠被低特權級的代碼通過call和jmp訪問到。所以,如果系統代碼不訪問受保護的資源和某些類型的異常處理(比如,除法錯誤或溢出錯誤),那麼此系統代碼可被放在一致代碼段中。對於那些為了避免被低特權級的程序訪問而保護起來的系統代碼應該放到非一致代碼段中。

另外,如果目標代碼的特權級低,無論它是不是一致代碼段,都不能通過call或者jmp轉移進去,嘗試這樣的轉移將會導致常規保護性錯誤。

所有的數據段都是非一致的,這意味著不可能被低特權級的代碼訪問到。然而,與代碼段不同的是,數據段可以被更高特權級的代碼訪問到,而不需要使用特定的門。

綜上,通過call和jmp的轉移遵從下表的規則:

引入特權級之後call和jmp能夠實現的直接轉移類型




	
	 
	特權級`低->高`
	特權級`高->低`
	相同特權級之間
	適用於何種代碼
	
	
	一致代碼段
	Yes
	No
	Yes
	不訪問受保護的資源和某些
類型的異常處理的系統代碼 非一致代碼段 No No Yes 避免低特權級的程序訪問而
被保護器來的系統代碼 數據段 No Yes 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。

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將會被區別對待,下面介紹一下各種類型的段或者門的情況。

  • 數據段 : DPL規定了可以訪問此段的最低特權級。比如,一個數據段的DPL是1,那麼只有運行在CPL為0或者1的程序才有權訪問它。
  • 非一致代碼段(不使用調用門的情況下) : DPL規定訪問此段的特權級。比如,一個非一致代碼段的特權級為0,那麼只有CPL為0的程序才可以訪問它。
  • 調用門 : DPL規定了當前執行的程序或任務可以訪問此調用門的最低特權級(這與數據段的規則是一致的)。
  • 一致代碼段和通過調用門訪問的非一致代碼段 : DPL規定了訪問此段的最高特權級。比如,一個一致代碼段的DPL是2,那麼CPL為0和1的程序將無法訪問此段。
  • TSS : DPL規定了可以訪問此TSS的最低特權級(這與數據段的規則是一致的)。

    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能夠實現的四種轉移如下:

    • 目標操作數包含目標代碼段的段選擇子。
    • 目標操作數指向一個包含目標代碼段選擇子的調用門描述符。
    • 目標操作數指向一個包含目標代碼段選擇子的TSS。
    • 目標操作數指向一個任務門,這個任務門指向一個包含目標代碼段選擇子的TSS。

      這四種轉移可以看作兩大類,一類是通過jmp和call的直接轉移(上述第1種),另一類是通過某個描述符的間接轉移(上述第2、3、4種)。下面就來分別看一下。

      通過jmp和call進行的直接轉移

      上面介紹了很多通過jmp和call的直接轉移。這裡總結一下。

      目標是非一致代碼段的轉移條件:CPL==DPL且RPL<=DPL

      目標是一致代碼段的轉移條件:CPL>=DPL、RPL此時不做檢查

      上面已經說過jmp和call進行直接轉移的限制條件太多。如果向自由地進行不同特權級之間的轉移,需要通過門描述符或者TSS。

      門描述符結構

      \

      選擇子:目標代碼的選擇子,用來初始化cs。指明轉移處的目標代碼段。

      偏移地址:是用來初始化eip,指明轉移到目標代碼段的某個偏移處執行。

      屬性:

      BYTE5:與其他描述符完全相同,此時S位為0(代表門描述符)。

      BYTE4:轉移過程需要從調用者堆棧中將參數復制到被調用者堆棧(新堆棧)中,Param Count指明復制參數的數目。Param Count為0將不會復制參數。

      從上面門描述符的結構可以看出,一個門描述了由一個選擇子和一個偏移所指定的線性地址(關於虛擬地址,線性地址,物理地址的概念會在後面討論)。程序就是通過這個地址進行轉移的。

      門描述符有四種:

      • 調用門(Call gates)
      • 中斷門(Interupt gates)
      • 陷阱門(Trap gates)
      • 任務門(Task gates)

        下面用代碼實現調用門的使用。在下面這個例子中,先不涉及任何特權級的變換,只是實現通過調用門轉移代碼。

        相同特權級下使用調用門

        相對於上節的代碼,增加如下部分:

        通過調用門轉移的目標段

        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
        RPL <= DPL_G
        DPL_B <= CPL 目標是非一致代碼段 CPL <= DPL_G
        RPL <= DPL_G
        DPL_B <= CPL CPL <= DPL_G
        RPL <= DPL_G
        DPL_B == CPL

        從上表我們能夠看出,通過調用門和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所做的工作

        1. 根據目標代碼段的DPL(新的CPL)從TSS中選擇應該切換至哪個ss和esp。
        2. 從TSS中讀取新的ss和esp。在這過程中如果發現ss、esp或者TSS界限錯誤都會導致無效TSS異常(#TS)。
        3. 對ss描述符進行檢驗,如果發生錯誤,同樣產生#TS 異常。
        4. 暫時性地保存當前ss和esp的值。
        5. 加載新的ss和esp。
        6. 將剛剛保存起來的ss和esp的值壓入新棧。
        7. 從調用者堆棧中將參數復制到被調用者堆棧(新堆棧)中,復制參數的數目由調用門中Param Count一項來決定。如果Param Count是零的話,將不會復制參數。
        8. 將當前的cs和eip壓棧。
        9. 加載調用門中指定的新的cs和eip,開始執行被調用者過程。

          上面就是CPU在整個過程中所做的工作。調用過程結束後會通過ret(retf)返回,那麼返回過程中CPU做了哪些工作呢?看下一小節:

          調用門轉移結束後通過ret返回時CPU所做的工作

          1. 檢查保存的cs中的RPL以判斷返回時是否要變換特權級。
          2. 加載被調用者堆棧上的cs和eip(此時會進行代碼段描述符和選擇子類型和特權級檢驗)。
          3. 如果ret指令含有參數,則增加esp的值以跳過參數,然後esp將指向被保存過的調用者ss和esp。注意,ret的參數必須對應調用門中的Param Count 的值。
          4. 加載ss和esp,切換到調用者堆棧,被調用者的ss和esp被丟棄。在這裡將會進行ss描述符、esp以及ss段描述符的檢驗。
          5. 如果ret指令含有參數,增加esp的值以跳過參數(此時已經在調用者堆棧中)。
          6. 檢查ds、es、fs、gs的值,如果其中哪一個寄存器指向的段的DPL小於CPL(此規則不適用於一致代碼段),那麼一個空描述符會被加載到該寄存器。

            通過以上兩小節可以看出,使用調用門的過程分為兩個部分:

            一部分是從低特權級到高特權級,通過調用門和call指令來實現

            另一部分是從高特權級到低特權級,通過ret指令來實現。

            接下來我們就用ret指令實現由高特權級到低特權級的轉移:

            通過ret指令從ring0進入ring3

            通過上面的分析我們知道,在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]
Copyright © Linux教程網 All Rights Reserved