lyl610abc 发表于 2021-4-12 13:53

保护模式笔记三 段描述符和段选择子

本帖最后由 lyl610abc 于 2021-4-12 15:41 编辑

# 前言

所有保护模式索引链接:[保护模式笔记一 保护模式介绍](https://www.52pojie.cn/thread-1415096-1-1.html)

先前了解了段寄存器,现在继续深入学习段寄存器

------

# 段描述符

## 引出问题

首先要解决的就是上个笔记遗留下来的问题:

```asm
mov bx,ds                //将段寄存器ds的Selector部分保存到bx(ecx的低16位)
mov ax,cs                //将段寄存器cs的Selector部分保存到ax(eax的低16位)
mov ds,ax                //将先前读出来的段寄存器去写ds这个段寄存器,也就是用cs段寄存器覆盖ds段寄存器
```

写寄存器是**对整个96位的段寄存器进行修改**,但是这里只给出了16位的段选择子Selector,**剩下的80位呢**

------

在回答问题之前,还需要了解两个结构:**GDT(全局描述符表)和 LDT(局部描述符表)**

**为什么要了解这两张表?**

因为当执行类似前面**对段寄存器进行修改的指令**:MOV DS,AX时,CPU**会先查表**,根据AX的值(段选择子)来决定查找GDT还是LDT

但在Windows中LDT并没有被使用,于是AX的值(**段选择子**)是用来决定**查询表中的哪个位置**

------

## GDT

### 什么是GDT

GDT全称:Global Descriptor Table,为全局描述符表,表中存储的数据项为**段描述符**

------

### GDT的数量

一个处理器对应一个GDT

------

### 定位GDT

大致了解了GDT是一张表,接下来则要定位到这张表,查看其内容

想要定位GDT表的位置,可以通过**gdtr寄存器**来定位

**gdtr寄存器**存储了GDT表的起始位置和GDT表的大小

------

#### 通过windbg定位GDT

通过在windbg中输入下列指令查看有关GDT的信息:

```
r gdtr                //读取gdt表的起始位置
r gdtl                 //读取gdt表的大小
```

------

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210411215712880.png)

------

得到了:

|          | GDT表的起始位置 | GDT表的大小 |
| -------- | --------------- | ----------- |
| 值       | 0x8003f000      | 0x3ff       |
| 数据宽度 | DWORD(4字节)    | WORD(2字节) |

------

得到了GDT表的起始位置后,就可以查看GDT表的内容了:

```
dq 0x8003f000
```

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210411221254633.png)

------

## 段描述符

知道了查询的表为GDT后,再说说GDT表存储的数据项:段描述符

------

### 什么是段描述符

段描述符顾名思义就是用来**描述段的信息**的,每个段对应一个段描述符

------

### 段描述符的数据宽度

每个段描述符的数据宽度为:64位=8字节(QWORD)

------

### 定位段描述符

**通过段选择子可以定位到对应的段描述符**

如何定位,则要先了解段选择子的结构

------

## 段选择子

### 什么是段选择子

段选择子顾名思义就是**用来选择段**的,通过段选择子可以定位到对应的段描述符

------

### 段选择子的结构

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210411222702417.png)

------

|          | Index | TI            | RPL                     |
| -------- | ----- | --------------- | ------------------------- |
| 含义   | 索引| 表指示器      | 请求特权等级            |
| 全称   | Index | Table Indicator | Requested Privilege Level |
| 数据宽度 | 13位| 1位             | 2位                     |

------

#### Index

索引,真正用来索引段描述符的数据

------

#### TI

表指示器,用来确定选择GDT(全局描述符表)还是LDT(局部描述符表)

|          | TI==0 | TI==1 |
| -------- | ----- | ----- |
| 选择的表 | GDT   | LDT   |

在Windows上并不使用LDT表,故TI恒等于0

------

#### RPL

请求的特权等级,会和请求的段描述符的特权等级进行比较,留作后续补充说明

------

### 根据段选择子定位段描述符

了解了段选择子的结构后,就可以通过段选择子来定位段描述符了

例子:以段选择子 = 0x001B为例

首先将段选择子转换为二进制 : 0000 0000 0001 1011

