保护模式 x86 页保护机制

2024-02-24 10:18
文章标签 x86 机制 保护 保护模式

本文主要是介绍保护模式 x86 页保护机制,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

页的机制

目录

文章目录

  • 页的机制
  • 目录
    • 分页
      • 物理地址-线性地址-有效地址
      • 分页机制
        • 2 9 9 12
        • 10 10 12
        • 设置分页方式
      • 实验:通过线性地址找到物理地址(10-10-12)
      • PDE PTE
    • PDE_PTE属性
      • P位 [0]
      • R/W位 [1]
      • U/S [2]
      • P/S PDE[7] pte没有
      • A位
      • D位
    • 页目录表基址 0xc0300000
    • 页表基址 0xc0000000
    • 线性地址转物理地址公式

分页

物理地址-线性地址-有效地址

地址有三种:
物理地址: 物理地址就是内存单元的绝对地址,比如你有一个4G的内存条插在电脑上,物理地址0x0000就表示内存条的第一个存储单元,0x0010就表示内存条的第17个存储单元,不管CPU内部怎么处理地址,最终访问的都是物理地址。在CPU实模式下“段基址+段内偏移地址”就是物理地址,CPU可以使用此地址直接访问内存。

线性地址、虚拟地址: CPU在保护模式下,“段基址+段内偏移地址”叫做线性地址,注意,保护模式下段基址寄存器中存储的不是真正的段基值(和实模式的含义不一样),而是被称为“段选择子”的东西,通过段选择子在GDT(全局描述表)中找到真正的段基值。另外,如果CPU在保护模式下没有开启分页功能,则线性地址就被当做最终的物理地址来用,若开启了分页功能,则线性地址就叫虚拟地址(在没开启分页功能的情况下线性地址和虚拟地址就是一回事)。但是,如果开启分页功能,虚拟地址(或线性地址)还要通过页部件电路转换成最终的物理地址。

逻辑地址、有效地址: 无论CPU在什么模式下,段内偏移地址又称为有效地址或者逻辑地址(只是叫法不一样罢了),例如实模式下 “mov ax, [0x7c00]”,0x7c00就是逻辑地址(或有效地址),但这条指令最终操作的物理地址是DS*16+0x7c00

在这里插入图片描述

如下指令:

MOV eax,dword ptr ds:[0x12345678]

其中,括号内的0x12345678是有效地址.
ds.Base + 0x12345678是线性地址

分页机制

标准页的大小:4KB

2 9 9 12

注意:pde pte可以理解为数组,每个元素4字节(32位,其中高20位保存地址,低10位保存属性)

在这里插入图片描述

10 10 12

在这里插入图片描述

设置分页方式

C盘根目录下找到该文件

在这里插入图片描述
将noexecute 改成execute 然后重启电脑
在这里插入图片描述
就可以将分页机制改为10 10 12

实验:通过线性地址找到物理地址(10-10-12)

比如用notepad程序创建一个进程,他肯定有自己一个独立的进程内存(4GB),我们在里面写上hello worde 字符串,并借用CE工具查找进程地址中的字符

在这里插入图片描述
得到线性地址(这个地址是在该进程空间的虚拟地址(线性地址))

地址:000B0ed0

由于在之前已经将系统分页模式切换到了10-10-12
现在我们将线性地址划分成10-10-12

0000000000 10(一级目录) hex = 0
0010110000 10(二级页表) hex = b0
111011010000 12(物理地址) hex = ed0

接下来 就要介绍CR3寄存器:
每一个进程都有CR3,并且只有CR3是直接指向物理地址的寄存器 准确的说是都有一个CR3的值,CR3本身是个寄存器,一个核,只有一套寄存器
CR3指向一个物理页,一共4096字节
(也就是指向页目录)

那我们接下来就查看notepad 的CR3值:

在这里插入图片描述
181e0000(其中前十个字节存储第一级页目录的首地址)
并查看

  
181e0000+(0*4)*4很明显是因为页目录单元是四字节

处的值:

在这里插入图片描述

183a1067(前十个字节存储第二级页表的首地址)
我们不用管183a1067的后三位,后三位代表的是(此时代表pte)页的属性

!dd 183a1000 + (0xb0)*4

在这里插入图片描述

0x18098067(前十个字节存储的第三级页的基址)

!dd 18098000+ (ed0)*1
//*1是因为物理地址是以一个字节为单位.

在这里插入图片描述

得到物理地址:

物理地址:0x18098ed0
原线性地址:0x000B0ED0

