保护模式概述

虚拟内存有什么含义?

有如下的两种含义:

1.x86进程的4GB空间,x64进程的8TB空间

2.整个计算机的虚拟内存(内存映射)

为什么会存在虚拟内存

windows系统是多任务轮询系统,物理内存不足,主要是为了解决内存空间不足的问题

控制转移

R0和R3不同特权级之间的跳转---调用门,中断门,陷阱门

中断异常

硬中断,软异常

256个中断向量号

输入输出

IOPL、IO允许位、优先级

特权指令

R0权限才可以使用的指令

所有的规则都是CPU厂商规定的规则



X86 CPU的三个模式:

实模式,保护模式,x86模式


 

实模式

保护模式

虚拟8086模式

内存寻址方式

段式寻址

支持内存分页和虚拟内存

段式寻址

寻址范围

任意寻址

有限

任意寻址

举例系统

dos系统

Windows系统

现在的操作系统大多都是处于保护模式之下的

保护模式的定义

基于安全性和稳定性的考量而产生的一种CPU模式

保护模式与其他模式的根本区别是什么?

所谓保护,就是对权限做出规定,根本在于进程内存受到了保护

保护模式的分类

段保护模式,页保护模式

为什么要有段保护呢?

多任务轮询,代码和数据分开,提高访问的效率

分页机制的目的是什么?

解决计算机内存不足的问题,避免内存碎片化

段寄存器

以下三种是附加段寄存器

ES:

FS:指向线程环境块的首地址TEB,在创建线程时创建的,用于支持操作系统的相关功能

GS:指向进程环境块的首地址PEB

在80386中的段寄存器中存储的是16位的段选择子

段寄存器的结构

组成

Base

Limit

Attribute

Selector

数据宽度

32位

32位

16位

16位

是否可见

不可见

不可见

不可见

可见

描述

基地址(当前段的起始地址)

大小限制(当前段的整个长度)

属性(当前段是否可读可写可执行)

段选择子

struct Segment{
    WORD Selector;
    WORD Attribute;
    DWORD Base;
    DWORD Limit;
}

在OD中查看段寄存器的结构信息

段寄存器

Selector

Attribute

Base

Limit

ES

002B

可读、可写

0

0xFFFFFFFF

CS

0023

可读、可执行

0

0xFFFFFFFF

SS

002B

可读、可写

0

0xFFFFFFFF

DS

002B

可读、可写

0

0xFFFFFFFF

FS

0053

可读、可写

258000

0xFFF

GS

002B

-

0

0xFFFFFFFF

段寄存器的读写

使用mov指令完成相关操作

读段寄存器
#include <stdio.h>
#include <windows.h>
int main() {
    WORD  selector1, selector2, selector3, selector4, selector5 = 0;
    _asm {
        mov selector1, cs
        mov selector2, ds
        mov selector3, es
        mov selector4, fs
        mov selector5, gs
    }
    printf("%x %x %x %x %x\n", selector1, selector2, selector3, selector4, selector5);
    return 0;
}

对寄存器的读操作,只能够读取寄存器的16位的selector选择子的部分

写段寄存器

以下是函数的核心代码

                push ds         //保存ds段寄存器
                mov ax,cs       //将cs段寄存器的段选择子赋值给ax
                mov ds,ax       //使用cs段寄存器覆盖ds段寄存器
                mov word ptr ds:[data],ax      
                pop ds          //还原ds段寄存器

编译时出现异常

为什么会报错呢?

因为此时ds段寄存器已经被覆盖为了CS段寄存器的值,CS的权限是可读可执行,没有可写

即段寄存器的权限不足导致的,

(在赋值时使用DS段寄存器来赋值,而不使用CS寄存器)

如果使用同样具有写权限的SS段寄存器堆DS段寄存器进行重新赋值,尝试运行如下

mov ax, ss      
mov ds, ax      
mov word ptr ds:[data],ax   

编译器能成功编译上述代码,并且程序运行过程中没有报错

上述代码证明,段寄存器的Attribute在写入时会被更改!

LIMIT属性

由上述的表中得知FS段的limit值为0xFFF

