《一個作業系統的實現》讀書筆記--第三章---不同特權級程式碼段之間的跳轉
1、特權級
2、一致程式碼段和非一致程式碼段
3、DPL、RPL、CPL分別代表的含義,儲存在什麼位置,以及它們之間的關係
4、不同特權級資料段之間的訪問規則
5、不同特權級程式碼段之間的轉移
6、程式碼段之間的轉移對堆疊的影響
7、結合pmtest5.asm來見證不同特權級程式碼段之間的跳轉
一、特權級
在IA32的分段機制下,特權級總共有4個特權級別,從高到低分別是0、1、2、3。數字越小表示的特權級越大。特權級如下圖所示:
較為核心的程式碼和資料,將被存放在特權級較高的層級中。處理器將用這樣的機制來避免低特權級的任務在不被允許的情況下訪問位於高特權級的段。
二、一致程式碼段 和 非一致程式碼段
系統要安全,必須保證核心與使用者程式分離開,核心要安全,必須不能被使用者來打擾。但是有的時候,使用者程式也是需要訪問核心中的部分資料,那怎麼辦?
於是作業系統就將核心中的段分為共享的程式碼段和非共享的程式碼段兩部分。
其中一致程式碼段就是作業系統拿出來被共享的程式碼段,可以被低特權級的使用者直接訪問的程式碼。
一致程式碼段的限制作用:
(1)特權級高的程式碼段不允許訪問特權級低的程式碼段:即核心態不允許呼叫使用者態下的程式碼。
(2)特權級低的程式碼段可以訪問特權級高的程式碼段,但是當前的特權級不發生變化。即:使用者態可以訪問核心態的程式碼,但是使用者態仍然是使用者態。
非一致程式碼段:為了避免低特權級的訪問而被作業系統保護起來的系統程式碼,也就是非共享程式碼。
非一致程式碼段的限制作用:
(1)只允許同特權級間訪問
(2)絕對禁止不同級間訪問,即:使用者態不能訪問核心態,核心態也不訪問使用者態。
下圖為一致碼段與非一致碼段的訪問規則:
三、CPL、DPL、RPL分別代表的含義,儲存在什麼位置,以及它們之間的關係
1、CPL(Current Privilege Level)是當前執行的程式或任務的特權級。它被儲存在CS和SS的第0位和第1位上。通常情況下,CPL等於程式碼的段的特權級。在遇到一致程式碼段時,一致程式碼段可以被相同或者更低特權級的程式碼訪問。當處理器訪問一個與CPL特權級不同的一致程式碼段時,CPL不會被改變。
2、DPL(Descriptor Privilege Level):DPL表示段或者門的特權級,它被儲存在段描述符或者門描述符的DPL欄位中。噹噹前程式碼段試圖訪問一個段或者門時,DPL將會和CPL以及段或門選擇子的RPL相比較,根據段或者門型別的不同,DPL將會被區別對待,下面介紹一下各種型別的段或者門的情況。
(1)資料段:DPL規定了可以訪問此段的最低特權級。比如,一個數據段的DPL是1,那麼只有執行在CPL為0或者1的程式才有權訪問它。
(2)非一致程式碼段(不使用呼叫門的情況下):DPL規定訪問此段的特權級。比如一個非一致程式碼段的特權級為0,那麼只有CPL為0的程式才可以訪問它。
(3)呼叫門:DPL規定了當前執行的程式或任務可以訪問此呼叫門的最低特權級(這與資料段的規則是一致的)。
(4)一致程式碼段和通過呼叫門訪問的非一致程式碼段:DPL規定了訪問此段的最高特權級。比如,一個一致程式碼段的DPL是2,那麼CPL為0和1的程式將無法訪問此段。
3、RPL(Requested Privilege Level):RPL是通過選擇子的第0位和第1位表現出來的。處理器通過檢查RPL和CPL來確認一個訪問請求是否合法。
四、不同特權級資料段之間的訪問規則
資料段中DPL規定了可以訪問此段的最低特權級,因此,對資料的訪問,只要CPL和RPL都小於被訪問的資料段的DPL就可以了,即CPL<=DPL和RPL<=DPL。
五、不同特權級程式碼段之間的轉移
使用jmp或call指令可以實現下列4種轉移
(1)目標運算元包含目的碼段的段選擇子。
(2)目標運算元指向一個包含目的碼段選擇子的呼叫門描述符。
(3)目標運算元指向一個包含目的碼段選擇子的TSS。
(4)目標運算元指向一個任務門,這個任務門指向一個包含目的碼段選擇子的TSS。
這4種方式可以看做是兩大類,一類是通過jmp和call的直接轉移(上述第一種),另一類是通過某個描述
符的間接轉移(上述第2,3,4種)。
1、通過jmp或call進行直接轉移
2、通過呼叫門進行轉移
(1)門描述符的結構
呼叫門描述符裡面儲存著目的碼段的段選擇子,偏移量,以及屬性。
(2)呼叫門的使用方式
假設我們想由程式碼A轉移到程式碼B,運用一個呼叫門G,即呼叫門G中的目標選擇子指向程式碼B的段。實際上,這個問題主要涉及這幾個元素:CPL、RPL、程式碼B的DPL(記做DPL_B),呼叫門G的DPL(記做DPL_G)。
呼叫門使用時特權級檢驗的規則如下:
也就是說,通過呼叫門和call指令,可以實現從低特權級到高特權級的轉移,無論目的碼段時一致的還是非一致的。
通過呼叫門和jmp指令,如果目的碼段是一致的,則可以實現從低特權級到高特權級的轉移。如果目的碼段是非一致的,則只能實現相同特權級的轉移。
六、程式碼段之間的轉移對堆疊的影響
1、“長”跳轉/呼叫 和 “短”跳轉/呼叫
如果一個呼叫或跳轉指令時段間而不是段內進行的,那麼我們稱之為“長”的(Far jmp/call),反之,如果在段內則是“短”的(Near jmp/call)。
那麼長的和短的jmp或call有什麼分別呢?
對於jmp而言,僅僅是結果不同罷了,短跳轉對應段內,長跳轉對應段間。
對於call來說,就比較複雜一些,因為call指令是會影響堆疊的,長呼叫和短呼叫對堆疊的影響是不同的。
下面我們討論短呼叫對堆疊的影響,call指令執行時下一條指令的eip壓棧,到ret指令執行時,這個eip會被從堆疊中彈出,
如下圖所示:
這是短呼叫的情況。
下面我們討論長呼叫對堆疊的影響,call指令執行時會將呼叫者的cs和eip壓棧,到ret指令執行時,這個eip和cs會被從堆疊中彈出,如下圖所示:
2、有特權級變換的轉移對堆疊的影響
在不同特權級下的堆疊段不同,所以每一個任務最多可能在4個特權級間轉移,所以,每個任務實際上需要4個堆疊。可是我們只有一個ss和一個esp,那麼當發生堆疊切換,我們該從哪裡獲得其餘堆疊的ss和esp呢?
解決這個問題,需要一個數據結構TSS(Task-State Stack),如圖:
當堆疊發生切換時,內層的ss和esp就是從這裡取得的,比如,我們當前所在的是ring3,當轉移至ring1時,堆疊將被自動切換到由ss1和esp1指定的位置。由於只是在由外層轉移到內層(低特權級到高特權級)切換時新堆疊才會從TSS中取得,所以TSS中沒有位於最外層的ring3的堆疊資訊。
下面讓我們來看看整個的轉移過程是怎麼樣的?
執行call前後堆疊段的變化:
(1)根據目的碼段的DPL(新的CPL)從TSS中選擇應該切換至哪個ss和esp
(2)從TSS中讀取新的ss和esp。在這過程中如果發現ss、esp或者TSS界限錯誤都會導致無效TSS異常
(3)對ss描述符進行檢驗,如果發生錯誤,同樣產生#TS異常
(4)暫時性地儲存當前ss和esp的值
(5)載入新的ss和esp
(6)將剛剛儲存起來的ss和esp的值壓入新棧
(7)從呼叫者堆疊中將引數複製到被呼叫者堆疊(新堆疊)中,複製引數的數目由呼叫門中Param Count一項來決定。
(8)如果Param Count是零的話,將不會複製引數。
(9)將當前的cs和eip壓棧
(10)載入呼叫門中指定的新的cs和eip,開始執行被呼叫者過程。
執行ret前後堆疊段的變化:
(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指令來實現。
七、結合pmtest5.asm來見證不同特權級程式碼段之間的跳轉
; ==========================================
; 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 ; 非一致程式碼段, 32
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; Stack, 32 位
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
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
; 初始化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
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]
; 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]
83—117初始化任務狀態堆疊段(TSS)
292—300 通過使用retf完成從Ring0-->Ring3的跳轉,即高特權級跳轉到低特權級,跳轉到LABEL_CODE_RING3。
380—380 通過呼叫門,完成從Ring3-->Ring0的跳轉,即低特權級跳轉到高特權級,跳轉到LABEL_SEG_CODE_DEST
336—336 通過retf,完成從Ring0-->Ring3的跳轉,即高特權級跳轉到低特權級,跳轉到379。
到目前為止,我們已經實現了兩次從高特權級到低特權級以及一次從低特權級到高特權級的轉移,最終在低特權級的程式碼中讓程式停住。