回顾一下:为什么是10-10-12呢?
10(代表2^10)也就是1024的大小,刚好页目录和页表拥有1024个元素

12(代表2^12)也就是4Kb,刚好一个标准页拥有4kb大小

PDE PTE

1.PTE(页表项)是PTT(页表)的成员
2.PDE(页目录表项)是PDT(页目录表)的成员
3.PTE可以不指向物理页
4.多个PTE可以指向同一个物理页
5.一个PTE只能指向一个物理页

在这里插入图片描述

思考:线性地址0为什么不能读写?

线性地址0,随便找一个进程,拆开成10-10-12
就会发现线性地址=0时,PTE根本没挂物理页!

这也就解释了为什么0地址不能读写

PDE_PTE属性

之前在前面的实验中 简单说过pde_pte(单独元素4字节32位)
其中的高20位表示基址,低12位表示的是属性

这个属性并不是独立的

物理页的属性 = PDE属性 & PTE属性

在这里插入图片描述

P位 [0]

问:线性地址0为什么不能访问?

答:没有指定物理页.

问:但是指定了物理页就一定能访问吗?

答:先看PDE & PTE的P位 P=1 才是有效的物理页

R/W位 [1]

R/W = 0 代表只读
R/W = 1 代表读写

实验:
目的:验证该位有效性
原理:通过定义一个只读的常量,获取线性地址从而得到真实的物理地址,用另一个PTE去指向这块物理地址,并通过修改PDE_PTE的属性从而修改常量的值
实验代码:

#include<stdio.h>
#include<windows.h>
int main()
{char *str = "Hello World"; //定义常量//修改只读变量//str[1] = 'M'printf("线性地址:%p\n",str);getchar(); DWORD dwVal = (DWORD)str;*(char *)dwVal = 'M';printf("修改后的值:%s\n",str);return 0;
}

在未做任何更改的情况下:发现这块内存地址是不允许写入的

在这里插入图片描述

并且我们得到了定义的常量的线性地址:

0x00423040

拆分:

0000000001    10  1
0000100011    10  23
000001000000  12  040 

在这里插入图片描述

继续运行修改成功:

在这里插入图片描述

U/S [2]

U/S = 0 该页只有特权用户可以使用
U/S = 1 普通用户以上就可以使用

这也就是为什么高2G内存为什么不能读,是因为PTE的U/S位被置为0

接下来 我们通过验证该U/S位 使得获得读取高2G地址内存权限:

代码:

#include<stdio.h>
#include<windows.h>
int main()
{PDWORD p = (PDWORD)0x8003F00C;getchar();printf("read high 2G address Value:%x\n",*p);return 0;
}

未作修改PDE_PTE(读取失败):

在这里插入图片描述

拆分0x8003f00c:

1000 0000 00    10   0x200
0000 1111 11    10  0x3F
0000 0000 1100  12  0xC

重新运行进程到getchar() 这次修改PDE-PTE属性

在这里插入图片描述

运行成功:

在这里插入图片描述

P/S PDE[7] pte没有

该位只对PDE有意义,PS == PageSize的意思

P/S = 1 表示PDE直接指向物理页,无PTE,低22位是页内偏移

举例:

拆分0x8043f00c线性地址的PDE属性
1000 0000 01  10 0x201

在这里插入图片描述

属性:0x1e3
二进制:0001 1110 0011 
P/S =  10000 1111 1100 0000 0011 00  22位组成大页内偏移  0x3F00C

在这里插入图片描述

A位

A = access
A = 0 该页暂未被访问过
A = 1 该页被访问过

D位

脏位
A = 0 该页暂未被写入过
A = 1 该页被写过

页目录表基址 0xc0300000

在调试环境下 我们通过windbg去获取CR3寄存器的值来到达PDT表
(这是表面,其实真实情况windbg只是封装了方法,看起来我们直接访问了物理地址,其实windbg也需要通过线性地址去寻找物理地址)

但是在软件中,系统是怎么走到PDT进行线性地址与物理地址的转换呢?

其实在程序进程一开始,系统就将本进程的一个线性地址挂上了PDT的基址:

页目录表基址:(线性地址   )0xC0300000

可以拆开验证,前面有很多拆开的例子,不重复了.

另外PDT 的本质 是一个PTT

在这里插入图片描述

页表基址 0xc0000000

有了页目录表基址:我们能读取其中存储pde,但是如果仅仅能访问pde 还没有办法做到对任意一个线性地址进行掌控.(读取pde中的物理地址在程序中无能为力,什么也干不了)

所以我们还需要一个线性地址来访问PTT表.