#include <stdio.h>
#include <windows.h>
int main(){

    unsigned char base;
    _asm{   

        mov al,fs:[0x1000]      //超过limit:0xfff,无法正常运行
        mov base,al
    }
    printf("%x\n",base);
    return 0;
}

编译成功但程序运行时报错

修改limit为0xFFF之内的值

程序正常运行

那么其他的值是否会发生改变呢?

此处需要注意的是

在读段寄存器时,得到的是16位的段选择子的值

在写段寄存器时,是对整个96位的段寄存器进行修改



GDT

如何确定全局描述符表的位置?

GDTR寄存器

在windbg中使用一下指令

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


得到了:

GDT表的起始位置

GDT表的大小

0x8003f000

0x3ff

数据宽度

DWORD(4字节)

WORD(2字节)


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

复制代码 隐藏代码
dq 0x8003f000

表中的存储项是什么呢?

段描述符:用来描述段的信息的,每个段对应一个段描述符

如何定位段描述符呢?

通过段选择子来定位

段选择子的结构


 

Index

TI

RPL

含义

索引

表指示器

请求特权等级

全称

Index

Table Indicator

Requested Privilege Level

数据宽度

13位

1位

2位

相关的属性说明如下

Index:在GDT中定位段描述符的索引

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

加载段描述符至段寄存器

加载段描述符至段寄存器的方法有以下几种:

1. 使用指令LDS(Load Segment)加载段描述符至DS(Data Segment)寄存器。

2. 使用指令LES(Load Effective Address)加载段描述符至ES(Extra Segment)寄存器。

3. 使用指令LSS(Load Segment Selector)加载段描述符至SS(Stack Segment)寄存器。

4. 使用指令LFS(Load Far Pointer to DS)加载段描述符至FS(File Segment)寄存器。

5. 使用指令LGS(Load Far Pointer to DS)加载段描述符至GS(General Segment)寄存器。

6. 使用指令LTR(Load Task Register)加载段描述符至TR(Task Register)寄存器。

注意:CS不能使用上述指令进行修改,因为CS不可写

以上汇编指令的具体使用

LDS,LES,LFS,LGS,LSS其指令格式都是

LDS reg16,mem32

其意义是同时给一个段寄存器和一个16位通用寄存器同时赋值

具体如下:reg16=mem32的低字,DS=mem32的高字

例如、

地址 100h 101h 102h 103h

内容 00h 41h 02h 03h

如果指令 LDS AX,[100h]

则结果为 AX=4100h DS=0302h

具体例子

#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

0x0023

执行后

0x11223344

0x001B


内存关系寻址图:

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



段寄存器剩下的80位是怎么进行赋值的呢?

段寄存器剩下的80位是通过段描述符来填充的

段描述符的大小是8字节

段描述符的结构


如何将段描述符中的数据加载到段寄存器中的呢?数据有什么对应关系呢?

段寄存器组成

Base

Limit

Attribute

数据宽度

32位

32位

16位

描述

基地址(当前段的起始地址)

大小限制(当前段的整个长度)

属性(当前段是否可读可写可执行)

对应段描述符

段描述符中的3段base

段描述符中的2段limit和G位共同决定

对应位

高4字节:24~31 0~7 ,低4字节:16~31

高4字节:16~19,低4字节:0~15

高4字节:8~23

加载前,首先就是要检查段描述符的P标志位

接着判断粒度标志位G

会发现这里的Limit分为了两段,但两段的总长度也只有4+16=20位,和段寄存器中的Limit的32位不匹配

这里的Limit的最大表示范围是:0~2的20次方-1=0~0xFFFFF

而段寄存器中的Limit的最大表示范围是:0~2的32次方-1=0~0xFFFFFFFF

很显然现在的Limit无法表示段寄存器中的Limit了,为了解决这个问题,引入了G位:粒度(用来表示Limit的单位)

G==0

G==1

含义

limit以字节为单位

limit以4096=2的12次方 字节为单位

表示范围

0~0xFFFFF

0xFFF~0xFFFFFFFF

也就是说:G位决定了limit的单位

S位

S位用来标记当前描述符的类型

S==0

S==1

含义

系统段描述符

代码段或数据段描述符

段描述符可以分为系统描述符、代码段描述符和数据段描述符

