导语:Windows以及几乎所有操作系统都会使用PFN数据库 ( PFN DataBase ) ,以便跟踪虚拟分配的页面。虚拟分配页面的管理过程都是通过一个名为页帧号(PFN)的列表进行管理的。本文,我将通过大量实际的案例,来解释Windows PFN的实现。

pfn-image.png

Windows页帧号(Page Frame NuMer ,PFN)介绍

Windows以及几乎所有操作系统都会使用PFN数据库 ( PFN DataBase ) ,以便跟踪虚拟分配的页面,进一步管理哪些页面要被写入或移出,哪些页面需要缓存页面等。

虚拟分配页面的管理过程都是通过一个名为页帧号(PFN)的列表进行管理的,关于每个物理和虚拟分配页面的状态及其相应属性都有一长串解释。

本文,我将通过大量实际的案例,来解释Windows PFN的实现。

如果你熟悉non-PAE模式和PAE模式系统,那么就应该注意到,在non-PAE模式下,每个PFN结构需要24个字节,而在PAE模式系统中,需要25个字节,因此如果你的页面是4096个字节,那么就分配大约24个字节以保持对每个页面的追踪。

在non-PAE模式下,每个物理页大小是4K,对于每一个物理页,PFN数据库会使用一个24字节长的结构来保存它的相关信息,比如该物理页是否已经被使用,这是170:1的比率。而在PAE模式下,每个物理页大小是4K,对于每一个物理页,PFN数据库会使用一个28字节长的结构来保存它的相关信息,这是146:1的比率。这意味着PFN数据库中,每1G大小的物理内存,都需要大约6M或7M来描述其中的信息。但是如果你有一个具有16G物理内存的32位系统,那么它只需要大约112M的2G内核虚拟地址空间来处理随机存取存储器(RAM)。这也是为什么拥有16G或更大物理内存的系统不允许使用3G模式(也称为expaneuserva)的原因,因为该模式会将用户虚拟地址空间增加到3G并将32位系统的内核虚拟地址空间减少到1G,扩展页面的好处之一(例如每页2M)需要更少的MMPFN结构。

在开始深入了解PFN之前,请记住术语“Page”主要用于操作系统级概念,而“Frame”用于CPU级概念,因此“Page”表示虚拟页面,“Page Frame”表示物理页面。

PFN列表

PFN由描述某些页面状态的列表组成,比如活动列表(Active List)显示活动页面(例如,在working sets中);待机列表(Standby List)表示先前在磁盘中备份的列表,页面本身可以在不产生磁盘IO的情况下清空和重用;修改列表(Modified List )显示该页面先前已被修改,并且必须以某种方式写入磁盘;移出列表(Freed List),顾名思义,它显示不再需要维护的页面,可以被移出;最后是零列表(Zero List),描述一个包含全零(0)的页面。

下图显示了PFN数据库列表之间的相互关联关系:

pfn.gif

这些列表是用于管理内存中“页面错误”状态的,每次发生“页面错误”后,Windows都试图找到一个可用的表单页,如果列表为空则是零列表,然后零列表则从移出列表中获取可用的表单页,另外,如果移出列表也为空,则移出列表从待机列表获取可用的表单页,并将页面用全零(0)表示。

零页面线程

在Windows中,有一个优先级为0的线程,负责在系统空闲时将内存归零,并且是整个系统中唯一优先级为0的线程。这是可用的最低优先级,因为用户线程至少是1,该线程尽可能清除移出列表。此外,Windows中有一个名为RtlSecureZeroMemory()的函数,它可以安全地移出一个位置,但是在以下的内核透视图中,nt!KeZeroPages负责移出页面。

下图显示了零页面线程的整个过程:

zero-thread.png

此时,零线程(Zero Thread)就可以看得清清楚楚。知道它来自系统进程,且它的优先级为0,这应该就足够了,不再需要更多的信息了。首先尝试查找系统的nt!_eprocess:

!process 0 System

现在我们可以看到系统的线程,我的目标线程(零线程)的细节如下:

        THREAD ffffd4056ed00040  Cid 0004.0040  Teb: 0000000000000000 Win32Thread: 0000000000000000 WAIT: (WrFreePage) KernelMode Non-Alertable
            fffff8034637f148  NotificationEvent
            fffff80346380480  NotificationEvent
        Not impersonating
        DeviceMap                 ffff99832ae1b010
        Owning Process            ffffd4056ec56040       Image:         System
        Attached Process          N/A            Image:         N/A
        Wait Start TickCount      4910           Ticks: 4 (0:00:00:00.062)
        Context Switch Count      21023          IdealProcessor: 3             
        UserTime                  00:00:00.000
        KernelTime                00:00:01.109
        Win32 Start Address nt!MiZeroPageThread (0xfffff80346144ed0)
        Stack Init ffffe700b7c14c90 Current ffffe700b7c14570
        Base ffffe700b7c15000 Limit ffffe700b7c0f000 Call 0000000000000000
        Priority 0 BasePriority 0 PriorityDecrement 0 IoPriority 2 PagePriority 5
        Child-SP          RetAddr           Call Site
        ffffe700`b7c145b0 fffff803`46016f8a nt!KiSwapContext+0x76
        ffffe700`b7c146f0 fffff803`46016951 nt!KiSwapThread+0x16a
        ffffe700`b7c147a0 fffff803`46014ba7 nt!KiCommitThreadWait+0x101
        ffffe700`b7c14840 fffff803`461450b7 nt!KeWaitForMultipleObjects+0x217
        ffffe700`b7c14920 fffff803`460bba37 nt!MiZeroPageThread+0x1e7
        ffffe700`b7c14c10 fffff803`46173456 nt!PspSystemThreadStartup+0x47
        ffffe700`b7c14c60 00000000`00000000 nt!KiStartSystemThread+0x16

