Windows保护模式下的分页机制(PART1:10-10-12模式)

本文深入探讨了计算机系统中虚拟地址与物理地址的概念,详细解析了10-10-12和2-9-9-12两种分页模式下的地址转换机制,包括逻辑地址、线性地址及物理地址的定义,以及如何在Windows环境下进行实验验证。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.概述

一个进程的虚拟地址空间是4GB,它的物理大小并不是真的4GB,如果真的是4GB,那运行中的所有进程都加起来占用的空间是一个天文数字,事实上系统会对这4GB的地址空间进行转换,然后映射到物理内存中。换句话说虚拟地址本身并不是真实存在的。
将虚拟地址转换为物理地址依赖的规则就是分页机制。
物理地址也并不是内存条地址,物理地址需要再进行一层转换才能找到真正的内存条地址。
我们只要会转换到物理地址就够用了。

1.1 逻辑地址

假如有指令:

MOV eax,dword ptr ds:[0x123456]

0x123456就是逻辑地址

1.2 线性地址

线性地址是对于虚拟空间来说的,还是上面的例子,ds.Base + 0x123456就是线性地址

1.3 物理地址

通过线性地址可以转换为物理地址。

1.4 分页模式

分页模式分为10-10-12和2-9-9-12,前者最多可以支持4GB内存,后者在32位操作系统下可以支持16GB内存,64位操作系统下可以支持128G内存,后者也叫PAE分页。
在Windows XP下,找到C盘下的boot.ini文件,将里面的noexecute改为execute即可将PAE分页改为10-10-12分页。

2. 10-10-12模式

2.1 10-10-12模式概述

一个32位的虚拟地址会被解释为三个部分:

  1. 页目录索引(PDT,10位)
  2. 页表索引(PTT,10位)
  3. 字节索引(12位)

系统中有一个CR3寄存器,它存储了页目录索引的地址,页目录占4KB,里面的成员占4个字节,因此有1024个成员,PDT里面的成员被称作PDE,PDE里面保存的是一个32位的物理页地址。

每个PDE又指向一个页表索引,页表索引里面的成员被称作PTE,PTE可以没有物理页(线性地址0一般就没有),多个PTE也可以指向同一个物理页。

10-10-12结构的第1个10是PDT中的索引,第2个10是PTT中的索引,最后的12是指物理页上的偏移,所以简单来说转换成物理地址只需要将10-10-12的结构分别在PDT和PTT中找到对应的位置再加上物理页的偏移就是最终的物理地址了。

在这里插入图片描述

(上面的图其实不是很准确,不过为了便于理解先以这个图为准吧,其实页目录表也是页表,只不过它比较特殊,后面会介绍)

2.2 线性地址转物理地址实验

本实验是在10-10-12模式下进行的,因此要将PAE关闭,PAE就是2-9-9-12模式,可以简单地理解它为增强型的分页模式。

bcdedit /set pae forcedisable

重新打开PAE模式

bcdedit /set pae forceenable

1.找到数据在内存中的线性地址

  • 新建一个.txt文件,文件中 有字符串“hello,world”,并保持.txt文件处于打开状态。
  • 使用CE附加到notepad.exe
  • 在Text栏中搜“hello,world”,找到可以找到多个跟它有关的地址,通过修改.txt文件中的字符串最终筛选出真正的内存地址0x000AA8A0,注意勾选上Unicode以及Value Type的类型,如下图所示:

在这里插入图片描述
在这里插入图片描述

在进程中查找字符串还有一方法:

  • 在WinDbg中执行!process 0 0 notepad.exe
    在这里插入图片描述
  • 继续执行 .process 9050c4e0,切换到记事本进程中
  • 执行s -u 0x00000000 L0x01000000 "hello,world"搜索内存中的字符串
  • 结果可能会有多个,但是只有一个是正确的线性地址,可以使用du 0x28e090指令查看Unicode字符串。

2.分解线性地址
000AA8A0转成二进制是32位,刚好可以按照10-10-12的格式分解成:

0000 0000 00
0010 1010 10
1000 1010 0000

3.根据分解后的线性地址找到物理地址

  • 首先在WinDbg中使用!process 0 0,在一堆进程中找到记事本进程,然后可以找到它的CR3寄存器0x146a0000,这个地址就是PDT的基址,它保存的是物理地址,所以下面的指令都是以!开头的。
    在这里插入图片描述

  • 10-10-12格式的第1个10部分是0000 0000 00,这个值指向了它在PDT中的下标,所以使用!dd 146a0000+0找到对应的PDE。
    在这里插入图片描述

  • 10-10-12格式的第2个10部分是0010 1010 10,上面找到的PDE是0x14dc0067,因此通过使用!dd 14dc0000+aa*4找到对应的PTE,为什么不是14dc0067而是14dc0000?因为PDE的最后12位是属性,下面PTE的末尾12位同样也是属性,PDE的属性&PTE属性=物理页的属性。
    PDE和PTE的属性如下:
    在这里插入图片描述
    在这里插入图片描述

P:1可用,0不可用
R/W:R/W=0只读,R/W=1可读可写
U/S:0特权用户,1普通用户
A:是否被访问,访问置1
D:是否被写过,写过置1
P/S:PageSize,只对PDE有意义,1直接指向物理页无PTE,低22位是页内偏移,页大小为4MB,俗称为“大页”
PAT:只对PTE有意义
G:CR3改变时,TLB会立即刷新,但不会刷新PDE/PTE的G位为1的页,一般高2G线性地址G为1,因为高2G是系统通用的,刷新影响效率

PWT、PCD、PAT在下篇介绍PAE时的时候会解释。

0010 1010 10对应aa,乘以4的原因是每个元素占4个字节
在这里插入图片描述

  • 找到的PTE是0x13fdd067,最后再加上物理页偏移1000 1010 0000(转换成十六进制是0x8a0)就是最终的物理地址了,使用!dd 13fdd000+8a0。
    在这里插入图片描述
  • 使用上面的指令还看不出是字符串“hello,world”,使用!db 13fdd000+8a0就可以看到字符串了

2.3 页目录表基址

系统要保证一个线性地址有效,必须提前将这个线性地址对应的PDE和PTE填充,那么系统是怎么填充PDE和PTE的呢?

PDE和PTE都是物理页,我们在程序中是无法直接访问物理页的,我们要访问任何东西必须要通过线性地址。

系统通过0xC0300000这个线性地址找到的物理页就是页目录表,这个物理页比较特殊,它是页目录表PDT,但是本身也是页表,即PTT,换句话说,其实所谓的PDT表本身也是一个PTT表。0xC030000指向了PDT,所以系统可以通过它填充PDE,PTE是怎么填充的呢?看下一节页表基址

2.4 页表基址

系统通过0xC0000000访问第一个PTT,0xC0001000指向第二个PTT。
一个4GB的进程中只能有一个PDT(上面说过,其实PDT是一种特殊的PTT),但是有1024个PTT,每个PTT都占4KB。

总结一下:

  1. 整个页表占4M地址空间,从0xC0000000到0xC03FFFFF,页表有1024个成员,每个成员都是一个PTT,占4KB。
  2. 在这1024个成员中,有一个PTT比较特殊,它就是PDT。

真正的页表是这样的:

在这里插入图片描述
访问页目录表的公式:

0xC0300000 + PDI * 4(PDI是页目录表的索引)

访问页表的公式:

0xC0000000 + PDI * 4096 + PTI * 4(PDI * 4096是因为每个PTT的大小是4K,PTI是页表的索引,PTI*4,每个PTE占4个字节)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值