根据S位的不同(描述符类型的不同)对应的Type域也不同

也就是说:S位决定了Type域的含义


Type域

Type占据了第11位到第8位,共4位


S位为1时

当S==1时,该段描述符为代码段描述符或者数据段描述符

十进制

11

10

9

8

描述符类型

描述

E(是否向下扩展)

W(是否可写)

A(是否可访问)

0

0

0

0

0

数据段描述符

只读

1

0

0

0

1

数据段描述符

只读,可访问

2

0

0

1

0

数据段描述符

可读可写

3

0

0

1

1

数据段描述符

可读可写可访问

4

0

1

0

0

数据段描述符

只读,向下扩展

5

0

1

0

1

数据段描述符

只读,向下扩展,可访问

6

0

1

1

0

数据段描述符

可读可写,向下扩展

7

0

1

1

1

数据段描述符

可读可写,向下扩展,可访问

C(是否可以从较低的特权级别调用)

R(是否可读)

A(是否可访问)

8

1

0

0

0

代码段描述符

只可执行

9

1

0

0

1

代码段描述符

只可执行,可访问

10

1

0

1

0

代码段描述符

可执行可读

11

1

0

1

1

代码段描述符

可执行可读,可访问

12

1

1

0

0

代码段描述符

只可执行,可从较低特权级别调用

13

1

1

0

1

代码段描述符

只可执行,可从较低特权级别调用,可访问

14

1

1

1

0

代码段描述符

可执行可读,可从较低特权级别调用

15

1

1

1

1

代码段描述符

可执行可读,可从较低特权级别调用,可访问


主要说明一下向下扩展和非向下扩展(向上扩展)

向上扩展就是:该段描述符所描述的地址的有效范围是从Base开始到Base+Limit结束

为下图中的红色部分:


与之相反,向下扩展则是:该段描述符所描述的地址的有效范围除了 从Base开始到Base+Limit结束 之外的部分

为下图中的红色部分


S位为0时

当S==0时,该段描述符为系统描述符

系统描述符有分为以下类型:


十进制

11

10

9

8

描述

0

0

0

0

0

保留

1

0

0

0

1

16位 任务状态段TSS(可用)

2

0

0

1

0

LDT

3

0

0

1

1

16位 任务状态段TSS(繁忙)

4

0

1

0

0

16位 调用门

5

0

1

0

1

任务门

6

0

1

1

0

16位 中断门

7

0

1

1

1

16位 陷阱门

8

1

0

0

0

保留

9

1

0

0

1

32位 任务状态段TSS(可用)

10

1

0

1

0

保留

11

1

0

1

1

32位 任务状态段TSS(繁忙)

12

1

1

0

0

32位 调用门

13

1

1

0

1

保留

14

1

1

1

0

32位 中断门

15

1

1

1

1

32位 陷阱门


Type域决定了当前段的属性


D/B位

D/B位分别作用于3种情况:

  1. 对CS段的影响
  2. 对SS段的影响(SS段也属于数据段)
  3. 对向下扩展的数据段的影响

D/B == 0

D/B == 1

CS段

采用16位寻址方式

采用32位寻址方式

SS段

使用16位堆栈指针寄存器SP

使用32位堆栈指针寄存器ESP

向下扩展的数据段

段上限为64KB

段上限为4GB


主要说明一下对 向下扩展的数据段的影响

D/B位为1时


D/B位为0时


DPL

DPL=Descriptor privilege level(描述符特权等级)为:访问这个描述符所需的特权级别(环ring)



CPU权限分级的作用

将系统权限和应用程序权限更好的分离出来,让操作系统更好的管理当前的系统资源

让系统更加的稳定:比如说普通的程序崩溃导致的结果是程序未响应或者停止运行,但不会影响到系统的正常运行

但是当驱动程序出现问题之后,就容易出现BSOD的问题

8086中的实模式程序在级别0(最高特权级别)上执行,而8086中的虚拟模式在级别3执行所有程序

如何去判断一个程序的特权级别

CPL是段寄存器CS和SS段选择子的后两位,也就是说当段寄存器为CS和SS时,其段选择子的RPL就是CPL

通过OD附加应用程序后查看其段寄存器:


