保护模式笔记三 段描述符和段选择子
本帖最后由 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 ̄)づ 我在52学代码 用心讨论,共获提升!感谢楼主无私奉献 大佬威武!我是真服!
大佬威武!谢谢!{:1_919:} 请问,段选择子又是怎么确定的呢?如果是那些cs、ss等那些段寄存器来决定的话,咋看呀r cs吗?(在windbg中) 用心讨论,共获提升!感谢楼主无私奉献
页:
[1]