3-段选择子与段描述符结构

    xiaoxiao2022-06-29  43

    看完了上一篇,我已经猜到了你一脸懵逼的表情。什么段寄存器,什么段选择子,什么段描述符,你这讲的都是啥啊!!!

    0. 回顾

    先来回顾一下,上一篇讲解了段寄存器,它是 CPU 中的一块存储数据的地方,共有 96 位。然后我们只能看得见其中的 16 位,剩下的 80 位是被隐藏起来的。本篇要讲的就是这 80 位的来源。

    1. 段描述符和段选择子

    我想 C 语言你应该是学了很久了,那么我来定义一个数组。

    // 一个 QWORD 是一个 8 字节的整数 QWORD gdt[1024];

    很明显,这是一个能容纳 1024 个元素的数组。现在我来定义:

    gdt 数组中的每个元素都是一个段描述符数组的索引号是段选择子这个 gdt 数组被称为 gdt 表

    只不过……,只不过这个段选择子,可能不会直接就表示成你想要的索引号,0就是0,5就是5,它稍微有些区别。

    另外,段描述符,就是一个 8 字节的整数,可是这个整数,包含的信息量有点大。后面我们要做的,就是破译这个整数。

    既然如此,后文自然是重点解析段选择子和段描述符,打通通往操作系统之路。

    2. 段描述符与段选择子的结构

    2.1 段选择子结构

    段选择子就是一个数字,一共有16位,结构如下:

    | 1 | 0 | 字节 |7654321076543 2 10| 比特 |-------------|-|--| 占位 | INDEX |T|R | 含义 | |I|P | | | |L | INDEX:在GDT数组或LDT数组的索引号TI:Table Indicator,这个值为0表示查找GDT,1则查找LDTRPL:请求特权级。以什么样的权限去访问段。

    2.2 段描述符结构

    | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 字节 |76543210|7 6 5 4 3210 |7 65 4 3210|76543210|76543210|76543210|76543210|76543210| 比特 |--------|-|-|-|-|---- |-|--|-|----|--------|--------|--------|--------|--------| 占位 | BASE |G|D|0|A|LIMIT|P|D |S|TYPE|<------- BASE 23-0 ------>|<-- LIMIT 15-0 ->| 含义 | 31-24 | |/| |V|19-16| |P | |B| |L| | |L | BASE: 段基址,由上图中的两部分(BASE 31-24 和 BASE 23-0)组成G:LIMIT的单位,该位 0 表示单位是字节,1表示单位是 4KBD/B: 该位为 0 表示这是一个 16 位的段,1 表示这是一个 32 位段AVL: 该位是用户位,可以被用户自由使用LIMIT: 段的界限,单位由 G 位决定。数值上(经过单位换算后的值)等于段的长度(字节)- 1。P: 段存在位,该位为 0 表示该段不存在,为 1 表示存在。DPL:段权限S: 该位为 1 表示这是一个数据段或者代码段。为 0 表示这是一个系统段(比如调用门,中断门等)TYPE: 根据 S 位的结果,再次对段类型进行细分。

    段描述符安装在 GDT 或者 LDT 数组中,可以在 WinDbg 中使用命令 r gdtr来查看 GDT 基址在哪里。

    有关段描述符属性的具体细节,后文陆续给出。

    Windows 操作系统并没有使用 LDT 数组。所以后面查的表基本上是 GDT。

    3. 实验——查看段描述符

    在 WinDbg 中调试 xp

    图1 点这个按钮,中断到 WinDbg

    图2 查看 GDT 数组

    r gdtr 查看 GDT 表的基址,使用 dq 表示以 8 字节单元显示内存单元的数据。命令 r 表示显示寄存器的值,gdtr 也是 CPU 中的一个寄存器,它保存了 GDT 的基址。

    4. 修改段寄存器

    mov 指令修改段寄存器

    例1:

    mov ax, 0x20 mov ds, ax

    例2:

    mov ax, 0x10 mov ds, ax

    上面这两个例子直接在 OD 中写会比较方便。具体操作方法是先用 OD 随意打开一个 exe 文件,然后双击第一行,就可以改代码了。执行的时候,选择单步执行,快捷键是 F8. 注意,是虚拟机里的 OD。以后的实验也是。如果你还不习惯 OD,上面的代码也可以放到 VC6.0 中,注意,使用 __asm {} 把汇编代码包围起来。

    lds, les, lfs, lgs, lss

    除了可以使用 mov指令修改段寄存器,也可以使用les、lds等指令修改段寄存器。 代码:

    int main() { char buffer[6] = {0}; __asm { // 高 2 字节加载到 ES 寄存器,低 4 字节复制到 ecx 寄存器。fword 表示 6 字节。 // LDS/LSS/LFS/LGS 用法是类似的。没有LCS指令,要修改CS,需要使用其它指令,这里就不给出。 // 这行指令是有坑的,不一定可以执行成功,取决于buffer中的值。 les ecx, fword ptr ds:[buffer] } return 0; }

    上面的代码,可以在虚拟机的 VC 6.0 中进行。

    5. 如何把段描述符填充到段选择子

    在第1篇中讲到过,段寄存器一共有 96 位,其中 16 可见部分来源于段选择子的索引部分。剩下 80 位来源于 GDT 表。

    下面来分析一下,如何把 0x1B 、0x23 这两个选择子对应的描述符填充到段寄存器。

    做这个练习的时候,先不要问这些字段是什么含义,只要把这些字段的值查出来就行了。

    原始数据:

    |--地址--|-------------16进制值---------------| 8003f000 00000000`00000000 00cf9b00`0000ffff 8003f010 00cf9300`0000ffff 00cffb00`0000ffff 8003f020 00cff300`0000ffff 80008b04`200020ab 8003f030 ffc093df`f0000001 0040f300`00000fff 0x1B 0x1B = 0000 0000 0001 1011b 索引号:0000 0000 0001 1= 3 (查找gdt[3]) RPL: 11b = 3 TI: 0 (查找 GDT 表) 查找到的 GDT 描述符为:gdt[3] = 00cffb00`0000ffff 段寄存器结构: selector = 0x001B attribute = 0xcffb (G = 1 DB = 1 P = 1 DPL = 3 S = 1 TYPE = 1011(非一致代码段,可读已访问过)) base = 0x00000000 limit = 0xffffffff 0x23 0x23 = 0000 0000 0010 0011b 索引号:0000 0000 0010 0 = 4 TI: 0 (查找 GDT 表) RPL: 11b = 3 查找到的 GDT 描述符为:gdt[4] = 00cff300`0000ffff 段寄存器结构: selector = 0x23 attribute = 0xcff3 (G = 1 DB = 1 P = 1 DPL = 3 S = 1 TYPE = 0011(可读可写向上扩展的数据段)) base = 0x00000000 limit = 0xffffffff

    这里最麻烦的应该是分析 limit 了。

    5.1 如何分析 limit

    limit 的含义是这个段的大小。实际上这么说的点不准确。limit 应该描述为,段大小再减去1字节。(这里的 limit 是换算后的 limit)。后面我用大写的 LIMIT 表示段描述符中的 20bit LIMIT。

    如果粒度 G=0,LIMIT= 0x3ff,这意味着该段的大小是 0x3ff+1=0x400 字节。如果 G=1,那意味着该段的大小是(0x3ff+1)*4KB=0x400000字节,所以换算后的 limit = 0x400000-1=0x003fffff.

    再举个例子。LIMIT=0xfffff, G=1,则该段的大小是 (0xfffff+1)*4KB=0x100000*0x1000=0x100000000字节,所以换算后的 limit=0x100000000-1=0xffffffff

    limit 简算法

    如果 G = 0,把段描述符中的 20 bit LIMIT取出来,比如 0x003ff,然后在前面补 0 至32bit,即 limit = 0x000003ff. 如果 G=1,把段描述符中的 20 bit LIMIT取出来,比如 0x003ff,然后在后面补 f 至 32bit, 即 LIMIT = 0x003fffff

    6. 总结

    本篇主要讲解了段寄存器 中的数据来源。上篇实验中,给出了几个实验,当时我们只是把另一个段寄存器中的数据读入到寄存器 ax(16bit),然后把 ax 代入到了 ds,可是 ax 明明只有 16 位啊,而 ds 有 96 位。

    CPU必然在背后帮我们做了一些事情,它从 GDT 表中取出对应的段描述符,经过分析后自动的填写的了段寄存器中。这个过程,是需要大家深刻理解和掌握的。

    转载请注明原文地址: https://ju.6miu.com/read-1125409.html

    最新回复(0)