该篇文章是我自己学完保护模式的10-10-12分页所记录下的心得和体会,因本人也只是初学者,难免会有知识上的欠缺,若有任何疑问或建议,欢迎在下方留言
4个关键词:
CR3寄存器
页目录表(PDT)
页表(PTT)
页(P)
1个关键点:
PTT里存的是每一个页的首地址,PDT里存的是PTT的首地址
页(P):
大小为4KB,共2^20个(一个进程大小为4GB)
页0的地址为0x00000000~0x00000fff,页1的地址为0x00001000~0x00001fff,依此类推,每一个页的首地址为0x****000,表示0x0~0xf中的任意一种情况。
页表(PTT):
PTT也是一张页,但是这张页不是存的指令或者数据,而是存的每张页的首地址,即0x*****000,存储一个地址需要4B,因此PTT一共能存2^10个地址,至少需要2^10个PTT才能放下所有页的首地址。
页目录表(PDT):
存放PTT的首地址,虽然PTT里也有PTT的首地址,但是你咋知道PTT的首地址是在哪个PTT里的哪一项?因此,PDT里存放了所有PTT的首地址,存储一个地址需要4B,因此PDT一共能存2^10个地址。
CR3寄存器:
存放PDT的首地址。
如果你已经看懂上述内容,接下来我将介绍一个线性地址如何转化成一个物理地址:
1、 首先读取CR3寄存器的值
2、 PDT的首地址+线性地址的最左边10bit 4 得到PTT的首地址
3、 PTT的首地址+线性地址的中间10bit 4 得到页的首地址
4、 页的首地址+线性地址的最后10bit 得到物理地址
注:10bit索引2^10个4B,12bit索引2^12个1B
下面这张草图可以帮助你更好理解:
接下来我将实现两个不同的线性地址指向同一物理地址的功能:
#include "stdafx.h"
#include <stdlib.h>
#include <windows.h>
int main(int argc, char* argv[])
{
int x = 0x1;
printf("x的地址:%x\n", &x);
// 申请一个新的物理页,将p的PTE和物理页内偏移改成和x一样
int* p = (int*)VirtualAlloc(NULL,0x1000,MEM_COMMIT,PAGE_READWRITE);
int* p_bak = p;
p = (int*)((int)p & 0xFFFFF000);
p = (int*)((int)p | ((int)&x & 0x00000FFF));
printf("新的线性地址:%x\n", p);
getchar(); // 在windbg里修改 p 的 PTE
// 用新的线性地址读x
printf("*addr:%x\n",*p); // 0x1
// 用新的线性地址写x
*p = 0x112233;
printf("x:%x\n",x); // 0x112233
getchar();
VirtualFree(p_bak,0x1000,MEM_DECOMMIT);
return 0;
}
在getchar函数执行后,需要将x所在页的首地址给到p对应PTT里的相应位置,具体操作可参考下图:
DirBase的值就是PDT的首地址,17a16000是PTT的首地址,0d15a000是x所在页的首地址,x与p对应的PDT与PTT的首地址是一样的,但是x所在页的首地址所存储的地方是不一样的,前者在17a164bc,后者在17a16e80,所以要在17a16e80写入x所在页的首地址,这样不同的线性地址就指向同样的物理地址。
再强调一下,x与p的PDT的首地址是一样的,PTT的首地址可以不一样,但是对应PTT里存的页的首地址必须一样,且线性地址的最后12bit必须一样。
最后的结果:
需要强调一点,图里面的每个数值仅供参考,不同的主机可能有不同的数值
|