# 前言

上一篇我们学习了 10-10-12 分页机制和 PDE/PTE。本篇将深入讲解页目录表基址、页表基址,以及 2-9-9-12(PAE)分页机制。

# 页目录表基址与页表基址

如果想填充 PDE 与 PTE,必须能够访问 PDTPT。这就引出两个问题:

  1. 一定存在某个线性地址能够访问 PDT 与 PT(并且已经挂好了 PDE 与 PTE),如何找到这个地址?
  2. 这个为我们挂好 PDE 与 PTE 的 "人" 是谁?

注意:Cr3 中存储的是物理地址,不能在程序中直接读取。如果想读取,也要把 Cr3 的值挂到 PDT 和 PT 中才能访问。

# 高地址内核空间的设计

在 x86 的虚拟地址空间(4GB)中:

  • 用户空间0x00000000 ~ 0x7FFFFFFF
  • 内核空间0x80000000 ~ 0xFFFFFFFF

Windows 为页表和页目录表保留了线性地址 0xC00000000xC03FFFFF4MB 空间。

最关键的设计:页目录表的第 0x300 项指向页目录表本身

因此:

  • 访问线性地址 0xC0000000 → 可以访问到自己的页表
  • 访问线性地址 0xC0300000 → 可以访问到自己的页目录表

# 页目录表基址

每个页表管理的大小为 4MB:

故得:

1
2
3
页表 1 映射的线性地址范围:0x00000000 ~ 0x003FFFFF
页表 2 映射的线性地址范围:0x00400000 ~ 0x007FFFFF
...

因此,页表在 Cr3 中的索引: 0xC0000000 / 4MB = 768 = 0x300

每个页表可以映射 4MB 的虚拟地址空间(1024 个 PTE × 4KB / 页 = 4MB),要映射 0xC0000000 (3GB)的地址空间,需要 0xC0000000 / 0x400000 = 0x300 个页表。

0xC0300000 拆分为 10-10-12 分组:

1
2
3
4
0xC0300000
= 1100 0000 0011 0000 0000 000000000000
= 1100000000 1100000000 000000000000
PDI = 0x300 PTI = 0x300 Offset = 0x0
  • PDI = 0x300 :Cr3 的第 0x300 项指向页表区,但保存的是页目录表的基地址
  • PTI = 0x300 :页表的第 0x300 项(其实还是页目录表的第 0x300 项)又指向了页目录表本身
  • Offset = 0x0 :加上偏移量,就可以访问页目录表项

因此,页目录表的线性地址范围为 0xC0300000 ~ 0xC0300FFF

# 实验 1:理解页目录表基址

一、选择任意进程,获得它的 Cr3。

1
!process 0 0

二、查看进程的页目录表(PDT)。

三、查看 0xC0300000 对应的物理页。

拆分线性地址:

1
2
3
0xC0300000
= 1100000000 1100000000 000000000000
PDI = 0x300 PTI = 0x300 Offset = 0x0

查看页目录表项:

查看页表项:

查看物理页:

# 页表基址

0xC0000000 拆分为 10-10-12 分组:

1
2
3
4
0xC0000000
= 1100 0000 0000 0000 0000 000000000000
= 1100000000 0000000000 000000000000
PDI = 0x300 PTI = 0x0 Offset = 0x0
  • PDI = 0x300 :Cr3 的第 0x300 项指向页表区
  • PTI = 0x0 :页表的第 0 项(即页目录表的第 0 项)指向第 1 张页表
  • Offset = 0x0 :偏移量

0xC0001000 拆分:

1
2
3
0xC0001000
= 1100000000 0000000001 000000000000
PDI = 0x300 PTI = 0x1 Offset = 0x0

PTI = 0x1 → 页目录表的第 1 项指向第 2 张页表。

以此类推,页表的线性地址范围为 0xC0000000 ~ 0xC02FFFFF

# 实验 2:理解页表基址

一、选择任意进程,获得它的 Cr3。

1
!process 0 0

二、查看进程的页目录表(PDT)。

三、查看 0xC0000000 对应的物理页。

拆分线性地址:

1
2
3
0xC0000000
= 1100000000 0000000000 000000000000
PDI = 0x300 PTI = 0x0 Offset = 0x0

查看页目录表项:

查看页表项:

查看物理页:

