前言
所有保护模式索引链接:保护模式笔记一 保护模式介绍
先前了解了段寄存器,现在继续深入学习段寄存器
段描述符
引出问题
首先要解决的就是上个笔记遗留下来的问题:
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表的大小
得到了:
|
GDT表的起始位置 |
GDT表的大小 |
值 |
0x8003f000 |
0x3ff |
数据宽度 |
DWORD(4字节) |
WORD(2字节) |
得到了GDT表的起始位置后,就可以查看GDT表的内容了:
dq 0x8003f000
段描述符
知道了查询的表为GDT后,再说说GDT表存储的数据项:段描述符
什么是段描述符
段描述符顾名思义就是用来描述段的信息的,每个段对应一个段描述符
段描述符的数据宽度
每个段描述符的数据宽度为:64位=8字节(QWORD)
定位段描述符
通过段选择子可以定位到对应的段描述符
如何定位,则要先了解段选择子的结构
段选择子
什么是段选择子
段选择子顾名思义就是用来选择段的,通过段选择子可以定位到对应的段描述符
段选择子的结构
|
Index |
TI |
RPL |
含义 |
索引 |
表指示器 |
请求特权等级 |
全称 |
Index |
Table Indicator |
Requested Privilege Level |
数据宽度 |
13位 |
1位 |
2位 |
Index
索引,真正用来索引段描述符的数据
TI
表指示器,用来确定选择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
加载段描述符至段寄存器
除了MOV指令,还可以使用LES、LSS、LDS、LFS、LGS指令修改寄存器.
CS不能通过上述的指令进行修改,CS为代码段,CS的改变会导致EIP的改变,要改CS,必须要保证CS与EIP一起改,在后续的笔记会提到
下面以lds为例子,观察指令执行前后寄存器的变化
#include <stdio.h>
#include <windows.h>
char buffer[6]={0x44,0x33,0x22,0x11,0x1B,0x00};
int main(){
_asm{
push ds
lds eax,fword ptr ds:[buffer] //fword为6字节
pop ds
}
return 0;
}
下断点观察
执行前
执行后
对比执行前后
|
EAX |
DS |
执行前 |
0xCCCCCCCC |
0x23 |
执行后 |
0x11223344 |
0x1B |
得出指令功能
LDS指令格式为:LDS OPRD1,OPRD2
OPRD1用来接收OPRD2的低(OPRD-2)字节
OPRD2的高2字节为段选择子,通过段选择子修改DS
其它指令:LES、LSS、LFS、LGS也是一样的格式,只不过修改的段寄存器不同罢了
内存寻址关系一览图
下面给出内存寻址的流程中,GDT、段描述符、段选择子的关系图:
以MOV EAX,DWORD PTR DS:[0x123456]为例
根据DS获得Segment Selector(段选择子):0x23(在 保护模式笔记二 段寄存器中获得的,不同机器可能不同)
根据地址获得Offset(偏移):0x123456
然后通过段选择子查询GDT(全局描述符表)得到对应的Segment Descriptor(段描述符)
通过段描述符可以得到Base(基地址)= 0 (DS段寄存器的Base为0)
最终要访问的内存地址为:Base+Offset = 0+0x123456=0x123456(期间也会根据段描述符进行一系列校验,这里暂且不提)
说明
该篇笔记主要介绍了如何通过段选择子定位到对应的段描述符并补充了段选择子的结构和修改段寄存器的指令
但关于段描述符的结构还没有深入介绍
前面引出的问题也尚未完全解决,通过前面的学习得知段寄存器剩下的80位是通过段描述符来填充的
但是段描述符的长度只有64位,如何填充80位?
这些都留作之后的笔记再作说明(づ ̄ 3 ̄)づ