关于段

什么是分段机制

分段机制就是把虚拟地址空间中的虚拟内存组织成一些长度可变的称为段的内存块单元。

什么是段

每个段由三个参数定义:段基地址、段限长和段属性。段的基地址、段限长以及段的保护属性存储在一个称为段描述符的结构项中。

段的作用

段可以用来存放程序的代码、数据和堆栈,或都用来存放系统数据结构。

段的存储地址

系统中所有使用的段都包含在处理器线性地址空间中。

关于寄存器

8086

在 8086 中,段基址存放在段寄存器中,它的寻址方式为 段基址 * 16 + 段内偏移,它有 16 根地址总线,所以段的最大长度为 16 KB,它还有 20 根数据总线,因此它能够访问到 4GB 的内存空间。顺便在这里展示下 8086 中所有寄存器,如下:
在 16 位汇编下,访问一个内存地址使用的是如下的形式:

1
mov ax, ds:[0x120]

它代表着从 ds * 16 + 0x120 这个位置取出数据放入 ax 中。

80286

80286 处理器是 Intel 在 1982 年推出的,它第一次提出了保护模式。在保护模式下,段寄存器中存储的不再是段基址,而是段选择子。真正的段基址存储在描述符高速缓存器中,80286 处理器访问内存,不需要段寄存器左移再加上偏移。但是由于 80286 还是 16 位寄存器,保护模式下,段的长度依然不能超过 64KB,妨碍了它的发展,16 位保护模式也就逐渐被人们淡忘。

80386

80386 处理器是 Intel 于 1985 年推出的,这是第一款 32 位处理器,是后续所有 32 位产品的基础。在刚加电时,处理器都自动运行在实模式,在经过一番设置后,才会进入保护模式,从 80386 起的 32 位 CPU 都称之为 x86 体系,在 x86 体系下的 CPU,都支持三种模式:

  • 实模式:兼容 16 位 CPU 模式。
  • 保护模式:操作系统所在的模式。
  • 虚拟的 8086 模式:可以模拟多个 8086 执行任务。

段寄存器

8086 下的段寄存器为 16 位,共 4 个:CS、DS、ES、SS,在 32 位处理器,新增了两个:FS、GS,32 位下的这 6 个寄存器又分为两部分,可见部分和不可见部分。不可见部分称之为描述高速缓冲器,用来存放段的基地址,范围、属性。另外,我们无法访问这些不可见部分,它是由处理器内部使用。
我们能够读取和设置的只能是可见部分,那么不可见部分的值是从哪来的呢?

段选择子

段寄存器可见部分存储的值我们称之为段选择子,共 16 位,如下所示:
段寄存器不可见部分的值,来自于一个叫做描述符表的数组,可见部分的高 13 位是数组的下标。当寄存器被赋值的时候,实际上会从描述符表中的一个描述符中读取数据,将数据加载到段寄存器中的不可见部分。上图中的 TI 位表明查找的是全局描述符表 GDT,还是局部描述符表 LDT,windows 系统并没有使用局部描述符表 LDT。
TI 位为 1 时,表示的是局部描述符表 GDT,当 TI 位为 0 时,表示的是全局描述符表 GDT。RPL 指的是请求的权限等级。用作权限检查. 一共有3个值: 0~2 , 数值越小,权限越大, 0代表最高权限。描述符表的数组的地址存储在 GDTR 寄存器中。

段描述符

下图为通用段描述符:
段描述符是 GDT 和 LDT 中的一个数据结构项,用于向处理器提供一个段的位置、大小以及访问控制的状态信息。每个段描述符的长度是 8 个字节,含有 3 个主要字段:

  • 段基地址。
  • 段限长。
  • 段属性。

基地址字段 Base:描述了一个段的起始位置,由三部分组成,一共 32 位,段基地址可以是 0 ~ 4GB 范围内的任意地址。

G 字段:G 为粒度位,当为 0 时,段限长单位是字节;当为 1 时,段限长单位是 4KB。

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

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

含义
S 位 为 1 代表这是一个代码段或者数据段,为 0 代表这是一个系统段。
TYPE 域 一共 4 位,在 S 位不同时,代表的含义不同。

L 字段:是 64 位代码的标志,保留此位给 64 位处理器使用。目前,只需将此设置 0 即可。

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

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

数据段和代码段

段分为数据段和代码段,由 S 与 TYPE 决定,如下图所示:
数据段描述符如下图所示:
B 位:当 B 为 0 时,内存寻址是 16 位,堆栈使用 SP,段的最大大小为 64KB,当 B 为 1 时,内存寻址是 32 位,堆栈使用 ESP,段的最大大小为 4GB。
A 访问位:表示该位最后一次被操作系统清零后,该段是否被访问过,每当处理器将该段选择符置入某个段寄存器时,就将该位置为 1。
W 是否可写:当 W 为 0 时,表示段不允许写入,否则会引发处理器异常中断;当 W 为 1 时,表示允许写入。
E 扩展方向:当 E 为 0 时,表示向上扩展,逻辑地址中的偏移值范围可以从 0 到 段限长;当 E 为 1 时,表示向下扩展,逻辑地址中的偏移范围可以从段限长到 0xFFFF 或者 0xFFFF_FFFF。
代码段描述符如下图所示:
D 位:默认操作数大小。当 D 为 0 时,默认值大小为 16 位地址和 16 位或者 8 位的操作数;当 D 为 1 时,默认值是 32 位地址和 32 位或者 8 位的操作数。
A 位:与数据描述符中的 A 相同。
R 位:表示是否可读位,堆栈段必须 R = 0,代码段不可读,只能执行;R = 1,代码段可读,可执行;在保护模式下,代码段是不可写的。
C 位:当 C 等于 0 时,表示非一致性代码段,这样的代码段可以被同级代码段调用,或者通过门调用;当 C 等于 1 时,表示一致性代码段,可以从低特权级的程序转移到该段执行(但是低特权级的程序仍然保持自身的特权级)。

段权限检查

当我们给段寄存器赋值的时候,实际上是从 GDT 中获取相应的描述符加载到段寄存器的不可见部分。在这个过程中有一个权限检查问题,请注意,在你给段寄存器赋值的时候,就进行段权限检查了,如果成功,就说明你可以访问这个段了。权限检查需理解三个概念:

  • CPL:当前代码的执行权限,CS 段的 B0 和 B1;
  • DPL:存在于段描述符中,描述了访问本段的内存所需要的权限;
  • RPL:存在于段寄存器加载的段选择子中,描述了访问者使用什么样的权限对目标进行了访问;
    对于数据段和代码段权限检查有不同的检查方式。

数据段检查

代码段权限检查

你要修改代码,实际上就是要修改程序执行位置,有以下一些指令可以做到这一点:JMP、CALL、RET、INTJMP、CALL、RET 指令段内跳不会产生检查,当进行段间跳则会进行权限检查。

留言

⬆︎TOP