保护模式-段选择符

目录

CPU的分级概念

段选择符的结构

GDT与GDTR

通过段选择符查找段描述符

快速定位段描述符在GDT表中的偏移


CPU的分级概念

        CPU的分级概念是指CPU对不同的程序和操作系统进程赋予不同的特权级别,特权级别包括0~3级,也称为“内核态”和“用户态”。如图所示:

  • 特权级0(Ring 0):也称为内核态,是CPU最高的特权级别,只有操作系统内核和部分设备驱动程序可以运行在这个特权级别下。在特权级0下,程序可以访问系统的所有资源,包括硬件、内存等,可以执行所有CPU指令。

  • 特权级1(Ring 1)和特权级2(Ring 2)虽然存在,但是Windows并没有使用。

  • 特权级3(Ring 3):也称为用户态,是普通应用程序所运行的特权级别。在这些特权级别下,程序只能访问被授权使用的系统资源,并且不能执行一些敏感的CPU指令。通常,操作系统会限制用户程序的权限,以确保系统的安全性和稳定性。

        操作系统内核通常运行在R0下,可以直接访问CPU、内存和其他系统资源,从而实现对整个系统的控制。用户程序则运行在R3下,只能访问授权的资源,不能直接访问CPU和内存等系统资源。在用户程序需要访问系统资源时,需要通过系统调用等方式向操作系统请求访问权限,操作系统会根据用户程序的权限和安全策略来决定是否授权访问。

        CPU的分级概念是操作系统和CPU配合实现的,它可以保护系统的安全性和稳定性,防止恶意程序对系统造成破坏。

段选择符的结构

        在上一章节中说过,段选择符是一个16位的段标识符,它是段寄存器的可见部分。它并不直接指向一个段,而是指向定义该段的段描述符。一个段选择符的结构如下:

        RPL:请求特权级别(RPL-Requestor Privilege Level)用于确定该选择符的特权级,也就是用来表示请求访问该段的代码或数据的特权级别,与段描述符中的特权级别(DPL-Descriptor Privilege Level)一起用于判断访问权限。RPL是一个2位的值,可以取0、1、2、3中的任意一个,其中0表示最高特权级别,3表示最低特权级别。

        当CPU执行一条访问内存的指令时,会先通过段选择符获取到相应的段描述符,然后根据段描述符中的DPL和段选择符中的RPL一起来判断是否有权限访问该段。其中,DPL是段描述符中的特权级别,表示该段的访问权限,取值范围为同样为0、1、2、3,其中0表示最高特权级别,3表示最低特权级别。RPL则表示当前访问该段的代码或数据的特权级别,如果RPL大于DPL(RPL的特权级别低于DPL的特权级别),则无权访问该段,否则可以访问。

        如果一个代码段的DPL为0,当一个特权级别为3的代码请求访问该段时,它的RPL为3,大于段描述符的DPL,因此无权访问该段。

        TI:Table Indicator是段选择符中的1个位,用来指示段选择符所指向的表是GDT表(全局描述符表,Global Descriptor Table)还是LDT表(局部描述符表,Local Descriptor Table)。当TI为0时,选择符所指向的表为GDT表;当TI为1时,选择符所指向的表为LDT表。在实际应用中,大多数操作系统都使用GDT表来管理内存段,而很少使用LDT表。

        Index:是一个索引值,用来指示在GDT表或LDT表中的段描述符的位置。它是一个13位的值,所以取值范围在0~8191之间。

        当CPU执行一条访问内存的指令时,会根据段选择符中的段索引和TI位来确定选择子所指向的表,然后根据段索引来获取相应的段描述符。CPU会将索引值乘以8(段描述符的字节数),然后加上GDT或LDT的基地址(基地址在GDTR或者LDTR寄存器中)。

GDT与GDTR

        GDT全局描述符表,全称为Global Descriptor Table,用于存储各种段描述符,包括代码段、数据段、堆栈段等。每个段描述符指定了该段的基地址、大小、访问权限等信息。在保护模式下,GDT被用来实现内存的保护和隔离,它定义了系统中所有可用的段,并为每个段分配一个唯一的段选择符。

        GDTR,全称为Global Descriptor Table Register,是一个48位的寄存器,用于存储GDT的地址和限长。具体来说,GDTR寄存器的低16位存储GDT表的限长,即GDT所占用的字节数。高32位存储GDT表的基地址,即GDT在内存中的起始地址。在保护模式下,CPU使用GDTR寄存器中存储的地址来找到GDT,并从中获取所需的段描述符。它的结构用C语言表示如下:

struct gdtr {
    unsigned short limit;   // GDT的大小
    unsigned int base;      // GDT的起始地址
};

        很多人认为GDTR寄存器结构的应该先是base,然后才是limit,也就意味着低32位存储着GDT表的基地址,高16位存储着GDT表的限长。但是笔者翻阅Intel白皮书发现恰恰相反,如图所示:

        在x86架构中,可以使用LGDT和SGDT指令来加载和保存GDTR寄存器中的值。LGDT指令用于将48位的GDT地址和限长加载到GDTR寄存器中,从而使CPU能够访问GDT。SGDT指令用于将GDTR寄存器中的值存储到指定的内存位置中,以便在需要时能够恢复GDT。 

通过段选择符查找段描述符

  1. 在虚拟机中使用OD附加任意一个程序,查看段寄存器的可见部分也就是段选择符,以ds段为例段选择符为0x0023,如图所示:

  2. 对0x0023按照段选择符的结构进行拆分,可以得到如下信息:
    0x00230000 0000 0010 0011 => 0 0000 0000 0100 0 11
    RPL11 => 3 => Ring 3级别
    TI0 => 0 => 查询GDT表
    Index0 0000 0000 0100 => 4 => 索引为4
  3. 使用windbg运行命令,查看GDTR寄存器中GDT表的基地址和限长:

  4. 访问GDT表,查看索引为4的段描述符:

快速定位段描述符在GDT表中的偏移

0x0023 >> 3 = 4    去除RPL和TI位,得到Index
4 * 8 = 0x20    每个段描述符占8字节,所以乘以8

上面的运算可以转换为如下的形式:

0x0023 >> 3 * 8  = 0x0023 >> 3 << 3  = 0x0023 & 0xFFF8

也就是说 只需要用段选择符的最后一个数字与8做与运算 再拼接前面的数字就是偏移了

0x23:3 & 8 = 0  拼接前面的数字2  偏移就是0x20
0x2B:B & 8 = 8  拼接前面的数字2  偏移就是0x28
0x28:8 & 8 = 8  拼接前面的数字2  偏移依然是0x28 同样的段选择符只不过请求的权限不同

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值