修改段寄存器时的保护
本文提到描述符都特指段描述符。
在保护模式下,内存访问是通过“段选择子:段内偏移量”的方式来执行的。处理器在访问某个内存地址时导致了段寄存器切换,那么处理器会在切换之前进行检查,具体流程如下:
- 选择子检查
- 选择子的TI为如果为0,则从GDT中加载描述符到描述符高速缓存器中,否则从LDT中加载。
- 选择子3~15位描述符表中的索引号,索引号×8+7必须小于或等于描述符表的界限。否则处理器终止处理器,段寄存器值不变,并产生13号异常中断。
- 描述符检查
- 检查描述符的TYPE字段,如果X为1,则只能加载到CS段寄存器。描述符的TYPE字段必须是有效值。
- 检查描述符的P位,如果P位为1,表示该段已经在内存中,处理器将描述符加载段寄存器对应的高速缓存器中,并更新段寄存器,同时设置TYPE字段的A位为1。否则,处理器终止处理器,并引发11号异常中断。
注:CS和SS段寄存器不允许传入值为0的选择子。DS、ES、FS和GS允许传入值为0的选择子,但在使用时会引发异常中断。
以下是TYPE字段的含义:
X | E | W | A | 描述符类型 | 含义 |
0 | 0 | 0 | - | 数据 | 只读,向高地址扩展 |
0 | 0 | 1 | - | 读写,向高地址扩展 | |
0 | 1 | 0 | - | 只读,向低地址扩展 | |
0 | 1 | 1 | - | 读写,向低地址扩展 | |
X | C | R | A | 描述符类型 | 含义 |
1 | 0 | 0 | - | 代码 | 非依从,只执行 |
1 | 0 | 1 | - | 非依从,执行、读取 | |
1 | 1 | 0 | - | 依从,只执行 | |
1 | 1 | 1 | - | 依从,执行、读取 |
X:指示段是否可执行。数据段总是不可执行,X=0;代码段总是可执行,X=1。
A:指示段是否已经被访问过。在描述符创建时应该为0之后,每当段被访问时,处理器自动将其置1。该位可由软件负责清零。
数据段特有:
E:指示段的扩展方向。E=0表示向高地址方向扩展;E=1表示向低地址方向扩展。
W:指示段是否允许写入。W=0表示段不允许写入,否则处理器会引发异常中断;W=1表示段可以写入。
代码段特有:
C:指示段是否为特权级依从。C=0表示非依从代码段;C=1表示依从段。依从段:只允许特权级更低或相同的代码段访问。控制转移到依从段,不会改变当前特权级。
R:指示代码段是否可读。R=0时,读取代码段会处理器会引发异常中断;R=1代码段可作为数据被读取。
地址变换时的保护
段界限的计算公式
描述符中的G位表示段大小的单位,G=0时表示段的大小以字节为单位;G=1时表示段的大小以4KB为单位。
如果G=0,实际段界限=段描述符中给出的值,实际段大小=|段描述符中给出的值+1|。
如果G=1,实际段界限=(段描述中给出的值+1)×0x1000-1。将公式展开:描述符中的段界限值×0x1000+0xFFF。实际段大小=(|段描述中给出的值+1|)×0x1000。
代码段和向高地址扩展的数据段的实际段界限(正值)给出了段内最大偏移量。向低地址扩展栈段的实际段界限(负值)+1给出了段内最小偏移量,其最大偏移量始终为0xFFFFFFFF,即-1。
代码段执行时的保护
代码段是向高地址扩展的,因此只要保证 EIP+指令长度-1的值落在闭区间 [0,实际段界限]内,便是有效的。否则处理器引发异常中断。,则
0≤下一条指令的EIP+指令长度-1≤实际段界限
向高地址扩展的数据段访问时的保护
在访问向高地址扩展的数据段时,使用的规则与代码段类似,不同之处在于。代码段使用指令长度参与检查EIP,而数据段使用数据尺寸参与检查有效地址EA,即:
0≤EA+数据尺寸-1≤实际段界限
栈操作时的保护
栈的保护容易让人困惑。一般情况下,栈都是使用向低地址扩展的内存段,但也可以使用向高地址扩展的内存段,即把普通数据段作为栈。在这种情况下,对栈的保护与上面一致。对于向低地址扩展的栈,其ESP必须始终满足:
实际段界限+1≤ESP-数据尺寸≤0xFFFFFFFF
这里有个不明白的地方:为什么栈的 最低有效偏移量 是 实际段界限+1?而代码段和普通数据段的 最高有效偏移量 就是 实际段界限。