引言
- 在上一章节中提过,调用门可以实现不同特权级的代码之间跳转执行
- 不能直接跳转吗?我们先来做个小实验
- 将 Task A 中代码段选择符中的特权级由 0 改 为 3 ,然后编译运行一下看看效果
; TASK_A_CODE32_SELECTOR equ (0x0000 << 3) + SA_RPL0 + SA_TIL TASK_A_CODE32_SELECTOR equ (0x0000 << 3) + SA_RPL3 + SA_TIL
- 实验结果:原本应该打印的字符串 “Task A”,并没有打印出来,程序崩溃了
- 结论:不同特权级的代码之间并不能随意跳转
- 想要搞清楚原因,就需要先对特权级进行一定的了解
特权级
- 我们知道,保护模式下, CPU 提供各种各样的保护机制,比如段界限,指定范围;段属性,指定读写等权限等等。这些都比较好理解,在这里就不总结了
- 保护模式下, CPU 还提供了有关特权级的保护机制
- 处理器的段保护机制可以识别 4 个特权级(或特权层),0 级到 3 级。数值越大,特权越小
- 处理器利用特权级来防止运行在较低特权级的程序或任务访问具有较高特权级的一个段
- 我们熟知的 linux 系统只使用了特权级 0(内核)和特权级 3(应用)
与特权级相关的三个概念
- 当前特权级 CPL(Current Privilege Level)
- CPL 是当前正在执行程序或任务的特权级。它存放在 CS 和 SS 段寄存器的位 0 和位 1 中
- 当程序把控制转移到另一个具有不同特权级的代码段中时,处理器就会改变 CPL
- 处理器进入保护模式后,CPL = 0(最高特权级)
- 描述符特权级 DPL(Descriptor Privilege Level)
- DPL 是一个段或门的特权级。它存放在段或门描述符的 DPL 字段中
- 在当前执行代码段试图访问一个段或门时,段或门的 DPL 会用来与 CPL 以及段或门选择符中的 RPL 作比较
- 根据被访问的段或门的类型不同,DPL 也有不同的含义,这里先不深究,后面再做介绍
- 请求特权级 RPL(Request Privilege Level)
- RPL 是一种赋予段选择符的超越特权级,它存放在选择符的位 0 和位 1 中
- 处理器会同时检查 RPL 和 CPL,以确定是否允许访问一个段
CPL、DPL 和 RPL 的关系
- 当处理器从 A 代码段成功跳转到 B 代码段执行
- 跳转前: CPL = DPL(A)
- 跳转后: CPL = DPL(B)
- 当处理器运行过程中需要访问数据段时
- CPL <= DPL(Data)
- 在请求某特权级为 DPL 级别的资源时,参与特权检查的不只是 CPL,还要加上 RPL. CPL 和 RPL 的特权必须同时大于等于受访者的特权 DPL,即:数值上 CPL <= DPL 并且 RPL <= DPL
再做小实验
- 先对段描述符中 DPL 属性进行宏定义
; 段描述符中 DPL 属性定义(段描述符高 32 位的 bit13-bit14 ) DA_DPL0 equ 0x00 ; DPL = 0 DA_DPL1 equ 0x20 ; DPL = 1 DA_DPL2 equ 0x40 ; DPL = 2 DA_DPL3 equ 0x60 ; DPL = 3
- 给全局描述符表中 32 位代码段增加属性 DPL = 3
; CODE32_DESC : Descriptor 0, CODE32_SEG_LEN - 1, DA_C + DA_32 CODE32_DESC : Descriptor 0, CODE32_SEG_LEN - 1, DA_C + DA_32 + DA_DPL3
- 运行一下,不出意外,果然不能运行,CODE32_START 代码段并没有运行,这说明高特权级代码段不能够跳转到低特权级的代码段
- 将本章节 RPL、DPL 有关的改动都恢复一下,恢复后代码(可以运行):loader.asm
- 同样的实验我们也在数据段上做一次,改动如下:
; DATA_DESC : Descriptor 0, DATA_SEG_LEN - 1, DA_DR + DA_32 DATA_DESC : Descriptor 0, DATA_SEG_LEN - 1, DA_DR + DA_32 + DA_DPL3
- 运行一下,居然未发生任何错误
- 再改一下:
; DATA_SELECTOR equ (0x0003 << 3) + SA_RPL0 + SA_TIG DATA_SELECTOR equ (0x0003 << 3) + SA_RPL3 + SA_TIG
- 还是未发生任何错误
- 再改一下:
; DATA_DESC : Descriptor 0, DATA_SEG_LEN - 1, DA_DR + DA_32 DATA_DESC : Descriptor 0, DATA_SEG_LEN - 1, DA_DR + DA_32 + DA_DPL2 ; DATA_SELECTOR equ (0x0003 << 3) + SA_RPL0 + SA_TIG DATA_SELECTOR equ (0x0003 << 3) + SA_RPL3 + SA_TIG
- 这回出错了,这说明请求特权级 RPL 不能够请求比它更高级的特权级数据段,但是可以请求到比它低级的特权级的数据段。
- 再对栈段做下实验
- 程序再次恢复到 loader.asm
- 改动如下:
; STACK32_DESC : Descriptor 0, TOP_OF_STACK32, DA_DRW + DA_32 STACK32_DESC : Descriptor 0, TOP_OF_STACK32, DA_DRW + DA_32 + DA_DPL3 ; STACK32_SELECTOR equ (0x0004 << 3) + SA_RPL0 + SA_TIG STACK32_SELECTOR equ (0x0004 << 3) + SA_RPL3 + SA_TIG
- 凭感觉栈段应该跟数据段一样,结果程序崩了。这说明栈段的特权级检测规则跟数据段不一样,好吧,代码段、数据段、栈段各自有各自的检测规则
- 是不是有点乱糟糟的,暂时先不要一下子理清楚所有的规则,以后再在各种场景下具体体会,目前先有个感性的认识就好