Windows11_22H2下的内存管理分析

虚拟内存管理

在64位的操作系统下,理论上可以使用的虚拟地址空间为2^64,也就是16EB。但是处理器对此做了限制,不能使用高16位,因为实现64位地址宽度会增加系统的复杂度和地址转换的成本,仅仅48位虚拟地址就用了四级页表甚至五级页表转换为物理地址。

Windows操作系统又限制使用了高16位的高4位即[44,47]位。在用户空间下,高20位为0;在内核空间下,高20位为1。

内核空间内存管理

Windows系统在初始化时创建了两种内存池,即可以换入磁盘的分页内存池和不可换入的非分页内存池。
内存管理1

分页内存无法从IRQL>=DPC调度级别上访问。因为会触发页面异常,在IRQL>=DPC时,无法处理页面异常(时钟中断用到了一个DPC的软中断。当IRQL>=DPC时,是不会被打断的,也就是不会进行由时钟中断引发的线程切换。当触发#PF会导致IO操作,进而引起线程切换,这时会将一个内核特殊APC插入线程以触发软中断。然而,此时不能进行线程切换,也无法接收APC的软中断。)。

可以通过某些API将内存地址“锁住”,如r3的VirtualLock,r0的MmProbeAndLockPage

进程的CR3指向的PML4表的高256项相同,内核空间的大部分是共享的。

在windbg中可以使用!poolused [Flags [TagString]] 查看系统内存使用摘要。
内存管理2

用户空间内存管理

进程用户空间分配的是虚拟内存,不能直接使用物理内存。虚拟地址会通过四级页表查询找到对应物理地址。分配连续的虚拟内存,在物理内存上不一定是连续的。

对于每个进程,操作系统用虚拟地址描述符VAD(平衡二叉树)树来管理,记录了进程中所分配内存的属性。

定位一下进程的VAD树,在进程结构体_EPROCESS的+0x7d8偏移处(不同版本的操作系统可能不同)。
内存管理3

VadRoot 字段指向一个 MM_AVL_TABLE 结构体,该结构体包含了一个平衡二叉树,用于存储该进程的虚拟地址空间描述符节点。每个节点都包含了虚拟地址空间的起始地址、结束地址以及一些其他描述符信息,如是否可读、可写、可执行等。

在Windbg中使用

!vad VAD-Root [Flag]
!vad Address 1

可显示vad树的信息。
查看进程KmdManager.exe的Vad树信息。
内存管理4

当系统调用VirtualAlloc等函数时,则会在vad树上增加一个结点(_MMVAD结构体)。

vad的那一列表示树节点结构体_MMVAD地址。
Level表示在树的第几层,根节点为0。
Start表示起始虚拟页号,End表示结束虚拟页号。
Commit表示提交了几个物理页。
Mapped或者Private 表示是映射内存还是私有内存。

  • Private Memory :VirtualAlloc/VirtualAllocEx , 私有内存。

  • Mapped Memory :CreateFileMapping ,映射内存,共享文件,镜像文件。

镜像文件通常通过LoadLibrary加载,属性WRITECOPY,避免影响到其它使用此文件的。

后面的话就是表示内存区域的属性,这里对WRITECOPY写拷贝进行较为详细解释:
当向此属性的页面中进行写操作时,会分配一个新的物理内存页面,将原来的数据复制到新的内存页面,重新执行该写操作。

当向内存页面中进行写操作时,会执行如下操作:
第0步:检查被写入内存页的PTE属性,如果页面的PTE属性的U/S位为0,触发页保护异常,写入失败返回0XC00000005错误码;否则进入下一步。
第1步:检查PTE的Write/Read位,如果为1表示检查通过则直接写入数据到内存,写入成功!如果为0,表示此PTE描述的内存页面为只读页,继续检查PTE的第9位,如果此位为0,触发页保护异常。
第2步:进入页保护异常后就完全将控制权交给操作系统,操作系统会先检查被写入页面的进程VAD属性,如果为不存在写拷贝属性,写入失败,返回0xc00000005错误码;如果存在写拷贝属性,进入下一步。
第3步:如果存在写拷贝属性,操作系统会在将被写入页面的PTE的第9位置1,将CPU的EIP设置为之前程序往内存写入数据的那条指令的地址,进入下一步。
第4步:继续执行写入数据到内存的那条指令,注意此时PTE属性的write/Read仍然为0,仍然会再次触发页保护异常,返回第0步,接着走第2步,此时操作系统会发现PTE的第9位被置1了,如果PTE属性的第9位为1,则分配一个新的物理内存页面,将原来的数据复制到新的内存页面,修改PTE并且将PTE的write/Read位置1,页保护异常返回到程序之前写入数据到内存的那条指令,再次执行这条指令因为PTE的Write/Read位为1,会直接写入成功!

需要注意的是,栈不受VAD树管理。系统在创建线程时,会为该线程分配一段物理内存页,并映射到该线程的栈空间中,随后,将栈空间的起始地址记录在该线程的 线程环境块TEB 中。

_MMVAD

VAD树结点——结构体_MMVAD

//0x88 bytes (sizeof)
struct _MMVAD
{
    struct _MMVAD_SHORT Core;                                               //0x0
    union
    {
        ULONG LongFlags2;                                                   //0x40
        volatile struct _MMVAD_FLAGS2 VadFlags2;                            //0x40
    } u2;                                                                   //0x40
    struct _SUBSECTION* Subsection;                                         //0x48
    struct _MMPTE* FirstPrototypePte;                                       //0x50
    struct _MMPTE* LastContiguousPte;                                       //0x58
    struct _LIST_ENTRY ViewLinks;                                           //0x60
    struct _EPROCESS* VadsProcess;                                          //0x70
    union
    {
        struct _MI_VAD_SEQUENTIAL_INFO SequentialVa;                        //0x78
        struct _MMEXTEND_INFO* ExtendedInfo;                                //0x78
    } u4;                                                                   //0x78
    struct _FILE_OBJECT* FileObject;                                        //0x80
}; 

内存管理5

需要注意,私有内存不涉及section对象,所以对于Privite Memory来说,在_MMVAD结构中, 仅仅只有 _MMVAD_SHORT类型的Core成员有用。至于_MMVAD结构扩展字段对于Privite Memory 是没有意义的。也可以说Privite Memory对应的vad树结点类型是_MMVAD_SHORT类型。

Core(_MMVAD_SHORT)
//0x40 bytes (sizeof)
struct _MMVAD_SHORT
{
    union
    {
        struct
        {
            struct _MMVAD_SHORT* NextVad;                                   //0x0
            VOID* ExtraCreateInfo;                                          //0x8
        };
        struct _RTL_BALANCED_NODE VadNode;                                  //0x0
    };
    ULONG StartingVpn;                                                      //0x18
    ULONG EndingVpn;                                                        //0x1c
    UCHAR StartingVpnHigh;                                                  //0x20
    UCHAR EndingVpnHigh;                                                    //0x21
    UCHAR CommitChargeHigh;                                                 //0x22
    UCHAR SpareNT64VadUChar;                                                //0x23
    LONG ReferenceCount;                                                    //0x24
    struct _EX_PUSH_LOCK PushLock;                                          //0x28
    union
    {
        ULONG LongFlags;                                                    //0x30
        struct _MMVAD_FLAGS VadFlags;                                       //0x30
        struct _MM_PRIVATE_VAD_FLAGS PrivateVadFlags;                       //0x30
        struct _MM_GRAPHICS_VAD_FLAGS GraphicsVadFlags;                     //0x30
        struct _MM_SHARED_VAD_FLAGS SharedVadFlags;                         //0x30
        volatile ULONG VolatileVadLong;                                     //0x30
    } u;                                                                    //0x30
    union
    {
        ULONG LongFlags1;                                                   //0x34
        struct _MMVAD_FLAGS1 VadFlags1;                                     //0x34
    } u1;                                                                   //0x34
    union
    {
        ULONGLONG EventListULongPtr;                                        //0x38
        UCHAR StartingVpnHigher:4;                                          //0x38
    } u5;                                                                   //0x38
}; 

内存管理6

_MMVAD_SHORT_MMVAD的主要结构。

StartingVpn为起始虚拟页号,EndingVpn为结束虚拟页号。

Core.u.VadFlags(_MMVAD_FLAGS )
//0x4 bytes (sizeof)
struct _MMVAD_FLAGS
{
    ULONG Lock:1;                                                           //0x0
    ULONG LockContended:1;                                                  //0x0
    ULONG DeleteInProgress:1;                                               //0x0
    ULONG NoChange:1;                                                       //0x0
    ULONG VadType:3;                                                        //0x0
    ULONG Protection:5;                                                     //0x0
    ULONG PreferredNode:7;                                                  //0x0
    ULONG PageSize:2;                                                       //0x0
    ULONG PrivateMemory:1;                                                  //0x0
}; 

内存管理7

NoChange可以用来锁页,但是只针对私有内存。

VadType枚举定义如下:

typedef enum _MI_VAD_TYPE {
    VadNone,
    VadDevicePhysicalMemory,
    VadImageMap,
    VadAwe,
    VadWriteWatch,
    VadLargePages,
    VadRotatePhysical,
    VadLargePageSection
} MI_VAD_TYPE, *PMI_VAD_TYPE;

Protection表示当前VAD结点内存区域的读写属性。

0x1为READONLY; 0x2为EXECUTE; 0x3为EXECUTE _READ; 0x4为READWITER; 0x5为WRITECOPY; 0x6为EXECUTE _READWITER; 0x7为EXECUTE_WRITECOPY

PrivateMemory表示是私有内存(Private)还是映射内存(Private)。

Subsection(_SUBSECTION*)
//0x38 bytes (sizeof)
struct _SUBSECTION
{
    struct _CONTROL_AREA* ControlArea;                                      //0x0
    struct _MMPTE* SubsectionBase;                                          //0x8
    struct _SUBSECTION* NextSubsection;                                     //0x10
    union
    {
        struct _RTL_AVL_TREE GlobalPerSessionHead;                          //0x18
        struct _MI_CONTROL_AREA_WAIT_BLOCK* CreationWaitList;               //0x18
        struct _MI_PER_SESSION_PROTOS* SessionDriverProtos;                 //0x18
    };
    union
    {
        ULONG LongFlags;                                                    //0x20
        struct _MMSUBSECTION_FLAGS SubsectionFlags;                         //0x20
    } u;                                                                    //0x20
    ULONG StartingSector;                                                   //0x24
    ULONG NumberOfFullSectors;                                              //0x28
    ULONG PtesInSubsection;                                                 //0x2c
    union
    {
        struct _MI_SUBSECTION_ENTRY1 e1;                                    //0x30
        ULONG EntireField;                                                  //0x30
    } u1;                                                                   //0x30
    ULONG UnusedPtes:30;                                                    //0x34
    ULONG ExtentQueryNeeded:1;                                              //0x34
    ULONG DirtyPages:1;                                                     //0x34
}; 

内存管理8

Subsection.ControlArea(_CONTROL_AREA*)

ControlArea是SubSection中的重要成员。

//0x80 bytes (sizeof)
struct _CONTROL_AREA
{
    struct _SEGMENT* Segment;                                               //0x0
    union
    {
        struct _LIST_ENTRY ListHead;                                        //0x8
        VOID* AweContext;                                                   //0x8
    };
    ULONGLONG NumberOfSectionReferences;                                    //0x18
    ULONGLONG NumberOfPfnReferences;                                        //0x20
    ULONGLONG NumberOfMappedViews;                                          //0x28
    ULONGLONG NumberOfUserReferences;                                       //0x30
    union
    {
        ULONG LongFlags;                                                    //0x38
        struct _MMSECTION_FLAGS Flags;                                      //0x38
    } u;                                                                    //0x38
    union
    {
        ULONG LongFlags;                                                    //0x3c
        struct _MMSECTION_FLAGS2 Flags;                                     //0x3c
    } u1;                                                                   //0x3c
    struct _EX_FAST_REF FilePointer;                                        //0x40
    volatile LONG ControlAreaLock;                                          //0x48
    ULONG ModifiedWriteCount;                                               //0x4c
    struct _MI_CONTROL_AREA_WAIT_BLOCK* WaitList;                           //0x50
    union
    {
        struct
        {
            union
            {
                ULONG NumberOfSystemCacheViews;                             //0x58
                ULONG ImageRelocationStartBit;                              //0x58
            };
            union
            {
                volatile LONG WritableUserReferences;                       //0x5c
                struct
                {
                    ULONG ImageRelocationSizeIn64k:16;                      //0x5c
                    ULONG SystemImage:1;                                    //0x5c
                    ULONG CantMove:1;                                       //0x5c
                    ULONG StrongCode:2;                                     //0x5c
                    ULONG BitMap:2;                                         //0x5c
                    ULONG ImageActive:1;                                    //0x5c
                    ULONG ImageBaseOkToReuse:1;                             //0x5c
                };
            };
            union
            {
                ULONG FlushInProgressCount;                                 //0x60
                ULONG NumberOfSubsections;                                  //0x60
                struct _MI_IMAGE_SECURITY_REFERENCE* SeImageStub;           //0x60
            };
        } e2;                                                               //0x58
    } u2;                                                                   //0x58
    struct _EX_PUSH_LOCK FileObjectLock;                                    //0x68
    volatile ULONGLONG LockedPages;                                         //0x70
    union
    {
        ULONGLONG IoAttributionContext:61;                                  //0x78
        ULONGLONG Spare:3;                                                  //0x78
        ULONGLONG ImageCrossPartitionCharge;                                //0x78
        ULONGLONG CommittedPageCount:36;                                    //0x78
    } u3;                                                                   //0x78
}; 

内存管理9

控制域ControlArea,描述section的一些内容,内存区对象的核心。

一般情况下,
一个完整的的控制域对象之后还紧跟着N个_SUBSECTION结构。
一个EXE或DLL有多个段,.text,.data有多少个段就有多少个Subsection,每个Subsection对应文件中的一个section,用于描述文件中每节的映射信息,可读可写写时复制。
PE文件有多少个节就有多少个Subsection,所有的Subsection构成一个单链表,每个Subsection都有一个指针回到_CONTROL_AREA结构。

FilePointer:这是映射文件特有的,如果是共享内存是靠内存页面支撑的,那么此处为空。指向的是内存块对应的_FILE_OBJECT文件对象(后3位需要置0),如果_FILE_OBJECT文件对象有效就可以得到对应文件的FileName等信息。

ControlArea.Segment(_SEGMENT*)
//0x48 bytes (sizeof)
struct _SEGMENT
{
    struct _CONTROL_AREA* ControlArea;                                      //0x0
    ULONG TotalNumberOfPtes;                                                //0x8
    struct _SEGMENT_FLAGS SegmentFlags;                                     //0xc
    ULONGLONG NumberOfCommittedPages;                                       //0x10
    ULONGLONG SizeOfSegment;                                                //0x18
    union
    {
        struct _MMEXTEND_INFO* ExtendInfo;                                  //0x20
        VOID* BasedAddress;                                                 //0x20
    };
    struct _EX_PUSH_LOCK SegmentLock;                                       //0x28
    union
    {
        ULONGLONG ImageCommitment;                                          //0x30
        ULONG CreatingProcessId;                                            //0x30
    } u1;                                                                   //0x30
    union
    {
        struct _MI_SECTION_IMAGE_INFORMATION* ImageInformation;             //0x38
        VOID* FirstMappedVa;                                                //0x38
    } u2;                                                                   //0x38
    struct _MMPTE* PrototypePte;                                            //0x40
}; 

内存管理10

_SEGMENT.TotalNumberOfPtes表示总共有多少Ptes
_SEGMENT.BasedAddress为对应内存块的基地址。
_SEGMENT.SizeOfSegment为对应内存块的大小。
_SEGMENT.PrototypePte表示PTE数组,这个与_MMVAD.FirstPrototypePte相同。记录的是内存区域第一页的PTE,查看物理内存的值,其值正是模块刚开始时的数据。
内存管理11

NextSubsection(_SUBSECTION*)

指向下一个SubSection。所有的SubSection的ControlArea都相等,同时与真正的Section对象的ControlArea一样。一个节区Section会被分成很多的SubSection,所以他们指向的ControlArea相等。
内存管理12

FirstPrototypePte与LastContiguousPte

VAD对于共享内存会包含他的原型PTE。
+0x50偏移FirstPrototypePte为第一个原型PTE即数组开始地址,+0x58偏移的LastContiguousPte为最后一个原型PTE。 对Private Memory无意义。

ViewLinks

链表结构。表示有哪些进程加载了这个镜像文件,如果是DLL,可以通过此链表遍历出加载此DLL的进程。
ViewLinks连接的是_EPROCESS+0x18的位置。

VadsProcess

内存区域所属主进程的_EPROCESS

虚拟内存的两种状态

reserved(保留)与comitted(提交)。

  • reserved 预留,表示预先分配的虚拟内存,但还没有映射到物理内存,在使用时需要先命中物理页
  • commited 已经提交,表示虚拟内存已经映射到了物理内存或已经缓存在磁盘
  • commited pages 也是 private pages,表示不能与其他进程共享

保留是指只在VAD树中进行的属性进行挂载,不会挂物理页,以后会用。
提交就是指挂上物理页。
当内存页面被挂上物理页才能被使用。

物理空间内存管理

Windows实现页面换出与换入,当页面换出时,对应PTE表项就会变成无效PTE。

Windows使用内存页帧数据库MMPFNDATABASE来描述每个物理页的属性。

MMPFNDATABASE

_MMPFN

MMPFNDATABASE是_MMPFN结构体数组。_MMPFN结构大小为0x30.

描述:
1.全局结构体数组,称为“页帧数据库”,包含了当前操作系统中所有的物理页。
2.每一个物理页都有一个对应的_MMPFN结构体。
3.通过全局变量MmPfnDatabase可以找到这个结构体的起始位置(在WinDbg中使用命令dq MmPfnDatabase进行查看)。
4.每一个MMPFN结构体之间在内存中是紧挨着的。

查看MmPfnDataBase。
内存管理13

查看第0个物理页。
内存管理14

//0x30 bytes (sizeof)
struct _MMPFN
{
    union
    {
        struct _LIST_ENTRY ListEntry;                                       //0x0
        struct _RTL_BALANCED_NODE TreeNode;                                 //0x0
        struct
        {
            union
            {
                struct _SINGLE_LIST_ENTRY NextSlistPfn;                     //0x0
                VOID* Next;                                                 //0x0
                ULONGLONG Flink:40;                                         //0x0
                ULONGLONG NodeFlinkLow:24;                                  //0x0
                struct _MI_ACTIVE_PFN Active;                               //0x0
            } u1;                                                           //0x0
            union
            {
                struct _MMPTE* PteAddress;                                  //0x8
                ULONGLONG PteLong;                                          //0x8
            };
            struct _MMPTE OriginalPte;                                      //0x10
        };
    };
    struct _MIPFNBLINK u2;                                                  //0x18
    union
    {
        struct
        {
            USHORT ReferenceCount;                                          //0x20
            struct _MMPFNENTRY1 e1;                                         //0x22
        };
        struct
        {
            struct _MMPFNENTRY3 e3;                                         //0x23
        struct
        {
            USHORT ReferenceCount;                                          //0x20
        } e2;                                                               //0x20
        };
        struct
        {
            ULONG EntireField;                                              //0x20
        } e4;                                                               //0x20
    } u3;                                                                   //0x20
    struct _MI_PFN_ULONG5 u5;                                               //0x24
    union
    {
        ULONGLONG PteFrame:40;                                              //0x28
        ULONGLONG ResidentPage:1;                                           //0x28
        ULONGLONG Unused1:1;                                                //0x28
        ULONGLONG Unused2:1;                                                //0x28
        ULONGLONG Partition:10;                                             //0x28
        ULONGLONG FileOnly:1;                                               //0x28
        ULONGLONG PfnExists:1;                                              //0x28
        ULONGLONG NodeFlinkHigh:5;                                          //0x28
        ULONGLONG PageIdentity:3;                                           //0x28
        ULONGLONG PrototypePte:1;                                           //0x28
        ULONGLONG EntireField;                                              //0x28
    } u4;                                                                   //0x28
}; 

第一个_MMPFN成员管理0-0xFFF的物理内存,第二个成员管理0x1000-0x1FFF的物理内存…以此类推,即0x30+PFN

MMPFN与物理页的对应关系:

  • 通过当前_MMPFN结构体找到对应的物理页:物理页 = 当前_MMPFN索引值*0x1000
  • 通过当前物理页找到对应的MMPFN结构体:_MMPFN = *MmPfnDatabase + 0x30 * (物理ff>页/0x1000)
_MMPFN.PteAddress

私有内存的情况下,_MMPFN.PteAddress==页表的PteAddress。对于私有内存_MMPFN的PteAddress成员保存的是进程PteBase区域的PteAddress。
对于共享内存_MMPFN.PteAddress是原型PTE,也就是Section(SubSection).ControlArea.Segment.PrototypePte

windbg出了点儿问题,图就先不发了,后面可能会补上。

什么是原型PTE呢?
原型PTE是共享内存所特有的,对于共享内存,可能会发生如下状况:

在处理可共享内存时,Windows 会为共享内存的每一页创建一种全局 PTE——称为原型
PTE。此原型始终表示共享页面的物理内存的真实状态。如果标记为Valid,则此原型 PTE 可以像在任何其他情况下一样充当硬件PTE。如果标记为Not Valid,原型将向页面错误处理程序指示内存需要从磁盘调回。当给定内存页存在原型 PTE 时,该页的页帧数据库条目将始终指向原型 PTE。

内存管理15

共享内存可能处于转换状态,也可能处于被换页出去硬件异常状态(意味着共享内存这个页被换到磁盘上),如果是转换状态,页面异常的处理只需要改变硬件PTE的V位即可,如果是硬件异常,则需要重新映射。
如下图,硬件PTE无效,但是原型PTE有效,因此Page Fault Handler处理时只需要把无效的硬件PTE修改V位即可。不必重新映射。
内存管理16

而对于私有内存,不需要原型PTE这个概念。因为私有内存不会转换,修建工作集。

windbg出了点儿问题,图就先不发了,后面可能会补上。

_MMPFN.OriginalPte

_MMPFN.OriginalPte是一个叫做“原始页表项”的内存管理数据结构中的一个成员变量。它用于存储Windows
API查询时的页面属性,以便在操作系统中正确管理页面。它由硬件管理器维护,当硬件发生变化时,硬件管理器会自动更新该值。
简而言之,使用Windows API查询时,返回的是_MMPFN.OriginalPte

MmGetVirtualForPhysical

可以用MmGetVirtualForPhysical从物理地址转换到虚拟地址。其实MmGetVirtualForPhysical也利用了页帧数据库来获取。

MmGetVirtualForPhysical proc near
mov     rax, rcx
shr     rax, 0Ch        ; 右移12, 索引
lea     rdx, [rax+rax*2] ; rax*3
add     rdx, rdx        ; rdx=rax*6
mov     rax, 0FFFFDE0000000008h ; MmPfnDataBase+8  PteAddress
mov     rax, [rax+rdx*8] ; (MmPfnDataBase+8)+索引*0x30 , 算出PteAddress
shl     rax, 19h
mov     rdx, 0FFFFF68000000000h ; rdx = PteBase
shl     rdx, 19h
and     ecx, 0FFFh      ; ecx = 页内偏移
sub     rax, rdx
sar     rax, 10h
add     rax, rcx
retn
MmGetVirtualForPhysical endp

对于共享物理内存,PteAddress到底是谁的进程的?不清楚,因此共享物理内存无法计算。
对于共享内存,这个地方是原型pte的地址。
MmGetVirtualForPhysical返回值是基于特定CR3的虚拟地址。如果没有附加进程,并且当前进程没有这块物理地址,所返回的虚拟地址也是没有意义的。

  • 25
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值