本文主要是介绍保护模式 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 页保护机制的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!