我们程序可能没有办法在一开始就访问PTT表,但是操作系统是可以的(因为我们随便申请个物理地址,系统都会为我们挂上正确的PDE_PTE),

拆分C000 0000 /C000 1000
线性地址:C000 0000 对应第一个PTT
C000 1000 对应第二个PTT
C000 2000 对应第三个PTT

当走到0xc0300000 的时候 就走到了上面说的那个特殊的ptt 它既是一个pdt 又是一个ptt

在这里插入图片描述
这个才是正确的ptt(pdt不一定是正确的)结构表

线性地址转物理地址公式

1.PDI , PTI
PDI: 拆分的第一个10
PTI: 拆分的第二个10

2.访问页目录表的公式

0xc0300000 + PDI*4

3.访问页表的公式

0xC0000000 + PDI * 0x1000 +PTI * 4

这篇关于保护模式 x86 页保护机制的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/741788

相关文章

基于Redis自动过期的流处理暂停机制

《基于Redis自动过期的流处理暂停机制》基于Redis自动过期的流处理暂停机制是一种高效、可靠且易于实现的解决方案,防止延时过大的数据影响实时处理自动恢复处理,以避免积压的数据影响实时性,下面就来详... 目录核心思路代码实现1. 初始化Redis连接和键前缀2. 接收数据时检查暂停状态3. 检测到延时过

Redis中哨兵机制和集群的区别及说明

《Redis中哨兵机制和集群的区别及说明》Redis哨兵通过主从复制实现高可用,适用于中小规模数据;集群采用分布式分片,支持动态扩展,适合大规模数据,哨兵管理简单但扩展性弱,集群性能更强但架构复杂,根... 目录一、架构设计与节点角色1. 哨兵机制(Sentinel)2. 集群(Cluster)二、数据分片

深入理解go中interface机制

《深入理解go中interface机制》本文主要介绍了深入理解go中interface机制,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录前言interface使用类型判断总结前言go的interface是一组method的集合,不

C# async await 异步编程实现机制详解

《C#asyncawait异步编程实现机制详解》async/await是C#5.0引入的语法糖,它基于**状态机(StateMachine)**模式实现,将异步方法转换为编译器生成的状态机类,本... 目录一、async/await 异步编程实现机制1.1 核心概念1.2 编译器转换过程1.3 关键组件解析

Redis客户端连接机制的实现方案

《Redis客户端连接机制的实现方案》本文主要介绍了Redis客户端连接机制的实现方案,包括事件驱动模型、非阻塞I/O处理、连接池应用及配置优化,具有一定的参考价值,感兴趣的可以了解一下... 目录1. Redis连接模型概述2. 连接建立过程详解2.1 连php接初始化流程2.2 关键配置参数3. 最大连

Spring Security 单点登录与自动登录机制的实现原理

《SpringSecurity单点登录与自动登录机制的实现原理》本文探讨SpringSecurity实现单点登录(SSO)与自动登录机制,涵盖JWT跨系统认证、RememberMe持久化Token... 目录一、核心概念解析1.1 单点登录(SSO)1.2 自动登录(Remember Me)二、代码分析三、

Go语言并发之通知退出机制的实现

《Go语言并发之通知退出机制的实现》本文主要介绍了Go语言并发之通知退出机制的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录1、通知退出机制1.1 进程/main函数退出1.2 通过channel退出1.3 通过cont

Spring Boot 中的默认异常处理机制及执行流程

《SpringBoot中的默认异常处理机制及执行流程》SpringBoot内置BasicErrorController,自动处理异常并生成HTML/JSON响应,支持自定义错误路径、配置及扩展,如... 目录Spring Boot 异常处理机制详解默认错误页面功能自动异常转换机制错误属性配置选项默认错误处理

Java中的xxl-job调度器线程池工作机制

《Java中的xxl-job调度器线程池工作机制》xxl-job通过快慢线程池分离短时与长时任务,动态降级超时任务至慢池,结合异步触发和资源隔离机制,提升高频调度的性能与稳定性,支撑高并发场景下的可靠... 目录⚙️ 一、调度器线程池的核心设计 二、线程池的工作流程 三、线程池配置参数与优化 四、总结:线程

Android ClassLoader加载机制详解

《AndroidClassLoader加载机制详解》Android的ClassLoader负责加载.dex文件,基于双亲委派模型,支持热修复和插件化,需注意类冲突、内存泄漏和兼容性问题,本文给大家介... 目录一、ClassLoader概述1.1 类加载的基本概念1.2 android与Java Class