天天看點

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

    建立記憶體映射,就是把檔案内容映射到程序虛拟空間,這個可以通過閱讀MSDN達成共識。NtCreateSection是建立記憶體映射的第一步,而MmCreateSection是建立記憶體映射NtCreateSection的具體實作。前面 <windows核心中Section/Segment/ControlArea/Subsection/MMVAD之間的關系>一文提到了Section/Segment等對象之間的關系,而

MmCreateSection就是用來建立這些對象的關系。

    MmCreateSection代碼過于繁雜,各對象之間的關系互相牽連,但是仍然能從MiCreateImageFileMap窺見他們之間的一縷關系。而MiCreateImageFileMap簡直就是一個解析PE檔案的過程,雖然較之于MiCreateDataFileMap解析資料檔案更為複雜,但是MiCreateImageFileMap更能展現出Section/Segment/ControlArea/Subsection/MMVAD各對象之間的關系。是以本文将從MmCreateSection進入,步入到MiCreateImageFileMap然後退出。

    本質上講,MmCreateSection隻涉及到為ControlArea建立子記憶體區對象,然後為Segment對象中的原型pte陣列與将被映射的檔案建立一對一的映射關系,以及建立Segment對象,但不包括建立虛拟記憶體,真正的建立虛拟記憶體要等到MmMapViewOfSection來完成,最終與實體記憶體建立關系則因為程式通路了MmMapViewOfSection建立的虛拟記憶體頁,産生缺頁異常,通過換頁異常建立起頁面映射。

    在整個記憶體映射中架構中,核心對象是ControlArea,為什麼這麼說?1.通過ControlArea可以找到被建立映射的檔案,即FileObject對象;2.檔案各部分被映射到程序空間的虛拟位址,即MMVAD(MMVAD本身就是管理程序虛拟位址空間的結構),這部分頁面可以通過ControlArea搜尋到;3.虛拟記憶體頁終究要反映到實體頁面上,但是實體頁面可能被換入換出,為了管理這些實體頁面win使用Segment對象的原型PTE陣列來完成這個使命,同樣這個Segment對象頁可以通過ControlArea中的指針搜尋到。鑒于ControlArea的核心地位,MmCreateSection開始時建立建立ControlArea對象,不過這個應該算是一個占位符,真正建立和映像檔案相關的ControlArea還是在MiCreateImageFileMap函數中。PE檔案頭記錄了檔案内的節區數,MiCreateImageFileMap建立ControlArea時,為每個節區包擴PE檔案頭建立一個子記憶體區對象。

代碼開始處,簡單的記錄映像檔案占用多少頁面,以此為依據建立Segment對象及其中的原型pte陣列。映像檔案對齊後的大小/PAGE_SIZE,即為将要建立的原型pte陣列中pte的數量:

ImageAlignment = NtHeader32->OptionalHeader.SectionAlignment;
        FileAlignment = NtHeader32->OptionalHeader.FileAlignment - 1;
        SizeOfImage = NtHeader32->OptionalHeader.SizeOfImage;
        LoaderFlags = NtHeader32->OptionalHeader.LoaderFlags;
        ImageBase = NtHeader32->OptionalHeader.ImageBase;
        SizeOfHeaders = NtHeader32->OptionalHeader.SizeOfHeaders;
...
        //檔案占用頁面的數量
        NumberOfPtes = BYTES_TO_PAGES (SizeOfImage);      

接下來計算PE檔案中節的數量,核心會為每個PE節建立一個Subsection對象,然後從非分頁池中建立ControlArea和與其記憶體上相鄰的SubSection陣列:

