使用windbg观察IA-32处理器启用PAE后内存分页映射关系

目的

    调试代码的过程中,在一些复杂情况下需要查看内存数据的实际物理地址。32位windows xp sp2系统在intel处理器下默认启用了PAE,这里就探讨一下在启用PAE后查看实际物理地址的方法。

原理

    PAE是 Physical Address Extension的缩写,即物理地址扩展。简单来说,就是把IA-32处理器的寻址能力从原来的4GB扩展到64GB。寻址4GB空间,要求物理地址的宽度为32位。类似的,要寻址64GB空间,那么物理地址的宽度就是36位。因为这个原因,PAE又被称为PAE-36bit。

    如果从CPU的引脚来讲,那么支持PAE的CPU原则上应该有36根地址线,但因为IA-32 CPU的前端总线是64位宽,每次可同时传递8个字节数据,所以CPU在访问内存时,它总是向前端总线给出一个低3位为0的地址,因此实际上省略了低三位地址线。这实际上也起到了将地址按8字节对齐的功效。以奔腾4 CPU为例,它的地址线是A[35:3]#。

    当IA-32 CPU工作在32位模式时,软件中使用的是32位宽的虚拟地址和线性地址,因此便产生了一个问题,如何将32位宽的线性地址翻译为36位宽的物理地址,寻址能力从原来的4GB扩展到64GB呢?答案是将原来的二级映射扩展为三级映射,也就是在原来的页目录和页表基础上再增加一级,称为页目录指针表。根据单个内存页的大小不同(4KB或者2MB)又分为两种情况。

  下图中画出的是4KB内存页的情况。其中的PDPTR是Page Directory Pointer Table Register的缩写,它是启用PAE后CR3寄存器的别名,该寄存器中保存了页目录指针表所在地址。此时,32位线性地址被分割为如下三个部分:

    * 2位(位30和位31)的页目录指针表索引,用来索引本地址在页目录指针表中的对应表项。
    * 9位(位21-29)的页目录表索引,用来索引本地址在页目录表中的对应表项。
    * 9位(位12-20)的页表索引,用来索引本地址在页表中的对应表项。
    * 12位(位0-11)的页内偏移,这与以前是相同的。

http://advdbg.org/img/inset/pae_tran_4kb.bmp
图1 启用PAE时将线性地址翻译为物理地址(4KB内存页)(摘自IA-32手册卷3A)

    和没有启用PAE的4KB情况相比,表的数量由两张变成了三张,增加了页目录指针表。

    因为表中的地址都是物理地址,所以三张表的每个表项都有原来的32位变为64位,具体格式如下图所示。
http://advdbg.org/img/inset/pae_entries.bmp
图2 启用PAE时的地址转换表项格式(4KB内存页)(摘自IA-32手册卷3A)

    也就是说,每个64位的表项中有24位是用来代表基地址的,这24位对应的是物理地址的高24位,低12位为0。这意味着,页目录表、页表和每个页的基地址都是按4KB对齐的。

    2MB内存页的情形。与未启用PAE的情况一样,不再需要页表。如下图所示。
http://advdbg.org/img/inset/pae_tran_2mb.bmp
图3 启用PAE时将线性地址翻译为物理地址(2MB内存页)(摘自IA-32手册卷3A)

调试观察

    有了上面所说的原理,我们需要观察验证一下。这里使用windbg在Windows XP SP2系统下,利用Windows自带的计算器程序(calc.exe)进行调试。步骤如下。

1.启动计算器程序(calc.exe),键入一串数字(如123456789)以便后面观察。

2.启动Windbg,并附加到计算器程序上开始调试。

3.加载符号表文件,Windows的符号服务器地址为http://msdl.microsoft.com/download/symbols,因此选中File=>Symbol File Path菜单项,加入符号路径SRV*d:symbols*http://msdl.microsoft.com/download/symbols,然后勾选reload框,加载调试计算器程序所需符号表。

4.在Windbg命令区输入x calc!g*命令列出以g开头所有符号。注意其中包含的gpszNum行。

5.在Windbg命令区输入dd calc!gpszNum l1命令,查看该符号地址内容。我这里该变量地址为000b4650。

6.继续查看地址000b4650处的内容:db 000b4650

