IA-32e模式下,虚拟地址宽度为64位,但只有低48位有效,最多可以寻址256TB,高16位用作符号拓展(全0或全1)。CPU 分页机制变为4级,分别对应 PML4、PDPT、PD、PT,并将48位虚拟地址按 9-9-9-9-12 索引格式划分。

其中,Cr3 寄存器中的物理地址指向 PML4 表的首地址。上图中表项均占8个字节,物理页面大小仍然为4KB。
Windbg 中手动拆分64位虚拟地址,并按照上面的分页规则计算出物理地址。实验选用 idt 表首地址进行拆分。(在计算物理地址时,需要对页表项的属性位清0。)
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      
      10
      
      11
      | kd> r idtridtr=fffff8037888e000kd> dq fffff8037888e000fffff803`7888e000761e8e00`00107e0000000000`fffff803fffff803`7888e010761e8e04`0010814000000000`fffff803fffff803`7888e020761e8e03`0010860000000000`fffff803fffff803`7888e030761eee00`00108ac000000000`fffff803fffff803`7888e040761eee00`00108e0000000000`fffff803fffff803`7888e050761e8e00`0010914000000000`fffff803fffff803`7888e060761e8e00`0010968000000000`fffff803fffff803`7888e070761e8e00`00109b8000000000`fffff803 | 
将虚拟地址按照 9-9-9-9-12 格式划分(注意低48位有效)
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      | fffff803`7888e000-> f803`7888e0001111100000x1f0PML4I0000011010xdPDPTI1110001000x1c4PTI0100011100x8ePDI0000000000000x0Offset | 
访问 Cr3 + PML4I * 8 指向的物理地址得到 PDPTE 的物理地址
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      
      10
      
      11
      | kd> r cr3cr3=0000000052c76000kd> !dq 52c76000+1f0*8#52c76f80 00000000`00c08063 00000000`00000000#52c76f90 00000000`00000000 00000000`00000000#52c76fa0 00000000`00000000 00000000`00000000#52c76fb0 0a000000`0bafc863 00000000`00000000#52c76fc0 00000000`00000000 00000000`00000000#52c76fd0 00000000`00000000 00000000`00000000#52c76fe0 00000000`00000000 00000000`00000000#52c76ff0 00000000`00000000 00000000`00ca8063 | 
访问 PDPTE + PDPTI * 8 指向的物理地址得到 PTE 的物理地址
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      | kd> !dq c08000+d*8#  c08068 00000000`00c09063 00000000`00000000#  c08078 00000000`00000000 00000000`00000000#  c08088 00000000`00000000 00000000`00000000#  c08098 00000000`00000000 00000000`00000000#  c080a8 00000000`00000000 00000000`00000000#  c080b8 00000000`00000000 00000000`00000000#  c080c8 00000000`00000000 00000000`00000000#  c080d8 00000000`00000000 00000000`00000000 | 
访问 PTE + PTI * 8 指向的物理地址得到 PDE 的物理地址
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      | kd> !dq c09000+1c4*8#  c09e20 00000000`00ca7063 0a000000`03996863#  c09e30 0a000000`0f5bc863 0a000000`0f5bd863#  c09e40 0a000000`0f5be863 0a000000`0f5bf863#  c09e50 0a000000`032c0863 0a000000`032c1863#  c09e60 0a000000`040c3863 0a000000`02bc4863#  c09e70 0a000000`02bc5863 0a000000`02bc6863#  c09e80 0a000000`02bc7863 0a000000`02bc8863#  c09e90 0a000000`02bc9863 0a000000`02bca863 | 
访问 PDE + PDI * 8 指向的物理地址得到物理页面
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      | kd> !dq ca7000+8e*8#  ca7470 89000000`0588e121 89000000`0588f963#  ca7480 89000000`05890963 89000000`05891963#  ca7490 89000000`05892963 89000000`05893963#  ca74a0 00000000`00000000 89000000`05895963#  ca74b0 89000000`05896963 89000000`05897963#  ca74c0 89000000`05898963 89000000`05899963#  ca74d0 89000000`0589a963 89000000`0589b963#  ca74e0 00000000`00000000 89000000`0589d963 | 
访问 物理页面 + Offset 指向的物理地址得到内容
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      | kd> !dq 0588e000# 588e000 761e8e00`00107e00 00000000`fffff803# 588e010 761e8e04`00108140 00000000`fffff803# 588e020 761e8e03`00108600 00000000`fffff803# 588e030 761eee00`00108ac0 00000000`fffff803# 588e040 761eee00`00108e00 00000000`fffff803# 588e050 761e8e00`00109140 00000000`fffff803# 588e060 761e8e00`00109680 00000000`fffff803# 588e070 761e8e00`00109b80 00000000`fffff803 | 
与访问虚拟内存得到的结果一致。
 在64位模式下,高等级页表项都指向低等级页表项的物理地址,依次类推,直到最低级别页表项,即可获取物理页面进而读取内容。在此过程中 Cr3 寄存器中存储了最高级页表(PML4)的表基物理地址。为了更好的管理这些页表,微软采取了最高级页表基址自映射的方式实现仅仅利用8字节物理内存,就可以在每次访问分页管理相关的内存时,少做一次页表查询操作来优化速度。
 在四级页表的最高级 PML4 页表中存在一项,里面保存了 PML4 页表的表基物理地址,即 Cr3 。假设这一项在 PML4 表中的索引为 0x100,如下图所示:
 
此时满足:( ![物理地址] 表示读取物理地址的内容)
| 
      1
      | ![Cr3 +0x100*8]  =Cr3 | 