段寄存器

段选择子(Selector)

二进制段选择子

二进制RPL

十进制RPL

ES

23

0010 0011

11

3

CS

1B

0001 1011

11(CPL)

3(CPL)

SS

23

0010 0011

11(CPL)

3(CPL)

DS

23

0010 0011

11

3

FS

3B

0011 1011

11

3

根据CS和SS的段选择子可以得到CPL为3,印证了应用程序的CPU权限分级为RING3

EPL(有效特权级别)

上面提到了CPL(当前特权级别)和RPL(请求特权级别)

所谓的有效特权级别EPL(Effective Privilege Level),顾名思义就是最终的确定可否执行的特权

EPL = max(RPL,CPL)

即 EPL 等于 RPL和CPL的最大值,EPL为RPL和CPL中较低的权限

DPL(描述符特权级别)

关于DPL,在保护模式笔记四 段描述符结构中已经略微说明了,现在展开细说

DPL的作用

DPL存储在段描述符中,规定了访问该段所需要的特权级别是什么;即 如果想要加载某个段描述符,就必须具备对应的特权级别

DPL权限检查

当加载一个段描述符时,首先CPU要判断其P位(有效位),如果该段描述符有效,则继续进行DPL权限检查

所谓的DPL权限检查 就是 判断 DPL是否满足:EPL=max(RPL,CPL)<=DPL是否成立

只有当EPL<=DPL时,权限检查才通过,段描述符才能够被加载到段寄存器中

RPL

CPL

EPL

DPL

权限检查是否通过

0

0

0

0

0

0

0

3

0

3

3

0

×

0

3

3

3

3

0

3

0

×

3

0

3

3

3

3

3

0

×

3

3

3

3


总结

RPL

CPL

EPL

DPL

含义

请求特权级别

当前特权级别

有效特权级别

描述符特权级别

说明

用什么权限去访问一个段

CPU当权的权限级别

max(RPL,CPL)

访问该段所需要的特权级别

代码跨段跳转

段间跳转分为两种情况:

  1. 要跳转的段是一致代码段(共享代码段)
  2. 要跳转的段是非一致代码段

同时修改CS和EIP的指令

指令

含义

JMP FAR

远跳转

CALL FAR

远调用

RETF(return far)

远返回

INT(interrupt)

中断

IRET(interrupt return)

执行到中断程序或过程的远返回


只改变EIP的指令

指令

含义

JMP

跳转

CALL

调用

JCC(jump condition code)

跳转指令状态码/条件跳转

RET

返回

此处选择JMP FAR 进行分析

JMP Selector:Offset

形如:JMP 0x20:0x00452610

  • Selector为段选择子
  • Offset为要跳转的偏移

CPU是如何执行的呢?

  1. 拆分段选择子
  2. 根据段选择子查表得到段描述符
  3. 权限检查
  4. 加载段描述符
  5. 代码执行

1.首先确定段选择子

0x20 即 0000 0000 0010 0000

Index

TI

RPL

二进制值

0000 0000 0001 1

0

11

十进制值

3

0

3

含义

索引为3

查询GDT表

请求特权等级为3

四种情况可以跳转:

  1. 代码段
  2. 调用门
  3. TSS任务段
  4. 任务门

对应的段描述符地址 = GDT表首地址 + 索引× 段描述符长度 = GDT表首地址 + 索引 × 8

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

使用windbg查看对应的段描述符地址


得到对应的段描述符为:00cffb00`0000ffff

LDT

IDTR

中断描述符表寄存器,用于存放中断描述符表IDT的32位线性基地址和16位表长度值

TR

任务寄存器,用于存放当前任务TSS段的16位段选择符、32位基地址、16位段长度和描述符属性值

什么是段选择子?

就是在GDT/LDT中的偏移,就是索引

在保护模式下,段寄存器是96位:32位基址,32位界限域,16位属性,16位段选择子

段寄存器的值都是操作系统默认给分配的

控制寄存器

CR0

PE位,为0是实模式,为1是保护模式

TS任务切换,R3进R0的时候,TS置1

WP写保护,当写保护位置1,对只读页面进行写操作的时候,会产生页故障

段寄存器的结构

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tntlbb

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值