0:002> db 000b4650
000b4650 31 00 32 00 33 00 34 00-35 00 36 00 37 00 38 00 1.2.3.4.5.6.7.8.
000b4660 39 00 2e 00 00 00 00 00-00 00 65 00 72 00 00 00 9.........e.r...

可以看到该内存处的具体内容。下面我们看一看如何把这个字符串地址(000b4650)翻译为物理地址。

1.再启动一个Windbg实例,选中File=>Kernel Debug菜单中的local页签,点击确定进入本地内核调试模式,然后如前所述方式加载符号表。

2.列出calc进程的概况:!process 0 0 calc.exe

lkd> !process 0 0 calc.exe
PROCESS 8679f180 SessionId: 0 Cid: 1d6c Peb: 7ffd5000 ParentCid: 01fc
DirBase: 0a4c27e0 ObjectTable: e14594b0 HandleCount: 77.
Image: calc.exe

上面的DirBase便是CR3寄存器的值,即PDPTR的内容,其格式如下,可以看出低5位为0,页目录指针表起始地址是32Byte对齐的。
http://advdbg.org/img/inset/pae_pdptr.bmp

3.将要转换的线性地址000b4650 显示为二进制:

lkd> .formats 000b4650
Evaluate expression:
Hex: 000b4650
Decimal: 738896
Octal: 00002643120
Binary: 00000000 00001011 01000110 01010000
Chars: ..FP
Time: Fri Jan 09 21:14:56 1970
Float: low 1.03541e-039 high 0
Double: 3.65063e-318

    根据图1,分解为如下4个部分:

    * 最高两位为0,是页目录指针表的索引。
    * 接下来的9位(000000 000)为页目录索引,即0。
    * 再接下来的9位(01011 0100)为页目录索引,即0xb4。lkd> ? 0y010110100 Evaluate expression: 180 = 000000b4
    * 最后的12位(0110 01010000)是页内偏移,即0x650。

4.因为CR3值(0a4c27e0) 的低5位都是0,所以可以知道计算器进程的页目录指针表的基地址就是0x0a4c27e0。观察这一地址附近的内容:

lkd> !dd 0a4c27e0
# a4c27e0 22e14801 00000000 74295801 00000000
# a4c27f0 7d156801 00000000 27053801 00000000
# a4c2800 1e975801 00000000 78e36801 00000000
# a4c2810 282f7801 00000000 394f4801 00000000
# a4c2820 1b78a801 00000000 6bd8b801 00000000

每个页目录指针表共有4个表项,每个表项是8字节(64位),因此,可以知道上面的前两行内容是计算器进程的页目录指针表。

5.根据上面的分解,1b1c0aa0对应的是0号表项,即00000000`22e14801。根据图2,位12到位35是页目录表基地址的高24位,因此,可以知道对应的页目录表基地址是0x22e14000,观察它的0号表项:

lkd> !dq 0x22e14000
#22e14000 00000000'3445a867 00000000'42130867
#22e14010 00000000'77246867 00000000'345c9867
#22e14020 00000000'392e1867 00000000'4830a867
#22e14030 00000000'310d4867 00000000'1c16c867
#22e14040 00000000'7a459867 00000000'00000000

可见,我们要翻译的线性地址对应的页目录表项是00000000`3445a867,其中位12到位35是页表基地址的高24位,因此可以知道我们要寻找的页表基地址是3445a000,观察它的0xb4号表项:

lkd> !dq 0x3445a000+0xb4*8
#3445a5a0 80000000'45505867 80000000'55986867
#3445a5b0 80000000'29e47867 80000000'75008867
#3445a5c0 00000000'00000080 00000000'00000000

这样便得到页表表项:80000000`45505867, 它的位12到位35是内存页基地址的高24位,即线性地址000b4650所对应的内存页基地址是45505000,加上页内偏移0x650便得到最终的物理地址,即0x45505650,显示其内容:

lkd> !db 45505650
#45505650 31 00 32 00 33 00 34 00-35 00 36 00 37 00 38 00 1.2.3.4.5.6.7.8.
#45505660 39 00 2e 00 00 00 00 00-00 00 65 00 72 00 00 00 9.........e.r...

可见,其内容与前面使用用户态调试器观察的线性地址内容是一致的。

原文地址:https://www.cnblogs.com/elvisxu/p/3403380.html