将其按段选择子的结构填入:

|          | Index            | TI      | RPL             |
| -------- | ---------------- | --------- | --------------- |
| 二进制值 | 0000 0000 0001 1 | 0         | 11            |
| 十进制值 | 3                | 0         | 3               |
| 含义   | 索引为3          | 查询GDT表 | 请求特权等级为3 |

得到的索引为3

拿到索引之后就可以定位对应的段描述符了

**对应的段描述符地址 = GDT表首地址 + 索引× 段描述符长度 =GDT表首地址 + 索引 × 8**(注意这里的单位为字节,64位=8字节)

所以:对应的段描述符地址 =0x8003f000 + 3×8= 0x8003f000 + 24 = 0x8003f000 + 0x18 = 0x8003f018

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210411225609994.png)

------

## 加载段描述符至段寄存器

除了MOV指令,还可以使用LES、LSS、LDS、LFS、LGS指令修改寄存器.

CS不能通过上述的指令进行修改,CS为代码段,CS的改变会导致EIP的改变,要改CS,必须要保证CS与EIP一起改,在后续的笔记会提到

------

下面以lds为例子,观察指令执行前后寄存器的变化

```c
#include <stdio.h>
#include <windows.h>

char buffer={0x44,0x33,0x22,0x11,0x1B,0x00};

int main(){
      
        _asm{
                push ds
                lds eax,fword ptr ds:        //fword为6字节
                pop ds
       
        }
      return 0;
}
```

------

### 下断点观察

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210412134301577.png)

------

### 执行前

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210412132910807.png)

------

### 执行后

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210412132938532.png)

------

### 对比执行前后

|      | EAX      | DS   |
| ------ | ---------- | ---- |
| 执行前 | 0xCCCCCCCC | 0x23 |
| 执行后 | 0x11223344 | 0x1B |

------

### 得出指令功能

LDS指令格式为:LDS OPRD1,OPRD2

OPRD1用来接收OPRD2的低(OPRD-2)字节

OPRD2的高2字节为段选择子,通过段选择子修改DS

其它指令:LES、LSS、LFS、LGS也是一样的格式,只不过修改的段寄存器不同罢了

------

## 内存寻址关系一览图

下面给出内存寻址的流程中,GDT、段描述符、段选择子的关系图:

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210412153414326.png)

------

以MOV EAX,DWORD PTR DS:为例

根据DS获得Segment Selector(段选择子):0x23(在[ 保护模式笔记二 段寄存器](https://www.52pojie.cn/thread-1415421-1-1.html)中获得的,不同机器可能不同)

根据地址获得Offset(偏移):0x123456

然后通过段选择子查询GDT(全局描述符表)得到对应的Segment Descriptor(段描述符)

通过段描述符可以得到Base(基地址)= 0 (DS段寄存器的Base为0)

最终要访问的内存地址为:Base+Offset = 0+0x123456=0x123456(期间也会根据段描述符进行一系列校验,这里暂且不提)

------

# 说明

该篇笔记主要介绍了如何**通过段选择子定位到对应的段描述符**并补充了**段选择子的结构和修改段寄存器的指令**

但关于段描述符的结构还没有深入介绍

前面引出的问题也尚未完全解决,通过前面的学习得知**段寄存器剩下的80位是通过段描述符来填充的**

但是段描述符的长度只有64位,如何填充80位?

这些都留作之后的笔记再作说明(づ ̄ 3 ̄)づ

yellowpeach 发表于 2021-4-15 11:18

我在52学代码

bestwars 发表于 2021-4-12 15:12

用心讨论,共获提升!感谢楼主无私奉献

辉哥j 发表于 2021-4-13 00:17

大佬威武!我是真服!

c259179 发表于 2022-8-18 11:52


大佬威武!谢谢!{:1_919:}

huchen 发表于 2024-3-8 14:28

请问,段选择子又是怎么确定的呢?如果是那些cs、ss等那些段寄存器来决定的话,咋看呀r cs吗?(在windbg中)

namesensen 发表于 2024-3-9 14:46

用心讨论,共获提升!感谢楼主无私奉献
页: [1]
查看完整版本: 保护模式笔记三 段描述符和段选择子