海哥说过:做出来的东西才懂,没做出来就是没懂
认识2-9-9-12分页
首先要思考,为什么会有2-9-9-12分页?
2-9-9-12分页到底拓展了什么?
计算:2+9+9+12 = 32
依旧是32位,所以我们能表示的虚拟地址的极限依旧是:2
32
= 4GB (0x0000 0000 – 0xFFFF FFFF)
假设我有一张8GB的内存条(物理地址能取到8G(0x1 FFFF FFFF))
如何将 进程地址 映射到 物理地址 0x1 0000 0001 上
如何和10-10-12那样,PTE(页表项,视频里称作PTI)是32位的,那么最大的寻址就是
PTE & 0xFFFF F000 + offset。所以想要突破这个点,那么就必须让PTE的长度突破32位。
所以只要
0x1 0000 0
000 + 1 就能实现
实际上,操作系统允许我们使用到36位,这样一来,就能映射到64G的物理地址上了
所以2-9-9-12分页拓展了
物理地址的寻址范围
那么问题来了,如果PT(页表)在地址 0x1 0000 0001 怎么办,原来的32位PDE可就找不到PT了。所以PDE也变成36位好了。
那为什么不把PD 和 PT都放在0-4GB里?我也不知道
通过之前学的,最后指针指向那个物理页还得是PTE说了算。所以PTE怎么指都行
PDPT (page directory pointer table) : 页目录指针表
PDPTE (page directory pointer talble entry) : 页目录指针表中的 一项 (条目)
给0地址挂物理页
还是用上次的代码
#include <stdio.h>
int main(){
int i = 0;
printf("%p\n", &i); // %p打印 十六进制 指针
*(int*)0 = 0x123;
printf("%d", *(int*)0);
return 0;
}
在修改之前断下来
我们把
i
的物理页挂到0的线性地址上
稍微拆分一下:0 | 0 | 12F | F7C
左边是
i
,右边是
0
没有
!eq
,只能写成
!ed
,记得最后面的两个的DWORD要交换一下位置
(忘了是%d了) 291 = 0x123
修改高2G页属性
简单的代码
#include <stdio.h>
int main(){
printf("%x", *(int*)0x8003f00c);
return 0;
}
简单地拆分一下:2 | 0 | 3F | C
我们将
U/S置1
,
G位置0
G为什么要置0,后面再解释,如果没懂,那么先记住,看下一集视频后会通透的
修改后运行程序打印结果
说一下上一次没讲到的G位:全局页 位 (这将在下一个视频有讲解)
试想一下,程序每读一个地址,就要拆分一次PDPTE,PDE,PTE效率十分低下,有些地址是同一个页内的,根本没有必要每次都进行检测,所以有一块离CPU很近,速度很快的地方,存放了拆分过的页,以及页的各种属性,这么一来就不用一直拆分了。我们暂且把这个地方称为
快表
快表中的页属性:当G == 1时,快表中的项(entry)不会被换出,当 G == 0 时代表快表项会被换出
请注意:当我们修改了PDE,PTE时,若物理页是全局页,则那个快表中的那个页属性并没有改变,所以我们的修改无效,必须让快表中的那一项重新加载,才能读取修改后的属性。
代码挂物理页
PDE = 0xc0600000 + ((addr >> 18) & 0x3ff8);
PTE = 0xc0000000 + ((addr >> 9) & 0x7ffff8);
对应关系图:
代码:
#include <windows.h>
#include <stdio.h>
PDWORD pde, pte, pde0, pte0;
void __declspec(naked) func(){
_asm{
pushad
pushf
//if (*pde != 0) *pde0 = *pde;
mov eax, pde
mov eax, [eax] //eax = *pde
cmp eax, 0
jnz editPte0 //如果PDE存在,不要去动他
mov ebx, pde0
mov[ebx], eax //复制给0xC030 0000 ( (void*)0的PDE )
editPte0:
//*pte0 = *pte;
mov eax, pte
mov eax, [eax]
mov ebx, pte0
mov[ebx], eax
popf
popad
retf
}
}
int x = 0x12345678;//主要是想让 变量x的PDE != 0,如果是0那将少踩很多坑
int main(){
BYTE addrs[6] = {0x00, 0x00, 0x00, 0x00, 0x48, 0x00 };
int selector = 0x0008;
DWORD xaddr = (DWORD)&x; //方便下面的计算
printf("x addr: %X\n", &x);
// PDI (page directory index) ; PTI (page table index) (就是拆分后的那10个二进制位)
// pde = (xaddr >> 22) * 4 + 0xC0300000 = PTI * 4 + 0xC0600000
pde = (DWORD*)(((xaddr >> 18) & 0x3FF8)+ 0xC0600000); //x的pde
// pte = (xaddr >> 12) * 4 + 0xC0000000 = PDI * 4MB + PTI * 4 + 0xC0000000
pte = (DWORD*)(((xaddr >> 9) & 0x7FFFF8) + 0xC0000000);//x的pte
pde0 = (DWORD*)0xC0600000; //0的pde
pte0 = (DWORD*)0xC0000000; //0的pte
printf("windbg input:\t eq 8003f048 %04xEC00`%04x%04x\n",
(((DWORD)&func)>>16), selector, (DWORD)&func & 0xFFFF);
_asm{
push fs
call fword ptr[addrs]
pop fs
}
*((int*)0) = 0x11112222;
printf("%x\n", *((int*)0)); //打印 指针0 的值
printf("%x\n", *((int*)(0 + ( ((DWORD)&x) & 0xFFF) )));//print x
return 0;
}
结果: