内核保护模式之分段机制

 

CPU的三种模式

1982年,intel推出了80286处理器,第一次提出了保护模式,在保护模式下,段寄存器中存储的不再是段基址,而是段选择子。

真正的段基址存储在描述符高速缓存中,80286处理器访问内存,不需要段寄存器左移加上偏移。

在x86体系的CPU下,支持三种模式

  1. 实模式:兼容16位CPU的模式,当前的PC系统处于实模式(16位模式)运行状态,在这种状态下软件可访问的物理内存空间不能超过1MB,且无法发挥Intel 80386以上级别的32位CPU的4GB内存管理能力。实模式将整个物理内存看成分段的区域,程序代码和数据位于不同区域,操作系统和用户程序并没有区别对待,而且每一个指针都是指向实际的物理地址。这 样,用户程序的一个指针如果指向了操作系统区域或其他用户程序区域,并修改了内容,那么其后果就很可能是灾难性的。

  2. 保护模式:操作系统所在模式,只有在保护模式下,80386的32根地址线有效,可以寻址高达4G字节的线性内存空间和物理内存空间,可访问64TB的逻辑地址空间(有214个段,每个段最大空间为232个字节),可采用分段管理存储机制分页管理存储机制。这不仅为存储共享和保护提供了硬件支持,而且为实现虚拟存储提供了硬件支持。通过提供4个特权级(R0 ~ R3)和完善的特权级检查制,既能实现资源共享又能保证代码数据的安全及任务的隔离。保护模式下有两个段表:GDT和LDT

  1. 虚拟8086模式:可以模拟多个8086执行多任务

保护模式的分段机制

保护模式部分内容:

保护模式下的分段机制

保护模式下的分页机制

CS段寄存器的值,决定特权等级R0 ~ R3

8086寄存器主要是16位的,共4个CS DS ES SS

32位处理器内,增加了两个:FS GS

 

32下的段寄存器又分为两部分,16位可见部分,每一个寄存器还有一个不可见部份,称之为描述符高速缓存器,用来存放段的基地址,范围和属性。

段选择子概述

什么是段选择子?

段寄存器可见部分存储的值我们称之为段选择子,共16位

13位描述符表索引 1位:T1 TI = 0 GDT TI = 1 LDT 2位:RPL
     

段寄存器中不可见部分的值,来自于一个叫做描述符表的数组,可见部分的高13位是数组的下标。

当段寄存器被赋值的时候,实际上会从描述符表中的一个描述符中读取数据,将数据加载至段寄存器不可见部分。

TI位表明的是查找全局描述表GDT(Global Descriptor Table),还是局部描述表LDT(L**ocal Descriptor Table)。Windows系统并没有使用局部描述符表LDT。

段描述符

段描述符的结构:

typedef struct Descriptor{
   unsigned int base;  // 段基址
   unsigned int limit; // 段限长
   unsigned short attribute; // 段属性
}

在保护模式下,增加了很多机制,使得段产生了不少种类:

  • 数据段(用于存储程序,供程序读写)

  • 代码段(用于执行代码)

  • 系统段(用于操作系统提供特殊功能)

每个段描述能够描述出一段内存从哪里开始,到哪里结束,还能描述这个段是什么类型(代码段、数据段、系统段),当然,也能描述这个段是否可读,是否可写,是否可执行,甚至还能描述这个段的权限是什么,在什么权限下才能使用这一段内存。

通用断描述符

 

  • 描述符有效位P位: 0无效 1有效

  • 段限长Limit:处理器会把段描述符。两个段限长字段组合成一个20位的值,并根据颗粒度标志G来指定段限长Limit值的实际含义

  • 粒度位G:

    G = 0;Limit的单位是字节,段的大小范围为1字节到1Mb(0~0xFFFFF) , 每个单位为1字节。

    G = 1;Limit的单位是4KB,段的大小范围是1字节到4Gb(0~0xFFFFFFFF), 每个单位为4Kb。

  • 基地址字段Base:描述了一个段的起始位置,由三个部分组成,一共是32位,段基地址可以是0~4GB范围内的任意地址(这同实模式不同,实模式下段基地址要求16字节对齐)。但是,为了让这个程序具有最佳性能,还是建议段基地址对齐16字节边界。

  • S与TYPE:应用程序有数据段和代码段,CPU还有系统段和门描述符,他们用来管理事物、异常和中断。并非所有的描述符都定义一个段,门描述符中存放着一个指向过程入口点的指针,S和Type字段表明了描述符的类型信息。

S位 S = 1代表是一个代码段或者数据段,S = 0代表是一个系统段
Type域 一共4位,S为不同代表的含义不同
  • L位:是64位代码段标志,保留此位给64位处理器使用

  • AVL:是软件可以使用的位,通常由操作系统来用,处理器并不使用它

  • DPL:存在于段描述中,描述了访问本段的内存所需要的权限

数据段描述符

  • B位:

B = 1,内存寻址是32位,堆栈使用ESP,段的内存最大大小为4GB

B = 0,内存寻址是16位,堆栈使用SP,段的最大大小位64KB

  • A访问位

