Windows x64 分页机制——从虚拟地址到物理地址

本文将详细探讨 Windows x64 系统的分页机制,测试环境如下:

操作系统 Windows 10 x64 22H2(2024 年 11 月更新)
调试软件 WinDbg、VMware Workstation Pro

 

分页模式概述


分页模式是现代 x86-64 架构处理器的核心机制,用于将虚拟地址映射到物理地址。目前大多数基于 x86-64 架构的处理器支持的物理地址位宽通常为 46 位(64 TB 寻址空间),而理论上最大支持 48 位(256 TB)。

虚拟地址空间则通过分页机制进行管理,目前主流实现使用 48 位虚拟地址空间(9-9-9-9-12 分页结构),并支持在 5 层分页表(Page Map Level 5)的硬件和操作系统上扩展到 57 位(128 PB 寻址空间)。

目前 Windows x64 系统最大支持 48 位虚拟地址空间(基于 PML4)和最多 46 位物理地址空间(64 TB)。

 

分页结构表


Paging Structure

 

虚拟地址结构


在 64 位模式下,段寄存器的基地址通常为 0,因此程序中的虚拟地址线性地址几乎相同。

页表名称 解析位数
PML5(Page Map Level 5) 9(56 位 ~ 48 位)
PML4(Page Map Level 4) 9(47 位 ~ 39 位)
PDPT(Page Directory Pointer Table) 9(38 位 ~ 30 位)
PD(Page Directory) 9(29 位 ~ 21 位)
PT(Page Table) 9(20 位 ~ 12 位)
Offset(页内偏移) 12(11 位 ~ 0 位)

 

CR3 寄存器通常指向最高层页表的物理地址,其具体结构如下:

  • CR3 指向 PML5 页表的物理基地址,采用 9-9-9-9-9-12 分页结构
  • CR3 指向 PML4 页表的物理基地址,采用 9-9-9-9-12 分页结构

 

由于物理页的大小为 4 KB,我们可以计算得到以下结论:

  • PML5:每个页表包含 = 512 个项,每项占用 4 KB / 512 = 8 字节。
  • PML4:每个页表包含 = 512 个项,每项占用 4 KB / 512 = 8 字节。
  • PDPT:每个页表包含 = 512 个项,每项占用 4 KB / 512 = 8 字节。
  • PD:每个页表包含 = 512 个项,每项占用 4 KB / 512 = 8 字节。
  • PT:每个页表包含 = 512 个项,每项占用 4 KB / 512 = 8 字节。
  • Offset: 页内偏移部分为 = 4 KB,可以完整索引单个物理页的范围。

 

线性地址的索引和偏移计算

  1. 提取线性地址的各级索引和页内偏移

    // 56 bit ~ 48 bit
    PML5E_index = linear_address >> 48 & 0x1FF;
    
    // 47 bit ~ 39 bit
    PML4E_index = linear_address >> 39 & 0x1FF;
    
    // 38 bit ~ 30 bit
    PDPTE_index = linear_address >> 30 & 0x1FF;
    
    // 29 bit ~ 21 bit
    PDE_index = linear_address >> 21 & 0x1FF;
    
    // 20 bit ~ 12 bit
    PTE_index = linear_address >> 12 & 0x1FF;
    
    // 11 bit ~ 0 bit
    Offset(页内偏移) = linear_address & 0xFFF;
    

     

  2. 索引与页表项的大小相乘计算出表项偏移

    PML5E_offset = PML5E_index x 8;
    PML4E_offset = PML4E_index x 8;
    PDPTE_offset = PDPTE_index x 8;
    PDE_offset = PDE_index x 8;
    PTE_offset = PTE_index x 8;
    

 

Windows 支持的页面大小

  • 4 KB 小页:最常见的页面大小。

  • 2 MB 中页:页目录表(PD)的 PS 位为 1 时,跳过页表(PT),直接指向 2 MB 物理页。

    将页目录表项(PDE)的低 21 位清零,再加上线性地址的低 21 位偏移量,得到物理地址:

    // 高位 (PDE & 0xFFFFFFFE00000) 表示 2 MB 物理页的基地址
    // 低位 (线性地址 & 0x1FFFFF) 用于页内偏移
    // 寻址范围: 2^21 = 2 MB
    物理地址 = (PDE & 0xFFFFFFE00000 + 线性地址 & 0x1FFFFF)
    
  • 1 GB 大页:页目录指针表(PDPT)的 PS 位为 1 时,跳过页目录表(PD)和页表(PT),直接指向 1 GB 物理页。

    将页目录指针表项(PDPTE)的低 30 位清零,再加上线性地址的低 30 位偏移量,得到物理地址:

    // 高位 (PDPTE & 0xFFFFC0000000) 表示 1 GB 物理页的基地址
    // 低位 (线性地址 & 0x3FFFFFFF) 用于页内偏移
    // 寻址范围: 2^30 = 1 GB
    物理地址 = (PDPTE & 0xFFFFC0000000 + 线性地址 & 0x3FFFFFFF)
    

 

