段
段知识点
- CS 代码寄存器 描述的代码
- SS 栈段 描述的栈 比如局部变量
- DS 数据段 堆地址 比如全局变量
- ES 扩展段 串copy movsb so edi esi
- FS 上下文环境段 R3: 代表TEB R0: 代表KPCR
段选择子
段选择子是一个16位的段的标识码。它并不直接指向段, 而是指向定义段的描述符。段选择子包含以下项目:
Index(索引): 第3位到第15位。选择GDT或LDT中8192个描述符中的某一个。处理器将索引值乘以8(段描述符的字节数), 然后加上GDT或LDT的基地址(分别在GDTR寄存器或者LDTR寄存器中)。
TI(table indivator) flag: 第2位。确定使用哪一个描述符表: 将这个标志置0, 则表示用GDT。将这个标志置1, 标识用LDT;
Request Privilege Level(RPL 请求的特权级): 第0位和第1位。确定选择子的特权级。特权级的范围是0到3, 0为最高特权级。
段描述符
段描述符
是GDT或LDT中的一个数据结构, 它为处理器提供诸如段基地址、段大小、访问权限以及状态等信息。段描述符
主要是由编译器、连接器、装载器或者操作系统/管理程序设立的, 而不是由应用程序产生的。
段描述符中的标志和域
段界限域: 指定段的大小。处理器将两个段界限域合在一起组成一个20位的段界限值。根据G标志位(粒度)的不同设置, 处理器按两种不同的方式解释段界限
- 如果粒度标志位为0, 则段大小可以从1字节到1M字节, 段长增量单位为字节(以字节为单位);
- 如果粒度标志位为1, 则段大小可以从4K字节到4G字节, 段长增量单位为4K字节(以页为单位 (0xFFFFF +1) * 4096(0x1000) = (0xFFFFF +1) * 4096(0x1000) - 1);
基地址域: 确定段的第一个字节(字节0)在4GB线性地址空间中位置。处理器将3个基地址域组合在一起构成了一个32位地址值。段基地址应当是16字节边界对齐的。16字节边界对齐不是必须的, 当这种段边界的对齐能够是程序把代码和数据对齐到16字节边界上而最大化其性能;
类型域: 指明段或门的类型, 确定段的访问权限和增长方向。如何解释这个域, 取决于该描述符是应用程序描述符(代码和数据)还是系统描述符。代码段、数据段和系统段对类型域有不同的编码
S(描述符类型)标志: 确定段描述符四系统描述符(S标记为0)或者代码、数据段描述符(S标记为1);
DPL(描述符特权级)域: 指明段的特权级。特权级从0到3, 0为最高特权。DPL用来控制对段的访问;
P(段有效位)标志: 指明段当前是否在内存中(1表示在内存中, 0表示不在)。当指向段描述符的段选择子被装进段寄存器时, 如果这个标志为0, 处理器会产生段不存在异常(#NP)。内存管理软件可以通过这个标志, 来控制在某个特定时间有哪些段是真正的被载入物理内存的。这是除分页之外的另一个虚拟内存控制机制;
D/B(默认操作数大小/默认栈指针大小和/或上限)标志: 根据段描述符所指的是可执行代码段、向下扩展的数据段还是堆栈段, 这个标志有不同的功能。(对32位的代码和数据段, 这个标志总是被设置成1, 而16位的代码和数据段, 这个标志总是被置为0)
可执行代码段: 这个标志被称为D标志, 它指明段中指令的有效地址和操作符的默认位数。如果该标志为1, 则默认32位的地址, 32位或者8位的操作符; 若为0, 则默认16位的地址, 16位或者8位的操作符。指令前缀66H可以指定操作符的长度而不使用缺省长度。前缀67H可用来指定地址值的长度。
堆栈段(SS寄存器所指的数据段): 这个标志位被称为B(大的)标志, 它为隐含的栈操作(如push、pop和call)确定栈指针的大小。如果该标志为1, 则使用32位的栈指针, 栈指针放在32位的ESP寄存器中; 如该标志为0, 则使用16位的栈指针, 栈指针存放在SP寄存器。 如果堆栈段为一个向下扩展的数据段, 则B标志还确定堆栈段的地址上界。
向下扩展的数据段: 这个标志称为B标志, 确定段的地址上界。如果该标志为1, 则段地址上界为FFFFFFFFH(4GB); 若该标志为0, 则段地址上界为FFFFH(64KB)。
G(粒度)标志: 确定段界限扩展的增量。当G标志为0, 则段界限以字节为单位扩展; G标志为1, 则段界限以4KB为单位扩展。(这个标志不影响段基址的粒度, 段基址的粒度永远都是字节)如果G标志为1, 那么当检测偏移是否超越段界限时, 不用测试偏移的低12位。
代码实验
1.段存在权限限制
1 | // Segments.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 |
作业
拆分GDTR
1 | kd> dq 80b98800 L30 |
00cf9b00`0000ffff
Base Limit Type S DPL P AVL 0 D\B G 00000000 fffff b 1 0 1 0 0 1 1
00cf9300`0000ffff
Base Limit Type S DPL P AVL 0 D\B G 00000000 fffff 3 1 0 1 0 0 1 1
00cffb00`0000ffff
Base Limit Type S DPL P AVL 0 D\B G 00000000 fffff b 1 3 1 0 0 1 1
00cff300`0000ffff
Base Limit Type S DPL P AVL 0 D\B G 00000000 fffff 3 1 3 1 0 0 1 1
80008bb9`8c0020ab
Base Limit Type S DPL P AVL 0 D\B G 80b98c00 000020ab b 0 0 1 0 0 0 0
804093b9`b0004fff
Base Limit Type S DPL P AVL 0 D\B G 80b9b000 4fff 3 1 0 1 0 0 1 0
0040f300`00000fff
Base Limit Type S DPL P AVL 0 D\B G 00000000 00fff 3 1 3 1 0 0 1 0
0000f200`0400ffff
Base Limit Type S DPL P AVL 0 D\B G 00000400 ffff 2 1 3 1 0 0 0 0
00cff300`0001ffff
Base Limit Type S DPL P AVL 0 D\B G 00000001 fffff 3 1 3 1 0 0 1 1
800089b9`ad200067
Base Limit Type S DPL P AVL 0 D\B G 80b9ad20 0067 9 0 0 1 0 0 0 0
800089b9`acb00067
Base Limit Type S DPL P AVL 0 D\B G 80b9acb0 0067 9 0 0 1 0 0 0 0
800092b9`880003ff
Base Limit Type S DPL P AVL 0 D\B G 80b98800 03ff 2 1 0 1 0 0 0 0
800089b9`ad900067
Base Limit Type S DPL P AVL 0 D\B G 80b9ad90 0067 9 0 0 1 0 0 0 0
D/B标志知识点
D/B标志
- 当D/B位作用于代码段时为D位, 当作用于数据段时为B位;
- 当描述代码段时, 默认操作数将会改变成16位的段;
- 当描述堆栈段时, 堆栈寻址将变成16位;
- 当描述普通数据段时, 没有任何反应;
代码实验
1 | // 描述代码段时 |
TYPE域知识点
当段描述符中S标志(描述符类型)为1时, 描述符为代码段描述符或者数据段描述符。类型域的最高位(段描述符的第二个双字的第11位)将决定该描述符为数据段描述符(为0)还是代码段描述符(为1)。
代码和数据段描述类型
对于数据段而言, 描述符的类型域的低3位(第8、9、10位)被解释为访问控制(A)、是否可写(W)、扩展方向(E)。数据段可以是只读或者可读写的段, 这取决于”是否可写”的标志。
堆栈段必须是可读写的数据段。将一个不可写的数据段的选择子置入SS寄存器会导致一般保护异常(#GP)。如果堆栈段的大小需要动态变化, 可以将其置为向下扩展数据段(扩展方向标志为1)。这里, 动态改变段界限将导致栈空间朝着栈底部空间扩展。如果栈的长度保持不变, 堆栈段可以是向上扩展的, 也可以是向下扩展的。
访问位表示自最后一次被操作系统清零后, 段是否被访问过。每当处理器将段的段选择子置入段寄存器时, 就将访问位置为1。该位一直保持1直到被显式清零。该位可以用于虚拟内存管理和测试。
对于代码段而言, 类型域的低3位被解释为访问位(A)、可读位(R)、一致位(C)。根据可读位的设置, 代码段可以为”只执行”或者”可执行可读”。当有常量或者其它静态数据与指令代码一起在ROM中时, 必须使用”可执行可读”的段。要从代码段读取数据, 可以通过带有CS前缀的指令或者将代码段选择子置入数目段寄存器(DS、ES、FS或者GS)。在保护模式下, 代码段是不可写的。
代码段可以是一致性的, 也可以是不一致性的。转入一个特权级更高的一致性段的进程可以在当前特权级继续执行下去。除非使用了调用门或者任务门, 否则, 转入一个不同特权级的非一致性段将使处理器产生一个”一般保护异常”(#GP)。不访问受保护程序的系统程序和某些类型的异常处理程序(比如除法错误或者溢出)可以被载入一致性的代码段。不能被特权级更低的程序和过程访问的程序应该被载入非一致性的代码段。
注意
无论目标段是否为一致性代码段, 进程都不能因为call
或jump
而转入一个特权级较低(特权值较大)的代码段执行。试图进行这样的执行转换将导致一个一般保护异常(#GP);
所有数据段都是非一致性的, 这就意味着数据段不能被更低特权级的进程访问(特权级数值较大的执行代码)。然而, 和代码段不同, 数据段可以被更高优先级的程序或过程(特权级数值较小的执行代码)访问, 不需要使用特别的访问门.
如果GDT或者LDT中的段描述符被放置在ROM中, 当程序或者处理器试图更改在ROM中的段描述符时, 处理器将进入一个死循环。为了防止此类问题的发生, 可以将所有放置在ROM中的段描述符设置访问位。同时, 删除所有操作系统代码中试图更改放置在ROM中的段描述符的代码。
段控制内存访问方向知识点
代码实验
1 | // 1. 修改gdtr中0x48位置为00cff600`0000ffff(type域为6表示读/写, 内存向下扩展) |
- 非一致代码段: 应用层不能调用内核层代码;
- 一致代码段: 应用层可以直接调用CS描述的一致代码段;