保护模式 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

相关文章

一文带你理解Python中import机制与importlib的妙用

《一文带你理解Python中import机制与importlib的妙用》在Python编程的世界里,import语句是开发者最常用的工具之一,它就像一把钥匙,打开了通往各种功能和库的大门,下面就跟随小... 目录一、python import机制概述1.1 import语句的基本用法1.2 模块缓存机制1.

Redis主从/哨兵机制原理分析

《Redis主从/哨兵机制原理分析》本文介绍了Redis的主从复制和哨兵机制,主从复制实现了数据的热备份和负载均衡,而哨兵机制可以监控Redis集群,实现自动故障转移,哨兵机制通过监控、下线、选举和故... 目录一、主从复制1.1 什么是主从复制1.2 主从复制的作用1.3 主从复制原理1.3.1 全量复制

Redis缓存问题与缓存更新机制详解

《Redis缓存问题与缓存更新机制详解》本文主要介绍了缓存问题及其解决方案,包括缓存穿透、缓存击穿、缓存雪崩等问题的成因以及相应的预防和解决方法,同时,还详细探讨了缓存更新机制,包括不同情况下的缓存更... 目录一、缓存问题1.1 缓存穿透1.1.1 问题来源1.1.2 解决方案1.2 缓存击穿1.2.1

Java如何通过反射机制获取数据类对象的属性及方法

《Java如何通过反射机制获取数据类对象的属性及方法》文章介绍了如何使用Java反射机制获取类对象的所有属性及其对应的get、set方法,以及如何通过反射机制实现类对象的实例化,感兴趣的朋友跟随小编一... 目录一、通过反射机制获取类对象的所有属性以及相应的get、set方法1.遍历类对象的所有属性2.获取

MySQL中的锁和MVCC机制解读

《MySQL中的锁和MVCC机制解读》MySQL事务、锁和MVCC机制是确保数据库操作原子性、一致性和隔离性的关键,事务必须遵循ACID原则,锁的类型包括表级锁、行级锁和意向锁,MVCC通过非锁定读和... 目录mysql的锁和MVCC机制事务的概念与ACID特性锁的类型及其工作机制锁的粒度与性能影响多版本

Spring使用@Retryable实现自动重试机制

《Spring使用@Retryable实现自动重试机制》在微服务架构中,服务之间的调用可能会因为一些暂时性的错误而失败,例如网络波动、数据库连接超时或第三方服务不可用等,在本文中,我们将介绍如何在Sp... 目录引言1. 什么是 @Retryable?2. 如何在 Spring 中使用 @Retryable

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

【编程底层思考】垃圾收集机制,GC算法,垃圾收集器类型概述

Java的垃圾收集(Garbage Collection,GC)机制是Java语言的一大特色,它负责自动管理内存的回收,释放不再使用的对象所占用的内存。以下是对Java垃圾收集机制的详细介绍: 一、垃圾收集机制概述: 对象存活判断:垃圾收集器定期检查堆内存中的对象,判断哪些对象是“垃圾”,即不再被任何引用链直接或间接引用的对象。内存回收:将判断为垃圾的对象占用的内存进行回收,以便重新使用。

【Tools】大模型中的自注意力机制

摇来摇去摇碎点点的金黄 伸手牵来一片梦的霞光 南方的小巷推开多情的门窗 年轻和我们歌唱 摇来摇去摇着温柔的阳光 轻轻托起一件梦的衣裳 古老的都市每天都改变模样                      🎵 方芳《摇太阳》 自注意力机制(Self-Attention)是一种在Transformer等大模型中经常使用的注意力机制。该机制通过对输入序列中的每个元素计算与其他元素之间的相似性,