表示该位最后一次被操作系统清零后,该段是否被访问过,每当处理器将该段选择符置入某个段寄存器时,就将该位置1

  • W是否可写

    • 指示段的读写属性

    • W = 0;段不允许写入,否则会引发处理器异常中断

    • W = 1;段允许写入

  • 扩展方向

    • E = 0;表示向上扩展的段(上扩段),逻辑地址中的偏移范围可以从0到Limit

    • E = 1;表示向下扩展的段(下扩段,通常是栈段),逻辑地址中的偏移范围可以从Limit到0xFFFF(B = 0)或者0xFFFF_FFFF(当B = 1)时。

代码段描述符

 

  • B位:

B = 1,内存寻址是32位,堆栈使用ESP,段的内存最大大小为4GB

B = 0,内存寻址是16位,堆栈使用SP,段的最大大小位64KB

  • A访问位

表示该位最后一次被操作系统清零后,该段是否被访问过,每当处理器将该段选择符置入某个段寄存器时,就将该位置1

  • W是否可写

    • 指示段的读写属性

    • W = 0;段不允许写入,否则会引发处理器异常中断

    • W = 1;段允许写入

  • 扩展方向

    • E = 0;表示向上扩展的段(上扩段),逻辑地址中的偏移范围可以从0到Limit

    • E = 1;表示向下扩展的段(下扩段,通常是栈段),逻辑地址中的偏移范围可以从Limit到0xFFFF(B = 0)或者0xFFFF_FFFF(当B = 1)时。

代码段描述符

 

  • D位:

D = 1,默认值是32位的地址和32位或者8位的操作数

D = 0,默认值是16位的地址和16位或者8位的操作数

  • A(已访问)

表示该位最后一次被操作系统清零后,该段是否被访问过,每当处理器将该段选择符置入某个段寄存器时,就将该位置1

  • R位(可读)

    • 堆栈必须R = 0,代码段不可读,只能执行

    • R = 1;代码段可读,可执行

    • 在保护模式下,代码段是不可写的

  • C位(一致性

    • C = 0;表示非一致性代码段,这样的代码段可以被同级代码段调用,或者通过门调用。

    • C = 1;表示一致性代码段,可以从低特权级的程序转移到该段执行(但是低特权级的程序仍然保持自身的特权级)

 

段类型检查

  • 加载段选择符进入段寄存器的时候

    • CS寄存器只能存放可执行段的选择符

    • 不可读可执行段的选择符不能被加载进入数据段寄存器(因为数据段都是可读的)

  • 指令访问一个段,段描述符已经被加载到段寄存器,指令只能用某些预定义的方法来访问某些段

    • 不能写可执行段(代码段不可写)

    • 不能写一个可写位没有设置的数据段

    • 不能读可读位没有设置的可执行段

段权限检查

当我们给段寄存器赋值的时候,实际上是从GDT中获取相应的段描述符加载到段寄存器的不可见部分。

在这个过程中有一个权限检查的问题,请注意,在你给段寄存器赋值的时候就已经进行段权限检查了,如果成功,就说明可以访问这个段

权限检查的三个概念:

  • CPL:当前代码的执行权限,CS段的B0和B1

  • DPL:存在于段描述符中,描述了访问本段的内存所需要的权限

  • RPL:存在于段寄存器加载时的段选择子中,描述了访问者使用什么样的权限对目标进行访问

 

 

全局描述符表

在一个系统中, 描述符的种类有多个, 分别有数据段,代码段, 系统段. 系统段又分为多种,有调用门,中断门,陷阱门,任务门. 因此, 在一个系统中, 描述符是存在多个的. 这些描述符被统一打包存储在内存中, 它们所形成的一个数组被称之为全局描述符表.

全局描述符的小标则保存于16位的段寄存器中.

一个16位的段寄存器实际由一下部分组成:

 

段寄存器实际的长度为96位, 16位的值, 只是寄存器的可见部分, 段寄存器还有80位是隐藏部分 , 这个隐藏部分只能被CPU所操作,无法通过任何指令来操作它.

这可见部分的16位的值也并非全部用于保存全局描述符的下标, 它被划分为以下格式:

 

也就是说, 只有13位是用于保存全局描述符表的下标.

T1 - 用于记录,保存的下标是GDT(全局描述符表)的还是LDT(本地描述符表)的(windows操作系统没有LDT)

RPL - 当前请求级别 , 用作权限检查. 一共有4个值: 0~3 , 数值越小,权限越大, 0代表最高权限.

由于段寄存器用于保存段选择子, 因此, 给一个段寄存器赋值,就不单单是赋值一个数字了,例如:

 

mov ax,2Bh
mov ds,ax

这条指令可看成将0x2B赋值给ds寄存器, 实际不是.

将0x2b的二进制展开: 0000 0000 0010 1011 , 段选择子的格式为: 13 : 1 : 3.

那么在0x2b这个数中, 描述符表索引,T1位,RPL分别为:

 

也就说, 0x2b这个数代表的是GDT表中第5个段描述符. 当前请求级别为最低权限的2。

mov ds,ax这条指令执行之后做了什么?

CPU执行这条指令后, 会将GDT表中第5个段描述符存储在段寄存器隐藏部分, 将段选择子存储到16位可见部分. 当然, 在做这些之前, CPU还需要做权限检查。

原文地址:https://www.cnblogs.com/TJTO/p/11414726.html