正如你所看到的,它的起始地址是nt!MiZeroPageThread,且它的优先级是0,如果你看到调用栈,那么就可以看到之前调用的 nt!MiZeroPageThread。

更多细节,请访问《隐藏的内存分配成本》一文。

PFN的Windows结构是nt!_MMPFN,如下图所示:

mmpfn.png

如你所见,_MMPFN需要28个字节。

PFN记录会根据其物理地址顺序存储在存储器中,这意味着你可以在PFN的帮助下计算出物理地址。

Physical Address = PFN * page size(e.g 4096 Byte) + offset

PFN数据库的地址位于nt!MmPfnDatabase,你可以使用以下所示的代码段在Windbg中获取PFN数据库地址。

2: kd> x nt!MmPfnDatabase
fffff800`a2a76048 nt!MmPfnDatabase = <no type information>

!memusage

windbg中另一个非常有用的命令是!memusage,这个命令几乎提供了内存布局中关于PFN和页面的几乎所有内容及其相关细节,例如文件、字体、系统驱动程序、DLL模块、可执行文件(包括它们的名称和它们的分页位修改) 。

!memusage命令从物理内存角度显示内存统计信息,无数个页面信息将被打印出来,可以说是“最内存”的信息。此命令会查看所有的页帧,所以运行时会非常的耗时。

该命令的简要介绍如下所示:

2: kd> !memusage
 loading PFN database
loading (100% complete)
Compiling memory usage data (99% Complete).
             Zeroed:     9841 (   39364 kb)
               Free:   113298 (  453192 kb)
            Standby:   105520 (  422080 kb)
           Modified:     7923 (   31692 kb)
    ModifiedNoWrite:        0 (       0 kb)
       Active/Valid:   286963 ( 1147852 kb)
         Transition:       45 (     180 kb)
          SLIST/Bad:      567 (    2268 kb)
            Unknown:        0 (       0 kb)
              TOTAL:   524157 ( 2096628 kb)

Dangling Yes Commit:      140 (     560 kb)
 Dangling No Commit:    37589 (  150356 kb)
  Building kernel map
  Finished building kernel map

 (Master1 0 for 80)

 (Master1 0 for 580)

 (Master1 0 for 800)

 (Master1 0 for 980)
Scanning PFN database - (97% complete) 
 (Master1 0 for 7d100)
Scanning PFN database - (100% complete) 

  Usage Summary (in Kb):
Control       Valid Standby Dirty Shared Locked PageTables  name
ffffffffd 11288      0     0     0 11288     0    AWE
ffffd4056ec4c460     0    112     0     0     0     0  mapped_file( LeelUIsl.ttf )
ffffd4056ec4c8f0     0    160     0     0     0     0  mapped_file( malgun.ttf )
ffffd4056ec4d6b0     0    108     0     0     0     0  mapped_file( framd.ttf )

.....

ffffd4057034ecd0   328    148     0     0     0     0  mapped_file( usbport.sys )
ffffd4057034f0e0    48     28     0     0     0     0  mapped_file( mouclass.sys )
ffffd4057034f7d0    32     28     0     0     0     0  mapped_file( serenum.sys )
ffffd405703521a0     0     20     0     0     0     0  mapped_file( swenum.sys )

.....

--------     0     20     0 ----- -----     0  session 0 0
--------     4      0     0 ----- -----     0  session 0 ffffe700b8b45000
--------     4      0     0 ----- -----     0  session 1 ffffe700b8ead000
--------  32520      0    84 ----- -----  1324  process ( System ) ffffd4056ec56040
--------  2676      0     0 ----- -----   304  process ( msdtc.exe ) ffffd405717567c0
--------  4444      0     0 ----- -----   368  process ( WmiPrvSE.exe ) ffffd405718057c0
--------  37756      0    60 ----- -----  1028  process ( SearchUI.exe ) ffffd405718e87c0

.....

--------     8      0     0 -----     0 -----  driver ( condrv.sys )
--------     8      0     0 -----     0 -----  driver ( WdNisDrv.sys )
--------    52      0     0 -----     0 -----  driver ( peauth.sys )
--------  24744      0     0 -----     0 -----         ( PFN Database )
Summary   1147852  422260 31692 129996 204428 25156  Total

.....

    b45b    64      0     0    60     0     0   Page File Section
    b56b     4      0     0     4     0     0   Page File Section
    b7ec    84      0     0    64     0     0   Page File Section
    b905    12      0     0     0     0     0   Page File Section
    bf5c     4      0     0     0     0     0   Page File Section
	
.....

有关这些页面的更多信息,Windbg帮助文档是这样解释的:

你可以使用!vm扩展命令分析虚拟内存使用情况,这个扩展通常比!memusage更有用。有关内存管理的更多信息,请参阅Mark Russinovich和David Solomon撰写的Microsoft Windows Internals。!pfn扩展命令可用于在PFN数据库中显示特定页帧条目。

在下一节,我会介绍如何更详细的查看这些命令。

源链接

Hacking more

...