用于分页管理的物理页面大小总计 512 512 512 * 4KB = 512GB,而一个 PML4 表项恰好可以管理512GB内存。
PML4 表中索引位置0x100的元素用于内存管理且满足上述关系,那么此时用于内存管理的虚拟地址空间为:
| 
      1
      | 0xFFFF8000`00000000~ 0xFFFF807F`FFFFF000 | 
按照 9-9-9-9-12 分页方式去拆分上述边界物理地址:(只使用低48位)
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      
      10
      
      11
      
      12
      
      13
      
      14
      
      15
      | //起始地址0x8000`000000001000000000x1000000000000x00000000000x00000000000x00000000000000x0//结束地址0x807F`FFFFF0001000000000x1001111111110x1FF1111111110x1FF1111111110x1FF0000000000000x0 | 
常规查询流程:
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      
      10
      
      11
      | //起始地址![Cr3 +0x100*8] =PDPTE![PDPTE +0x0*8] =PDE![PDE +0x0*8] =PTE![PTE +0x0*8] =物理页面//结束地址![Cr3 +0x100*8] =PDPTE![PDPTE +0x1FF*8] =PDE![PDE +0x1FF*8] =PTE![PTE +0x0*8] =物理页面 | 
根据上述等式,![Cr3 + 0x100 * 8] = Cr3,所以查询流程变为:
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      | //起始地址![Cr3 +0x0*8] =PDE![PDE +0x0*8] =PTE![PTE +0x0*8] =物理页面//结束地址![Cr3 +0x1FF*8] =PDE![PDE +0x1FF*8] =PTE![PTE +0x0*8] =物理页面 | 
很神奇,查询页表操作由四次变成了三次,效率大大提升。而且只是使用了8字节的物理地址空间来保存 Cr3 。下图展示了优化后的查询过程:
 
为了写代码方便读写页表属性,四级页表都应该有自己的表基虚拟地址,以便访问其中的元素。
PML4 页表基址有两个特点:
假设该虚拟地址按照 9-9-9-9-12 分页规则拆分得到的索引依次为 x、y、z、r,根据页表解析规则:
| 
      1
      
      2
      
      3
      
      4
      | ![Cr3 +x *8] =PDPTE![PDPTE +y *8] =PDE![PDE +z *8] =PTE![PTE +r *8] =物理页面 =Cr3 | 
还需要满足 ![Cr3 + x * 8] = Cr3,所以当 x = y = z = r 的时候上述条件均满足。
PDPT 页表基址有两个特点:
假设该虚拟地址按照 9-9-9-9-12 分页规则拆分得到的索引依次为 x、y、z、r,根据页表解析规则:
| 
      1
      
      2
      
      3
      
      4
      | ![Cr3 +x *8] =PDPTE![PDPTE +y *8] =PDE![PDE +z *8] =PTE![PTE +r *8] =![Cr3] | 
还需要满足 ![Cr3 + x * 8] = Cr3,所以当 x = y = z 且 r = 0 的时候上述条件均满足。
 方法同理。
页内偏移均为0
 上面得到结论中的 Index 就是自映射表项在 PML4 表中的索引,这个值的变化就是造成各级页表基址变化的原因。
系统重启前的 PML4 基址:
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      | 0xFB7DBEDF60001111101100x1F6PML41111101100x1F6PDPT1111101100x1F6PD1111101100x1F6PT0000000000000x0Index为:0x1F6 | 
系统重启后的PML4基址:
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      | 0x8D46A351A0001000110100x11A1000110100x11A1000110100x11A1000110100x11A0000000000000Index为:0x11A | 
 页表基址随机化导致写代码读写页表属性变得不方便,但可以利用页表自映射的一些结论来获取 PML4 表基址。PML4 表基址的内容为Cr3的值,并且位于 PML4 表所在的页面内。因为Cr3里保存了 PML4 的表基物理地址,所以可以通过映射Cr3物理地址的虚拟地址,遍历这个虚拟地址页面的512个地址,哪个地址符合上述条件,哪个地址就是 PML4 表基址。下面给出驱动代码实现:
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      
      10
      
      11
      
      12
      
      13
      
      14
      
      15
      
      16
      
      17
      | ULONG64 GetPml4Base(){    PHYSICAL_ADDRESS pCr3 ={ 0};    pCr3.QuadPart =__readcr3();    PULONG64 pCmpArr =MmGetVirtualForPhysical(pCr3);    intcount =0;    while((*pCmpArr & 0xFFFFFFFFF000) !=pCr3.QuadPart)    {        if(++count >=512)        {            return-1;        }        pCmpArr++;    }    return(ULONG64)pCmpArr & 0xFFFFFFFFFFFFF000;} | 
得到了 PML4 表基址,就可以得到 Index 索引值,其他各级页表基址也就都可以得到了。
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      
      10
      
      11
      
      12
      
      13
      
      14
      | ULONG64 GetPdptBase(ULONG64 ulPml4Base){    return(ulPml4Base >> 21) << 21;}ULONG64 GetPdBase(ULONG64 ulPml4Base){    return(ulPml4Base >> 30) << 30;}ULONG64 GetPtBase(ULONG64 ulPml4Base){    return(ulPml4Base >> 39) << 39;} | 
得到了 PML4 表基址,就可以得到 Index 索引值,其他各级页表基址也就都可以得到了。
有错误欢迎指出,一起交流进步。
更多【四级分页下的页表自映射与基址随机化原理介绍】相关视频教程:www.yxfzedu.com