调用门特权级转移实践
前一章节增加了ring3的代码,这个是低特权级。该章实验调用门的使用,将调用门的描述符和选择子以及[SECTION .ring3]的代码修改一下!
代码对比如下:
可以看出,将调用门的描述符和段选择子都设置权限3!
LABEL_CALL_GATE_TEST: Gate SelectorCodeDest, 0, 0, DA_386CGate+DA_DPL3
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
在第393行 死循环之前,增加了使用调用门的指令!修改描述符和选择子是为了满足CPL 和 RPL 都小于等于调用门DPL 的条件!
低特权级到高特权级转移的时候,需要用到TSS!
增加TSS
LABEL_DESC_TSS: Descriptor 0, TSSLen-1, DA_386TSS
....
SelectorTSS equ LABEL_DESC_TSS - LABEL_GDT
....
; 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 描述符
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
.....
mov ax, SelectorTSS
ltr ax
; 在任务内发生特权级变换时要切换堆栈,而内层堆栈的指针存放在当前任务的TSS中,所以要设置任务状态段寄存器 TR。
.....
retf ;Ring0 -> Ring3,历史性转移!将打印数字 '3'。
ud2 ; should never arrive here
.....
目前我们只初始化了0级堆栈,其他的没有初始化,因为没有用到!
需要在特权级变化之前加载它!
mov ax, SelectorTSS
ltr ax
push SelectorStack3
push TopOfStack3
push SelectorCodeRing3
push 0
retf
mov ax, SelectorTSS
ltr ax
这两条指令装载任务状态段寄存器TR,使其指向已预置好的任务的TSS!
LTR指令是专门用于装载任务状态段寄存器TR的指令。该指令的操作数是对应TSS段描述符的选择子。LTR指令从GDT中取出相应的TSS段描述符,把TSS段描述符的基地址和界限等信息装入TR的高速缓冲寄存器中。
编译运行后,运行成功!不但看到数字3 , 也看到字母C! 这表明我们在ring3下对调用门的使用也是成功的!
所以我们目前实现了两次从高特权级到低特权级别以及依次从低特权级到高特权级的转移,最终在低特权级的代码中让程序停住!
为了顺利返回到实模式,将调用局部任务的代码加入到调用门的目标代码([SECTION .sdest]),程序进入局部任务,然后由原路返回实模式!
新增代码如下:
; 测试调用门(无特权级变换),将打印字母 'C'
call SelectorCallGateTest:0
;call SelectorCodeDest:0
; Load LDT
mov ax, SelectorLDT
lldt ax
jmp SelectorLDTCodeA:0 ; 跳入局部任务,将打印字母 'L'。
...
; Load LDT
mov ax, SelectorLDT
lldt ax
jmp SelectorLDTCodeA:0 ; 跳入局部任务,将打印字母 'L'。
;retf
...
编译运行,屏幕同时出现C、L、3。这个是各个程序段的输出!