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;