操作系统开发系列—5.特权级及特权级的转移
CPL——当前执行的程序或任务的特权级,它被存储在cs和ss的第0位和第1位上。
DPL——段或者门的特权级,如果是数据段DPL则规定了可以访问此段的最低特权级
RPL——通过段选择子的第0位和第1位表现出来的。处理器通过检查RPL和CPL来确认一个访问请求是否合法。RPL保证了操作系统不会越俎代庖地代表一个程序去访问一个段。
我们先来展示一下特权级错误访问版本。
先把LABEL_DESC_DATA对应的段描述符的DPL修改为1:
LABEL_DESC_DATA: Descriptor 0, DataLen-1, DA_DRW+DA_DPL1 ; Data
继续修改,把选择子的RPL改为3:
SelectorData equ LABEL_DESC_DATA - LABEL_GDT + SA_RPL3
运行结果如下:
虚拟机崩溃。因为RPL & CPL 必须 <= DPL
下面就演示一下从低特权转到高特权的方法:
通过jmp和call所能进行的代码段间转移是非常有限的,对于非一致代码段,只能在相同特权级代码段之间转移。遇到一致代码段也最多能从低到高,而且CPL不会改变。如果想自由地进行不同特权级之间的转移,显然需要其他几种方式,即运用门描述符或者TSS。调用门描述符格式如下:
一个门描述了由一个选择子和一个偏移所指定的线性地址,程序正是通过这个地址进行转移的。
以下是通过调用门转移的目标段:
[SECTION .sdest]; 调用门目标段 [BITS 32] LABEL_SEG_CODE_DEST: ;jmp $ 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]
下面是代码段描述符,选择子及初始化描述符的代码:
LABEL_DESC_CODE_DEST: Descriptor 0,SegCodeDestLen-1, DA_C+DA_32; 非一致代码段,32 SelectorCodeDest equ LABEL_DESC_CODE_DEST - LABEL_GDT ; 初始化测试调用门的代码段描述符 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
现在添加调用门:
LABEL_CALL_GATE_TEST: Gate SelectorCodeDest, 0, 0, DA_386CGate+DA_DPL0
宏Gate的定义在pm.inc中
描述符的属性是DA_386CGate表明是一个调用门。里面指定的选择子是SelectorCodeDest,表明目标代码段是刚刚新添加的代码段。偏移地址是0,表示将跳转到目标代码段的开头处。另外,我们把其DPL指定为0.
现在调用门准备就绪,它指向的位置是SelectorCodeDest:0,即标号LABEL_SEG_CODE_DEST处的代码。
假设我们想由代码A转移到代码B,运用一个调用门G,即调用门G中的目标选择子指向代码B的段。代码B的DPL记做DPL_B,在用call指令时,要求目标代码DPL_B<=CPL;在用jmp指令时,只能是DPL_B=CPL。
现在添加一个低特权的代码段ring3和堆栈:
LABEL_DESC_CODE_RING3: Descriptor 0,SegCodeRing3Len-1, DA_C+DA_32+DA_DPL3 LABEL_DESC_STACK3: Descriptor 0, TopOfStack3, DA_DRWA+DA_32+DA_DPL3 ; 堆栈段ring3 [SECTION .s3] ALIGN 32 [BITS 32] LABEL_STACK3: times 512 db 0 TopOfStack3 equ $ - LABEL_STACK3 - 1 ; END of [SECTION .s3] ; CodeRing3 [SECTION .ring3] ALIGN 32 [BITS 32] LABEL_CODE_RING3: mov ax, SelectorVideo mov gs, ax mov edi, (80 * 14 + 0) * 2 mov ah, 0Ch mov al, '3' mov [gs:edi], ax jmp $ SegCodeRing3Len equ $ - LABEL_CODE_RING3 ; END of [SECTION .ring3]
执行如下:
打印了红色的3,表明我们由ring0到ring3的转移成功。接下来试验一下调用门的使用。
把调用门的描述符和选择子改成特权等级为3.还有从低特权级到高特权级转移的时候,需要用到TSS,我们来准备一个TSS。
LABEL_DESC_TSS: Descriptor 0, TSSLen-1, DA_386TSS ; 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
我们需要在特权级变换之前加载它
mov ax, SelectorTSS ltr ax
运行结果如下:
看到字母C表明从低特权级到高特权级的转移。
【源码】