特权级
特权级总共有4个级别,数字越小表示的特权级越大
- CPL - Current Privilege Level(当前特权级):用于指示处理器当前运行的特权级别
- DPL - Descriptor Privilege Level(描述符特权级):用于指示段描述符或门描述符的特权级别。每个段描述符或门描述符都有一个DPL字段,它决定了访问该段或门的特权级别限制
- RPL - Requested Privilege Level(请求特权级):这是指x86架构中在进行段选择器加载或者调用门调用时,请求访问某个段或门时指定的特权级别
- RPL指定了进程在访问某个段或门时所希望的特权级别,它可以是0、1、2或3,与CPL进行比较决定是否允许访问
特权级转移
- 通过jmp或call进行直接转移
- 目标为非一致代码段:CPL=DPL 且 RPL<=DPL
- 目标为一致代码段:CPL>=DPL
- 运用门描述符
- 调用门 (Call gates)
- 中断门 (Interrupt gates)
- 陷阱门 (Trap gates)
- 任务门 (Task gates)
门描述符
- 门描述符结构
- 门描述符定义
; 门
; usage: Gate Selector, Offset, DCount, Attr
; Selector: dw
; Offset: dd
; DCount: db
; Attr: db
%macro Gate 4
dw (%2 & 0FFFFh) ; 偏移1
dw %1 ; 选择子
dw (%3 & 1Fh) | ((%4 << 8) & 0FF00h) ; 属性
dw ((%2 >> 16) & 0FFFFh) ; 偏移2
%endmacro ; 共 8 字节
通过调用门进行有特权级变换的转移
特权级变换的转移的复杂之处,不但在于严格的特权级检验,还在于特权级变化的时候,堆栈也要发生变化
由于每一个人物最多都可能在4个特权级间转移,所以每个任务实际上需要4个堆栈
自身已经有了一个ss和esp,当发生堆栈切换时,从TSS(Task-State Stack)这个数据结构中获得
TSS 偏移4到偏移27的字段中存储另外3个堆栈的信息
- 低--->高:通过调用门和call指令实现
步骤:
- 准备好TSS信息(TSS数据结构的字段信息---新堆栈、描述符、选择子的初始化)
; 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 ; LABEL_DESC_TSS为TSS的描述符
shr eax, 16
mov byte [LABEL_DESC_TSS + 4], al
mov byte [LABEL_DESC_TSS + 7], ah
- 加载TSS
; Load TSS
mov ax, SelectorTSS
ltr ax ; 在任务内发生特权级变换时要切换堆栈,而内层堆栈的指针存放在当前任务的TSS中,所以要设置任务状态段寄存器 TR。
假设由代码A到代码B,运用一个调用门G
- 高--->低:通过带参数的ret指令实现
步骤:
- 准备好代码段和堆栈段(描述符、选择子的初始化)
- 准备好目标代码段的cs、eip以及ss、esp(依次将ss、esp、cs、eip压入栈中)
push SelectorStack3 ; ss,堆栈段的选择子
push TopOfStack3 ; esp
push SelectorCodeRing3 ; cs,代码段的选择子
push 0 ; eip
retf ; Ring0 -> Ring3
- 执行retf 指令