现在的位置: 首页 > 综合 > 正文

分析Windows NT/2000堆内存与虚拟内存组织(http://webcrazy.yeah.net)

2013年08月02日 ⁄ 综合 ⁄ 共 7820字 ⁄ 字号 评论关闭
 分析Windows NT/2000堆内存与虚拟内存组织   
                  WebCrazy(http://webcrazy.yeah.net/)

    在讨论今天这个主题时,我觉得是应该给大家重复推介以下两本书:

    Matt Pietrek
        <<Windows 95 System Programming Secrets>>
    Jeffrey Richter
        <<Programming Microsoft Windows,fourth Edition>>

    两者分别从核心态与用户态出发全面阐述了Windows(虽然不全是Windows NT/2000)的堆内存与虚拟内存管理与组织。在阅读之前,最好是先浏览浏览。我不对一些基本API或是概念进行说明。

    首先谈谈堆内存的组织。Windows NT/2000组织堆内存的一些核心数据结构均存于进程PEB中。PEB结构中定义如下的成员(我不确认PEB的结构):

    typedef struct _PEB
    {
          .
          .
          .
       PVOID ProcessHeap;                   // 进程的默认堆地址,位于PEB后的0x18处
          .
          .
          .
       ULONG NumberOfHeaps;                 // 进程当前堆的数目,位于PEB后的0x88处
       ULONG MaximumNumberOfHeaps;          // 进程能拥有的堆的最大数目,,位于PEB后的0x8C处
       PVOID** ProcessHeaps;                // 进程拥有的所有堆的地址列表,位于PEB后的0x90处
          .
          .
          .
    } PEB, *PPEB;

    关于PEB的地址及其初步的分析请参阅我的《深入Windows NT/2000模块的组织》 。为了更好的理解,我将SoftICE的部分分析过程列于底下:

    :addr cmd       //以下将只针对cmd.exe进程

    :heap 32 cmd    //显示cmd进程的堆
        Base      Id  Cmmt/Psnt/Rsvd  Segments  Flags     Process
        00130000  01  0025/0024/00DB         1  00000002  cmd
           |
           |_进程默认堆地址(在Windows平台其也作为进程堆的句柄返回给用户)

        00230000  02  0003/0002/000D         1  00008000  cmd
        007C0000  03  0008/0008/0008         1  00001002  cmd
        00800000  04  0004/0004/000C         1  00001002  cmd

    :? #dword(@(7ffdf000+18))   //显示进程的默认堆的地址
    00130000  0001245184  "  "

    :dd 7ffdf000+88 l 10        //显示PEB中的几个与Heap有关的成员,见上
    0010:7FFDF088 00000004  00000010  77FCE380  00450000      ...........w..E.
                     |          |        |
                     |          |        |  ProcessHeaps值,Windows 2000提供API GetProcessHeaps
                     |          |        |_ 用于枚举进程拥有的所有堆的地址列表
                     |          |_MaximumNumberOfHeaps值
                     |_ NumberOfHeaps值

    :dd @(7ffdf000+90) l dword(@(7ffdf000+88))*4
    0023:77FCE380 00130000  00230000  007C0000  00800000      ......#...|.....
                  --------------------------------------
                            |
                            |_cmd进程当前所有堆的地址列表(与上SoftICE的heap 32命令输出比一比)

    上面的分析只针对堆的大概描述,对每个堆内部内存使用情况,Windows NT/2000提供了HeapWalk API用于调试目的,它能遍历指定堆的内容。SoftICE的heap 32命令加上-w参数也可以遍历这些内容。在Windows NT/2000中提供了以Heap(RtlHeap)头始的函数族用于堆内存的操作,在这些函数的内部Windows NT/2000最终调用VirtualAlloc等来实现内存的使用。而这正是我以下要讨论的关于虚拟内存的部分。由于堆内存在Windows NT/2000中的操作是十分频繁的,为避免频繁的Ring切换的时间损耗,Heap内存的一些主要数据结构存于用户态空间的PEB结构中,而不存于内核态空间的KPEB中。要熟悉PE装载例程(Ldr函数族)的话,就不得不看看Microsoft CRT源代码,CRT代码中关于建立CRT使用堆(主要供new语法使用)的代码对理解Heap的应用可以有比较全面的理解,如HeapInit.c、HeapHook.c、HeapDump.c、HeapWalk.c等等。

    下面谈谈虚拟内存的组织。构成虚拟内存管理基础的结构是VAD(Virtual Address Descriptors)。由于Windows NT/2000内存子系统在真正使用存储空间时才真正分配实际物理存储空间的,所以内核就提供VAD这个结构用于存放虚拟内存空间保留、提交等一些数据。VAD结构位于EPROCESS(KPEB)中:

    kd> !processfields
     EPROCESS structure offsets:
          .
          .
          .
    VadRoot:                           0x194
          .
          .
          .

    VAD使用二叉树结构组织,从上面i386kd/windbg的输出结果可看出VAD在KPEB中的位置。SoftICE的query命令可以导出这个系统中非常重要的结构,下面我列出实现这一命令的函数:

    //-----------------------------------------------------------------
    //
    //  QuerySystemProcessVAD--Portion cut from 《Undocumented Windows NT》
    //  Rewrite By WebCrazy(http://webcray.yeah.net) on 11-20-2000!
    //  Only test on Windows 2000 Server Build 2195!
    //
    //-----------------------------------------------------------------

    #define MAX_VAD_ENTRIES 0x200
    #define VADOffset                   0x194

    typedef struct vad {
        void *StartingAddress;
        void *EndingAddress;
        struct vad *ParentLink;
        struct vad *LeftLink;
        struct vad *RightLink;
        ULONG Flags;
        ULONG MMCI;
        ULONG ProtoPTE;
    }VAD, *PVAD;

    #pragma pack(1)
    typedef struct VadInfo {
        void *VadLocation;
        VAD Vad;
    }VADINFO, *PVADINFO;
    #pragma pack()

    VADINFO VadInfoArray[MAX_VAD_ENTRIES];
    int VadInfoArrayIndex;
    PVAD VadTreeRoot;

    void _stdcall VadTreeWalk(PVAD VadNode)
    {
        if (VadNode==NULL) {
           return;
        }

        VadTreeWalk(VadNode->LeftLink);

        if (VadInfoArrayIndex<MAX_VAD_ENTRIES) {
 
            VadInfoArray[VadInfoArrayIndex].VadLocation=VadNode;
            VadInfoArray[VadInfoArrayIndex].Vad.StartingAddress=VadNode->StartingAddress;
            VadInfoArray[VadInfoArrayIndex].Vad.EndingAddress=VadNode->EndingAddress;

           (ULONG)VadInfoArray[VadInfoArrayIndex].Vad.StartingAddress<<=12;
           (ULONG)VadInfoArray[VadInfoArrayIndex].Vad.EndingAddress+=1;
           (ULONG)VadInfoArray[VadInfoArrayIndex].Vad.EndingAddress<<=12;
           (ULONG)VadInfoArray[VadInfoArrayIndex].Vad.EndingAddress-=1;

            VadInfoArray[VadInfoArrayIndex].Vad.ParentLink=VadNode->ParentLink;
            VadInfoArray[VadInfoArrayIndex].Vad.LeftLink=VadNode->LeftLink;
            VadInfoArray[VadInfoArrayIndex].Vad.RightLink=VadNode->RightLink;
            VadInfoArray[VadInfoArrayIndex].Vad.Flags=VadNode->Flags;
            if(VadNode->MMCI>0x80000000)
                 VadInfoArray[VadInfoArrayIndex].Vad.MMCI=VadNode->MMCI;
            else VadInfoArray[VadInfoArrayIndex].Vad.MMCI=0;
            if(VadNode->ProtoPTE>0x80000000)
                 VadInfoArray[VadInfoArrayIndex].Vad.ProtoPTE=VadNode->ProtoPTE;
            else VadInfoArray[VadInfoArrayIndex].Vad.ProtoPTE= 0;
            VadInfoArrayIndex++;

        }
        VadTreeWalk(VadNode->RightLink);
    }

    void VadTreeDisplay()
    {
        int i;
        DbgPrint("/nVadRoot is located @%08x/n", VadTreeRoot);
        DbgPrint("Vad@     Starting Ending   Parent   LeftLink 
                RrightLink Flags    MMCI     PTE/n");
        for (i=0; i<VADINFOARRAYINDEX; i++) {
             DbgPrint("%08X %08X %08X %08X %08X %08X %08X %08X %08X/n",
                 VadInfoArray[i].VadLocation, 
                 VadInfoArray[i].Vad.StartingAddress, 
                 VadInfoArray[i].Vad.EndingAddress, 
                 VadInfoArray[i].Vad.ParentLink, 
                 VadInfoArray[i].Vad.LeftLink, 
                 VadInfoArray[i].Vad.RightLink,
                 VadInfoArray[i].Vad.Flags,
                 VadInfoArray[i].Vad.MMCI,
                 VadInfoArray[i].Vad.ProtoPTE);
        }
    }

    void QuerySystemProcessVAD()
    {
        if(((USHORT)NtBuildNumber)!=2195){
            DbgPrint("Only test on Windows 2000 Server Build 2195!/n");
            return;
        }
      
        // Query system process VAD
        VadTreeRoot=(PVAD)*(ULONG *)((char *)PsInitialSystemProcess+VADOffset);
        VadInfoArrayIndex=0;
        VadTreeWalk(VadTreeRoot);
        VadTreeDisplay();
    }

    看看QuerySystemProcessVAD例程的输出结果吧:

      VadRoot is located @fe4ddf28
      Vad@     Starting Ending   Parent   LeftLink RrightLink Flags    MMCI     PTE
      FE4DDF28 00010000 00042FFF 00000000 00000000 FE354DC8 04000000 FE4DD0E8 E1008A40
      FF874348 00060000 00060FFF FE354DC8 00000000 FF8389A8 C4000001 00000000 00000000
      FF8389A8 00070000 00070FFF FF874348 00000000 FF7B72C8 04000000 FF8345A8 E2747880
      FF7B72C8 00080000 0017FFFF FF8389A8 00000000 00000000 04000000 FE33DB48 E2936040
      FE354DC8 77F80000 77FF8FFF FE4DDF28 FF874348 00000000 07100003 FE354B68 E1630040

    与下面SoftICE的query命令的输出结果,再与上面的输出比较比较:

      :query system
      Address Range      Flags     MMCI      PTE       Name
      00010000-00042000  04000000  FE4DD0E8  E1008A40
      00060000-00060000  C4000001
      00070000-00070000  04000000  FF8345A8  E2747880
      00080000-0017F000  04000000  FE33DB48  E2936040  Heap
      77F80000-77FF8000  07100003  FE354B68  E1630040  ntdll.dll

    好了,到这儿我已经将Windows NT/2000用户代码使用内存的两个主要机制(另一个途径可能是FileMapping对象吧)呈现在你面前了,当然内部使用的方法还牵涉到许多内容,也只能靠你自己了。

    参考资料:
      1.Jeffrey Richter 
          <<Programming Applications for Microsoft Windows,Fourth Edition>>
      2.Matt Pietrek<<Windows 95 System Programming Secrets>>
      3.<<Undocumented Windows NT>>附带源码

抱歉!评论已关闭.