从虚拟地址到物理地址


我们首先编写一个简单的 C 语言程序,用于打印一个虚拟地址。代码如下:

// TestProject.exe
#include <stdio.h>
#include <Windows.h>

int main(int argc, char* argv[])
{
    int value = 0x12345678;

    printf("Virtual Address: %p\n", &value);
    system("pause");

    return 0;
}

 

运行程序后,输出示例如下:

Virtual Address: 0x000000E9700FFBE4

 

解析上述虚拟地址对应的线性地址

PML4E_index = 0xE9700FFBE4 >> 39 & 0x1FF = 0x1
PDPTE_index = 0xE9700FFBE4 >> 30 & 0x1FF = 0x1A5
PDE_index = 0xE9700FFBE4 >> 21 & 0x1FF = 0x180
PTE_index = 0xE9700FFBE4 >> 12 & 0x1FF = 0xFF
Offset = 0xE9700FFBE4 & 0xFFF = 0xBE4

 

索引与每级页表项的大小相乘计算出偏移

PML4E_offset = 0x1 x 8 = 0x8
PDPTE_offset = 0x1A5 x 8 = 0xD28
PDE_offset = 0x180 x 8 = 0xC00
PTE_offset = 0xFF x 8 = 0x7F8

 

我们利用 WinDbg 调试工具,获取当前程序的 CR3 寄存器。

kd> !process 0 0
PROCESS ffffa8839c77f080
    SessionId: 1  Cid: 0d48    Peb: e96ff2e000  ParentCid: 1170
    DirBase: 12e6bc000  ObjectTable: ffffcd8108554980  HandleCount: 53
    Image: TestProject.exe

 

DirBase 指向 4 层分页表(PML4)的物理地址,DirBase 类似于 CR3 但不完全相同。

kd> !dq 12e6bc000
#12e6bc000 0a000000`33ae4867 0a000001`1dad1867
#12e6bc010 00000000`00000000 00000000`00000000
#12e6bc020 0a000000`057d7867 00000000`00000000
#12e6bc030 00000000`00000000 00000000`00000000

 

PML4 加上表项偏移得到 PML4E,获取页目录指针表(PDPT)

// PML4E = 0x12e6bc000 + 0x8 = 0x12e6bc008

kd> !dq 0x12e6bc008 L1
#12e6bc008 0a000001`1dad1867 // 页目录指针表(PDPT)

 

PDPT 加上表项偏移得到 PDPTE,获取页目录表(PD)

// PDPTE = 0x0a000001`1dad1867 & 0xfffffffff000 + 0xd28 = 0x11dad1d28

kd> !dq 0x11dad1d28 L1
#11dad1d28 0a000000`a16d2867 // 页目录表(PD)

 

PD 加上表项偏移得到 PDE,获取页表(PT)

// PDE = 0x0a000000`a16d2867 & 0xfffffffff000 + 0xc00 = 0xa16d2c00

kd> !dq 0xa16d2c00 L1
#a16d2c00 0a000001`22fdd867 // 页表(PT)

 

PT 加上表项偏移得到 PTE,获取物理页(4 KB Page)

// PTE = 0x0a000001`22fdd867 & 0xfffffffff000 + 0x7f8 = 0x122fdd7f8

kd> !dq 0x122fdd7f8 L1
#122fdd7f8 81000000`313e2847 // 物理页(4 KB Page)

 

物理页加上 Offset(页内偏移)得到物理地址

// 物理地址 = 0x81000000`313e2847 & 0xfffffffff000 + 0xbe4 = 0x313e2be4
// int value = 0x12345678;

kd> !db 0x313e2be4
#313e2be4 78 56 34 12 cc cc cc cc-cc cc cc cc cc cc cc cc xV4.............
#313e2bf4 cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc ................

 


本文由 l4kkS41 创作,采用 CC BY-SA 4.0 许可协议。您可以自由分享与修改,但需注明出处并使用相同协议。
如需转载,请遵守许可协议并附上原文链接:
https://l4kks41.com/virtual-to-physical-address-translation-in-windows-x64-paging/