# 总结

  1. 有了 0xC03000000xC0000000 ,就掌握了一个进程所有的物理内存读写权限

  2. 公式总结:

    • 访问页目录表0xC0300000 + PDI × 4
    • 访问页表0xC0000000 + PDI × 4096 + PTI × 4
    • 通过线性地址定位所在页表0xC0000000 + (addr >> 10)

# 2-9-9-12 分页

# 10-10-12 分页的设计思路回顾

  1. Intel 认为一张页的大小为 4KB 是比较合理的,所以页大小确定为 4KB(2^12),最后 12 位确定为页内偏移

  2. 当初的物理内存比较小,4 个字节的页表项(PTE)就够了,加上页的大小是 4KB,所以一个页能存储 1024 个 PTE(2^10),第二个 10 也就确定了。

  3. 同理,PDT 也需要 10 个比特位,10 + 10 + 12 刚好 32 位。

在这种分页方式下,物理地址最多可达 4GB。但随着硬件的发展,4GB 已经无法满足需求。Intel 在 1996 年设计了新的分页方式 ——2-9-9-12 分页,又称 PAE(Physical Address Extension,物理地址扩展)分页

# 2-9-9-12 分页的设计思路

  1. 页大小确定为 4KB 不变,所以 12 位页内偏移不变

  2. 为了增大物理内存的访问范围,需要增大 PTE。考虑对齐因素,PTE 增加到 8 个字节。由于页表大小不变(4KB),每张页表能容纳的 PTE 由 1024 个减少到 512 个(2^9),因此 PTI = 9

  3. 同理 PDI = 9

  4. 还剩前 2 位未分配。与 10-10-12 不同,Cr3 不直接指向 PDT 表,而是指向一张新的表 ——PDPT(Page-Directory-Pointer Table,页目录指针表)。PDPT 表中的每个成员叫做 PDPTE,每项 8 个字节。PDPT 表只有 4 个成员(2 位只能表示 4 种情况: 00 01 10 11 )。

# PDPTE(页目录指针表项)

属性位 含义
第 0 位 P 位,有效位
第 9-11 位 供操作系统软件使用,CPU 不使用
Address of page table 指向 PDT 表地址,共 24 位,后 12 位补 0

# PDE

属性位 含义
PS 页大小。
PS=1:页大小为 2MB(第 35~21 位表示大页物理地址,低 21 位为 0)
PS=0:页大小为 4KB(第 35~12 位表示页表基址,低 12 位补 0)
PAT 页属性表。仅当 PS=1 时有意义

# PTE

属性位 含义
Address of 4KB page frame 物理页基址,低 12 位补 0。
物理页基址 + 12 位页内偏移 → 具体数据

# XD/NX 标志位

  1. 在 PAE 分页模式下,PDE 与 PTE 的最高位为 XD/NX 位。
  2. Intel 中称为 XD,AMD 中称为 NX(No eXecute)。
  3. 的属性包含可读、可写和可执行;的属性包含可读、可写。

当 RET 执行返回时,如果把堆栈里的数据指向一段提前准备好的数据,然后通过栈溢出等方法使程序返回到堆栈中,就会产生任意代码执行的后果。Intel 为此设置了硬件保护 ——XD/NX 位。当 XD=1 时,即使 EIP 跳到了 "数据区",也不可以执行。

# 环境配置

C:\boot.ini 中的 execute 改为 noexecute ,重启即可切换到 2-9-9-12 分页模式。

# 实验:理解 2-9-9-12 分页

一、新建一个记事本,写入 “Hello World”。

二、使用 Cheat Engine 附加进程。

三、找到 “Hello World” 的线性地址。

确认是否为真实地址:

四、将最后一个字符 ‘d’ 改为 ‘m’,观察 CE 中的值是否变化。

线性地址最终确定为: 000AB2C0

五、将线性地址拆分为 2-9-9-12 四组。

1
2
3
4
0x000AB2C0
= 0000 0000 0000 1010 1011 0010 1100 0000
= 00 000000000 010101011 001011000000
PDPTI = 0x0 PDI = 0x0 PTI = 0xAB Offset = 0x2C0

六、获得进程的 Cr3。

1
!process 0 0

七、通过 Cr3 找到字符串的物理地址。

第一层:PDPT 表

第二层:PDT 表

第三层:PT(页表)

第四层:物理页

以字符形式查看数据:


上一篇:保护模式(五)| 10-10-12 分页与 PDE/PTE
下一篇:保护模式(七)| TLB、中断异常与控制寄存器