NumberOfSubsections = FileHeader->NumberOfSections;

    if ((ImageAlignment >= PAGE_SIZE) || (CheckSplitPages == TRUE)) {

        //
        // Allocate a control area and a subsection for each section
        // header plus one for the image header which has no section.
        //
        //PE頭獨占一個子記憶體區,是以會多一個子記憶體區
        SubsectionsAllocated = NumberOfSubsections + 1;

        ControlArea = ExAllocatePoolWithTag (NonPagedPool,
                                             sizeof(CONTROL_AREA) +
                                                    (sizeof(SUBSECTION) *
                                                    SubsectionsAllocated),
                                            'iCmM');
RtlZeroMemory (ControlArea, sizeof(CONTROL_AREA) + sizeof(SUBSECTION));      

按檔案将占用的pte數量建立Segment對象,從下面的代碼大概就能猜到Segment對象中的原型pte陣列中的原型pte将和檔案中的頁面一一對應:

//pte包含在segment中
    //前面NumberOfPtes = BYTES_TO_PAGES (SizeOfImage);按映像檔案的大小計算出将要用到多少pte(頁面)
    //sizeof(MMPTE) * ((ULONG)NumberOfPtes--->段對象的原型pte陣列指向檔案各頁面的另一個輔證
    SizeOfSegment = sizeof(SEGMENT) + (sizeof(MMPTE) * ((ULONG)NumberOfPtes - 1)) +
                    sizeof(SECTION_IMAGE_INFORMATION);

    NewSegment = ExAllocatePoolWithTag (PagedPool | POOL_MM_ALLOCATION,
                                        SizeOfSegment,
                                        MMSECT);
    *Segment = NewSegment;

    RtlZeroMemory (NewSegment, sizeof(SEGMENT));      

前面已經提到了Segment與ControlArea的關系,ControlArea是整個記憶體映射的核心,下面的代碼應證了這點:

NewSegment->ControlArea = ControlArea;

NewSegment->u2.ImageInformation->ImageCharacteristics =
                            FileHeader->Characteristics;
    NewSegment->u2.ImageInformation->Machine = FileHeader->Machine;
    NewSegment->u2.ImageInformation->LoaderFlags = LoaderFlags;

    ControlArea->Segment = NewSegment;
    ControlArea->NumberOfSectionReferences = 1;      

前面提起過,Segment中有原型pte陣列。同時,也說到,Segment對象中的原型pte陣列指向檔案中相應的偏移處。潘愛民的書中提到過原型pte可以指向交換檔案中頁面,這些原型pte反應了這些頁面在那個交換裝置中以及在交換裝置的哪段偏移處。這有點類似linux swap分區的實作,當pte有效位為0,該pte解釋為無效pte,其剩餘的31位用來在swap檔案中查找swap頁面。放到win核心中,Segment中的原型pte陣列也充當了這個角色。另外,對應映像檔案,每個節對應一個Subsection對象。這子記憶體區對象幹啥的?下面是我個人的了解:

假設,一個PE檔案有2個節(包含PE頭),每個節占10個頁面,那麼Segment->ThePtes (即原型pte陣列)中共20個原型pte。那麼經過MiCreateImageFileMap函數的折騰,會建立兩個子記憶體區對象,第一個子記憶體區對象代表PE檔案的第一個節,第二個子記憶體區對象代表PE檔案第二個節。第一個子記憶體區對象用Subsection->SubsectionBase = &Segment->ThePtes[0];

第二個子記憶體區對象用Subsection->SubsectionBase = &Segment->ThePtes[10];

這樣看着,就可以用各個Subsection來管理Segment->ThePtes,并最終影響到被映射到記憶體中的各個節的頁面了。具體的代碼在MmMapViewOfSection中得以展現。

不過在MiCreateImageFileMap中先是初始化了PE頭對應的子記憶體區對象:

//前面NewSegment->PrototypePte = &NewSegment->ThePtes[0];
    PointerPte = NewSegment->PrototypePte;
    Subsection->SubsectionBase = PointerPte;
    //設定一個模版pte,後面用它初始化segment中原型pte陣列
    //Temp.u.Long為首個子記憶體區對象的位址
    TempPte.u.Long = MiGetSubsectionAddressForPte (Subsection);
    TempPte.u.Soft.Prototype = 1;

    NewSegment->SegmentPteTemplate = TempPte;
    SectorOffset = 0;      

前面已經解釋過NewSegment->ThePtes[0],ThePtes數組就是Segment中的原型pte數組。然後代碼中建立一個無效pte模闆,其原型位置位。這很明顯的,MmCreateSection還沒有建立虛拟記憶體,就算建立了虛拟記憶體也沒有映射到實體頁面,是以必然是無效pte。而且,這些頁面目前還在檔案中,得用原型pte去标注這些頁面的位置:

Subsection->u.SubsectionFlags.Protection = MM_EXECUTE_WRITECOPY;

        //
        // Set all the PTEs to the execute-read-write protection.
        // The section will control access to these and the segment
        // must provide a method to allow other users to map the file
        // for various protections.
        //

        TempPte.u.Soft.Protection = MM_EXECUTE_WRITECOPY;
    //前面TempPte.u.Long = MiGetSubsectionAddressForPte (Subsection);
        NewSegment->SegmentPteTemplate = TempPte;

        TempPteDemandZero.u.Long = 0;
        TempPteDemandZero.u.Soft.Protection = MM_EXECUTE_WRITECOPY;
        SectorOffset = 0;

        for (i = 0; i < NumberOfPtes; i += 1) {

            //
            // Set prototype PTEs.
            //
      //步進的方式給Segment對象中原型pte陣列依次指派
      /*
      如果在檔案偏移内的頁面,變為需要寫時複制的原型pte;
      超過檔案偏移的,全部變為求零頁面pte,檔案空洞啊
      */
            if (SectorOffset < EndOfFile.LowPart) {

                //
                // Data resides on the disk, refer to the control section.
                //

                MI_WRITE_INVALID_PTE (PointerPte, TempPte);

            }
            else {

                //
                // Data does not reside on the disk, use Demand zero pages.
                //

                MI_WRITE_INVALID_PTE (PointerPte, TempPteDemandZero);
            }

            SectorOffset += PAGE_SIZE;
            PointerPte += 1;
        }

        NewSegment->u1.ImageCommitment = NumberOfPtes;      

這段代碼的for循環深深的出賣了Segment中原型pte陣列和磁盤檔案的關系:每個pte對應磁盤檔案上的一個頁面的偏移,但是這裡仍然沒有建立Segment與檔案的關聯,可以看到pte陣列中的每個原型pte都是無效pte。

到這裡,隻是建立了PE頭對應的原型pte陣列。後面是對PE檔案中的各個節加以相同或類似的操作,最終使得原型pte陣列中剩下的原型pte指向磁盤上的檔案頁面。

NewControlArea->Segment = NewSegment;

        Subsection = (PSUBSECTION)(ControlArea + 1);
        NewSubsection = (PSUBSECTION)(NewControlArea + 1);
        NewSubsection->PtesInSubsection += AdditionalBasePtes;
    //跟上面的過程一樣,設定每個PE節對應于Segment中相應偏移處的原型pte陣列
        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);

            NewPointerPte = NewSegment->PrototypePte;
            OldPointerPte = OldSegment->PrototypePte;

            TempPte.u.Long = MiGetSubsectionAddressForPte (NewSubsection);
            TempPte.u.Soft.Prototype = 1;
      //這些原型pte陣列全部設定為TempPte.u.Soft.Prototype = 1;
      //虛拟位址指向Subsection對象的起始位址
            for (j = 0; j < OldSegment->TotalNumberOfPtes+AdditionalBasePtes; j += 1) {

                if ((OldPointerPte->u.Soft.Prototype == 1) &&
                    (MiGetSubsectionAddress (OldPointerPte) == Subsection)) {
                    OriginalProtection = MI_GET_PROTECTION_FROM_SOFT_PTE (OldPointerPte);
                    TempPte.u.Soft.Protection = OriginalProtection;
                    MI_WRITE_INVALID_PTE (NewPointerPte, TempPte);
                }
                else if (i == 0) {

                    //
                    // Since the outer for loop is executed only once, there
                    // is no need for the i == 0 above, but it is safer to
                    // have it. If the code changes later and other sections
                    // are added, their PTEs will get initialized here as
                    // DemandZero and if they are not DemandZero, they will be
                    // overwritten in a later iteration of the outer loop.
                    // For now, this else if clause will be executed only
                    // for DemandZero PTEs.
                    //

                    OriginalProtection = MI_GET_PROTECTION_FROM_SOFT_PTE (OldPointerPte);
                    TempPteDemandZero.u.Long = 0;
                    TempPteDemandZero.u.Soft.Protection = OriginalProtection;
                    MI_WRITE_INVALID_PTE (NewPointerPte, TempPteDemandZero);
                }

                NewPointerPte += 1;

                //
                // Stop incrementing the OldPointerPte at the last entry
                // and use it for the additional base PTEs.
                //

                if (j < OldSegment->TotalNumberOfPtes - 1) {
                    OldPointerPte += 1;
                }
            }

            Subsection += 1;
            NewSubsection += 1;
        }      

在MiCreateImageFileMap的結尾處,除了處理各個Subsection和ControlArea的關系,還做了一些統計資訊,比如各個Subsection中有多少pte等

MiCreateImageFileMap結束退到MmCreateSection,ControlArea和Segment兩對象的關系已經成功建立,但是Section對象尚未建立,是以建立Section,并和MiCreateImageFileMap傳回的Segment建立關系:

Section.Segment = NewSegment;
    Section.u.LongFlags = ControlArea->u.LongFlags;