当前位置: 首页 >> 开源操作系统 >> Windows内存与进程管理器底层分析
 

Windows内存与进程管理器底层分析

作者:      来源:zz     发表时间:2006-06-26     浏览次数:      字号:    


每个链表的表头都是下面这个样子:

typedef struct _PfnListHeader{
     DWORD Counter; // 链表中frame的数目
     DWORD LogNum;  // 链表号.0 - zeroed, 1- Free etc...
     DWORD FirstFn; // MmPfnDatabase中的第一个frame号
     DWORD LastFn;  // --//--- 最后一个.
     }PfnListHeader PPfnListHeader;

除此之外,可以用“color”(就是cache)来寻址空闲frame(zeroed或是free)。如果看一下附录中的伪代码就容易理解了。我给出两个结构体:

struct  {
     ColorHashItem* Zeroed; //(-1) нет
     ColorHashItem* Free;
     }MmFreePagesByColor;

typedef struct _ColorHashItem{
          DWORD FrameNum;
                PfnDatabaseEntry* Pfn;
                } ColorHashItem;

有一套函数使用color来处理frame(处理cache)。例如,MiRemovePageByColor(FrameNum, Color); 看一下这些函数及其参数返回值的名称和函数的反汇编代码,很容易猜到相应的内容,所以这里就不描述了,在说一句,这些函数都是未导出的。在使用color的时候,要考虑color掩码,最后选择color。

Windows NT符合C2安全等级,所以应该在为进程分配页的时候应将页清零。我们来看一下将frame清零的系统进程的线程。最后,在Phase1Initialization()中所作的是调用MmZeroPageThread。不难猜到——线程将空闲页清零并将其移动到zeroed页的链表中。

MmZeroPageThread
{
//
//.... 没意思的东西我们略过 ;)
//
while(1)
{
KeWaitForSingleObject(MmZeroingPageEvent,8,0,0,0); // 等待事件
while(!KeTryToAcquireSpinLock(MmPfnLock,&OldIrql)); // 获取 PfnDatabase
while(MmFreePageListHead.Count){
            MiRemoveAnyPage(MmFreePageListHead.FirstFn&MmSecondaryColorMask);
               // 从空闲链表中取出页
            Va=MiMapPageToZeroInHyperSpace(MmFreePageListHead.FirstFn);
            KeLowerIrql(OldIrql);

            memset(Va,0,0x1000); // clear page

            while(!KeTryToAcquireSpinLock(MmPfnLock,&OldIrql);
            MiInsertPageInList(&MmZeroedpageListHead,FrameNum);
                         // 将已清零的页插入Zero链表
            }
  MmZeroingPageThreadActive=0; // 清标志
  KeLowerIrql(OldIrql);
  }
// 永不退出
}

// 函数只是将frame映射到定义的地址上
// 以使其可被清零
DWORD MiMapPageToZeroInHyperSpace(FrameNum)
{
if(FrameNum<MmKseg2Frame)return ((FrameNum+0x80000)<<12); // 落入直接映射区域
                                      
TmpPte=0xc0301404;
TmpVa=0xc0501000;
*TmpPte=0;
invlpg((void*)TmpVa); // asm instruction in fact
*TmpPte=FrameNum<<12|ValidPtePte;
return TmpVa; // always 0xc0501000;
}

在何时MmZeroingPageEvent被激活?这发生在向空闲页链表中添加frame的时候:

MiInsertPageInList()
{
.....
if(MmFreePageListHead.Count>=MmMinimumFreePagesToZero&&
       !MmZeroingPageThreadActive)
    {
     MmZeroingPageThreadActive=1;
     KeSetEvent(&MmZeroingPageEvent,0,0);
    }
....
}

注:内核并不总是依赖这个线程,有时会遇到这样的代码,它获取一个空闲页,用过后自己将其清零。

05.Working Set
==============
Working Set——工作集,是属于当前进程的物理页集。内存管理器使用一定的机制跟踪进程的工作集。working set有两个限额:maximum  working set和minimum working set。这是工作集的最大值和最小值。内存管理器以这两个值为依据来维护进程的工作集(工作集大小不小于最小值,不大于最大值)。在定义条件的时候,工作集被裁减,这时工作集的frame落入空闲链表。内核工作集是结构体的总和。

在进程结构体的偏移0xc8(NT4.0)有以下结构体。

typedef struct _VM{
/* C8*/   LARGE_INTEGER UpdateTime;              //0
/* D0*/   DWORD Pages;                           //8 called so, by S-Ice authors
/* D4*/   DWORD PageFaultCount                   //0c faults;
//                    in fact number of MiLocateAndReserveWsle calls
/* D8*/   DWORD PeakWorkingSetSize;              //10 all
/* DC*/   DWORD WorkingSetSize;                  //14  in
/* E0*/   DWORD  MinimumWorkingSet;              //18   pages, not in
/* E4*/   DWORD  MaximumWorkingSet;              //1c     bytes
/* E8*/   PWS_LIST WorkingSetList;               //20 data table
/* EC*/   LIST_ENTRY WorkingSetExpansion;        //24 expansion
/* F4*/   BYTE fl0; // Operation???              //2c
     BYTE fl1; // always 2???               //2d
     BYTE fl2; // reserved??? always 0      //2e
     BYTE fl3; //                           //2f
     }VM *PVM;

WinDbg !procfields的扩展命令用到VM。这里重要的是,跟踪page fault的数量(PageFaultCount),MaximumWorkingSet和MinimumWorkingSet,管理器以它们为基础来支持工作集。

注:实际上,PageFaultCount并非是严格的计数。这个计数在MiLocateAndReserveWsle函数中被扩大,因为这个函数不只在page fault时被调用,在某些其它情况下也会被调用(真的,很少见)。

下面这个结构体描述了包含工作集页的表。

typedef struct _WS_LIST{
DWORD        Quota;              //0 ??? i'm not shure....
DWORD        FirstFreeWsle;      // 4 start of indexed list of free items
DWORD        FirstDynamic;       // 8 Num of working set wsle entries in the start
                                 // FirstDynamic
DWORD        LastWsleIndex;      // c above - only empty items
DWORD        NextSlot;           // 10 in fact always == FirstDynamic
                                 // NextSlot
PWSLE        Wsle;               // 14 pointer to table with Wsle
DWORD        Reserved1           // 18 ???
DWORD        NumOfWsleItems;     // 1c Num of items in Wsle table
                       // (last initialized)
DWORD        NumOfWsleInserted;  // 20 of Wsle items inserted (WsleInsert/
                                 //                              WsleRemove)
PWSHASH_ITEM HashPtr;            // 24 pinter to hash, now we can get index of
                       //   Wsle item by address. Present only if
                                 //   NumOfWsleItems>0x180
DWORD        HashSize;           // 28 hash size
DWORD        Reserved2;          // 2c ???
}WS_LIST *PWS_LIST;

typedef struct _WSLE{ // 工作集表的元素
        DWORD PageAddress;
     }WSLE *PWSLE;

// PageAddress 本身是工作集页的虚地址
// 低12位用作页属性(虚地址总是4K的倍数)

#define WSLE_DONOTPUTINHASH 0x400 // 不放在cache中
#define WSLE_PRESENT 0x1 // 非空元素
#define WSLE_INTERNALUSE 0x2 // 被内存管理器使用的frame

// 未设置WSLE_PRESENT的空闲WSLE本身是下一个空闲WSLE的索引。这样,空闲的WSLE就组织成了链表。最后一个空闲WSLE表示为-1。

#define EMPTY_WSLE (next_emty_wsle_index) (next_emty_wsle_index<<4)
#define LAST_EMPTY_WSLE 0xfffffff0

typedef struct _WSHASH_ITEM{
     DWORD PageAddress; //Value
     DWORD WsleIndex; //index in Wsle table
}WSHASH_ITEM *PWSHASH_ITEM;

//cache函数很简单。内部函数的伪代码:
//MiLookupWsleHashIndex(Value,WorkingSetList)
//{
//Val=value&0xfffff000;
//TmpPtr=WorkingSetList->HashPtr;
//Mod=(Val>>0xa)%(WorkingSetList->HashSize-1);
//if(*(TmpPtr+Mod*8)==Val)return Mod;
//while(*(TmpPtr+Mod*8)!=Val)){
//   Mod++;
//   if(WorkingSetList->HashSize>Mod)continue;
//   Mod=0;
//   if(fl)KeBugCheckEx(0x1a,0x41884,Val,Value,WorkingSetList);
//   fl=1;
//   }
//return Mod;
//}

我们来看一下典型的进程working set。WorkingSetList位于地址MmWorkingSetList (0xc0502000)。这是hyper space的区域,所以在进程切换时,要更新这些虚地址,这样,每个进程都有自己的工作集结构体。在地址MmWsle (0xc0502690)上是Wsle动态表的起始地址。表的结尾的地址总是0x1000的倍数,也就是说表可以结束在地址0xc0503000、0xc0504000等等上(这是为了简化对Wsle表大小的操作)。Cache(如果有)位于一个偏移上,Wsle不会向这个偏移增长。我们来详细看一下这个表:

// WsList-0xc0502000---
// ....
// -------0xc0502030----
// pde 00 fault counter
// pde 01 fault counter
// pde 02 fault counter
//
// +-Wsle==0xc0502690---             +--Pde/pte     +-----Pfn[0]------
// |0 c0300000|403 Page Directory    |c0300c00 pde  |pProcess
// |4 c0301000|403 Hyper Space       |c0300c04 pte  |1
// |8 MmWorkingSetList(c0502000)|403 |c0301408 pte  |2
// |c MmWorkingSetList+0x1000 | 403  |.             |3
// |10 MmWorkingSetList+0x2000 | 403 |.              .
// |         ....
// |FirstDynamic*4 FrameN
// |....                             |.              .
//                                                   .
// |LastWsleIndex*4 FrameM
// +--------                         +------        +-------
// | free items
// ....
// | 0xfffffff0
// +-------------------


// Cache
// ....

这里有个有意思的地方,在表的起始部分有FirstDynamic的页,用于建立Wsle,WorkingSetList和cache。同时这里还有页目录frame,HyperSpace和某些其它的页,这些页是内存管理器所需要的,不能从工作集中移出(标志WSLE_INTERNALUSE)。之后,我们还能看到两种对Pfn frame域偏移0使用的变体。对于页目录frame,这是指向进程的指针,对于通常的属于工作集的页,这是在表内的索引。

在WorkingSetList和Wsle表的起始地址之间还有不大的0x660字节的空闲空间。关于如何分配这些空间的信息是没有的,但是很快在WorkingSetList开始有用于用户空间(通常为低2GB)的page fault counter,也就是说如果,譬如说,索引0x100的元素有值3,则表示从3开始(如果不考虑可能的溢出)page fault用于范围[0x40000000-0x403fffff]的页。

工作集的限额在内核模式下可以通过导出的未公开函数来修改:

NTOSKRNL MmAdjustWorkingSetSize(
          DWORD MinimumWorkingSet OPTIONAL, // if both == -1
          DWORD MaximumWorkingSet OPTIONAL, // empty working set
          PVM Vm OPTIONAL);

为处理WorkingSet,管理器使用了许多内部函数,了解了这些函数就能明白其工作的原理。


06.向pagefile换页
========================================

frame可以是空闲的——当RefCounter等于0且位于一个链表中时。frame可以属于工作集。在缺少空闲frame时或是在达到treshhold时,就会发生frame的换出。这方面的高层次函数是有的。这里的任务是用伪代码来证实。

在NT中有最多16个pagefile。pagefile的创建发生于模块SMSS.EXE。这时打开文件及其句柄向PsInitialSystemProcess进程的句柄表拷贝。我给出创建pagefile的未公开系统函数的原型(如果不从核心调用的话就必须有创建这种文件的权限)。

NTSTATUS NTAPI NtCreatePagingFile(
     PUNICODE_STRING FileName,
     PLARGE_INTEGER MinLen, // 高位双字应为0
     PLARGE_INTEGER MaxLen, // minlen应大于1M
     DWORD Reserved // 忽略
       );

每个pagefile都有一个PAGING_FILE结构体。

typedef struct _PAGING_FILE{
       DWORD MinPagesNumber;      //0
       DWORD MaxPagesNumber;      //4
       DWORD MaxPagesForFlushing; //8 (换出页的最大值)
       DWORD FreePages;           //c(Free pages in PageFile)
       DWORD UsedPages;           //10 忙着的页
       DWORD MaxUsedPages;        //14
       DWORD CurFlushingPosition; //18 -???
       DWORD Reserved1;           //1c
       PPAGEFILE_MDL Mdl1; //       20 0x61 - empty ???
       PPAGEFILE_MDL Mdl2; //       24 0x61 - empty ???
       PRTL_BITMAP PagefileMap; //  28 0 - 空闲, 1 - 包含换出页
       PFILE_OBJECT FileObject;   //2c
       DWORD NumberOfPageFile;    //30
       UNICODE_STRING FileName;   //34
       DWORD Lock;                //3d
     }PAGING_FILE *PPAGING_FILE;

DWORD MmNumberOfActiveMdlEntries;
DWORD MmNumberOfPagingFiles;

#define MAX_NUM_OF_PAGE_FILES 16
PPAGING_FILE MmPagingFile[MAX_NUM_OF_PAGE_FILES];

在内存子系统启动时(MmInitSystem(...))会启动线程MiModifiedPageWriter,该线程进行以下工作:初始化MiPaging和 MiMappedFileHeader,在非换出域中创建并初始化MmMappedFileMdl,建立优先级LOW_REALTIME_PRIORITY+1,等待KEVENT,初始化MmMappedPageWriterEvent和MmMappedPageWriterList链表,启动MiMappedPageWriter线程,启动函数MiModifiedPageWriterWorker。

在任务MiModifiedPageWriterWorker中会等待事件MmModifiedPageWriterEvent,处理链表MmModifiedNoWritePageList和MmModifiedPageList并准备实现向映象文件或pagefile的页换出(调用MiGatherMappedPages或是MiGatherPagefilePages)。

在MiGatherPagefilePages中使用IoAsynchronousPageWrite( )函数进行frame的换出。而且不是一个frame,而是一簇(页数目总和为MmModifiedWriteClasterSize)。向pagefile换出页是由PAGING_FILE结构体中的PagefileMap来跟踪的。

研究函数的伪代码在appendix.txt中。这里描述伪代码没有什么意义——都很简单。


07.page fault的处理
==============================

对于转向对pagefault的研究,我们现在有了所有必须的信息了。转换线性地址时,当线性地址(分页机制打开)的所用的PDE/PTE的P(present)位无效或是违反了保护规则,在+i386处理器里会产生异常14。这时,在堆栈中有错误代号,包含有以下信息:用户/内核错误位(异常发生在ring3还是ring0?),读写错误位(试图读还是写?),页存在位。除此之外,在CR2寄存器中存有产生异常的32位线性地址。内核中处理14号中断的是_KiTrap0E。

当要转换的页没有相应的物理页时,内存管理器执行确定好的工作来“修正”。这些是由异常处理函数调用高层函数MmAccessFault   (Wr,Addr,P);来完成的。在对伪代码的进行分析之前,想一下在什么样的情况下会发生page fault是很有用的。

最显然的就是访问错误,这时ring3的代码试图写入PTE/PDE中未设置U位的页或是写入了只读的页(PTE/PDE中未设置W位)。再有,页可以被换出到页面文件中,对应于这些页的PTE中未设置P位,但有信息指示在哪个页面文件中寻找frame,以及frame的偏移。还有一个类似的情况——frame属于映象文件。除此之外,所转换的页可能只属于已分配的内存区(使用NtAllocateMemory),也可能转换的是原先没转换过的页,这中情况下,VMM分配清零过的frame(这是C2的要求)。最后,异常还可能是由写copy on write页和转换共享内存引发。以上只列出了主要的情况。

处理的结果通常是向当前进程的Working Set中添加相应的frame。

异常的每一种情况都相应有一个内部的结构体与之相关联,VMM就处理这些结构体。这些结构体十分复杂,要对它们进行完整的描述的话,需要反汇编大量的函数。目前还没有大部分结构体的完整信息,但对于理解异常处理程序来说并不要求知道这些。我来大致描述一下VAD和PPTE的概念,研究异常处理程序的伪代码要用到。


VAD

操作虚拟地址需要用到VAD (Virtual Address Descriptor)。我们熟知的(有一个几乎与之同名的Win32函数调用这个函数)未公开函数NtAllocateVirtualMemory(ring0下是ZwAllocateVirtualMemory)操作这些结构体。

每一个VAD都描述了虚地址空间中的区域,实际上,除了区域的起止地址外还有保护信息(见ZwAllocateVirualMemory函数的参数)。而同时还有其它一些特殊的信息(目前除了首部之外还没有VAD的完整信息)。VAD结构体只对用户地址(低2GB)有意义,使用这些结构体VMM可以捕获到发生异常的区域。VAD的结构是一个平衡二叉树(有内部函数负责修整此树),这是为查找而进行的优化。在VAD中有两个指向后面元素——左右子树——的指针。树的根位于EPROCESS结构体的VadRoot域(NT 4.0下是偏移0x170)。当然,每一个进程都有自己的VAD树。VAD的首部形式如下:

typedef struct vad_header {
     void *StartingAddress;
     void *EndingAddress;
     struct vad *ParentLink;
     struct vad *LeftLink;
     struct vad *RightLink;
     ULONG Flags;
}VAD_HEADER, *PVAD;


PPTE

Prototype Pte是又一级的线性地址转换并用于共享内存。假设有个文件映射到了几个(3个)进程的地址空间。PPTE表包含有PPTE,这些PPTE描述了加载到内存的文件的物理页。某些PPTE可以有P位(其位置与含义与PTE/PDE的相同),而某些则没有,没有P位的有信息用来决定是从页来加载frame还是从映象文件来加载文件。所有三个进程的文件都映射在不同的地址上,对应于这些页的PTE的P位未设置,并且包含有文件页的PPTE的引用。这样,在转换映射到文件的线性地址的时候,在一号进程中发生异常14,VMM找到PTE,得到对PPTE的引用,现在可以直接“修正”相应的PTE,以使其指向属于文件的frame,这时必需从文件中加载frame。我给出未设置P位PTE的格式,在页表中其指向原型PTE。

PTE points to PPTE
+-----------------------------------------+-+---+-------------+-+
|3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1|1|0 0|0 0 0 0 0 0 0|0|
|1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1|0|9 8|7 6 5 4 3 2 1|0|
+-----------------------------------------+-+---+-------------+-+
| Address [7:27]                          |1|Un | Address     |0|
|                                         | |use|   [0:6]     | |
|                                         | |d  |             | |
+-----------------------------------------+-+---+-------------+-+


*MmAccessFault

我们开始来研究一下MmAccessFault的伪代码。其原型:

NTSTATUS MmAccessFault (BOOL Wr,DWORD Addr, BOOL P)

参数的意义很明显:写入标志,发生异常的地址和页存在位。对于确定异常的原因,这些信息就足够了。根据Addr是属于内核地址空间还是用户地址空间,处理程序从两个执行分支中选择一个。第一种情况下的处理程序较为简单,跟踪ACCESS VIOLATION或是收回在Working Set中的页(MiDispatchFault)。若是用户空间的地址情况就就更为复杂一些。首先,如果PDE不在内存中则执行用于PDE的异常处理程序。然后,出现了一个分支。第一个分支——页存在。这表示要么是ACCESS VIOLATION,要么就是对copy on write的处理。第二个分支——处理清零页请求、ACCESS VIOLATION、页边界(GUARD)(堆栈增长)以及必须的对working set中页的回收。有趣的是,在大量发生page fault的时候,系统会增大working set的大小。在零PTE的情况下,为确定状况,处理程序不得不使用VAD树来确定试图访问区域的属性。这些都是MiAccessCheck的工作,这个函数返回访问的状态。

一般情况下,异常处理程序的主要奠基工作是由MiDispatchFault函数执行的。它能更精确的确定状况并决定下一步的工作。

轮到MiDispatchFault了,它主要是基于一些更低级的函数:MiResolveTransitionFault、MiResolveDemandZeroFault、MiResolveDemandZeroFault、MiResolveProtoPteFault和MiResolvePageFileFault。从这些函数的名字可以明显看出,这个函数用于确定更为具体的情况:状态为'transition'(可能会很快回收入Working Set)的页应该是空白的frame,PTE指向PPTE并且frame换出到相应的页面文件中。在与页面文件有关的和某些与PPTE有关的情况下,接着可能需要从文件中读取frame,此时函数返回值为0xc0033333,表示必须从文件中读取页。这在MiDispatchFault中是靠IoPageRead进行的。我们来更仔细的研究一下所提到的函数。我们从MiResolveDemandZeroFault开始。

如果看一下这个函数的伪代码,则可以轻易的明白它的工作逻辑。请求zero frame并且进程得到这个frame。这时执行函数MiRemoveZeroPage或是MiRemoveAnyPage。第一个函数从zero页的链表中取一页。如果未能成功,则通过第二个函数选择任何一页。这样的话,该页就由MiZeroPhysicalPage来清零。最终,在MiAddValidPageToWorkingSet中,该清零的页被添加到工作集中(恰好,这个事实证明在分配内存时进程不能取得对未处理页的访问)。现在我们来研究一下更为复杂的情况——页位于页面文件中。

前面的伪代码需要一个结构体。在准备从文件中读取页的时候,会填充PAGE_SUPPORT_BLOCK结构体。之后,对所有即将参与到操作中来的PFN进行以下操作:设置read in progress标志并在Misc域中写入PAGE_SUPPORT_BLOCK的地址(函数MiInitializeReadInProgressPfn)。最后,函数返回magic number 0xc0033333,表示随后要在IoPageRead调用中使用此结构体(恰巧,IoPageRead被导出了,但是未公开的。从其伪码中可以很容易地得到其原型)。

typedef struct _PAGE_SUPPORT_BLOCK{     //  size: 0x98
         DISPATCHER_HEADER DispHeader;  // 0 FastMutex
         IO_STATUS_BLOCK IoStatusBlock; // 0x10
         LARGE_INTEGER AddrInPageFile;  // 0x18 (file offset)
         DWORD RefCounter;              // 0x20 (0|1) ???
         KTHREAD Thread;                // 0x24
         PFILE_OBJECT FileObject;       // 0x28
         DWORD AddrPte;            // 0x2c
         PPFN pPfn;                // 0x30
         MDL Mdl;                       // 0x34
         DWORD MdlFrameBuffer[0x10];    // 0x50
         LIST_ENTRY PageSupportList;    // 0x90 与MmInPageSupportList有关的链表
}PAGE_SUPPORT_BLOCK *PAGE_SUPPORT_BLOCK;

struct _MmInPageSupportList{
     LIST_ENTRY PageSupportList;
     DWORD Count;
     }MmInPageSupportList;

函数MiResolvePageFileFault本身非常简单,除了填充相应的结构体并返回0xc0033333之外什么也不干。剩下的就是执行MiDispatchFault。这很合乎情理,如果还记得复用代码的原则的话。

还有一个不太复杂的函数MiResolveTransitionFault。对于状态为transition的frame还需要再多说几句。从这个状态中frame可以很快地返回到进程的Working Set中。

于是,剩下了最后一种情况——PROTO PTE。这种情况的处理函数也不太复杂,而且支撑其的基础我们已经讲过了。实际上还有一个函数与这种情况有关,这就是MiCompleteProtoPteFault,从MiDispatchFault中调用。要想理解这些函数的工作就去看一下伪代码。


07. section 对象
================

NT 中的section对象就是一块内存,这块内存由一个进程独有或几个进程共享。在Win32子系统中section就是文件映射(file mapping object)。我们来看一下section对象到底是什么。

section是NT下非常常用的对象,执行系统使用section来将可执行映象加载到内存中并用其来管理cache。section同时也用在向进程地址空间中映射文件。这时访问文件就像访问内存。section对象,就像其它的对象一样,是由对象管理器创建的。高层次的信息告诉我们,对象的body中包含着以下类型的信息:section的最大值,保护属性,其它属性。什么是section的最大可访问值,这不说也知道。保护属性是用于section页的属性。其它section属性有表示是文件section还是为空值(映射入页面文件)的标志,以及section是否是base的。base的section以相同的虚拟地址映射入所有进程的地址空间。

为了得到此对象结构的真实信息,我反汇编了一些用于section的内存管理器函数。下面的信息可是在别的地方见不到的。我们先来看结构体。

系统中的每一个文件都是对象(NTDDK.H中有描述)FILE_OBJECT。在这个结构体中有SectionObjectPointer。NTDDK.H中同样有它的结构。

//
:
PSECTION_OBJECT_POINTERS SectionObjectPointer;
:
//

typedef struct _SECTION_OBJECT_POINTERS {
    PVOID DataSectionObject;
    PVOID SharedCacheMap;
    PVOID ImageSectionObject;
} SECTION_OBJECT_POINTERS;

在结构体中有两个指针——DataSectionObject 和 ImageSectionObject。NTDDK.H把它们写成了PVOID,因为它们引用的是未公开的结构体。DataSectionObject用在将文件作为数据打开的时候。ImageSectionObject——此时当作映象。这些指针的类型全都一样,且可以称之为PCONTROL_AREA。所有下面这些结构体都是Windows 2K的,较之NT 4.0的有些变化。

typedef struct _CONTROL_AREA { // for NT 5.0, size = 0x38
                    PSEGMENT  pSegment;      //00
                    PCONTROL_AREA  Flink;         //04
                    PCONTROL_AREA  Blink;         //08
                    DWORD          SectionRef;    //0c
                    DWORD          PfnRef;           //10
                    DWORD          MappedViews;   //14
                    WORD      Subsections;   //18
                    WORD      FlushCount;    //1a
                    DWORD          UserRef;       //1c
                    DWORD          Flags;         //20
                    PFILE_OBJECT   FileObject;    //24
                    DWORD          Unknown;       //28
                    WORD      ModWriteCount; //2c
                    WORD      SystemViews;   //2e
                    DWORD PagedPoolUsage;         //30
                    DWORD NonPagedPoolUsage; //34
                    } CONTROL_AREA, *PCONTROL_AREA;

我们可以看到,CONTROL_AREA形成了一个链表,结构体中包含着统计值和标志。为了理解标志所代表的信息,我给出它们的值(用于NT5.0

[1] [2] [3] [4]

责任编辑 webmaster

 
 
 
 
 
评论更多>>
 
 
 
发表
 
姓名: QQ:
性别: MSN:
E-mail: 主页:
评分: 1 2 3 4 5
评论内容:
验证码:
  
  • 请遵守《互联网电子公告服务管理规定》及中华人民共和国其他各项有关法律法规。
  • 严禁发表危害国家安全、损害国家利益、破坏民族团结、破坏国家宗教政策、破坏社会稳定、侮辱、诽谤、教唆、淫秽等内容的评论 。
  • 用户需对自己在使用本站服务过程中的行为承担法律责任(直接或间接导致的)。
  • 本站管理员有权保留或删除评论内容。
  • 评论内容只代表网友个人观点,与本网站立场无关。
  •