检查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
mov dword [GdtPtr + 2], eax ; [GdtPtr + 2]
; 加载 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
p 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]