---- 整理自狄泰软件唐佐林老师课程
文章目录
- 1. 保护模式小结
- 1.1 使用选择子访问段描述符表时,索引值的合法性检测
- 1.2 内存段类型合法性检测
- 1.3 实例分析
- 2. 问题一
- 3. 保护模式中的特权级
- 3.1 特权级的表现形式
- 3.2 初探特权级
- 3.2.1 CPL和DPL的关系
- 3.2.2 段描述符中的DPL常量定义
- 3.3 编程实验:保护模式特权级初探
- 4. 问题二
- 4.1 一种新的描述符:门描述符(Gate Descriptor)
- 4.2 门描述符的内存结构
- 4.3 调用门描述符(Call Gate)的定义
- 4.3.1 调用门描述符的工作原理
- 4.3.2 调用门描述符的使用
- 4.3.3 汇编小贴士
- 4.3.4 编程实验:初探调用门
- 4.4 历史遗留问题
- 4.5 编程实验:解决历史遗留问题
- 5. 问题三
- 5.1 调用门的特权级跳转分析
- 5.2 再论函数调用的过程
- 5.3 需要提前知道的事实
- 5.4 解决方案(高特权级==>低特权级)
- 5.5 编程实验:高特权级==>低特权级
1. 保护模式小结
1.1 使用选择子访问段描述符表时,索引值的合法性检测
- 当索引值越界时,引发异常
- 判断规则:索引值 * 8 + 7 ==> 段描述符表界限值
1.2 内存段类型合法性检测
具备可执行属性的段(代码段)只能加载到 CS 寄存器
具备可写属性的段才能加载到 SS 寄存器
具备可读属性的段才能加载到 DS、ES、FS、GS 寄存器
- 代码段和数据段的保护:
- 处理器每访问一个地址都要 确认该地址 不超过界限值
- 判断规则:
- 代码段:IP(指令指针寄存器) + 指令长度 <= 代码段界限
- 数据段:访问起始地址 + 访问数据长度 <= 数据段界限
- 注意:
- 保护模式中,在代码中定义的界限值通常为:相对于段基地址的 最大偏移地址值
1.3 实例分析
自行修改代码查看报错的信息进行学习。
【参看链接】:15-16-17 - 保护模式中的特权级 / 15
2. 问题一
- 保护模式中除了利用 段界限 对内存访问进行保护,是否还提供其它的保护机制?
3. 保护模式中的特权级
- x86架构中的保护模式提供了4个特权级( 0、1、2、3 )
- 特权级从高到低分别是0、1、2、3(数字越大特权级越低)
3.1 特权级的表现形式
- CPL(Current Privilege Level)
- 当前可执行代码段的特权级,由CS段寄存器 最低2位 定义
- DPL(Descriptor Privilege Level)
- 内存段的特权级,在段描述符表中定义
- RPL(Request Privilege Level)
- 选择子的特权级,由选择子最低2位定义
3.2 初探特权级
- 段描述符中的DPL用于标识内存段的特权级,可执行代码访问内存段时必须满足一定特权级CPL,否则,处理器将产生异常。
3.2.1 CPL和DPL的关系
- 保护模式中,每一个 代码段 都定义了一个 DPL
- 当处理器从A代码段成功跳转到B代码段执行,不同代码段之间成功跳转后CPL可能发生改变:
- 跳转前:
CPL = DPL_A
==> 跳转后:CPL = DPL_B
- 跳转前:
- 当处理器从A代码段成功跳转到B代码段执行,不同代码段之间成功跳转后CPL可能发生改变:
- 保护模式中,每一个 数据段 都定义了一个 DPL
- 当处理器执行过程中需要访问数据段时:
(CPL数值上小于等于数据段DPL才能成功访问数据,即:当前可执行代码段的特权级高于数据段的特权级,通俗来说,就是有权访问才能访问)
- 当处理器执行过程中需要访问数据段时:
3.2.2 段描述符中的DPL常量定义
3.3 编程实验:保护模式特权级初探
- 实验结论:
- 处理器进入保护模式之后CPL=0(最高特权级)
- 处理器不能直接从高特权级转换到低特权级执行
- 选择子RPL大于对应段描述符的DPL时(RPL > DPL),产生异常
- 引出的问题:
- 如何在不同特权级的代码段之间跳转执行?
- 高特权级代码为什么不能使用低特权级栈段?
- 选择子的RPL具体有什么用?
4. 问题二
- 如何在 不同特权级的代码段 之间跳转执行?
4.1 一种新的描述符:门描述符(Gate Descriptor)
门结构就是记录 一段程序 起始地址 的描述符
- 通过 门描述符 在不同特权级的代码间进行跳转
- 根据应用场景的不同,门描述符分为:
- 调用门(Call Gate)
- 中断门(Interrupt Gate)
- 陷阱门(Trap Gate)
- 任务门(Task Gate)
4.2 门描述符的内存结构
- 每一个门描述符占用8字节内存
- 不同类型门描述符的内存含义不同
4.3 调用门描述符(Call Gate)的定义
4.3.1 调用门描述符的工作原理
4.3.2 调用门描述符的使用
4.3.3 汇编小贴士
- 汇编语言中的跳转方式
- 段内跳转:
call
、jmp
- 参数为 相对地址,函数调用时只需要保存当前偏移地址
- 段间地址:
call far
、jmp far
- 参数为 选择子 和 偏移地址
- 函数调用时需要同时保存段基地址和偏移地址
- 段内跳转:
4.3.4 编程实验:初探调用门
【参看链接】:15-16-17 - 保护模式中的特权级 / 16 / 00
- 实验结论
- 门描述符是一种特殊的描述符,需要注册于段描述符表中
- 调用门可以看作一个 函数指针(保存具体函数的入口地址)
- 通过调用门选择子对相应的函数进行调用(
call far
),对应的函数调用方式为 远调用 - 可以直接使用 选择子 : 偏移地址 的方式调用其它段的函数
- 使用调用门时 偏移地址无意义,仅仅是语法需要(为什么?)
4.4 历史遗留问题
- 保护模式下的不同段之间如何进行代码复用(如:调用同一个函数)?
- 解决方案:
- 将不同代码段需要复用的函数定义到独立的段中(函数用retf 返回)
- 计算每一个可复用函数的偏移量(FuncName - $$)
- 通过 选择子 : 偏移地址 的方式对目标函数进行远调用
4.5 编程实验:解决历史遗留问题
https://gitee.com/wuxiang16/myos/blob/master/16/01/loader.asm
FUNCTION_DESC : Descriptor 0, FunctionSegLen - 1, DA_C + DA_32
FunctionSelector equ (0x0008 << 3) + SA_TIG + SA_RPL0
mov esi, FUNCTION_SEGMENT
mov edi, FUNCTION_DESC
call InitDescItem
call FunctionSelector : PrintString
PrintString equ PrintStringFunc - $$
FunctionSegLen equ $ - FUNCTION_SEGMENT
5. 问题三
- 使用调用门如何实现 不同特权级 代码之间的跳转(如:从高特权级到低特权级)?
- 不幸的事实
- 调用门 只支持 从低特权级跳转到高特权级执行
5.1 调用门的特权级跳转分析
- 思路整理:
- 特权级跳转:
- 通过 远调用(call far):低特权级 ==> 高特权级
- 通过 远返回(retf):高特权级 ==> 低特权级
- 远返回(retf)能够实现高特权级到低特权级的代码跳转,那么,考虑如何利用其机制完成这个跳转。
- 特权级跳转:
5.2 再论函数调用的过程
- 近调用
- 执行 ret 指令,相当于执行
pop eip
- 调用func时eip被压入栈中,调用结束ret后,eip被重新取回到eip中
- 执行 ret 指令,相当于执行
- 远调用
- 执行 retf 指令,相当于执行
pop eip,pop cs
- 执行 retf 指令,相当于执行
5.3 需要提前知道的事实
- x86处理器对于 不同的特权级 需要使用 不同的栈
- 每一个特权级对应一个私有的栈(最多4个栈)
- 特权级跳转变化之前 必须指定好相应的栈
- 任务是由处理器执行的,任务在特权级变换时,本质上是处理器的当前特权级在变化,由一个特权级变成了另一个特权级。这就涉及到了栈的问题,处理器固定,处理器在不同特权级下,应该用不同特权级的栈,原因 是:
- 如果在同一个栈中容纳所有特权级的数据时,这种交叉引用会使栈变得混乱
- 用一个栈容纳多个特权级下的数据,栈容量有限,容易溢出
5.4 解决方案(高特权级==>低特权级)
- 指定目标栈段选择子
- 指定栈顶指针位置
- 指定目标代码段选择子
- 指定目标代码段偏移
- retf 跳转
5.5 编程实验:高特权级==>低特权级
【参看链接】:15-16-17 - 保护模式中的特权级 / 17
- 默认code是0特权级,从0跳到3
retf之后,相当于pop eip
,pop cs
,即pop 0
,pop Code32Selector
,此时栈顶指针esp指到了TopOfStack32。
- 如果都是0的话,这里就是用的同一个栈段,因此不需要指定栈段
结果是一样的: