天天看點

MmCreateSection/MmMapViewOfSection個人注釋及了解(二)

    MmCreateSection/MmMapViewOfSection個人注釋及了解(一)中提到記憶體映射的第一步建立Section對象建立Segment與檔案的關系,彼時尚未建立虛拟記憶體是以不能被通路。此文是記憶體映射的第二步,MmMapViewOfSection建立虛拟映射。<windows核心中Section\Segment\ControlArea\Subsection\MMVAD之間的關系>一文中列出了5種和記憶體映射有關的對象,而<MmCreateSection/MmMapViewOfSection個人注釋及了解(一)>中已經描述了其中4種對象,剩下的MMVAD對象肯定是要在這篇文章中出現了。

    MmMapViewOfSection說的是映射一個視圖到程序位址空間中,視圖怎麼了解?個人認為,就是映射整個Segment->ThePtes原型pte陣列的一部分(也可以全部)當程序位址空間中。MmMapViewOfSection的核心思想是在程序vad樹中搜尋一段大小能容下将要被映射的視圖的大小,且沒有被占用的虛拟位址空間。

     MmMapViewOfSection的入口參數SectionToMap/Process是最主要的參數,從Section對象可以找到ControlArea,有了它可以找到其他所有相關的對象。而Process提供vad樹,所有已被占用的虛拟記憶體都被記錄在這顆樹中。對于映像檔案,進入MiMapViewOfImageSection函數。

首先設定映像檔案起始位址:

BasedAddress = ControlArea->Segment->BasedAddress;      

在MiCreateImageFileMap函數中設定:NewSegment->BasedAddress = (PVOID) NextVa;而NextVa的值為PE檔案中設定的加載記憶體,就熟悉的0x40000。

然後計算将要被映射的檔案占用的虛拟空間的起止,同時計算這段空間是否被占用,如果被占用還要在程序vad樹中查找空閑的虛拟空間(要不然還不讓加載程式不成?):

/*
    BasedAddress是整個段對象的起始位址,下面的運算
    (PVOID)((ULONG_PTR)BasedAddress +
                                    (ULONG_PTR)MI_64K_ALIGN(SectionOffset->LowPart));獲得要建立視圖的
              起始位址應該相對于BasedAddress偏移多少                    
    */
        StartingAddress = (PVOID)((ULONG_PTR)BasedAddress +
                                    (ULONG_PTR)MI_64K_ALIGN(SectionOffset->LowPart));

        EndingAddress = (PVOID)(((ULONG_PTR)StartingAddress +
                                    *CapturedViewSize - 1) | (PAGE_SIZE - 1));
。。。
<pre name="code" class="cpp">        Vad = (PMMVAD) TRUE;
        NeededViewSize = *CapturedViewSize;

        if ((StartingAddress >= MM_LOWEST_USER_ADDRESS) &&
            (StartingAddress <= MM_HIGHEST_VAD_ADDRESS) &&
            (((ULONG_PTR)MM_HIGHEST_VAD_ADDRESS + 1) -
                                (ULONG_PTR)StartingAddress >= *CapturedViewSize) &&

            (EndingAddress <= MM_HIGHEST_VAD_ADDRESS)) {

            Vad = (PMMVAD) (ULONG_PTR) MiCheckForConflictingVadExistence (Process, StartingAddress, EndingAddress);
        }

        //
        // If the VAD address is not NULL, then a conflict was discovered.
        // Attempt to select another address range in which to map the image.
        //

    //vad!=NULL,程序已被占用,隻能另外找空閑的虛拟位址空間
        if (Vad != NULL) {      

。。。

            if (Process->VmTopDown == 1) {

                if (ZeroBits != 0) {

                    HighestUserAddress = (PVOID)((ULONG_PTR)MM_USER_ADDRESS_RANGE_LIMIT >> ZeroBits);

                    if (HighestUserAddress > MM_HIGHEST_VAD_ADDRESS) {

                        HighestUserAddress = MM_HIGHEST_VAD_ADDRESS;

                    }

                }

                else {

                    HighestUserAddress = MM_HIGHEST_VAD_ADDRESS;

                }

                Status = MiFindEmptyAddressRangeDown (&Process->VadRoot,

                                                      NeededViewSize,

                                                      HighestUserAddress,

                                                      X64K,

                                                      &StartingAddress);

            }

            else {

                Status = MiFindEmptyAddressRange (NeededViewSize,

                                                  X64K,

                                                  (ULONG)ZeroBits,

                                                  &StartingAddress);

            }

            if (!NT_SUCCESS (Status)) {

                MiDereferenceControlArea (ControlArea);

                return Status;

            }

            EndingAddress = (PVOID)(((ULONG_PTR)StartingAddress +

                                        *CapturedViewSize - 1) | (PAGE_SIZE - 1));

MiFindEmptyAddressRange完成在程序vad樹中搜尋符合大小要求的空閑位址的任務,并傳回其起始位址。

有了虛拟位址,這就要建立和初始化MMVAD來進行管理,這是和記憶體映射相關的5個對象中最後一個出場的:

Vad = ExAllocatePoolWithTag (NonPagedPool, sizeof(MMVAD), MMVADKEY);
。。。
    RtlZeroMemory (Vad, sizeof(MMVAD));
    Vad->StartingVpn = MI_VA_TO_VPN (LargeStartingAddress);
    Vad->EndingVpn = MI_VA_TO_VPN (LargeEndingAddress);      

還記得,Segment對象中的ThePtes原型pte陣列麼?他記錄了被映射的各個檔案頁面在整個檔案中的偏移情況。現在這段頁面即将被映射到程序空間中,總的有相應的對象記錄吧,于是MMVAD承擔了這項工作:

Vad->FirstPrototypePte = &Subsection->SubsectionBase[PteOffset];
    Vad->LastContiguousPte = MM_ALLOCATION_FILLS_VAD;      

看看與Subsection->SubsectionBase相關的設定,在MiCreateImageFileMap中可以找到如下的代碼段:

//NewSegment->PrototypePte = &NewSegment->ThePtes[0];
      PointerPte = NewSegment->PrototypePte;
  Subsection->SubsectionBase = PointerPte;

  。。。
  *Segment = NewSegment;
          RtlCopyMemory (NewSegment, OldSegment, sizeof(SEGMENT));

          //
          // Align the prototype PTEs on the proper boundary.
          //

          NewPointerPte = &NewSegment->ThePtes[0];
          NewSegment->PrototypePte = &NewSegment->ThePtes[0];

  PointerPte = NewSegment->PrototypePte +
                                         (PointerPte - OldSegment->PrototypePte);

  。。。
  for (i = 0; i < SubsectionsAllocated; i += 1) {

              //
              // Note: SubsectionsAllocated is always 1 (for wx86), so this loop
              // is executed only once.
              //

              NewSubsection->ControlArea = (PCONTROL_AREA) NewControlArea;

              NewSubsection->SubsectionBase = NewSegment->PrototypePte +
                      (Subsection->SubsectionBase - OldSegment->PrototypePte);


  。。。
  Subsection->SubsectionBase = PointerPte;      
MiInsertVad (Vad, Process);
 Process->VirtualSize += LargeOutputViewSize;
。。。
*CapturedViewSize = OutputViewSize;
    *CapturedBase = OutputStartingAddress;