4671 lines
160 KiB
C
4671 lines
160 KiB
C
/*++
|
|
Copyright (c) 1989 Microsoft Corporation
|
|
|
|
Module Name:
|
|
procsup.c
|
|
|
|
Abstract:
|
|
This module contains routines which support the process structure.
|
|
|
|
Author:
|
|
Lou Perazzoli (loup) 25-Apr-1989
|
|
Landy Wang (landyw) 02-June-1997
|
|
*/
|
|
|
|
#include "mi.h"
|
|
|
|
#if defined (_WIN64)
|
|
|
|
#include "wow64t.h"
|
|
|
|
#if !defined(_IA64_)
|
|
#define MM_PROCESS_COMMIT_CHARGE 4
|
|
#define MM_PROCESS_CREATE_CHARGE 6
|
|
#else
|
|
#define MM_PROCESS_COMMIT_CHARGE 5
|
|
#define MM_PROCESS_CREATE_CHARGE 7
|
|
#endif
|
|
|
|
#else
|
|
|
|
#if !defined (_X86PAE_)
|
|
#define MM_PROCESS_COMMIT_CHARGE 3
|
|
#define MM_PROCESS_CREATE_CHARGE 5
|
|
#else
|
|
#define MM_PROCESS_COMMIT_CHARGE 7
|
|
#define MM_PROCESS_CREATE_CHARGE 9
|
|
#define MM_HIGHEST_PAE_PAGE 0xFFFFF
|
|
|
|
#define PAES_PER_PAGE (PAGE_SIZE / sizeof(PAE_ENTRY))
|
|
|
|
#define MINIMUM_PAE_THRESHOLD (PAES_PER_PAGE * 4)
|
|
#define EXCESS_PAE_THRESHOLD (PAES_PER_PAGE * 8)
|
|
|
|
PAE_ENTRY MiFirstFreePae;
|
|
ULONG MiFreePaes;
|
|
|
|
// Turn off U/S, R/W and any other appropriate bits required by the processor.
|
|
|
|
#define MM_PAE_PDPTE_MASK 0x1e6
|
|
|
|
ULONG MiPaeAllocate(PPAE_ENTRY *);
|
|
PVOID MiPaeFree(PPAE_ENTRY Pae);
|
|
VOID MiPaeFreeEntirePage(PVOID VirtualAddress);
|
|
|
|
extern POOL_DESCRIPTOR NonPagedPoolDescriptor;
|
|
#endif
|
|
|
|
#endif
|
|
|
|
#define HEADER_FILE
|
|
|
|
extern ULONG MmProductType;
|
|
extern ULONG MmWorkingSetReductionMax;
|
|
extern MM_SYSTEMSIZE MmSystemSize;
|
|
extern PVOID BBTBuffer;
|
|
|
|
SIZE_T MmProcessCommit;
|
|
|
|
ULONG MmKernelStackPages;
|
|
PFN_NUMBER MmKernelStackResident;
|
|
ULONG MmLargeStacks;
|
|
ULONG MmSmallStacks;
|
|
|
|
MMPTE KernelDemandZeroPte = {MM_KERNEL_DEMAND_ZERO_PTE};
|
|
|
|
CCHAR MmRotatingUniprocessorNumber;
|
|
|
|
extern ULONG MiFaultRetries;
|
|
|
|
ULONG MiGetSystemPteListCount(IN ULONG ListSize);
|
|
PFN_NUMBER MiMakeOutswappedPageResident(IN PMMPTE ActualPteAddress, IN PMMPTE PointerTempPte, IN ULONG Global, IN PFN_NUMBER ContainingPage);
|
|
PVOID MiCreatePebOrTeb(IN PEPROCESS TargetProcess, IN ULONG Size);
|
|
VOID MiDeleteAddressesInWorkingSet(IN PEPROCESS Process);
|
|
VOID MiDeleteValidAddress(IN PVOID Va, IN PEPROCESS CurrentProcess);
|
|
VOID MiDeleteFreeVm(IN PVOID StartingAddress, IN PVOID EndingAddress);
|
|
VOID VadTreeWalk(IN PMMVAD Start);
|
|
PMMVAD MiAllocateVad(IN ULONG_PTR StartingVirtualAddress, IN ULONG_PTR EndingVirtualAddress, IN LOGICAL Deletable);
|
|
PVOID MiPaeReplenishList(VOID);
|
|
|
|
extern LOGICAL MiNoLowMemory;
|
|
|
|
PVOID
|
|
MiAllocateLowMemory(
|
|
IN SIZE_T NumberOfBytes,
|
|
IN PFN_NUMBER LowestAcceptablePfn,
|
|
IN PFN_NUMBER HighestAcceptablePfn,
|
|
IN PFN_NUMBER BoundaryPfn,
|
|
IN PVOID CallingAddress,
|
|
IN ULONG Tag
|
|
);
|
|
|
|
LOGICAL MiFreeLowMemory(IN PVOID BaseAddress, IN ULONG Tag);
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#if defined (_X86PAE_)
|
|
#pragma alloc_text(INIT,MiPaeInitialize)
|
|
#endif
|
|
#pragma alloc_text(PAGE,MmCreateTeb)
|
|
#pragma alloc_text(PAGE,MmCreatePeb)
|
|
#pragma alloc_text(PAGE,MiCreatePebOrTeb)
|
|
#pragma alloc_text(PAGE,MmDeleteTeb)
|
|
#endif
|
|
|
|
|
|
BOOLEAN MmCreateProcessAddressSpace(IN ULONG MinimumWorkingSetSize, IN PEPROCESS NewProcess, OUT PULONG_PTR DirectoryTableBase)
|
|
/*++
|
|
Routine Description:
|
|
This routine creates an address space which maps the system portion and contains a hyper space entry.
|
|
Arguments:
|
|
MinimumWorkingSetSize - Supplies the minimum working set size for
|
|
this address space. This value is only used to ensure that ample physical pages exist to create this process.
|
|
NewProcess - Supplies a pointer to the process object being created.
|
|
DirectoryTableBase - Returns the value of the newly created address space's Page Directory (PD) page and hyper space page.
|
|
Return Value:
|
|
Returns TRUE if an address space was successfully created, FALSE
|
|
if ample physical pages do not exist.
|
|
Environment:
|
|
Kernel mode. APCs Disabled.
|
|
*/
|
|
{
|
|
PFN_NUMBER HyperDirectoryIndex;
|
|
PFN_NUMBER PageDirectoryIndex;
|
|
PMMPTE PointerPte;
|
|
PMMPTE PointerPde;
|
|
PMMPTE PointerPpe;
|
|
PFN_NUMBER HyperSpaceIndex;
|
|
PFN_NUMBER PageContainingWorkingSet;
|
|
MMPTE TempPte;
|
|
PMMPTE LastPte;
|
|
PMMPTE PointerFillPte;
|
|
PMMPTE CurrentAddressSpacePde;
|
|
PEPROCESS CurrentProcess;
|
|
KIRQL OldIrql;
|
|
PMMPFN Pfn1;
|
|
ULONG Color;
|
|
#if defined (_X86PAE_)
|
|
ULONG TopQuad;
|
|
MMPTE TopPte;
|
|
PPAE_ENTRY PaeVa;
|
|
PFN_NUMBER PageDirectoryIndex2;
|
|
KIRQL OldIrql2;
|
|
ULONG i;
|
|
PFN_NUMBER HyperSpaceIndex2;
|
|
PVOID PoolBlock;
|
|
#endif
|
|
#if defined(_IA64_)
|
|
PFN_NUMBER SessionParentIndex;
|
|
#endif
|
|
|
|
// Get the PFN LOCK to prevent another thread in this
|
|
// process from using hyper space and to get physical pages.
|
|
CurrentProcess = PsGetCurrentProcess();
|
|
|
|
// Charge commitment for the page directory pages, working set page table
|
|
// page, and working set list.
|
|
if (MiChargeCommitment(MM_PROCESS_COMMIT_CHARGE, NULL) == FALSE) {
|
|
return FALSE;
|
|
}
|
|
|
|
MM_TRACK_COMMIT(MM_DBG_COMMIT_PROCESS_CREATE, MM_PROCESS_COMMIT_CHARGE);
|
|
|
|
NewProcess->NextPageColor = (USHORT)(RtlRandom(&MmProcessColorSeed));
|
|
KeInitializeSpinLock(&NewProcess->HyperSpaceLock);
|
|
|
|
#if defined (_X86PAE_)
|
|
TopQuad = MiPaeAllocate(&PaeVa);
|
|
if (TopQuad == 0) {
|
|
MiReturnCommitment(MM_PROCESS_COMMIT_CHARGE);
|
|
return FALSE;
|
|
}
|
|
|
|
// This page must be in the first 4GB of RAM.
|
|
ASSERT((TopQuad >> PAGE_SHIFT) <= MM_HIGHEST_PAE_PAGE);
|
|
#endif
|
|
|
|
LOCK_WS(CurrentProcess);
|
|
LOCK_PFN(OldIrql);
|
|
|
|
// Check to make sure the physical pages are available.
|
|
if (MmResidentAvailablePages <= (SPFN_NUMBER)MinimumWorkingSetSize) {
|
|
#if defined (_X86PAE_)
|
|
PoolBlock = MiPaeFree(PaeVa);
|
|
#endif
|
|
|
|
UNLOCK_PFN(OldIrql);
|
|
UNLOCK_WS(CurrentProcess);
|
|
MiReturnCommitment(MM_PROCESS_COMMIT_CHARGE);
|
|
MM_TRACK_COMMIT(MM_DBG_COMMIT_RETURN_PROCESS_CREATE_FAILURE1, MM_PROCESS_COMMIT_CHARGE);
|
|
|
|
#if defined (_X86PAE_)
|
|
if (PoolBlock != NULL) {
|
|
MiPaeFreeEntirePage(PoolBlock);
|
|
}
|
|
#endif
|
|
// Indicate no directory base was allocated.
|
|
return FALSE;
|
|
}
|
|
|
|
MmResidentAvailablePages -= MinimumWorkingSetSize;
|
|
MM_BUMP_COUNTER(6, MinimumWorkingSetSize);
|
|
MmProcessCommit += MM_PROCESS_COMMIT_CHARGE;
|
|
|
|
NewProcess->AddressSpaceInitialized = 1;
|
|
NewProcess->Vm.MinimumWorkingSetSize = MinimumWorkingSetSize;
|
|
|
|
// Allocate a page directory (parent for 64-bit systems) page.
|
|
MiEnsureAvailablePageOrWait(CurrentProcess, NULL);
|
|
|
|
Color = MI_PAGE_COLOR_PTE_PROCESS(PDE_BASE, &CurrentProcess->NextPageColor);
|
|
|
|
PageDirectoryIndex = MiRemoveZeroPageIfAny(Color);
|
|
if (PageDirectoryIndex == 0) {
|
|
PageDirectoryIndex = MiRemoveAnyPage(Color);
|
|
UNLOCK_PFN(OldIrql);
|
|
MiZeroPhysicalPage(PageDirectoryIndex, Color);
|
|
LOCK_PFN(OldIrql);
|
|
}
|
|
|
|
#if defined (_X86PAE_)
|
|
TempPte = ValidPdePde;
|
|
MI_SET_GLOBAL_STATE(TempPte, 0);
|
|
|
|
for (i = 0; i < PD_PER_SYSTEM - 1; i += 1) {
|
|
MiEnsureAvailablePageOrWait(CurrentProcess, NULL);
|
|
Color = MI_PAGE_COLOR_PTE_PROCESS(PDE_BASE, &CurrentProcess->NextPageColor);
|
|
PageDirectoryIndex2 = MiRemoveZeroPageIfAny(Color);
|
|
if (PageDirectoryIndex2 == 0) {
|
|
PageDirectoryIndex2 = MiRemoveAnyPage(Color);
|
|
UNLOCK_PFN(OldIrql);
|
|
MiZeroPhysicalPage(PageDirectoryIndex2, Color);
|
|
LOCK_PFN(OldIrql);
|
|
}
|
|
|
|
// Recursively map each page directory page so it points to itself.
|
|
|
|
TempPte.u.Hard.PageFrameNumber = PageDirectoryIndex2;
|
|
PointerPte = (PMMPTE)MiMapPageInHyperSpace(PageDirectoryIndex, &OldIrql2);
|
|
PointerPte[i] = TempPte;
|
|
MiUnmapPageInHyperSpace(OldIrql2);
|
|
TopPte.u.Long = TempPte.u.Long & ~MM_PAE_PDPTE_MASK;
|
|
PaeVa->PteEntry[i].u.Long = TopPte.u.Long;
|
|
}
|
|
|
|
// Recursively map the topmost page directory page so it points to itself.
|
|
|
|
TempPte.u.Hard.PageFrameNumber = PageDirectoryIndex;
|
|
PointerPte = (PMMPTE)MiMapPageInHyperSpace(PageDirectoryIndex, &OldIrql2);
|
|
PointerPte[PD_PER_SYSTEM - 1] = TempPte;
|
|
MiUnmapPageInHyperSpace(OldIrql2);
|
|
TopPte.u.Long = TempPte.u.Long & ~MM_PAE_PDPTE_MASK;
|
|
PaeVa->PteEntry[PD_PER_SYSTEM - 1].u.Long = TopPte.u.Long;
|
|
NewProcess->PaePageDirectoryPage = PageDirectoryIndex;
|
|
NewProcess->PaeTop = (PVOID)PaeVa;
|
|
DirectoryTableBase[0] = TopQuad;
|
|
#else
|
|
INITIALIZE_DIRECTORY_TABLE_BASE(&DirectoryTableBase[0], PageDirectoryIndex);
|
|
#endif
|
|
|
|
#if defined (_WIN64)
|
|
|
|
PointerPpe = KSEG_ADDRESS(PageDirectoryIndex);
|
|
TempPte = ValidPdePde;
|
|
|
|
// Map the top level page directory parent page recursively onto itself.
|
|
TempPte.u.Hard.PageFrameNumber = PageDirectoryIndex;
|
|
|
|
#if defined (_AXP64_)
|
|
ASSERT(TempPte.u.Hard.Global == 0);
|
|
PointerPpe[MiGetPpeOffset(PDE_TBASE)] = TempPte;
|
|
#endif
|
|
|
|
#if defined(_IA64_)
|
|
// For IA64, the self-mapped entry is forced to be the last entry of PPE table.
|
|
PointerPpe[(PDE_SELFMAP & ((sizeof(MMPTE)*PTE_PER_PAGE) - 1)) / sizeof(MMPTE)] = TempPte;
|
|
#endif
|
|
|
|
// Allocate the page directory for hyper space and map this directory page into the page directory parent page.
|
|
MiEnsureAvailablePageOrWait(CurrentProcess, NULL);
|
|
|
|
Color = MI_PAGE_COLOR_PTE_PROCESS(MiGetPpeAddress(HYPER_SPACE), &CurrentProcess->NextPageColor);
|
|
|
|
HyperDirectoryIndex = MiRemoveZeroPageIfAny(Color);
|
|
if (HyperDirectoryIndex == 0) {
|
|
HyperDirectoryIndex = MiRemoveAnyPage(Color);
|
|
UNLOCK_PFN(OldIrql);
|
|
MiZeroPhysicalPage(HyperDirectoryIndex, Color);
|
|
LOCK_PFN(OldIrql);
|
|
}
|
|
|
|
TempPte.u.Hard.PageFrameNumber = HyperDirectoryIndex;
|
|
PointerPpe[MiGetPpeOffset(HYPER_SPACE)] = TempPte;
|
|
|
|
#if defined (_IA64_)
|
|
// Allocate the page directory parent for the session space
|
|
MiEnsureAvailablePageOrWait(CurrentProcess, NULL);
|
|
|
|
Color = MI_PAGE_COLOR_PTE_PROCESS(MiGetPpeAddress(SESSION_SPACE_DEFAULT), &CurrentProcess->NextPageColor);
|
|
|
|
SessionParentIndex = MiRemoveZeroPageIfAny(Color);
|
|
if (SessionParentIndex == 0) {
|
|
SessionParentIndex = MiRemoveAnyPage(Color);
|
|
UNLOCK_PFN(OldIrql);
|
|
MiZeroPhysicalPage(SessionParentIndex, Color);
|
|
LOCK_PFN(OldIrql);
|
|
}
|
|
|
|
INITIALIZE_DIRECTORY_TABLE_BASE(&NewProcess->Pcb.SessionParentBase, SessionParentIndex);
|
|
|
|
PointerPpe = KSEG_ADDRESS(SessionParentIndex);
|
|
TempPte.u.Hard.PageFrameNumber = SessionParentIndex;
|
|
PointerPpe[(PDE_SSELFMAP & ((sizeof(MMPTE)*PTE_PER_PAGE) - 1)) / sizeof(MMPTE)] = TempPte;
|
|
#endif // _IA64_
|
|
|
|
#endif
|
|
// Allocate the hyper space page table page.
|
|
MiEnsureAvailablePageOrWait(CurrentProcess, NULL);
|
|
|
|
Color = MI_PAGE_COLOR_PTE_PROCESS(MiGetPdeAddress(HYPER_SPACE), &CurrentProcess->NextPageColor);
|
|
|
|
HyperSpaceIndex = MiRemoveZeroPageIfAny(Color);
|
|
if (HyperSpaceIndex == 0) {
|
|
HyperSpaceIndex = MiRemoveAnyPage(Color);
|
|
UNLOCK_PFN(OldIrql);
|
|
MiZeroPhysicalPage(HyperSpaceIndex, Color);
|
|
LOCK_PFN(OldIrql);
|
|
}
|
|
|
|
#if defined (_WIN64)
|
|
PointerPde = KSEG_ADDRESS(HyperDirectoryIndex);
|
|
TempPte.u.Hard.PageFrameNumber = HyperSpaceIndex;
|
|
PointerPde[MiGetPdeOffset(HYPER_SPACE)] = TempPte;
|
|
#endif
|
|
|
|
#if defined (_X86PAE_)
|
|
// Allocate the second hyper space page table page.
|
|
// Save it in the first PTE used by the first hyperspace PDE.
|
|
MiEnsureAvailablePageOrWait(CurrentProcess, NULL);
|
|
|
|
Color = MI_PAGE_COLOR_PTE_PROCESS(MiGetPdeAddress(HYPER_SPACE2), &CurrentProcess->NextPageColor);
|
|
|
|
HyperSpaceIndex2 = MiRemoveZeroPageIfAny(Color);
|
|
if (HyperSpaceIndex2 == 0) {
|
|
HyperSpaceIndex2 = MiRemoveAnyPage(Color);
|
|
UNLOCK_PFN(OldIrql);
|
|
MiZeroPhysicalPage(HyperSpaceIndex2, Color);
|
|
LOCK_PFN(OldIrql);
|
|
}
|
|
|
|
// Unlike DirectoryTableBase[0], the HyperSpaceIndex is stored as an absolute PFN and does not need to be below 4GB.
|
|
DirectoryTableBase[1] = HyperSpaceIndex;
|
|
#else
|
|
INITIALIZE_DIRECTORY_TABLE_BASE(&DirectoryTableBase[1], HyperSpaceIndex);
|
|
#endif
|
|
|
|
// Remove page for the working set list.
|
|
MiEnsureAvailablePageOrWait(CurrentProcess, NULL);
|
|
|
|
Color = MI_PAGE_COLOR_VA_PROCESS(MmWorkingSetList, &CurrentProcess->NextPageColor);
|
|
|
|
PageContainingWorkingSet = MiRemoveZeroPageIfAny(Color);
|
|
if (PageContainingWorkingSet == 0) {
|
|
PageContainingWorkingSet = MiRemoveAnyPage(Color);
|
|
UNLOCK_PFN(OldIrql);
|
|
MiZeroPhysicalPage(PageContainingWorkingSet, Color);
|
|
LOCK_PFN(OldIrql);
|
|
}
|
|
|
|
// Release the PFN mutex as the needed pages have been allocated.
|
|
|
|
UNLOCK_PFN(OldIrql);
|
|
|
|
NewProcess->WorkingSetPage = PageContainingWorkingSet;
|
|
|
|
// Initialize the page reserved for hyper space.
|
|
|
|
MI_INITIALIZE_HYPERSPACE_MAP(HyperSpaceIndex);
|
|
|
|
// Set the PTE address in the PFN for the top level page directory page.
|
|
|
|
#if defined (_WIN64)
|
|
Pfn1 = MI_PFN_ELEMENT(PageDirectoryIndex);
|
|
ASSERT(Pfn1->u3.e1.PageColor == 0);
|
|
CONSISTENCY_LOCK_PFN(OldIrql);
|
|
Pfn1->PteAddress = MiGetPteAddress(PDE_TBASE);
|
|
CONSISTENCY_UNLOCK_PFN(OldIrql);
|
|
|
|
// Set the PTE address in the PFN for the hyper space page directory page.
|
|
Pfn1 = MI_PFN_ELEMENT(HyperDirectoryIndex);
|
|
ASSERT(Pfn1->u3.e1.PageColor == 0);
|
|
CONSISTENCY_LOCK_PFN(OldIrql);
|
|
Pfn1->PteAddress = MiGetPpeAddress(HYPER_SPACE);
|
|
CONSISTENCY_UNLOCK_PFN(OldIrql);
|
|
#if defined (_AXP64_)
|
|
// All of the system mappings are global.
|
|
MI_SET_GLOBAL_STATE(TempPte, 1);
|
|
|
|
PointerFillPte = &PointerPpe[MiGetPpeOffset(MM_SYSTEM_SPACE_START)];
|
|
CurrentAddressSpacePde = MiGetPpeAddress(MM_SYSTEM_SPACE_START);
|
|
RtlCopyMemory(PointerFillPte,
|
|
CurrentAddressSpacePde,
|
|
((1 + (MiGetPpeAddress(MM_SYSTEM_SPACE_END) - MiGetPpeAddress(MM_SYSTEM_SPACE_START))) * sizeof(MMPTE)));
|
|
// Session space and win32k.sys are local on Hydra configurations.
|
|
// However, as an optimization, it can be made global on non-Hydra.
|
|
if (MiHydra == TRUE) {
|
|
MI_SET_GLOBAL_STATE(TempPte, 0);
|
|
}
|
|
|
|
PointerFillPte = &PointerPpe[MiGetPpeOffset(MM_SESSION_SPACE_DEFAULT)];
|
|
CurrentAddressSpacePde = MiGetPpeAddress(MM_SESSION_SPACE_DEFAULT);
|
|
MI_WRITE_VALID_PTE(PointerFillPte, *CurrentAddressSpacePde);
|
|
#endif
|
|
|
|
#if defined(_IA64_)
|
|
if ((MiHydra == TRUE) && (CurrentProcess->Vm.u.Flags.ProcessInSession != 0)) {
|
|
PointerPpe = KSEG_ADDRESS(SessionParentIndex);
|
|
PointerFillPte = &PointerPpe[MiGetPpeOffset(MM_SESSION_SPACE_DEFAULT)];
|
|
CurrentAddressSpacePde = MiGetPpeAddress(MM_SESSION_SPACE_DEFAULT);
|
|
MI_WRITE_VALID_PTE(PointerFillPte, *CurrentAddressSpacePde);
|
|
}
|
|
#endif
|
|
|
|
#else // the following is for !WIN64 only
|
|
|
|
#if defined (_X86PAE_)
|
|
// Stash the second hyperspace PDE in the first PTE for the initial hyperspace entry.
|
|
TempPte = ValidPdePde;
|
|
TempPte.u.Hard.PageFrameNumber = HyperSpaceIndex2;
|
|
MI_SET_GLOBAL_STATE(TempPte, 0);
|
|
|
|
PointerPte = (PMMPTE)MiMapPageInHyperSpace(HyperSpaceIndex, &OldIrql2);
|
|
PointerPte[0] = TempPte;
|
|
MiUnmapPageInHyperSpace(OldIrql2);
|
|
#endif
|
|
|
|
// Set the PTE address in the PFN for the page directory page.
|
|
Pfn1 = MI_PFN_ELEMENT(PageDirectoryIndex);
|
|
ASSERT(Pfn1->u3.e1.PageColor == 0);
|
|
CONSISTENCY_LOCK_PFN(OldIrql);
|
|
Pfn1->PteAddress = (PMMPTE)PDE_BASE;
|
|
CONSISTENCY_UNLOCK_PFN(OldIrql);
|
|
|
|
TempPte = ValidPdePde;
|
|
TempPte.u.Hard.PageFrameNumber = HyperSpaceIndex;
|
|
MI_SET_GLOBAL_STATE(TempPte, 0);
|
|
|
|
// Map the page directory page in hyperspace.
|
|
// Note for PAE, this is the high 1GB virtual only.
|
|
PointerPte = (PMMPTE)MiMapPageInHyperSpace(PageDirectoryIndex, &OldIrql);
|
|
PointerPte[MiGetPdeOffset(HYPER_SPACE)] = TempPte;
|
|
|
|
#if defined (_X86PAE_)
|
|
// Map in the second hyperspace page directory.
|
|
// The page directory page is already recursively mapped.
|
|
TempPte.u.Hard.PageFrameNumber = HyperSpaceIndex2;
|
|
PointerPte[MiGetPdeOffset(HYPER_SPACE2)] = TempPte;
|
|
#else
|
|
// Recursively map the page directory page so it points to itself.
|
|
TempPte.u.Hard.PageFrameNumber = PageDirectoryIndex;
|
|
PointerPte[MiGetPdeOffset(PTE_BASE)] = TempPte;
|
|
#endif
|
|
// Map in the non paged portion of the system.
|
|
#if defined(_ALPHA_)
|
|
PointerFillPte = &PointerPte[MiGetPdeOffset(MM_SYSTEM_SPACE_START)];
|
|
CurrentAddressSpacePde = MiGetPdeAddress(MM_SYSTEM_SPACE_START);
|
|
RtlCopyMemory(PointerFillPte,
|
|
CurrentAddressSpacePde,
|
|
((1 + (MiGetPdeAddress(MM_SYSTEM_SPACE_END) - MiGetPdeAddress(MM_SYSTEM_SPACE_START))) * sizeof(MMPTE)));
|
|
|
|
// KSEG0 is identity-mapped on the Alpha. Copy the PDEs for this region.
|
|
PointerFillPte = &PointerPte[MiGetPdeOffset(MM_KSEG0_BASE)];
|
|
CurrentAddressSpacePde = MiGetPdeAddress(MM_KSEG0_BASE);
|
|
RtlCopyMemory(PointerFillPte, CurrentAddressSpacePde, MiGetPdeOffset(KSEG2_BASE - KSEG0_BASE) * sizeof(MMPTE));
|
|
#else // the following is for x86 only
|
|
// If the system has not been loaded at a biased address, then system PDEs
|
|
// exist in the 2gb->3gb range which must be copied.
|
|
#if defined (_X86PAE_)
|
|
|
|
// For the PAE case, only the last page directory is currently mapped, so
|
|
// only copy the system PDEs for the last 1GB - any that need copying in
|
|
// the 2gb->3gb range will be done a little later.
|
|
if (MmVirtualBias != 0) {
|
|
PointerFillPte = &PointerPte[MiGetPdeOffset(CODE_START + MmVirtualBias)];
|
|
CurrentAddressSpacePde = MiGetPdeAddress(CODE_START + MmVirtualBias);
|
|
|
|
RtlCopyMemory(PointerFillPte,
|
|
CurrentAddressSpacePde,
|
|
(((1 + CODE_END) - CODE_START) / MM_VA_MAPPED_BY_PDE) * sizeof(MMPTE));
|
|
}
|
|
#else
|
|
PointerFillPte = &PointerPte[MiGetPdeOffset(CODE_START + MmVirtualBias)];
|
|
CurrentAddressSpacePde = MiGetPdeAddress(CODE_START + MmVirtualBias);
|
|
|
|
RtlCopyMemory(PointerFillPte,
|
|
CurrentAddressSpacePde,
|
|
(((1 + CODE_END) - CODE_START) / MM_VA_MAPPED_BY_PDE) * sizeof(MMPTE));
|
|
#endif
|
|
|
|
LastPte = &PointerPte[MiGetPdeOffset(NON_PAGED_SYSTEM_END)];
|
|
PointerFillPte = &PointerPte[MiGetPdeOffset(MmNonPagedSystemStart)];
|
|
CurrentAddressSpacePde = MiGetPdeAddress(MmNonPagedSystemStart);
|
|
|
|
RtlCopyMemory(PointerFillPte,
|
|
CurrentAddressSpacePde,
|
|
((1 + (MiGetPdeAddress(NON_PAGED_SYSTEM_END) - CurrentAddressSpacePde))) * sizeof(MMPTE));
|
|
|
|
// Map in the system cache page table pages.
|
|
LastPte = &PointerPte[MiGetPdeOffset(MmSystemCacheEnd)];
|
|
PointerFillPte = &PointerPte[MiGetPdeOffset(MM_SYSTEM_CACHE_WORKING_SET)];
|
|
CurrentAddressSpacePde = MiGetPdeAddress(MM_SYSTEM_CACHE_WORKING_SET);
|
|
|
|
RtlCopyMemory(PointerFillPte,
|
|
CurrentAddressSpacePde,
|
|
((1 + (MiGetPdeAddress(MmSystemCacheEnd) - CurrentAddressSpacePde))) * sizeof(MMPTE));
|
|
|
|
#if !defined (_X86PAE_)
|
|
// Map in any additional system cache page table pages.
|
|
if (MiSystemCacheEndExtra != MmSystemCacheEnd) {
|
|
LastPte = &PointerPte[MiGetPdeOffset(MiSystemCacheEndExtra)];
|
|
PointerFillPte = &PointerPte[MiGetPdeOffset(MiSystemCacheStartExtra)];
|
|
CurrentAddressSpacePde = MiGetPdeAddress(MiSystemCacheStartExtra);
|
|
|
|
RtlCopyMemory(PointerFillPte,
|
|
CurrentAddressSpacePde,
|
|
((1 + (MiGetPdeAddress(MiSystemCacheEndExtra) - CurrentAddressSpacePde))) * sizeof(MMPTE));
|
|
}
|
|
#endif
|
|
|
|
#endif // end of x86 specific else
|
|
|
|
#if !defined (_X86PAE_)
|
|
if (MiHydra == TRUE) {
|
|
// Copy the bootstrap entry for session space.
|
|
// The rest is faulted in as needed.
|
|
PointerFillPte = &PointerPte[MiGetPdeOffset(MmSessionSpace)];
|
|
CurrentAddressSpacePde = MiGetPdeAddress(MmSessionSpace);
|
|
if (CurrentAddressSpacePde->u.Hard.Valid == 1) {
|
|
MI_WRITE_VALID_PTE(PointerFillPte, *CurrentAddressSpacePde);
|
|
} else {
|
|
MI_WRITE_INVALID_PTE(PointerFillPte, *CurrentAddressSpacePde);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if defined(_X86_)
|
|
// Map in the additional system PTE range if present.
|
|
#if !defined (_X86PAE_)
|
|
if (MiNumberOfExtraSystemPdes) {
|
|
PointerFillPte = &PointerPte[MiGetPdeOffset(KSTACK_POOL_START)];
|
|
CurrentAddressSpacePde = MiGetPdeAddress(KSTACK_POOL_START);
|
|
RtlCopyMemory(PointerFillPte, CurrentAddressSpacePde, MiNumberOfExtraSystemPdes * sizeof(MMPTE));
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
MiUnmapPageInHyperSpace(OldIrql);
|
|
|
|
#if defined (_X86PAE_)
|
|
// Map all the virtual space in the 2GB->3GB range when it's not user space.
|
|
if (MmVirtualBias == 0) {
|
|
PageDirectoryIndex = MI_GET_PAGE_FRAME_FROM_PTE(&PaeVa->PteEntry[PD_PER_SYSTEM - 2]);
|
|
PointerPte = (PMMPTE)MiMapPageInHyperSpace(PageDirectoryIndex, &OldIrql);
|
|
PointerFillPte = &PointerPte[MiGetPdeOffset(CODE_START)];
|
|
CurrentAddressSpacePde = MiGetPdeAddress(CODE_START);
|
|
RtlCopyMemory(PointerFillPte,
|
|
CurrentAddressSpacePde,
|
|
(((1 + CODE_END) - CODE_START) / MM_VA_MAPPED_BY_PDE) * sizeof(MMPTE));
|
|
if (MiSystemCacheEndExtra != MmSystemCacheEnd) {
|
|
LastPte = &PointerPte[MiGetPdeOffset(MiSystemCacheEndExtra)];
|
|
PointerFillPte = &PointerPte[MiGetPdeOffset(MiSystemCacheStartExtra)];
|
|
CurrentAddressSpacePde = MiGetPdeAddress(MiSystemCacheStartExtra);
|
|
RtlCopyMemory(PointerFillPte,
|
|
CurrentAddressSpacePde,
|
|
((1 + (MiGetPdeAddress(MiSystemCacheEndExtra) - CurrentAddressSpacePde))) * sizeof(MMPTE));
|
|
}
|
|
|
|
if (MiHydra == TRUE) {
|
|
// Copy the bootstrap entry for session space.
|
|
// The rest is faulted in as needed.
|
|
PointerFillPte = &PointerPte[MiGetPdeOffset(MmSessionSpace)];
|
|
CurrentAddressSpacePde = MiGetPdeAddress(MmSessionSpace);
|
|
if (CurrentAddressSpacePde->u.Hard.Valid == 1) {
|
|
MI_WRITE_VALID_PTE(PointerFillPte, *CurrentAddressSpacePde);
|
|
} else {
|
|
MI_WRITE_INVALID_PTE(PointerFillPte, *CurrentAddressSpacePde);
|
|
}
|
|
}
|
|
|
|
if (MiNumberOfExtraSystemPdes) {
|
|
PointerFillPte = &PointerPte[MiGetPdeOffset(KSTACK_POOL_START)];
|
|
CurrentAddressSpacePde = MiGetPdeAddress(KSTACK_POOL_START);
|
|
RtlCopyMemory(PointerFillPte, CurrentAddressSpacePde, MiNumberOfExtraSystemPdes * sizeof(MMPTE));
|
|
}
|
|
MiUnmapPageInHyperSpace(OldIrql);
|
|
}
|
|
#endif
|
|
|
|
#endif // end of !WIN64 specific else
|
|
|
|
// Up the session space reference count.
|
|
if (MiHydra == TRUE) {
|
|
MiSessionAddProcess(NewProcess);
|
|
}
|
|
|
|
// Release working set mutex and lower IRQL.
|
|
UNLOCK_WS(CurrentProcess);
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
MmInitializeProcessAddressSpace(
|
|
IN PEPROCESS ProcessToInitialize,
|
|
IN PEPROCESS ProcessToClone OPTIONAL,
|
|
IN PVOID SectionToMap OPTIONAL,
|
|
OUT PUNICODE_STRING * AuditName OPTIONAL
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
This routine initializes the working set and mutexes within an
|
|
newly created address space to support paging.
|
|
|
|
No page faults may occur in a new process until this routine is
|
|
completed.
|
|
|
|
Arguments:
|
|
|
|
ProcessToInitialize - Supplies a pointer to the process to initialize.
|
|
|
|
ProcessToClone - Optionally supplies a pointer to the process whose
|
|
address space should be copied into the
|
|
ProcessToInitialize address space.
|
|
|
|
SectionToMap - Optionally supplies a section to map into the newly
|
|
initialized address space.
|
|
|
|
Only one of ProcessToClone and SectionToMap may be specified.
|
|
|
|
Return Value:
|
|
None.
|
|
Environment:
|
|
Kernel mode. APCs Disabled.
|
|
*/
|
|
{
|
|
PMMPTE PointerPte;
|
|
MMPTE TempPte;
|
|
PVOID BaseAddress;
|
|
SIZE_T ViewSize;
|
|
KIRQL OldIrql;
|
|
NTSTATUS Status;
|
|
PFN_NUMBER PpePhysicalPage;
|
|
PFN_NUMBER PdePhysicalPage;
|
|
PFN_NUMBER PageContainingWorkingSet;
|
|
LARGE_INTEGER SectionOffset;
|
|
PSECTION_IMAGE_INFORMATION ImageInfo;
|
|
PMMVAD VadShare;
|
|
PMMVAD VadReserve;
|
|
PLOCK_HEADER LockedPagesHeader;
|
|
#if defined (_X86PAE_)
|
|
ULONG i;
|
|
PFN_NUMBER PdePhysicalPage2;
|
|
#endif
|
|
#if defined (_WIN64)
|
|
PWOW64_PROCESS Wow64Process;
|
|
#endif
|
|
|
|
|
|
// Initialize Working Set Mutex in process header.
|
|
|
|
|
|
KeAttachProcess(&ProcessToInitialize->Pcb);
|
|
ProcessToInitialize->AddressSpaceInitialized = 2;
|
|
|
|
ExInitializeFastMutex(&ProcessToInitialize->AddressCreationLock);
|
|
|
|
ExInitializeFastMutex(&ProcessToInitialize->WorkingSetLock);
|
|
|
|
|
|
// NOTE: The process block has been zeroed when allocated, so
|
|
// there is no need to zero fields and set pointers to NULL.
|
|
|
|
|
|
ASSERT(ProcessToInitialize->VadRoot == NULL);
|
|
|
|
KeQuerySystemTime(&ProcessToInitialize->Vm.LastTrimTime);
|
|
ProcessToInitialize->Vm.VmWorkingSetList = MmWorkingSetList;
|
|
|
|
|
|
// Obtain a page to map the working set and initialize the
|
|
// working set. Get PFN mutex to allocate physical pages.
|
|
|
|
|
|
LOCK_PFN(OldIrql);
|
|
|
|
|
|
// Initialize the PFN database for the Page Directory and the
|
|
// PDE which maps hyper space.
|
|
|
|
|
|
#if defined (_WIN64)
|
|
|
|
PointerPte = MiGetPteAddress(PDE_TBASE);
|
|
PpePhysicalPage = MI_GET_PAGE_FRAME_FROM_PTE(PointerPte);
|
|
|
|
MiInitializePfn(PpePhysicalPage, PointerPte, 1);
|
|
|
|
PointerPte = MiGetPpeAddress(HYPER_SPACE);
|
|
MiInitializePfn(MI_GET_PAGE_FRAME_FROM_PTE(PointerPte), PointerPte, 1);
|
|
|
|
#if defined(_IA64_)
|
|
PointerPte = MiGetPteAddress(PDE_STBASE);
|
|
MiInitializePfn(MI_GET_PAGE_FRAME_FROM_PTE(PointerPte), PointerPte, 1);
|
|
#endif
|
|
|
|
#else
|
|
|
|
#if defined (_X86PAE_)
|
|
PointerPte = MiGetPdeAddress(PDE_BASE);
|
|
#else
|
|
PointerPte = MiGetPteAddress(PDE_BASE);
|
|
#endif
|
|
PdePhysicalPage = MI_GET_PAGE_FRAME_FROM_PTE(PointerPte);
|
|
|
|
MiInitializePfn(PdePhysicalPage, PointerPte, 1);
|
|
|
|
#endif
|
|
|
|
PointerPte = MiGetPdeAddress(HYPER_SPACE);
|
|
MiInitializePfn(MI_GET_PAGE_FRAME_FROM_PTE(PointerPte), PointerPte, 1);
|
|
|
|
#if defined (_X86PAE_)
|
|
|
|
for (i = 0; i < PD_PER_SYSTEM - 1; i += 1) {
|
|
PointerPte = MiGetPteAddress(PDE_BASE + (i << PAGE_SHIFT));
|
|
PdePhysicalPage2 = MI_GET_PAGE_FRAME_FROM_PTE(PointerPte);
|
|
MiInitializePfn(PdePhysicalPage2, PointerPte, 1);
|
|
}
|
|
|
|
PointerPte = MiGetPdeAddress(HYPER_SPACE2);
|
|
MiInitializePfn(MI_GET_PAGE_FRAME_FROM_PTE(PointerPte), PointerPte, 1);
|
|
#endif
|
|
|
|
PageContainingWorkingSet = ProcessToInitialize->WorkingSetPage;
|
|
|
|
PointerPte = MiGetPteAddress(MmWorkingSetList);
|
|
PointerPte->u.Long = MM_DEMAND_ZERO_WRITE_PTE;
|
|
|
|
MiInitializePfn(PageContainingWorkingSet, PointerPte, 1);
|
|
|
|
UNLOCK_PFN(OldIrql);
|
|
|
|
MI_MAKE_VALID_PTE(TempPte, PageContainingWorkingSet, MM_READWRITE, PointerPte);
|
|
|
|
MI_SET_PTE_DIRTY(TempPte);
|
|
MI_WRITE_VALID_PTE(PointerPte, TempPte);
|
|
|
|
ASSERT(ProcessToInitialize->LockedPagesList == NULL);
|
|
|
|
if (MmTrackLockedPages == TRUE) {
|
|
LockedPagesHeader = ExAllocatePoolWithTag(NonPagedPool, sizeof(LOCK_HEADER), 'xTmM');
|
|
if (LockedPagesHeader) {
|
|
RtlZeroMemory(LockedPagesHeader, sizeof(LOCK_HEADER));
|
|
ProcessToInitialize->LockedPagesList = (PVOID)LockedPagesHeader;
|
|
InitializeListHead(&LockedPagesHeader->ListHead);
|
|
}
|
|
}
|
|
|
|
MiInitializeWorkingSetList(ProcessToInitialize);
|
|
|
|
KeInitializeSpinLock(&ProcessToInitialize->AweLock);
|
|
InitializeListHead(&ProcessToInitialize->PhysicalVadList);
|
|
|
|
// Page faults may be taken now.
|
|
|
|
// If the system has been biased to an alternate base address to allow
|
|
// 3gb of user address space and a process is not being cloned, then create a VAD for the shared memory page.
|
|
|
|
#if defined(_X86_) && defined(MM_SHARED_USER_DATA_VA)
|
|
if ((MmVirtualBias != 0) && (ProcessToClone == NULL)) {
|
|
// Allocate a VAD to map the shared memory page.
|
|
// If a VAD cannot be allocated, then detach from the target process and return a failure status.
|
|
// This VAD is marked as not deletable.
|
|
VadShare = MiAllocateVad(MM_SHARED_USER_DATA_VA, MM_SHARED_USER_DATA_VA, FALSE);
|
|
if (VadShare == NULL) {
|
|
KeDetachProcess();
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
// If a section is being mapped and the executable is not large address space aware, then create a VAD that reserves the address space between 2gb and the highest user address.
|
|
if (SectionToMap != NULL) {
|
|
if (!((PSECTION)SectionToMap)->u.Flags.Image) {
|
|
KeDetachProcess();
|
|
ExFreePool(VadShare);
|
|
return STATUS_SECTION_NOT_IMAGE;
|
|
}
|
|
ImageInfo = ((PSECTION)SectionToMap)->Segment->ImageInformation;
|
|
if ((ExVerifySuite(Enterprise) == FALSE) || ((ImageInfo->ImageCharacteristics & IMAGE_FILE_LARGE_ADDRESS_AWARE) == 0)) {
|
|
// Allocate a VAD to map the address space between 2gb and the highest user address. If a VAD can not be allocated,
|
|
// then deallocate the shared address space VAD, detach from the target process, and return a failure status.
|
|
// This VAD is marked as not deletable.
|
|
VadReserve = MiAllocateVad(_2gb, (ULONG_PTR)MM_HIGHEST_USER_ADDRESS, FALSE);
|
|
if (VadReserve == NULL) {
|
|
KeDetachProcess();
|
|
ExFreePool(VadShare);
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
|
|
// Insert the VAD.
|
|
|
|
// N.B. No exception can occur since there is no commit charge.
|
|
|
|
|
|
MiInsertVad(VadReserve);
|
|
}
|
|
}
|
|
|
|
|
|
// Insert the VAD.
|
|
|
|
// N.B. No exception can occur since there is no commit charge.
|
|
|
|
|
|
MiInsertVad(VadShare);
|
|
}
|
|
|
|
#endif
|
|
|
|
#if defined(_WIN64)
|
|
if (ProcessToClone == NULL) {
|
|
// Reserve the address space just below KUSER_SHARED_DATA as the compatibility area.
|
|
// This range can be unreserved by user mode code such as WOW64 or csrss.
|
|
ASSERT(MiCheckForConflictingVad(WOW64_COMPATIBILITY_AREA_ADDRESS, MM_SHARED_USER_DATA_VA) == NULL);
|
|
VadShare = MiAllocateVad(WOW64_COMPATIBILITY_AREA_ADDRESS, MM_SHARED_USER_DATA_VA, TRUE);
|
|
if (VadShare == NULL) {
|
|
KeDetachProcess();
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
// Reserve the memory above 2GB to prevent 32 bit (WOW64) process access.
|
|
if (SectionToMap != NULL) {
|
|
if (!((PSECTION)SectionToMap)->u.Flags.Image) {
|
|
KeDetachProcess();
|
|
ExFreePool(VadShare);
|
|
return STATUS_SECTION_NOT_IMAGE;
|
|
}
|
|
ImageInfo = ((PSECTION)SectionToMap)->Segment->ImageInformation;
|
|
|
|
if ((ImageInfo->ImageCharacteristics & IMAGE_FILE_LARGE_ADDRESS_AWARE) == 0 ||
|
|
#if defined(_AXP64_)
|
|
ImageInfo->Machine == IMAGE_FILE_MACHINE_ALPHA ||
|
|
#endif
|
|
ImageInfo->Machine == IMAGE_FILE_MACHINE_I386) {
|
|
|
|
|
|
// Allocate a VAD to reserve the address space between 2gb and
|
|
// the highest user address. If a VAD cannot be allocated,
|
|
// then deallocate the compatibility VAD, detach from the target
|
|
// process and return a failure status.
|
|
|
|
|
|
VadReserve = MiAllocateVad(_2gb,
|
|
(ULONG_PTR)MM_HIGHEST_USER_ADDRESS,
|
|
TRUE);
|
|
|
|
if (VadReserve == NULL) {
|
|
KeDetachProcess();
|
|
ExFreePool(VadShare);
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
|
|
// Insert the VAD.
|
|
|
|
// N.B. No exception can occur since there is no commit charge.
|
|
MiInsertVad(VadReserve);
|
|
|
|
// Initialize Wow64 Process structure
|
|
Wow64Process = (PWOW64_PROCESS)ExAllocatePoolWithTag(NonPagedPool, sizeof(WOW64_PROCESS), 'WowM');
|
|
if (Wow64Process == (PWOW64_PROCESS)NULL) {
|
|
KeDetachProcess();
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
RtlZeroMemory(Wow64Process, sizeof(WOW64_PROCESS));
|
|
ProcessToInitialize->Wow64Process = Wow64Process;
|
|
|
|
#if defined(_MIALT4K_)
|
|
// Initialize the alternate page table for the 4kb page function
|
|
Status = MiInitializeAlternateTable(ProcessToInitialize);
|
|
if (Status != STATUS_SUCCESS) {
|
|
KeDetachProcess();
|
|
return Status;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
// Insert the VAD.
|
|
|
|
// N.B. No exception can occur since there is no commit charge.
|
|
|
|
|
|
MiInsertVad(VadShare);
|
|
}
|
|
|
|
#endif
|
|
|
|
if (SectionToMap != (PSECTION)NULL) {
|
|
|
|
|
|
// Map the specified section into the address space of the
|
|
// process but only if it is an image section.
|
|
|
|
|
|
if (!((PSECTION)SectionToMap)->u.Flags.Image) {
|
|
Status = STATUS_SECTION_NOT_IMAGE;
|
|
} else {
|
|
UNICODE_STRING UnicodeString;
|
|
ULONG n;
|
|
PWSTR Src;
|
|
PCHAR Dst;
|
|
|
|
UnicodeString = ((PSECTION)SectionToMap)->Segment->ControlArea->FilePointer->FileName;
|
|
Src = (PWSTR)((PCHAR)UnicodeString.Buffer + UnicodeString.Length);
|
|
n = 0;
|
|
if (UnicodeString.Buffer != NULL) {
|
|
while (Src > UnicodeString.Buffer) {
|
|
if (*--Src == OBJ_NAME_PATH_SEPARATOR) {
|
|
Src += 1;
|
|
break;
|
|
} else {
|
|
n += 1;
|
|
}
|
|
}
|
|
}
|
|
Dst = ProcessToInitialize->ImageFileName;
|
|
if (n >= sizeof(ProcessToInitialize->ImageFileName)) {
|
|
n = sizeof(ProcessToInitialize->ImageFileName) - 1;
|
|
}
|
|
|
|
while (n--) {
|
|
*Dst++ = (UCHAR)*Src++;
|
|
}
|
|
*Dst = '\0';
|
|
|
|
if (AuditName) {
|
|
*AuditName = &((PSECTION)SectionToMap)->Segment->ControlArea->FilePointer->FileName;
|
|
}
|
|
|
|
ProcessToInitialize->SubSystemMajorVersion =
|
|
(UCHAR)((PSECTION)SectionToMap)->Segment->ImageInformation->SubSystemMajorVersion;
|
|
ProcessToInitialize->SubSystemMinorVersion =
|
|
(UCHAR)((PSECTION)SectionToMap)->Segment->ImageInformation->SubSystemMinorVersion;
|
|
|
|
BaseAddress = NULL;
|
|
ViewSize = 0;
|
|
ZERO_LARGE(SectionOffset);
|
|
|
|
Status = MmMapViewOfSection((PSECTION)SectionToMap,
|
|
ProcessToInitialize,
|
|
&BaseAddress,
|
|
0, // ZeroBits,
|
|
0, // CommitSize,
|
|
&SectionOffset, //SectionOffset,
|
|
&ViewSize,
|
|
ViewShare, //InheritDisposition,
|
|
0, //allocation type
|
|
PAGE_READWRITE // Protect
|
|
);
|
|
|
|
ProcessToInitialize->SectionBaseAddress = BaseAddress;
|
|
|
|
#if DBG
|
|
if (MmDebug & MM_DBG_PTE_UPDATE) {
|
|
DbgPrint("mapped image section vads\n");
|
|
VadTreeWalk(ProcessToInitialize->VadRoot);
|
|
}
|
|
#endif //DBG
|
|
}
|
|
|
|
KeDetachProcess();
|
|
return Status;
|
|
}
|
|
|
|
if (ProcessToClone != (PEPROCESS)NULL) {
|
|
#if DEVL
|
|
strcpy(ProcessToInitialize->ImageFileName, ProcessToClone->ImageFileName);
|
|
#endif // DEVL
|
|
|
|
|
|
// Clone the address space of the specified process.
|
|
|
|
|
|
|
|
// As the page directory and page tables are private to each
|
|
// process, the physical pages which map the directory page
|
|
// and the page table usage must be mapped into system space
|
|
// so they can be updated while in the context of the process
|
|
// we are cloning.
|
|
|
|
|
|
KeDetachProcess();
|
|
return MiCloneProcessAddressSpace(ProcessToClone,
|
|
ProcessToInitialize,
|
|
#if defined (_WIN64)
|
|
PpePhysicalPage,
|
|
#else
|
|
PdePhysicalPage,
|
|
#endif
|
|
PageContainingWorkingSet
|
|
);
|
|
}
|
|
|
|
// System Process.
|
|
KeDetachProcess();
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
VOID MmDeleteProcessAddressSpace(IN PEPROCESS Process)
|
|
/*++
|
|
Routine Description:
|
|
This routine deletes a process's Page Directory and working set page.
|
|
Arguments:
|
|
Process - Supplies a pointer to the deleted process.
|
|
Return Value:
|
|
None.
|
|
Environment:
|
|
Kernel mode. APCs Disabled.
|
|
*/
|
|
{
|
|
PMMPFN Pfn1;
|
|
KIRQL OldIrql;
|
|
PFN_NUMBER PageFrameIndex;
|
|
PFN_NUMBER PageFrameIndex2;
|
|
#if defined (_WIN64)
|
|
PMMPTE PageDirectoryParent;
|
|
PMMPTE Ppe;
|
|
#endif
|
|
#if defined (_X86PAE_)
|
|
ULONG i;
|
|
KIRQL OldIrql2;
|
|
PMMPTE PointerPte;
|
|
PVOID PoolBlock;
|
|
PFN_NUMBER PageDirectories[PD_PER_SYSTEM];
|
|
|
|
PoolBlock = NULL;
|
|
#endif
|
|
|
|
// Return commitment.
|
|
MiReturnCommitment(MM_PROCESS_COMMIT_CHARGE);
|
|
MM_TRACK_COMMIT(MM_DBG_COMMIT_RETURN_PROCESS_DELETE, MM_PROCESS_COMMIT_CHARGE);
|
|
ASSERT(Process->CommitCharge == 0);
|
|
|
|
// Remove the working set list page from the deleted process.
|
|
Pfn1 = MI_PFN_ELEMENT(Process->WorkingSetPage);
|
|
|
|
LOCK_PFN(OldIrql);
|
|
MmProcessCommit -= MM_PROCESS_COMMIT_CHARGE;
|
|
|
|
if (Process->AddressSpaceInitialized == 2) {
|
|
MI_SET_PFN_DELETED(Pfn1);
|
|
|
|
MiDecrementShareAndValidCount(Pfn1->PteFrame);
|
|
MiDecrementShareCountOnly(Process->WorkingSetPage);
|
|
|
|
ASSERT((Pfn1->u3.e2.ReferenceCount == 0) || (Pfn1->u3.e1.WriteInProgress));
|
|
|
|
|
|
// Remove the hyper space page table page from the deleted process.
|
|
|
|
#if defined (_X86PAE_)
|
|
|
|
PageFrameIndex = (PFN_NUMBER)Process->Pcb.DirectoryTableBase[1];
|
|
|
|
// Remove the second hyper space page table page.
|
|
|
|
|
|
PointerPte = (PMMPTE)MiMapPageInHyperSpace(PageFrameIndex, &OldIrql2);
|
|
PageFrameIndex2 = MI_GET_PAGE_FRAME_FROM_PTE(PointerPte);
|
|
MiUnmapPageInHyperSpace(OldIrql2);
|
|
|
|
Pfn1 = MI_PFN_ELEMENT(PageFrameIndex2);
|
|
|
|
MI_SET_PFN_DELETED(Pfn1);
|
|
|
|
MiDecrementShareAndValidCount(Pfn1->PteFrame);
|
|
MiDecrementShareCountOnly(PageFrameIndex2);
|
|
|
|
ASSERT((Pfn1->u3.e2.ReferenceCount == 0) || (Pfn1->u3.e1.WriteInProgress));
|
|
#else
|
|
PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE((PMMPTE)(&(Process->Pcb.DirectoryTableBase[1])));
|
|
#endif
|
|
|
|
Pfn1 = MI_PFN_ELEMENT(PageFrameIndex);
|
|
|
|
MI_SET_PFN_DELETED(Pfn1);
|
|
|
|
MiDecrementShareAndValidCount(Pfn1->PteFrame);
|
|
MiDecrementShareCountOnly(PageFrameIndex);
|
|
ASSERT((Pfn1->u3.e2.ReferenceCount == 0) || (Pfn1->u3.e1.WriteInProgress));
|
|
|
|
// Remove the page directory page.
|
|
|
|
PageFrameIndex = MI_GET_DIRECTORY_FRAME_FROM_PROCESS(Process);
|
|
|
|
#if defined (_X86PAE_)
|
|
|
|
PointerPte = (PMMPTE)MiMapPageInHyperSpace(PageFrameIndex, &OldIrql2);
|
|
for (i = 0; i < PD_PER_SYSTEM - 1; i += 1) {
|
|
PageDirectories[i] = MI_GET_PAGE_FRAME_FROM_PTE(&PointerPte[i]);
|
|
}
|
|
MiUnmapPageInHyperSpace(OldIrql2);
|
|
|
|
for (i = 0; i < PD_PER_SYSTEM - 1; i += 1) {
|
|
Pfn1 = MI_PFN_ELEMENT(PageDirectories[i]);
|
|
|
|
MI_SET_PFN_DELETED(Pfn1);
|
|
|
|
MiDecrementShareAndValidCount(PageDirectories[i]);
|
|
MiDecrementShareAndValidCount(Pfn1->PteFrame);
|
|
|
|
ASSERT((Pfn1->u3.e2.ReferenceCount == 0) || (Pfn1->u3.e1.WriteInProgress));
|
|
}
|
|
#endif
|
|
|
|
#if defined (_WIN64)
|
|
|
|
|
|
// Get a pointer to the top-level page directory parent page via
|
|
// its KSEG0 address.
|
|
|
|
|
|
PageDirectoryParent = KSEG_ADDRESS(PageFrameIndex);
|
|
|
|
|
|
// Remove the hyper space page directory page from the deleted process.
|
|
|
|
|
|
Ppe = &PageDirectoryParent[MiGetPpeOffset(HYPER_SPACE)];
|
|
PageFrameIndex2 = MI_GET_PAGE_FRAME_FROM_PTE(Ppe);
|
|
Pfn1 = MI_PFN_ELEMENT(PageFrameIndex2);
|
|
|
|
MI_SET_PFN_DELETED(Pfn1);
|
|
MiDecrementShareAndValidCount(Pfn1->PteFrame);
|
|
MiDecrementShareCountOnly(PageFrameIndex2);
|
|
ASSERT((Pfn1->u3.e2.ReferenceCount == 0) || (Pfn1->u3.e1.WriteInProgress));
|
|
#endif
|
|
|
|
Pfn1 = MI_PFN_ELEMENT(PageFrameIndex);
|
|
MI_SET_PFN_DELETED(Pfn1);
|
|
MiDecrementShareAndValidCount(PageFrameIndex);
|
|
MiDecrementShareCountOnly(PageFrameIndex);
|
|
ASSERT((Pfn1->u3.e2.ReferenceCount == 0) || (Pfn1->u3.e1.WriteInProgress));
|
|
|
|
#if defined (_X86PAE_)
|
|
// Free the page directory page pointers.
|
|
PoolBlock = MiPaeFree((PPAE_ENTRY)Process->PaeTop);
|
|
#endif
|
|
|
|
#if defined(_IA64_)
|
|
|
|
// Free the session space page directory parent page
|
|
PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE((PMMPTE)(&(Process->Pcb.SessionParentBase)));
|
|
|
|
Pfn1 = MI_PFN_ELEMENT(PageFrameIndex);
|
|
MI_SET_PFN_DELETED(Pfn1);
|
|
MiDecrementShareAndValidCount(Pfn1->PteFrame);
|
|
MiDecrementShareCountOnly(PageFrameIndex);
|
|
ASSERT((Pfn1->u3.e2.ReferenceCount == 0) || (Pfn1->u3.e1.WriteInProgress));
|
|
#endif
|
|
} else {
|
|
// Process initialization never completed, just return the pages
|
|
// to the free list.
|
|
MiInsertPageInList(MmPageLocationList[FreePageList],
|
|
Process->WorkingSetPage);
|
|
#if defined (_WIN64)
|
|
|
|
// Get a pointer to the top-level page directory parent page via
|
|
// its KSEG0 address.
|
|
|
|
PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE((PMMPTE)(&(Process->Pcb.DirectoryTableBase[0])));
|
|
|
|
PageDirectoryParent = KSEG_ADDRESS(PageFrameIndex);
|
|
Ppe = &PageDirectoryParent[MiGetPpeOffset(HYPER_SPACE)];
|
|
PageFrameIndex2 = MI_GET_PAGE_FRAME_FROM_PTE(Ppe);
|
|
MiInsertPageInList(MmPageLocationList[FreePageList], PageFrameIndex2);
|
|
#endif
|
|
|
|
#if defined (_X86PAE_)
|
|
PageFrameIndex = MI_GET_DIRECTORY_FRAME_FROM_PROCESS(Process);
|
|
|
|
PointerPte = (PMMPTE)MiMapPageInHyperSpace(PageFrameIndex, &OldIrql2);
|
|
for (i = 0; i < PD_PER_SYSTEM - 1; i += 1) {
|
|
PageDirectories[i] = MI_GET_PAGE_FRAME_FROM_PTE(&PointerPte[i]);
|
|
}
|
|
MiUnmapPageInHyperSpace(OldIrql2);
|
|
|
|
for (i = 0; i < PD_PER_SYSTEM - 1; i += 1) {
|
|
MiInsertPageInList(MmPageLocationList[FreePageList], PageDirectories[i]);
|
|
}
|
|
|
|
// Free the second hyper space page table page.
|
|
|
|
PageFrameIndex = (PFN_NUMBER)Process->Pcb.DirectoryTableBase[1];
|
|
|
|
PointerPte = (PMMPTE)MiMapPageInHyperSpace(PageFrameIndex, &OldIrql2);
|
|
PageFrameIndex2 = MI_GET_PAGE_FRAME_FROM_PTE(PointerPte);
|
|
MiUnmapPageInHyperSpace(OldIrql2);
|
|
MiInsertPageInList(MmPageLocationList[FreePageList], PageFrameIndex2);
|
|
|
|
// Free the first hyper space page table page.
|
|
|
|
MiInsertPageInList(MmPageLocationList[FreePageList], (PFN_NUMBER)Process->Pcb.DirectoryTableBase[1]);
|
|
|
|
MiInsertPageInList(MmPageLocationList[FreePageList], MI_GET_DIRECTORY_FRAME_FROM_PROCESS(Process));
|
|
|
|
// Free the page directory page pointers.
|
|
PoolBlock = MiPaeFree((PPAE_ENTRY)Process->PaeTop);
|
|
#else
|
|
|
|
MiInsertPageInList(MmPageLocationList[FreePageList],
|
|
MI_GET_PAGE_FRAME_FROM_PTE((PMMPTE)(&(Process->Pcb.DirectoryTableBase[1]))));
|
|
|
|
MiInsertPageInList(MmPageLocationList[FreePageList],
|
|
MI_GET_PAGE_FRAME_FROM_PTE((PMMPTE)(&(Process->Pcb.DirectoryTableBase[0]))));
|
|
#endif
|
|
#if defined(_IA64_)
|
|
MiInsertPageInList(MmPageLocationList[FreePageList], Process->Pcb.SessionParentBase);
|
|
#endif
|
|
}
|
|
|
|
MmResidentAvailablePages += MM_PROCESS_CREATE_CHARGE;
|
|
MM_BUMP_COUNTER(7, MM_PROCESS_CREATE_CHARGE);
|
|
|
|
UNLOCK_PFN(OldIrql);
|
|
|
|
#if defined (_X86PAE_)
|
|
if (PoolBlock != NULL) {
|
|
MiPaeFreeEntirePage(PoolBlock);
|
|
}
|
|
#endif
|
|
|
|
// Check to see if the paging files should be contracted.
|
|
MiContractPagingFiles();
|
|
return;
|
|
}
|
|
|
|
|
|
VOID MmCleanProcessAddressSpace()
|
|
/*++
|
|
Routine Description:
|
|
This routine cleans an address space by deleting all the
|
|
user and pagable portion of the address space. At the
|
|
completion of this routine, no page faults may occur within
|
|
the process.
|
|
Arguments:
|
|
None.
|
|
Return Value:
|
|
None.
|
|
Environment:
|
|
Kernel mode, APCs disabled.
|
|
*/
|
|
{
|
|
PEPROCESS Process;
|
|
PMMVAD Vad;
|
|
KEVENT Event;
|
|
KIRQL OldIrql;
|
|
#if defined(_ALPHA_) && !defined(_AXP64_)
|
|
KIRQL OldIrql2;
|
|
#endif
|
|
PMMPTE LastPte;
|
|
PMMPTE PointerPte;
|
|
PMMPTE PointerPde;
|
|
PMMPTE PointerPpe;
|
|
PMMPFN Pfn1;
|
|
PVOID TempVa;
|
|
LONG AboveWsMin;
|
|
MMPTE_FLUSH_LIST PteFlushList;
|
|
|
|
PteFlushList.Count = 0;
|
|
Process = PsGetCurrentProcess();
|
|
if ((Process->AddressSpaceDeleted != 0) ||
|
|
(Process->AddressSpaceInitialized == 0)) {
|
|
// This process's address space has already been deleted. However,
|
|
// this process can still have a session space. Get rid of it now.
|
|
if (MiHydra == TRUE) {
|
|
MiSessionRemoveProcess();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (Process->AddressSpaceInitialized == 1) {
|
|
// The process has been created but not fully initialized.
|
|
// Return partial resources now.
|
|
|
|
MmResidentAvailablePages += (Process->Vm.MinimumWorkingSetSize - MM_PROCESS_CREATE_CHARGE);
|
|
|
|
MM_BUMP_COUNTER(41, Process->Vm.MinimumWorkingSetSize - MM_PROCESS_CREATE_CHARGE);
|
|
|
|
// This process's address space has already been deleted. However,
|
|
// this process can still have a session space. Get rid of it now.
|
|
|
|
if (MiHydra == TRUE) {
|
|
MiSessionRemoveProcess();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// If working set expansion for this process is allowed, disable
|
|
// it and remove the process from expanded process list if it
|
|
// is on it.
|
|
|
|
LOCK_EXPANSION(OldIrql);
|
|
|
|
if (Process->Vm.u.Flags.BeingTrimmed) {
|
|
// Initialize an event and put the event address
|
|
// in the blink field. When the trimming is complete,
|
|
// this event will be set.
|
|
|
|
KeInitializeEvent(&Event, NotificationEvent, FALSE);
|
|
|
|
Process->Vm.WorkingSetExpansionLinks.Blink = (PLIST_ENTRY)&Event;
|
|
|
|
// Release the mutex and wait for the event.
|
|
KeEnterCriticalRegion();
|
|
UNLOCK_EXPANSION_AND_THEN_WAIT(OldIrql);
|
|
|
|
KeWaitForSingleObject(&Event,
|
|
WrVirtualMemory,
|
|
KernelMode,
|
|
FALSE,
|
|
(PLARGE_INTEGER)NULL);
|
|
KeLeaveCriticalRegion();
|
|
} else if (Process->Vm.WorkingSetExpansionLinks.Flink == MM_NO_WS_EXPANSION) {
|
|
// No trimming is in progress and no expansion allowed, so this cannot
|
|
// be on any lists.
|
|
ASSERT(Process->Vm.WorkingSetExpansionLinks.Blink != MM_WS_EXPANSION_IN_PROGRESS);
|
|
UNLOCK_EXPANSION(OldIrql);
|
|
} else {
|
|
RemoveEntryList(&Process->Vm.WorkingSetExpansionLinks);
|
|
|
|
// Disable expansion.
|
|
Process->Vm.WorkingSetExpansionLinks.Flink = MM_NO_WS_EXPANSION;
|
|
|
|
// Release the pfn mutex.
|
|
UNLOCK_EXPANSION(OldIrql);
|
|
}
|
|
|
|
if (MiHydra == TRUE) {
|
|
MiSessionRemoveProcess();
|
|
}
|
|
|
|
// Delete all the user owned pagable virtual addresses in the process.
|
|
LOCK_WS_AND_ADDRESS_SPACE(Process);
|
|
|
|
// Synchronize address space delete with NtReadVirtualMemory and NtWriteVirtualMemory.
|
|
|
|
MiLockSystemSpace(OldIrql);
|
|
Process->AddressSpaceDeleted = 1;
|
|
if (Process->VmOperation != 0) {
|
|
// A Vm operation is in progress, set the event and
|
|
// indicate this process is being deleted to stop other
|
|
// Vm operations.
|
|
KeInitializeEvent(&Event, NotificationEvent, FALSE);
|
|
Process->VmOperationEvent = &Event;
|
|
|
|
do {
|
|
MiUnlockSystemSpace(OldIrql);
|
|
|
|
UNLOCK_WS_AND_ADDRESS_SPACE(Process);
|
|
KeWaitForSingleObject(&Event, WrVirtualMemory, KernelMode, FALSE, (PLARGE_INTEGER)NULL);
|
|
LOCK_WS_AND_ADDRESS_SPACE(Process);
|
|
|
|
// Synchronize address space delete with NtReadVirtualMemory and NtWriteVirtualMemory.
|
|
MiLockSystemSpace(OldIrql);
|
|
} while (Process->VmOperation != 0);
|
|
|
|
MiUnlockSystemSpace(OldIrql);
|
|
} else {
|
|
MiUnlockSystemSpace(OldIrql);
|
|
}
|
|
|
|
// Delete all the valid user mode addresses from the working set
|
|
// list. At this point NO page faults are allowed on user space
|
|
// addresses. Faults are allowed on page tables for user space, which
|
|
// requires that we keep the working set structure consistent until we
|
|
// finally take it all down.
|
|
|
|
MiDeleteAddressesInWorkingSet(Process);
|
|
|
|
// Remove hash table pages, if any. This is the first time we do this
|
|
// during the deletion path, but we need to do it again before we finish
|
|
// because we may fault in some page tables during the VAD clearing. We
|
|
// could have maintained the hash table validity during the WorkingSet
|
|
// deletion above in order to avoid freeing the hash table twice, but since
|
|
// we're just deleting it all anyway, it's faster to do it this way. Note
|
|
// that if we don't do this or maintain the validity, we can trap later
|
|
// in MiGrowWsleHash.
|
|
|
|
PointerPte = MiGetPteAddress(&MmWsle[MM_MAXIMUM_WORKING_SET]) + 1;
|
|
LastPte = MiGetPteAddress(MmWorkingSetList->HighestPermittedHashAddress);
|
|
|
|
#if defined (_WIN64)
|
|
PointerPpe = MiGetPdeAddress(PointerPte);
|
|
PointerPde = MiGetPteAddress(PointerPte);
|
|
|
|
if ((PointerPpe->u.Hard.Valid == 1) &&
|
|
(PointerPde->u.Hard.Valid == 1) &&
|
|
(PointerPte->u.Hard.Valid == 1)) {
|
|
PteFlushList.Count = 0;
|
|
LOCK_PFN(OldIrql);
|
|
while (PointerPte->u.Hard.Valid) {
|
|
TempVa = MiGetVirtualAddressMappedByPte(PointerPte);
|
|
MiDeletePte(PointerPte, TempVa, FALSE, Process, NULL, &PteFlushList);
|
|
|
|
PointerPte += 1;
|
|
Process->NumberOfPrivatePages += 1;
|
|
|
|
// If all the entries have been removed from the previous page
|
|
// table page, delete the page table page itself. Likewise with
|
|
// the page directory page.
|
|
|
|
if ((MiIsPteOnPdeBoundary(PointerPte)) ||
|
|
((MiGetPdeAddress(PointerPte))->u.Hard.Valid == 0) ||
|
|
((MiGetPteAddress(PointerPte))->u.Hard.Valid == 0) ||
|
|
(PointerPte->u.Hard.Valid == 0)) {
|
|
MiFlushPteList(&PteFlushList, FALSE, ZeroPte);
|
|
PointerPde = MiGetPteAddress(PointerPte - 1);
|
|
ASSERT(PointerPde->u.Hard.Valid == 1);
|
|
Pfn1 = MI_PFN_ELEMENT(MI_GET_PAGE_FRAME_FROM_PTE(PointerPde));
|
|
|
|
if (Pfn1->u2.ShareCount == 1 && Pfn1->u3.e2.ReferenceCount == 1) {
|
|
MiDeletePte(PointerPde,
|
|
PointerPte - 1,
|
|
FALSE,
|
|
Process,
|
|
NULL,
|
|
NULL);
|
|
Process->NumberOfPrivatePages += 1;
|
|
}
|
|
|
|
if (MiIsPteOnPpeBoundary(PointerPte)) {
|
|
PointerPpe = MiGetPteAddress(PointerPde);
|
|
ASSERT(PointerPpe->u.Hard.Valid == 1);
|
|
Pfn1 = MI_PFN_ELEMENT(MI_GET_PAGE_FRAME_FROM_PTE(PointerPpe));
|
|
|
|
if (Pfn1->u2.ShareCount == 1 && Pfn1->u3.e2.ReferenceCount == 1) {
|
|
MiDeletePte(PointerPpe, PointerPde, FALSE, Process, NULL, NULL);
|
|
Process->NumberOfPrivatePages += 1;
|
|
}
|
|
}
|
|
PointerPde = MiGetPteAddress(PointerPte);
|
|
PointerPpe = MiGetPdeAddress(PointerPte);
|
|
if ((PointerPpe->u.Hard.Valid == 0) || (PointerPde->u.Hard.Valid == 0)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
MiFlushPteList(&PteFlushList, FALSE, ZeroPte);
|
|
UNLOCK_PFN(OldIrql);
|
|
}
|
|
#else
|
|
if (PointerPte->u.Hard.Valid) {
|
|
PteFlushList.Count = 0;
|
|
LOCK_PFN(OldIrql);
|
|
while ((PointerPte < LastPte) && (PointerPte->u.Hard.Valid)) {
|
|
TempVa = MiGetVirtualAddressMappedByPte(PointerPte);
|
|
MiDeletePte(PointerPte, TempVa, FALSE, Process, NULL, &PteFlushList);
|
|
|
|
PointerPte += 1;
|
|
Process->NumberOfPrivatePages += 1;
|
|
}
|
|
MiFlushPteList(&PteFlushList, FALSE, ZeroPte);
|
|
UNLOCK_PFN(OldIrql);
|
|
}
|
|
#endif
|
|
|
|
// Clear the hash fields as a fault may occur below on the page table
|
|
// pages during VAD clearing and resolution of the fault may result in
|
|
// adding a hash table. Thus these fields must be consistent with the
|
|
// clearing just done above.
|
|
MmWorkingSetList->HashTableSize = 0;
|
|
MmWorkingSetList->HashTable = NULL;
|
|
|
|
// Delete the virtual address descriptors and dereference any
|
|
// section objects.
|
|
|
|
Vad = Process->VadRoot;
|
|
|
|
while (Vad != (PMMVAD)NULL) {
|
|
MiRemoveVad(Vad);
|
|
|
|
// If the system has been biased to an alternate base address to allow 3gb of user address space,
|
|
// then check if the current VAD describes the shared memory page.
|
|
#if defined(_X86_) && defined(MM_SHARED_USER_DATA_VA)
|
|
if (MmVirtualBias != 0) {
|
|
// If the VAD describes the shared memory page, then free the VAD and continue with the next entry.
|
|
if (Vad->StartingVpn == MI_VA_TO_VPN(MM_SHARED_USER_DATA_VA)) {
|
|
goto LoopEnd;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (((Vad->u.VadFlags.PrivateMemory == 0) && (Vad->ControlArea != NULL)) || (Vad->u.VadFlags.PhysicalMapping == 1)) {
|
|
// This VAD represents a mapped view or a driver-mapped physical
|
|
// view - delete the view and perform any section related cleanup operations.
|
|
MiRemoveMappedView(Process, Vad);
|
|
} else {
|
|
if (Vad->u.VadFlags.UserPhysicalPages == 1) {
|
|
// Free all the physical pages that this VAD might be mapping.
|
|
// Since only the AWE lock synchronizes the remap API, carefully remove this VAD from the list first.
|
|
MiPhysicalViewRemover(Process, Vad);
|
|
|
|
MiRemoveUserPhysicalPagesVad((PMMVAD_SHORT)Vad);
|
|
|
|
MiDeletePageTablesForPhysicalRange(MI_VPN_TO_VA(Vad->StartingVpn), MI_VPN_TO_VA_ENDING(Vad->EndingVpn));
|
|
} else {
|
|
if (Vad->u.VadFlags.WriteWatch == 1) {
|
|
MiPhysicalViewRemover(Process, Vad);
|
|
}
|
|
|
|
LOCK_PFN(OldIrql);
|
|
|
|
// Don't specify address space deletion as TRUE as the working set must be consistent as page faults may
|
|
// be taken during clone removal, protoPTE lookup, etc.
|
|
MiDeleteVirtualAddresses(MI_VPN_TO_VA(Vad->StartingVpn), MI_VPN_TO_VA_ENDING(Vad->EndingVpn), FALSE, Vad);
|
|
|
|
UNLOCK_PFN(OldIrql);
|
|
}
|
|
}
|
|
|
|
#if defined(_X86_) && defined(MM_SHARED_USER_DATA_VA)
|
|
LoopEnd:
|
|
#endif
|
|
|
|
ExFreePool(Vad);
|
|
Vad = Process->VadRoot;
|
|
}
|
|
|
|
ASSERT(IsListEmpty(&Process->PhysicalVadList) != 0);
|
|
|
|
MiCleanPhysicalProcessPages(Process);
|
|
|
|
// Delete the shared data page, if any.
|
|
LOCK_PFN(OldIrql);
|
|
|
|
#if defined(MM_SHARED_USER_DATA_VA)
|
|
MiDeleteVirtualAddresses((PVOID)MM_SHARED_USER_DATA_VA, (PVOID)MM_SHARED_USER_DATA_VA, FALSE, NULL);
|
|
#endif
|
|
|
|
// Delete the system portion of the address space.
|
|
// Only now is it safe to specify TRUE to MiDelete because now that the VADs have been deleted we can no longer fault on user space pages.
|
|
|
|
#if defined(_ALPHA_) && !defined(_AXP64_)
|
|
LOCK_EXPANSION_IF_ALPHA(OldIrql2);
|
|
#endif
|
|
Process->Vm.AddressSpaceBeingDeleted = 1;
|
|
#if defined(_ALPHA_) && !defined(_AXP64_)
|
|
UNLOCK_EXPANSION_IF_ALPHA(OldIrql2);
|
|
#endif
|
|
|
|
|
|
// Adjust the count of pages above working set maximum. This
|
|
// must be done here because the working set list is not
|
|
// updated during this deletion.
|
|
|
|
|
|
AboveWsMin = (LONG)Process->Vm.WorkingSetSize - (LONG)Process->Vm.MinimumWorkingSetSize;
|
|
if (AboveWsMin > 0) {
|
|
MmPagesAboveWsMinimum -= AboveWsMin;
|
|
}
|
|
|
|
UNLOCK_PFN(OldIrql);
|
|
|
|
|
|
// Return commitment for page table pages.
|
|
|
|
|
|
#ifdef _WIN64
|
|
ASSERT(MmWorkingSetList->NumberOfCommittedPageTables == 0);
|
|
#else
|
|
MiReturnCommitment(MmWorkingSetList->NumberOfCommittedPageTables);
|
|
|
|
MM_TRACK_COMMIT(MM_DBG_COMMIT_RETURN_PROCESS_CLEAN_PAGETABLES,
|
|
MmWorkingSetList->NumberOfCommittedPageTables);
|
|
|
|
if (Process->JobStatus & PS_JOB_STATUS_REPORT_COMMIT_CHANGES) {
|
|
PsChangeJobMemoryUsage(-(SSIZE_T)MmWorkingSetList->NumberOfCommittedPageTables);
|
|
}
|
|
Process->CommitCharge -= MmWorkingSetList->NumberOfCommittedPageTables;
|
|
#endif
|
|
|
|
|
|
// Check to make sure all the clone descriptors went away.
|
|
|
|
|
|
ASSERT(Process->CloneRoot == (PMMCLONE_DESCRIPTOR)NULL);
|
|
|
|
if (Process->NumberOfLockedPages != 0) {
|
|
if (Process->LockedPagesList) {
|
|
|
|
PLIST_ENTRY NextEntry;
|
|
PLOCK_TRACKER Tracker;
|
|
PLOCK_HEADER LockedPagesHeader;
|
|
|
|
LockedPagesHeader = (PLOCK_HEADER)Process->LockedPagesList;
|
|
if ((LockedPagesHeader->Count != 0) && (MiTrackingAborted == FALSE)) {
|
|
ASSERT(IsListEmpty(&LockedPagesHeader->ListHead) == 0);
|
|
NextEntry = LockedPagesHeader->ListHead.Flink;
|
|
|
|
Tracker = CONTAINING_RECORD(NextEntry,
|
|
LOCK_TRACKER,
|
|
ListEntry);
|
|
|
|
KeBugCheckEx(DRIVER_LEFT_LOCKED_PAGES_IN_PROCESS,
|
|
(ULONG_PTR)Tracker->CallingAddress,
|
|
(ULONG_PTR)Tracker->CallersCaller,
|
|
(ULONG_PTR)Tracker->Mdl,
|
|
Process->NumberOfLockedPages);
|
|
}
|
|
}
|
|
|
|
KeBugCheckEx(PROCESS_HAS_LOCKED_PAGES,
|
|
0,
|
|
(ULONG_PTR)Process,
|
|
Process->NumberOfLockedPages,
|
|
(ULONG_PTR)Process->LockedPagesList);
|
|
return;
|
|
}
|
|
|
|
if (Process->LockedPagesList) {
|
|
ASSERT(MmTrackLockedPages == TRUE);
|
|
ExFreePool(Process->LockedPagesList);
|
|
Process->LockedPagesList = NULL;
|
|
}
|
|
|
|
#if DBG
|
|
if ((Process->NumberOfPrivatePages != 0) && (MmDebug & MM_DBG_PRIVATE_PAGES)) {
|
|
DbgPrint("MM: Process contains private pages %ld\n",
|
|
Process->NumberOfPrivatePages);
|
|
DbgBreakPoint();
|
|
}
|
|
#endif //DBG
|
|
|
|
|
|
#if defined(_WIN64)
|
|
|
|
// Delete the WowProcess structure
|
|
|
|
|
|
if (Process->Wow64Process != NULL) {
|
|
#if defined(_MIALT4K_)
|
|
MiDeleteAlternateTable(Process);
|
|
#endif
|
|
ExFreePool(Process->Wow64Process);
|
|
Process->Wow64Process = NULL;
|
|
}
|
|
#endif
|
|
|
|
|
|
// Remove the working set list pages (except for the first one).
|
|
// These pages are not removed because DPCs could still occur within
|
|
// the address space. In a DPC, nonpagedpool could be allocated
|
|
// which could require removing a page from the standby list, requiring
|
|
// hyperspace to map the previous PTE.
|
|
|
|
|
|
PointerPte = MiGetPteAddress(MmWorkingSetList) + 1;
|
|
|
|
PteFlushList.Count = 0;
|
|
|
|
LOCK_PFN(OldIrql);
|
|
while (PointerPte->u.Hard.Valid) {
|
|
TempVa = MiGetVirtualAddressMappedByPte(PointerPte);
|
|
MiDeletePte(PointerPte, TempVa, TRUE, Process, NULL, &PteFlushList);
|
|
|
|
PointerPte += 1;
|
|
#if defined (_WIN64)
|
|
|
|
// If all the entries have been removed from the previous page
|
|
// table page, delete the page table page itself. Likewise with
|
|
// the page directory page.
|
|
if ((MiIsPteOnPdeBoundary(PointerPte)) ||
|
|
((MiGetPdeAddress(PointerPte))->u.Hard.Valid == 0) ||
|
|
((MiGetPteAddress(PointerPte))->u.Hard.Valid == 0) ||
|
|
(PointerPte->u.Hard.Valid == 0)) {
|
|
MiFlushPteList(&PteFlushList, FALSE, ZeroPte);
|
|
PointerPde = MiGetPteAddress(PointerPte - 1);
|
|
ASSERT(PointerPde->u.Hard.Valid == 1);
|
|
Pfn1 = MI_PFN_ELEMENT(MI_GET_PAGE_FRAME_FROM_PTE(PointerPde));
|
|
if (Pfn1->u2.ShareCount == 1 && Pfn1->u3.e2.ReferenceCount == 1) {
|
|
MiDeletePte(PointerPde, PointerPte - 1, TRUE, Process, NULL, NULL);
|
|
}
|
|
|
|
if (MiIsPteOnPpeBoundary(PointerPte)) {
|
|
PointerPpe = MiGetPteAddress(PointerPde);
|
|
ASSERT(PointerPpe->u.Hard.Valid == 1);
|
|
Pfn1 = MI_PFN_ELEMENT(MI_GET_PAGE_FRAME_FROM_PTE(PointerPpe));
|
|
if (Pfn1->u2.ShareCount == 1 && Pfn1->u3.e2.ReferenceCount == 1) {
|
|
MiDeletePte(PointerPpe, PointerPde, TRUE, Process, NULL, NULL);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Remove hash table pages, if any. Yes, we've already done this once
|
|
// during the deletion path, but we need to do it again because we may
|
|
// have faulted in some page tables during the VAD clearing.
|
|
PointerPte = MiGetPteAddress(&MmWsle[MM_MAXIMUM_WORKING_SET]) + 1;
|
|
|
|
#if defined (_WIN64)
|
|
PointerPpe = MiGetPdeAddress(PointerPte);
|
|
PointerPde = MiGetPteAddress(PointerPte);
|
|
|
|
if ((PointerPpe->u.Hard.Valid == 1) &&
|
|
(PointerPde->u.Hard.Valid == 1) &&
|
|
(PointerPte->u.Hard.Valid == 1)) {
|
|
while (PointerPte->u.Hard.Valid) {
|
|
TempVa = MiGetVirtualAddressMappedByPte(PointerPte);
|
|
MiDeletePte(PointerPte, TempVa, TRUE, Process, NULL, &PteFlushList);
|
|
|
|
PointerPte += 1;
|
|
|
|
|
|
// If all the entries have been removed from the previous page
|
|
// table page, delete the page table page itself. Likewise with
|
|
// the page directory page.
|
|
|
|
|
|
if ((MiIsPteOnPdeBoundary(PointerPte)) ||
|
|
((MiGetPdeAddress(PointerPte))->u.Hard.Valid == 0) ||
|
|
((MiGetPteAddress(PointerPte))->u.Hard.Valid == 0) ||
|
|
(PointerPte->u.Hard.Valid == 0)) {
|
|
MiFlushPteList(&PteFlushList, FALSE, ZeroPte);
|
|
PointerPde = MiGetPteAddress(PointerPte - 1);
|
|
ASSERT(PointerPde->u.Hard.Valid == 1);
|
|
Pfn1 = MI_PFN_ELEMENT(MI_GET_PAGE_FRAME_FROM_PTE(PointerPde));
|
|
if (Pfn1->u2.ShareCount == 1 && Pfn1->u3.e2.ReferenceCount == 1) {
|
|
MiDeletePte(PointerPde, PointerPte - 1, TRUE, Process, NULL, NULL);
|
|
}
|
|
|
|
if (MiIsPteOnPpeBoundary(PointerPte)) {
|
|
PointerPpe = MiGetPteAddress(PointerPde);
|
|
ASSERT(PointerPpe->u.Hard.Valid == 1);
|
|
Pfn1 = MI_PFN_ELEMENT(MI_GET_PAGE_FRAME_FROM_PTE(PointerPpe));
|
|
if (Pfn1->u2.ShareCount == 1 && Pfn1->u3.e2.ReferenceCount == 1) {
|
|
MiDeletePte(PointerPpe, PointerPde, TRUE, Process, NULL, NULL);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
while ((PointerPte < LastPte) && (PointerPte->u.Hard.Valid)) {
|
|
TempVa = MiGetVirtualAddressMappedByPte(PointerPte);
|
|
MiDeletePte(PointerPte, TempVa, TRUE, Process, NULL, &PteFlushList);
|
|
PointerPte += 1;
|
|
}
|
|
#endif
|
|
|
|
MiFlushPteList(&PteFlushList, FALSE, ZeroPte);
|
|
|
|
// Update the count of available resident pages.
|
|
ASSERT(Process->Vm.MinimumWorkingSetSize >= MM_PROCESS_CREATE_CHARGE);
|
|
MmResidentAvailablePages += Process->Vm.MinimumWorkingSetSize - MM_PROCESS_CREATE_CHARGE;
|
|
MM_BUMP_COUNTER(8, Process->Vm.MinimumWorkingSetSize - MM_PROCESS_CREATE_CHARGE);
|
|
ASSERT(Process->Vm.WorkingSetExpansionLinks.Flink == MM_NO_WS_EXPANSION);
|
|
UNLOCK_PFN(OldIrql);
|
|
|
|
UNLOCK_WS_AND_ADDRESS_SPACE(Process);
|
|
return;
|
|
}
|
|
|
|
|
|
#if DBG
|
|
typedef struct _MMKSTACK
|
|
{
|
|
PMMPFN Pfn;
|
|
PMMPTE Pte;
|
|
} MMKSTACK, *PMMKSTACK;
|
|
MMKSTACK MmKstacks[10];
|
|
#endif //DBG
|
|
|
|
|
|
PVOID MmCreateKernelStack(IN BOOLEAN LargeStack)
|
|
/*++
|
|
Routine Description:
|
|
This routine allocates a kernel stack and a no-access page within
|
|
the non-pagable portion of the system address space.
|
|
Arguments:
|
|
LargeStack - Supplies the value TRUE if a large stack should be
|
|
created. FALSE if a small stack is to be created.
|
|
Return Value:
|
|
Returns a pointer to the base of the kernel stack. Note, that the
|
|
base address points to the guard page, so space must be allocated
|
|
on the stack before accessing the stack.
|
|
|
|
If a kernel stack cannot be created, the value NULL is returned.
|
|
Environment:
|
|
Kernel mode. APCs Disabled.
|
|
*/
|
|
{
|
|
PMMPTE PointerPte;
|
|
MMPTE TempPte;
|
|
PFN_NUMBER NumberOfPages;
|
|
ULONG NumberOfPtes;
|
|
ULONG ChargedPtes;
|
|
ULONG RequestedPtes;
|
|
#if defined(_IA64_)
|
|
ULONG NumberOfBStorePtes;
|
|
#endif
|
|
PFN_NUMBER PageFrameIndex;
|
|
ULONG i;
|
|
PVOID StackVa;
|
|
KIRQL OldIrql;
|
|
|
|
// Acquire the PFN mutex to synchronize access to the dead stack
|
|
// list and to the pfn database.
|
|
LOCK_PFN(OldIrql);
|
|
|
|
// Check to see if any "unused" stacks are available.
|
|
if ((!LargeStack) && (MmNumberDeadKernelStacks != 0)) {
|
|
#if DBG
|
|
{
|
|
ULONG i = MmNumberDeadKernelStacks;
|
|
PMMPFN PfnList = MmFirstDeadKernelStack;
|
|
|
|
while (i > 0) {
|
|
i--;
|
|
if ((PfnList != MmKstacks[i].Pfn) ||
|
|
(PfnList->PteAddress != MmKstacks[i].Pte)) {
|
|
DbgPrint("MMPROCSUP: kstacks %p %ld. %p\n", PfnList, i, MmKstacks[i].Pfn);
|
|
DbgBreakPoint();
|
|
}
|
|
PfnList = PfnList->u1.NextStackPfn;
|
|
}
|
|
}
|
|
#if defined(_IA64_)
|
|
NumberOfPages = BYTES_TO_PAGES(KERNEL_STACK_SIZE + KERNEL_BSTORE_SIZE);
|
|
#else
|
|
NumberOfPages = BYTES_TO_PAGES(KERNEL_STACK_SIZE);
|
|
#endif
|
|
#endif //DBG
|
|
|
|
MmNumberDeadKernelStacks -= 1;
|
|
PointerPte = MmFirstDeadKernelStack->PteAddress;
|
|
MmFirstDeadKernelStack = MmFirstDeadKernelStack->u1.NextStackPfn;
|
|
} else {
|
|
UNLOCK_PFN(OldIrql);
|
|
#if defined(_IA64_)
|
|
if (LargeStack) {
|
|
NumberOfPtes = BYTES_TO_PAGES(KERNEL_LARGE_STACK_SIZE);
|
|
NumberOfBStorePtes = BYTES_TO_PAGES(KERNEL_LARGE_BSTORE_SIZE);
|
|
NumberOfPages = BYTES_TO_PAGES(KERNEL_LARGE_STACK_COMMIT + KERNEL_LARGE_BSTORE_COMMIT);
|
|
} else {
|
|
NumberOfPtes = BYTES_TO_PAGES(KERNEL_STACK_SIZE);
|
|
NumberOfBStorePtes = BYTES_TO_PAGES(KERNEL_BSTORE_SIZE);
|
|
NumberOfPages = NumberOfPtes + NumberOfBStorePtes;
|
|
}
|
|
ChargedPtes = NumberOfPtes + NumberOfBStorePtes;
|
|
RequestedPtes = ChargedPtes + 2 + (MM_STACK_ALIGNMENT ? 1 : 0);
|
|
#else
|
|
if (LargeStack) {
|
|
NumberOfPtes = BYTES_TO_PAGES(KERNEL_LARGE_STACK_SIZE);
|
|
NumberOfPages = BYTES_TO_PAGES(KERNEL_LARGE_STACK_COMMIT);
|
|
} else {
|
|
NumberOfPtes = BYTES_TO_PAGES(KERNEL_STACK_SIZE);
|
|
NumberOfPages = NumberOfPtes;
|
|
}
|
|
ChargedPtes = NumberOfPtes;
|
|
RequestedPtes = ChargedPtes + 1 + (MM_STACK_ALIGNMENT ? 1 : 0);
|
|
#endif // _IA64_
|
|
|
|
// Make sure there are at least 8 of the appropriate system PTE pool.
|
|
if (MiGetSystemPteListCount(RequestedPtes) < 8) {
|
|
return NULL;
|
|
}
|
|
|
|
// Charge commitment for the page file space for the kernel stack.
|
|
if (MiChargeCommitment(ChargedPtes, NULL) == FALSE) {
|
|
// Commitment exceeded, return NULL, indicating no kernel
|
|
// stacks are available.
|
|
return NULL;
|
|
}
|
|
MM_TRACK_COMMIT(MM_DBG_COMMIT_KERNEL_STACK_CREATE, ChargedPtes);
|
|
|
|
LOCK_PFN(OldIrql);
|
|
|
|
// Check to make sure the physical pages are available.
|
|
if (MmResidentAvailablePages <= (SPFN_NUMBER)NumberOfPages) {
|
|
UNLOCK_PFN(OldIrql);
|
|
MiReturnCommitment(ChargedPtes);
|
|
MM_TRACK_COMMIT(MM_DBG_COMMIT_RETURN_KERNEL_STACK_FAILURE1, ChargedPtes);
|
|
return NULL;
|
|
}
|
|
|
|
MmResidentAvailablePages -= NumberOfPages;
|
|
MM_BUMP_COUNTER(9, NumberOfPages);
|
|
|
|
UNLOCK_PFN(OldIrql);
|
|
|
|
// Obtain enough pages to contain the stack plus a guard page from
|
|
// the system PTE pool. The system PTE pool contains non-paged PTEs
|
|
// which are currently empty.
|
|
MmKernelStackPages += RequestedPtes;
|
|
PointerPte = MiReserveSystemPtes(RequestedPtes,
|
|
SystemPteSpace,
|
|
MM_STACK_ALIGNMENT,
|
|
MM_STACK_OFFSET,
|
|
FALSE);
|
|
if (PointerPte == NULL) {
|
|
LOCK_PFN(OldIrql);
|
|
MmResidentAvailablePages += NumberOfPages;
|
|
MM_BUMP_COUNTER(13, NumberOfPages);
|
|
UNLOCK_PFN(OldIrql);
|
|
|
|
MiReturnCommitment(ChargedPtes);
|
|
MM_TRACK_COMMIT(MM_DBG_COMMIT_RETURN_KERNEL_STACK_FAILURE2, ChargedPtes);
|
|
return NULL;
|
|
}
|
|
|
|
#if defined(_IA64_)
|
|
|
|
// StackVa is calculated here
|
|
StackVa = (PVOID)MiGetVirtualAddressMappedByPte(PointerPte + NumberOfPtes + 1);
|
|
|
|
|
|
// The PTEs are divided between kernel stack and RSE space.
|
|
|
|
// The kernel stack grows downward and the RSE grows upward.
|
|
|
|
// For large stacks, one chunk is allocated in the middle of the PTE
|
|
// range and split here.
|
|
|
|
// need better algorithm for RSE
|
|
if (LargeStack) {
|
|
PointerPte += BYTES_TO_PAGES(KERNEL_LARGE_STACK_SIZE - KERNEL_LARGE_STACK_COMMIT - 1);
|
|
}
|
|
|
|
#else
|
|
PointerPte += (NumberOfPtes - NumberOfPages);
|
|
#endif // _IA64_
|
|
|
|
LOCK_PFN(OldIrql);
|
|
|
|
for (i = 0; i < NumberOfPages; i += 1) {
|
|
PointerPte += 1;
|
|
ASSERT(PointerPte->u.Hard.Valid == 0);
|
|
MiEnsureAvailablePageOrWait(NULL, NULL);
|
|
PageFrameIndex = MiRemoveAnyPage(MI_GET_PAGE_COLOR_FROM_PTE(PointerPte));
|
|
|
|
PointerPte->u.Long = MM_KERNEL_DEMAND_ZERO_PTE;
|
|
|
|
#ifdef PROTECT_KSTACKS
|
|
PointerPte->u.Soft.Protection = MM_KSTACK_OUTSWAPPED;
|
|
#endif
|
|
|
|
MiInitializePfn(PageFrameIndex, PointerPte, 1);
|
|
|
|
MI_MAKE_VALID_PTE(TempPte, PageFrameIndex, MM_READWRITE, PointerPte);
|
|
MI_SET_PTE_DIRTY(TempPte);
|
|
|
|
MI_WRITE_VALID_PTE(PointerPte, TempPte);
|
|
}
|
|
MmProcessCommit += ChargedPtes;
|
|
MmKernelStackResident += NumberOfPages;
|
|
MmLargeStacks += LargeStack;
|
|
MmSmallStacks += !LargeStack;
|
|
|
|
#if defined(_IA64_)
|
|
UNLOCK_PFN(OldIrql);
|
|
return StackVa;
|
|
#endif
|
|
}
|
|
|
|
UNLOCK_PFN(OldIrql);
|
|
|
|
PointerPte += 1;
|
|
StackVa = (PVOID)MiGetVirtualAddressMappedByPte(PointerPte);
|
|
#if !defined(_IA64_)
|
|
#if DBG
|
|
{
|
|
PULONG p;
|
|
ULONG_PTR i;
|
|
|
|
p = (PULONG)((ULONG_PTR)StackVa - ((ULONG_PTR)NumberOfPages * PAGE_SIZE));
|
|
i = ((ULONG_PTR)NumberOfPages * PAGE_SIZE) >> 2;
|
|
while (i--) {
|
|
*p++ = 0x12345678;
|
|
}
|
|
|
|
}
|
|
#endif // DBG
|
|
#endif // _IA64_
|
|
|
|
return StackVa;
|
|
}
|
|
|
|
|
|
VOID MmDeleteKernelStack(IN PVOID PointerKernelStack, IN BOOLEAN LargeStack)
|
|
/*++
|
|
Routine Description:
|
|
This routine deletes a kernel stack and the no-access page within
|
|
the non-pagable portion of the system address space.
|
|
Arguments:
|
|
PointerKernelStack - Supplies a pointer to the base of the kernel stack.
|
|
LargeStack - Supplies the value TRUE if a large stack is being deleted.
|
|
FALSE if a small stack is to be deleted.
|
|
Return Value:
|
|
None.
|
|
Environment:
|
|
Kernel mode. APCs Disabled.
|
|
*/
|
|
{
|
|
PMMPTE PointerPte;
|
|
PMMPFN Pfn1;
|
|
PFN_NUMBER NumberOfPages;
|
|
ULONG NumberOfPtes;
|
|
PFN_NUMBER PageFrameIndex;
|
|
ULONG i;
|
|
KIRQL OldIrql;
|
|
MMPTE PteContents;
|
|
|
|
if (LargeStack) {
|
|
#if defined(_IA64_)
|
|
NumberOfPtes = BYTES_TO_PAGES(KERNEL_LARGE_STACK_SIZE + KERNEL_LARGE_BSTORE_SIZE);
|
|
#else
|
|
NumberOfPtes = BYTES_TO_PAGES(KERNEL_LARGE_STACK_SIZE);
|
|
#endif
|
|
} else {
|
|
#if defined(_IA64_)
|
|
NumberOfPtes = BYTES_TO_PAGES(KERNEL_STACK_SIZE + KERNEL_BSTORE_SIZE);
|
|
#else
|
|
NumberOfPtes = BYTES_TO_PAGES(KERNEL_STACK_SIZE);
|
|
#endif
|
|
}
|
|
|
|
PointerPte = MiGetPteAddress(PointerKernelStack);
|
|
|
|
// PointerPte points to the guard page, point to the previous
|
|
// page before removing physical pages.
|
|
PointerPte -= 1;
|
|
|
|
LOCK_PFN(OldIrql);
|
|
|
|
// Check to see if the stack page should be placed on the dead
|
|
// kernel stack page list. The dead kernel stack list is a
|
|
// singly linked list of kernel stacks from terminated threads.
|
|
// The stacks are saved on a linked list up to a maximum number
|
|
// to avoid the overhead of flushing the entire TB on all processors
|
|
// everytime a thread terminates. The TB on all processors must
|
|
// be flushed as kernel stacks reside in the non paged system part
|
|
// of the address space.
|
|
|
|
if ((!LargeStack) && (MmNumberDeadKernelStacks < MmMaximumDeadKernelStacks)) {
|
|
Pfn1 = MI_PFN_ELEMENT(PointerPte->u.Hard.PageFrameNumber);
|
|
|
|
#if DBG
|
|
{
|
|
ULONG i = MmNumberDeadKernelStacks;
|
|
PMMPFN PfnList = MmFirstDeadKernelStack;
|
|
|
|
while (i > 0) {
|
|
i--;
|
|
if ((PfnList != MmKstacks[i].Pfn) ||
|
|
(PfnList->PteAddress != MmKstacks[i].Pte)) {
|
|
DbgPrint("MMPROCSUP: kstacks %p %ld. %p\n", PfnList, i, MmKstacks[i].Pfn);
|
|
DbgBreakPoint();
|
|
}
|
|
PfnList = PfnList->u1.NextStackPfn;
|
|
}
|
|
MmKstacks[MmNumberDeadKernelStacks].Pte = Pfn1->PteAddress;
|
|
MmKstacks[MmNumberDeadKernelStacks].Pfn = Pfn1;
|
|
}
|
|
#endif //DBG
|
|
|
|
MmNumberDeadKernelStacks += 1;
|
|
Pfn1->u1.NextStackPfn = MmFirstDeadKernelStack;
|
|
MmFirstDeadKernelStack = Pfn1;
|
|
|
|
PERFINFO_DELETE_STACK(PointerPte, NumberOfPtes);
|
|
|
|
UNLOCK_PFN(OldIrql);
|
|
|
|
return;
|
|
}
|
|
|
|
#if defined(_IA64_)
|
|
|
|
// Since PointerKernelStack points to the center of the stack space,
|
|
// the size of kernel backing store needs to be added to get the
|
|
// top of the stack space.
|
|
|
|
PointerPte = MiGetPteAddress(LargeStack ?
|
|
(PCHAR)PointerKernelStack + KERNEL_LARGE_BSTORE_SIZE :
|
|
(PCHAR)PointerKernelStack + KERNEL_BSTORE_SIZE);
|
|
|
|
// PointerPte points to the guard page, point to the previous
|
|
// page before removing physical pages.
|
|
PointerPte -= 1;
|
|
#endif
|
|
|
|
// We have exceeded the limit of dead kernel stacks or this is a large
|
|
// stack, delete this kernel stack.
|
|
NumberOfPages = 0;
|
|
for (i = 0; i < NumberOfPtes; i += 1) {
|
|
PteContents = *PointerPte;
|
|
if (PteContents.u.Hard.Valid == 1) {
|
|
PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE(&PteContents);
|
|
Pfn1 = MI_PFN_ELEMENT(PageFrameIndex);
|
|
MiDecrementShareAndValidCount(Pfn1->PteFrame);
|
|
|
|
// Set the pointer to PTE as empty so the page
|
|
// is deleted when the reference count goes to zero.
|
|
MI_SET_PFN_DELETED(Pfn1);
|
|
MiDecrementShareCountOnly(MI_GET_PAGE_FRAME_FROM_PTE(&PteContents));
|
|
NumberOfPages += 1;
|
|
}
|
|
PointerPte -= 1;
|
|
}
|
|
|
|
#if defined(_IA64_)
|
|
MmKernelStackPages -= NumberOfPtes + 2 + (MM_STACK_ALIGNMENT ? 1 : 0);
|
|
MiReleaseSystemPtes(PointerPte,
|
|
NumberOfPtes + 2 + (MM_STACK_ALIGNMENT ? 1 : 0),
|
|
SystemPteSpace);
|
|
#else
|
|
MmKernelStackPages -= NumberOfPtes + 1 + (MM_STACK_ALIGNMENT ? 1 : 0);
|
|
MiReleaseSystemPtes(PointerPte,
|
|
NumberOfPtes + 1 + (MM_STACK_ALIGNMENT ? 1 : 0),
|
|
SystemPteSpace);
|
|
#endif
|
|
|
|
// Update the count of available resident pages.
|
|
|
|
MmKernelStackResident -= NumberOfPages;
|
|
MmResidentAvailablePages += NumberOfPages;
|
|
MM_BUMP_COUNTER(10, NumberOfPages);
|
|
MmProcessCommit -= NumberOfPtes;
|
|
|
|
MmLargeStacks -= LargeStack;
|
|
MmSmallStacks -= !LargeStack;
|
|
UNLOCK_PFN(OldIrql);
|
|
|
|
// Return commitment.
|
|
MiReturnCommitment(NumberOfPtes);
|
|
MM_TRACK_COMMIT(MM_DBG_COMMIT_RETURN_KERNEL_STACK_DELETE, NumberOfPtes);
|
|
return;
|
|
}
|
|
|
|
|
|
NTSTATUS MmGrowKernelStack(IN PVOID CurrentStack)
|
|
/*++
|
|
Routine Description:
|
|
This function attempts to grows the current thread's kernel stack
|
|
such that there is always KERNEL_LARGE_STACK_COMMIT bytes below the current stack pointer.
|
|
Arguments:
|
|
CurrentStack - Supplies a pointer to the current stack pointer.
|
|
Return Value:
|
|
STATUS_SUCCESS is returned if the stack was grown.
|
|
STATUS_STACK_OVERFLOW is returned if there was not enough space reserved for the commitment.
|
|
STATUS_NO_MEMORY is returned if there was not enough physical memory in the system.
|
|
*/
|
|
{
|
|
PMMPTE NewLimit;
|
|
PMMPTE StackLimit;
|
|
PMMPTE EndStack;
|
|
PETHREAD Thread;
|
|
PFN_NUMBER NumberOfPages;
|
|
KIRQL OldIrql;
|
|
PFN_NUMBER PageFrameIndex;
|
|
MMPTE TempPte;
|
|
|
|
Thread = PsGetCurrentThread();
|
|
ASSERT(((PCHAR)Thread->Tcb.StackBase - (PCHAR)Thread->Tcb.StackLimit) <= (KERNEL_LARGE_STACK_SIZE + PAGE_SIZE));
|
|
NewLimit = MiGetPteAddress((PVOID)((PUCHAR)CurrentStack - KERNEL_LARGE_STACK_COMMIT));
|
|
|
|
StackLimit = MiGetPteAddress(Thread->Tcb.StackLimit);
|
|
|
|
// If the new stack limit exceeds the reserved region for the kernel
|
|
// stack, then return an error.
|
|
EndStack = MiGetPteAddress((PVOID)((PUCHAR)Thread->Tcb.StackBase - KERNEL_LARGE_STACK_SIZE));
|
|
if (NewLimit < EndStack) {
|
|
// Don't go into guard page.
|
|
return STATUS_STACK_OVERFLOW;
|
|
}
|
|
|
|
ASSERT(StackLimit->u.Hard.Valid == 1);
|
|
|
|
// Lock the PFN database and attempt to expand the kernel stack.
|
|
StackLimit -= 1;
|
|
|
|
NumberOfPages = (PFN_NUMBER)(StackLimit - NewLimit + 1);
|
|
|
|
LOCK_PFN(OldIrql);
|
|
|
|
if (MmResidentAvailablePages <= (SPFN_NUMBER)NumberOfPages) {
|
|
UNLOCK_PFN(OldIrql);
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
// Note MmResidentAvailablePages must be charged before calling
|
|
// MiEnsureAvailablePageOrWait as it may let go of the PFN lock.
|
|
|
|
MmResidentAvailablePages -= NumberOfPages;
|
|
MM_BUMP_COUNTER(11, NumberOfPages);
|
|
|
|
while (StackLimit >= NewLimit) {
|
|
ASSERT(StackLimit->u.Hard.Valid == 0);
|
|
|
|
MiEnsureAvailablePageOrWait(NULL, NULL);
|
|
PageFrameIndex = MiRemoveAnyPage(MI_GET_PAGE_COLOR_FROM_PTE(StackLimit));
|
|
StackLimit->u.Long = MM_KERNEL_DEMAND_ZERO_PTE;
|
|
|
|
#ifdef PROTECT_KSTACKS
|
|
StackLimit->u.Soft.Protection = MM_KSTACK_OUTSWAPPED;
|
|
#endif
|
|
|
|
MiInitializePfn(PageFrameIndex, StackLimit, 1);
|
|
|
|
MI_MAKE_VALID_PTE(TempPte, PageFrameIndex, MM_READWRITE, StackLimit);
|
|
|
|
MI_SET_PTE_DIRTY(TempPte);
|
|
*StackLimit = TempPte;
|
|
StackLimit -= 1;
|
|
}
|
|
|
|
MmKernelStackResident += NumberOfPages;
|
|
UNLOCK_PFN(OldIrql);
|
|
|
|
#if DBG
|
|
ASSERT(NewLimit->u.Hard.Valid == 1);
|
|
if (NewLimit != EndStack) {
|
|
ASSERT((NewLimit - 1)->u.Hard.Valid == 0);
|
|
}
|
|
#endif
|
|
|
|
Thread->Tcb.StackLimit = MiGetVirtualAddressMappedByPte(NewLimit);
|
|
|
|
PERFINFO_GROW_STACK(Thread);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
#if defined(_IA64_)
|
|
|
|
NTSTATUS MmGrowKernelBackingStore(IN PVOID CurrentStack)
|
|
/*++
|
|
Routine Description:
|
|
This function attempts to grows the current thread's kernel stack
|
|
such that there is always KERNEL_LARGE_STACK_COMMIT bytes below
|
|
the current stack pointer.
|
|
Arguments:
|
|
CurrentStack - Supplies a pointer to the current stack pointer.
|
|
Return Value:
|
|
STATUS_SUCCESS is returned if the stack was grown,
|
|
STATUS_STACK_OVERFLOW is returned if there was not enough space reserved
|
|
for the commitment.
|
|
*/
|
|
{
|
|
PMMPTE NewLimit;
|
|
PMMPTE StackLimit;
|
|
PMMPTE EndStack;
|
|
PETHREAD Thread;
|
|
PFN_NUMBER NumberOfPages;
|
|
KIRQL OldIrql;
|
|
PFN_NUMBER PageFrameIndex;
|
|
MMPTE TempPte;
|
|
|
|
Thread = PsGetCurrentThread();
|
|
ASSERT(((PCHAR)Thread->Tcb.BStoreLimit - (PCHAR)Thread->Tcb.StackBase) <=
|
|
(KERNEL_LARGE_BSTORE_SIZE + PAGE_SIZE));
|
|
NewLimit = MiGetPteAddress((PVOID)((PUCHAR)CurrentStack +
|
|
KERNEL_LARGE_BSTORE_COMMIT - 1));
|
|
|
|
StackLimit = MiGetPteAddress((PCHAR)Thread->Tcb.BStoreLimit - (PCHAR)1);
|
|
|
|
// If the new stack limit is exceeds the reserved region for the kernel
|
|
// stack, then return an error.
|
|
EndStack = MiGetPteAddress((PVOID)((PUCHAR)Thread->Tcb.StackBase + KERNEL_LARGE_BSTORE_SIZE - 1));
|
|
if (NewLimit > EndStack) {
|
|
// Don't go into guard page.
|
|
return STATUS_STACK_OVERFLOW;
|
|
}
|
|
|
|
ASSERT(StackLimit->u.Hard.Valid == 1);
|
|
|
|
// Lock the PFN database and attempt to expand the kernel stack.
|
|
|
|
StackLimit += 1;
|
|
|
|
NumberOfPages = (PFN_NUMBER)(NewLimit - StackLimit + 1);
|
|
|
|
LOCK_PFN(OldIrql);
|
|
|
|
if (MmResidentAvailablePages <= (SPFN_NUMBER)NumberOfPages) {
|
|
UNLOCK_PFN(OldIrql);
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
// Note we must charge MmResidentAvailablePages before calling
|
|
// MiEnsureAvailablePageOrWait as it may let go of the PFN lock.
|
|
|
|
MmResidentAvailablePages -= NumberOfPages;
|
|
|
|
while (StackLimit <= NewLimit) {
|
|
ASSERT(StackLimit->u.Hard.Valid == 0);
|
|
|
|
MiEnsureAvailablePageOrWait(NULL, NULL);
|
|
PageFrameIndex = MiRemoveAnyPage(MI_GET_PAGE_COLOR_FROM_PTE(StackLimit));
|
|
StackLimit->u.Long = MM_KERNEL_DEMAND_ZERO_PTE;
|
|
|
|
#ifdef PROTECT_KSTACKS
|
|
StackLimit->u.Soft.Protection = MM_KSTACK_OUTSWAPPED;
|
|
#endif
|
|
|
|
MiInitializePfn(PageFrameIndex, StackLimit, 1);
|
|
|
|
MI_MAKE_VALID_PTE(TempPte, PageFrameIndex, MM_READWRITE, StackLimit);
|
|
|
|
MI_SET_PTE_DIRTY(TempPte);
|
|
*StackLimit = TempPte;
|
|
StackLimit += 1;
|
|
}
|
|
|
|
MmKernelStackResident += NumberOfPages;
|
|
UNLOCK_PFN(OldIrql);
|
|
|
|
#if DBG
|
|
ASSERT(NewLimit->u.Hard.Valid == 1);
|
|
if (NewLimit != EndStack) {
|
|
ASSERT((NewLimit + 1)->u.Hard.Valid == 0);
|
|
}
|
|
#endif
|
|
|
|
Thread->Tcb.BStoreLimit = MiGetVirtualAddressMappedByPte(NewLimit + 1);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
#endif // defined(_IA64_)
|
|
|
|
|
|
VOID MmOutPageKernelStack(IN PKTHREAD Thread)
|
|
/*++
|
|
Routine Description:
|
|
This routine makes the specified kernel stack non-resident and
|
|
puts the pages on the transition list. Note, that if the
|
|
CurrentStackPointer is within the first page of the stack, the
|
|
contents of the second page of the stack is not useful and the
|
|
page is freed.
|
|
Arguments:
|
|
Thread - Supplies a pointer to the thread whose stack should be removed.
|
|
Return Value:
|
|
None.
|
|
Environment:
|
|
Kernel mode.
|
|
*/
|
|
|
|
#if defined(_IA64_)
|
|
#define MAX_STACK_PAGES ((KERNEL_LARGE_STACK_SIZE + KERNEL_LARGE_BSTORE_SIZE) / PAGE_SIZE)
|
|
#else
|
|
#define MAX_STACK_PAGES (KERNEL_LARGE_STACK_SIZE / PAGE_SIZE)
|
|
#endif
|
|
{
|
|
PMMPTE PointerPte;
|
|
PMMPTE LastPte;
|
|
PMMPTE EndOfStackPte;
|
|
PMMPFN Pfn1;
|
|
PFN_NUMBER PageFrameIndex;
|
|
KIRQL OldIrql;
|
|
MMPTE TempPte;
|
|
PVOID BaseOfKernelStack;
|
|
PMMPTE FlushPte[MAX_STACK_PAGES];
|
|
PVOID FlushVa[MAX_STACK_PAGES];
|
|
MMPTE FlushPteSave[MAX_STACK_PAGES];
|
|
ULONG StackSize;
|
|
ULONG Count;
|
|
PMMPTE LimitPte;
|
|
PMMPTE LowestLivePte;
|
|
|
|
ASSERT(((PCHAR)Thread->StackBase - (PCHAR)Thread->StackLimit) <= (KERNEL_LARGE_STACK_SIZE + PAGE_SIZE));
|
|
|
|
if (NtGlobalFlag & FLG_DISABLE_PAGE_KERNEL_STACKS) {
|
|
return;
|
|
}
|
|
|
|
// The first page of the stack is the page before the base of the stack.
|
|
|
|
BaseOfKernelStack = ((PCHAR)Thread->StackBase - PAGE_SIZE);
|
|
PointerPte = MiGetPteAddress(BaseOfKernelStack);
|
|
LastPte = MiGetPteAddress((PULONG)Thread->KernelStack - 1);
|
|
if (Thread->LargeStack) {
|
|
StackSize = KERNEL_LARGE_STACK_SIZE >> PAGE_SHIFT;
|
|
|
|
// The stack pagein won't necessarily bring back all the pages.
|
|
// Make sure that we account now for the ones that will disappear.
|
|
|
|
LimitPte = MiGetPteAddress(Thread->StackLimit);
|
|
|
|
LowestLivePte = MiGetPteAddress((PVOID)((PUCHAR)Thread->InitialStack - KERNEL_LARGE_STACK_COMMIT));
|
|
|
|
if (LowestLivePte < LimitPte) {
|
|
LowestLivePte = LimitPte;
|
|
}
|
|
} else {
|
|
StackSize = KERNEL_STACK_SIZE >> PAGE_SHIFT;
|
|
LowestLivePte = MiGetPteAddress(Thread->StackLimit);
|
|
}
|
|
EndOfStackPte = PointerPte - StackSize;
|
|
|
|
ASSERT(LowestLivePte <= LastPte);
|
|
|
|
// Put a signature at the current stack location - 4.
|
|
|
|
*((PULONG_PTR)Thread->KernelStack - 1) = (ULONG_PTR)Thread;
|
|
|
|
Count = 0;
|
|
|
|
LOCK_PFN(OldIrql);
|
|
|
|
do {
|
|
ASSERT(PointerPte->u.Hard.Valid == 1);
|
|
PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE(PointerPte);
|
|
TempPte = *PointerPte;
|
|
MI_MAKE_VALID_PTE_TRANSITION(TempPte, 0);
|
|
|
|
#ifdef PROTECT_KSTACKS
|
|
TempPte.u.Soft.Protection = MM_KSTACK_OUTSWAPPED;
|
|
{
|
|
PMMPFN x;
|
|
x = MI_PFN_ELEMENT(PageFrameIndex);
|
|
x->OriginalPte.u.Soft.Protection = MM_KSTACK_OUTSWAPPED;
|
|
}
|
|
#endif
|
|
|
|
FlushPteSave[Count] = TempPte;
|
|
FlushPte[Count] = PointerPte;
|
|
FlushVa[Count] = BaseOfKernelStack;
|
|
|
|
MiDecrementShareCount(PageFrameIndex);
|
|
PointerPte -= 1;
|
|
Count += 1;
|
|
BaseOfKernelStack = ((PCHAR)BaseOfKernelStack - PAGE_SIZE);
|
|
} while (PointerPte >= LastPte);
|
|
|
|
while (PointerPte != EndOfStackPte) {
|
|
if (PointerPte->u.Hard.Valid == 0) {
|
|
break;
|
|
}
|
|
|
|
PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE(PointerPte);
|
|
Pfn1 = MI_PFN_ELEMENT(PageFrameIndex);
|
|
MiDecrementShareAndValidCount(Pfn1->PteFrame);
|
|
MI_SET_PFN_DELETED(Pfn1);
|
|
MiDecrementShareCountOnly(MI_GET_PAGE_FRAME_FROM_PTE(PointerPte));
|
|
|
|
FlushPteSave[Count] = KernelDemandZeroPte;
|
|
|
|
#ifdef PROTECT_KSTACKS
|
|
FlushPteSave[Count].u.Soft.Protection = MM_KSTACK_OUTSWAPPED;
|
|
#endif
|
|
|
|
FlushPte[Count] = PointerPte;
|
|
|
|
FlushVa[Count] = BaseOfKernelStack;
|
|
Count += 1;
|
|
|
|
// Account for any pages that won't ever come back in.
|
|
if (PointerPte < LowestLivePte) {
|
|
ASSERT(Thread->LargeStack);
|
|
MmResidentAvailablePages += 1;
|
|
MM_BUMP_COUNTER(12, 1);
|
|
}
|
|
|
|
PointerPte -= 1;
|
|
BaseOfKernelStack = ((PCHAR)BaseOfKernelStack - PAGE_SIZE);
|
|
}
|
|
|
|
#if defined(_IA64_)
|
|
// do for RSE stack space too.
|
|
BaseOfKernelStack = Thread->StackBase;
|
|
PointerPte = MiGetPteAddress(BaseOfKernelStack);
|
|
LastPte = MiGetPteAddress((PULONG)Thread->KernelBStore);
|
|
|
|
if (Thread->LargeStack) {
|
|
StackSize = KERNEL_LARGE_BSTORE_SIZE >> PAGE_SHIFT;
|
|
} else {
|
|
StackSize = KERNEL_BSTORE_SIZE >> PAGE_SHIFT;
|
|
}
|
|
EndOfStackPte = PointerPte + StackSize;
|
|
|
|
do {
|
|
ASSERT(PointerPte->u.Hard.Valid == 1);
|
|
PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE(PointerPte);
|
|
TempPte = *PointerPte;
|
|
MI_MAKE_VALID_PTE_TRANSITION(TempPte, 0);
|
|
|
|
#ifdef PROTECT_KSTACKS
|
|
TempPte.u.Soft.Protection = MM_KSTACK_OUTSWAPPED;
|
|
{
|
|
PMMPFN x;
|
|
x = MI_PFN_ELEMENT(PageFrameIndex);
|
|
x->OriginalPte.u.Soft.Protection = MM_KSTACK_OUTSWAPPED;
|
|
}
|
|
#endif
|
|
|
|
FlushPteSave[Count] = TempPte;
|
|
FlushPte[Count] = PointerPte;
|
|
FlushVa[Count] = BaseOfKernelStack;
|
|
|
|
MiDecrementShareCount(PageFrameIndex);
|
|
PointerPte += 1;
|
|
Count += 1;
|
|
BaseOfKernelStack = ((PCHAR)BaseOfKernelStack + PAGE_SIZE);
|
|
} while (PointerPte <= LastPte);
|
|
|
|
while (PointerPte != EndOfStackPte) {
|
|
if (PointerPte->u.Hard.Valid == 0) {
|
|
break;
|
|
}
|
|
|
|
PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE(PointerPte);
|
|
Pfn1 = MI_PFN_ELEMENT(PageFrameIndex);
|
|
MiDecrementShareAndValidCount(Pfn1->PteFrame);
|
|
MI_SET_PFN_DELETED(Pfn1);
|
|
MiDecrementShareCountOnly(MI_GET_PAGE_FRAME_FROM_PTE(PointerPte));
|
|
|
|
FlushPteSave[Count] = KernelDemandZeroPte;
|
|
|
|
#ifdef PROTECT_KSTACKS
|
|
FlushPteSave[Count].u.Soft.Protection = MM_KSTACK_OUTSWAPPED;
|
|
#endif
|
|
|
|
FlushPte[Count] = PointerPte;
|
|
FlushVa[Count] = BaseOfKernelStack;
|
|
Count += 1;
|
|
|
|
PointerPte += 1;
|
|
BaseOfKernelStack = ((PCHAR)BaseOfKernelStack + PAGE_SIZE);
|
|
}
|
|
#endif // _IA64_
|
|
|
|
ASSERT(Count <= MAX_STACK_PAGES);
|
|
|
|
if (Count < MM_MAXIMUM_FLUSH_COUNT) {
|
|
KeFlushMultipleTb(Count, &FlushVa[0], TRUE, TRUE, &((PHARDWARE_PTE)FlushPte[0]), ZeroPte.u.Flush);
|
|
} else {
|
|
KeFlushEntireTb(TRUE, TRUE);
|
|
}
|
|
|
|
// Increase the available pages by the number of pages that where
|
|
// deleted and turned into demand zero.
|
|
MmKernelStackResident -= Count;
|
|
|
|
// Put the right contents back into the PTEs
|
|
do {
|
|
Count -= 1;
|
|
*FlushPte[Count] = FlushPteSave[Count];
|
|
} while (Count != 0);
|
|
|
|
UNLOCK_PFN(OldIrql);
|
|
return;
|
|
}
|
|
|
|
|
|
VOID MmInPageKernelStack(IN PKTHREAD Thread)
|
|
/*++
|
|
Routine Description:
|
|
This routine makes the specified kernel stack resident.
|
|
Arguments:
|
|
Supplies a pointer to the base of the kernel stack.
|
|
Return Value:
|
|
Thread - Supplies a pointer to the thread whose stack should be made resident.
|
|
Environment:
|
|
Kernel mode.
|
|
*/
|
|
{
|
|
PVOID BaseOfKernelStack;
|
|
PMMPTE PointerPte;
|
|
PMMPTE EndOfStackPte;
|
|
PMMPTE SignaturePte;
|
|
ULONG DiskRead;
|
|
PFN_NUMBER ContainingPage;
|
|
KIRQL OldIrql;
|
|
|
|
ASSERT(((PCHAR)Thread->StackBase - (PCHAR)Thread->StackLimit) <= (KERNEL_LARGE_STACK_SIZE + PAGE_SIZE));
|
|
|
|
if (NtGlobalFlag & FLG_DISABLE_PAGE_KERNEL_STACKS) {
|
|
return;
|
|
}
|
|
|
|
// The first page of the stack is the page before the base
|
|
// of the stack.
|
|
if (Thread->LargeStack) {
|
|
PointerPte = MiGetPteAddress((PVOID)((PUCHAR)Thread->StackLimit));
|
|
|
|
EndOfStackPte = MiGetPteAddress((PVOID)((PUCHAR)Thread->InitialStack - KERNEL_LARGE_STACK_COMMIT));
|
|
|
|
// Trim back the stack. Make sure that the stack does not grow, i.e.
|
|
// StackLimit remains the limit.
|
|
|
|
if (EndOfStackPte < PointerPte) {
|
|
EndOfStackPte = PointerPte;
|
|
}
|
|
Thread->StackLimit = MiGetVirtualAddressMappedByPte(EndOfStackPte);
|
|
} else {
|
|
EndOfStackPte = MiGetPteAddress(Thread->StackLimit);
|
|
}
|
|
|
|
#if defined(_IA64_)
|
|
if (Thread->LargeStack) {
|
|
PVOID TempAddress = (PVOID)((PUCHAR)Thread->BStoreLimit);
|
|
|
|
BaseOfKernelStack = (PVOID)(((ULONG_PTR)Thread->InitialBStore +
|
|
KERNEL_LARGE_BSTORE_COMMIT) &
|
|
~(ULONG_PTR)(PAGE_SIZE - 1));
|
|
|
|
// Make sure the guard page is not set to valid.
|
|
if (BaseOfKernelStack > TempAddress) {
|
|
BaseOfKernelStack = TempAddress;
|
|
}
|
|
Thread->BStoreLimit = BaseOfKernelStack;
|
|
}
|
|
BaseOfKernelStack = ((PCHAR)Thread->BStoreLimit - PAGE_SIZE);
|
|
#else
|
|
BaseOfKernelStack = ((PCHAR)Thread->StackBase - PAGE_SIZE);
|
|
#endif // _IA64_
|
|
|
|
PointerPte = MiGetPteAddress(BaseOfKernelStack);
|
|
|
|
DiskRead = 0;
|
|
SignaturePte = MiGetPteAddress((PULONG_PTR)Thread->KernelStack - 1);
|
|
ASSERT(SignaturePte->u.Hard.Valid == 0);
|
|
if ((SignaturePte->u.Long != MM_KERNEL_DEMAND_ZERO_PTE) && (SignaturePte->u.Soft.Transition == 0)) {
|
|
DiskRead = 1;
|
|
}
|
|
|
|
LOCK_PFN(OldIrql);
|
|
|
|
while (PointerPte >= EndOfStackPte) {
|
|
#ifdef PROTECT_KSTACKS
|
|
if (!((PointerPte->u.Long == KernelDemandZeroPte.u.Long) ||
|
|
(PointerPte->u.Soft.Protection == MM_KSTACK_OUTSWAPPED))) {
|
|
KeBugCheckEx(MEMORY_MANAGEMENT, 0x3451, (ULONG_PTR)PointerPte, (ULONG_PTR)Thread, 0);
|
|
}
|
|
ASSERT(PointerPte->u.Hard.Valid == 0);
|
|
if (PointerPte->u.Soft.Protection == MM_KSTACK_OUTSWAPPED) {
|
|
PointerPte->u.Soft.Protection = PAGE_READWRITE;
|
|
}
|
|
#endif
|
|
|
|
ContainingPage = MI_GET_PAGE_FRAME_FROM_PTE(MiGetPteAddress(PointerPte));
|
|
MiMakeOutswappedPageResident(PointerPte, PointerPte, 1, ContainingPage);
|
|
|
|
PointerPte -= 1;
|
|
MmKernelStackResident += 1;
|
|
}
|
|
|
|
// Check the signature at the current stack location - 4.
|
|
if (*((PULONG_PTR)Thread->KernelStack - 1) != (ULONG_PTR)Thread) {
|
|
KeBugCheckEx(KERNEL_STACK_INPAGE_ERROR,
|
|
DiskRead,
|
|
*((PULONG_PTR)Thread->KernelStack - 1),
|
|
0,
|
|
(ULONG_PTR)Thread->KernelStack);
|
|
}
|
|
|
|
UNLOCK_PFN(OldIrql);
|
|
return;
|
|
}
|
|
|
|
|
|
VOID MmOutSwapProcess(IN PKPROCESS Process)
|
|
/*++
|
|
Routine Description:
|
|
This routine out swaps the specified process.
|
|
Arguments:
|
|
Process - Supplies a pointer to the process that is swapped out of memory.
|
|
Return Value:
|
|
None.
|
|
*/
|
|
{
|
|
KIRQL OldIrql;
|
|
KIRQL OldIrql2;
|
|
PEPROCESS OutProcess;
|
|
PMMPTE PointerPte;
|
|
PMMPFN Pfn1;
|
|
PFN_NUMBER HyperSpacePageTable;
|
|
PMMPTE HyperSpacePageTableMap;
|
|
PFN_NUMBER PpePage;
|
|
PFN_NUMBER PdePage;
|
|
PMMPTE PageDirectoryMap;
|
|
PFN_NUMBER ProcessPage;
|
|
MMPTE TempPte;
|
|
#if defined (_X86PAE_)
|
|
ULONG i;
|
|
MMPTE TempPte2;
|
|
PFN_NUMBER PdePage2;
|
|
PFN_NUMBER HyperPage2;
|
|
PPAE_ENTRY PaeVa;
|
|
#endif
|
|
|
|
OutProcess = CONTAINING_RECORD(Process, EPROCESS, Pcb);
|
|
OutProcess->ProcessOutswapEnabled = TRUE;
|
|
|
|
#if DBG
|
|
if ((MmDebug & MM_DBG_SWAP_PROCESS) != 0) {
|
|
return;
|
|
}
|
|
#endif //DBG
|
|
|
|
if (MiHydra == TRUE && OutProcess->Vm.u.Flags.ProcessInSession == 1) {
|
|
MiSessionOutSwapProcess(OutProcess);
|
|
}
|
|
|
|
if ((OutProcess->Vm.WorkingSetSize == MM_PROCESS_COMMIT_CHARGE) &&
|
|
(OutProcess->Vm.AllowWorkingSetAdjustment)) {
|
|
LOCK_EXPANSION(OldIrql);
|
|
|
|
ASSERT(OutProcess->ProcessOutswapped == FALSE);
|
|
if (OutProcess->Vm.u.Flags.BeingTrimmed == TRUE) {
|
|
// An outswap is not allowed at this point because the process
|
|
// has been attached to and is being trimmed.
|
|
UNLOCK_EXPANSION(OldIrql);
|
|
return;
|
|
}
|
|
|
|
// Swap the process working set info and page parent/directory/table
|
|
// pages from memory.
|
|
|
|
OutProcess->ProcessOutswapped = TRUE;
|
|
UNLOCK_EXPANSION(OldIrql);
|
|
LOCK_PFN(OldIrql);
|
|
|
|
// Remove the working set list page from the process.
|
|
|
|
#if !defined (_X86PAE_)
|
|
HyperSpacePageTable =
|
|
MI_GET_PAGE_FRAME_FROM_PTE((PMMPTE)(&(OutProcess->Pcb.DirectoryTableBase[1])));
|
|
#else
|
|
HyperSpacePageTable = (PFN_NUMBER)OutProcess->Pcb.DirectoryTableBase[1];
|
|
#endif
|
|
|
|
HyperSpacePageTableMap = MiMapPageInHyperSpace(HyperSpacePageTable, &OldIrql2);
|
|
TempPte = HyperSpacePageTableMap[MiGetPteOffset(MmWorkingSetList)];
|
|
MI_MAKE_VALID_PTE_TRANSITION(TempPte, MM_READWRITE);
|
|
HyperSpacePageTableMap[MiGetPteOffset(MmWorkingSetList)] = TempPte;
|
|
|
|
#if defined (_X86PAE_)
|
|
TempPte2 = HyperSpacePageTableMap[0];
|
|
HyperPage2 = MI_GET_PAGE_FRAME_FROM_PTE((PMMPTE)&TempPte2);
|
|
MI_MAKE_VALID_PTE_TRANSITION(TempPte2, MM_READWRITE);
|
|
HyperSpacePageTableMap[0] = TempPte2;
|
|
#endif
|
|
|
|
MiUnmapPageInHyperSpace(OldIrql2);
|
|
|
|
#if DBG
|
|
Pfn1 = MI_PFN_ELEMENT(OutProcess->WorkingSetPage);
|
|
ASSERT(Pfn1->u3.e1.Modified == 1);
|
|
#endif
|
|
MiDecrementShareCount(OutProcess->WorkingSetPage);
|
|
|
|
// Remove the hyper space page from the process.
|
|
Pfn1 = MI_PFN_ELEMENT(HyperSpacePageTable);
|
|
PdePage = Pfn1->PteFrame;
|
|
ASSERT(PdePage);
|
|
PageDirectoryMap = MiMapPageInHyperSpace(PdePage, &OldIrql2);
|
|
TempPte = PageDirectoryMap[MiGetPdeOffset(MmWorkingSetList)];
|
|
ASSERT(TempPte.u.Hard.Valid == 1);
|
|
ASSERT(TempPte.u.Hard.PageFrameNumber == HyperSpacePageTable);
|
|
MI_MAKE_VALID_PTE_TRANSITION(TempPte, MM_READWRITE);
|
|
PageDirectoryMap[MiGetPdeOffset(MmWorkingSetList)] = TempPte;
|
|
ASSERT(Pfn1->u3.e1.Modified == 1);
|
|
MiDecrementShareCount(HyperSpacePageTable);
|
|
|
|
#if defined (_X86PAE_)
|
|
// Remove the second hyper space page from the process.
|
|
Pfn1 = MI_PFN_ELEMENT(HyperPage2);
|
|
ASSERT(Pfn1->u3.e1.Modified == 1);
|
|
PdePage = Pfn1->PteFrame;
|
|
ASSERT(PdePage);
|
|
PageDirectoryMap[MiGetPdeOffset(HYPER_SPACE2)] = TempPte2;
|
|
MiDecrementShareCount(HyperPage2);
|
|
|
|
// Remove the additional page directory pages.
|
|
PaeVa = (PPAE_ENTRY)OutProcess->PaeTop;
|
|
for (i = 0; i < PD_PER_SYSTEM - 1; i += 1) {
|
|
TempPte = PageDirectoryMap[i];
|
|
PdePage2 = MI_GET_PAGE_FRAME_FROM_PTE((PMMPTE)&TempPte);
|
|
|
|
MI_MAKE_VALID_PTE_TRANSITION(TempPte, MM_READWRITE);
|
|
|
|
PageDirectoryMap[i] = TempPte;
|
|
Pfn1 = MI_PFN_ELEMENT(PdePage2);
|
|
ASSERT(Pfn1->u3.e1.Modified == 1);
|
|
|
|
MiDecrementShareCount(PdePage2);
|
|
PaeVa->PteEntry[i].u.Long = TempPte.u.Long;
|
|
}
|
|
|
|
#if DBG
|
|
TempPte = PageDirectoryMap[i];
|
|
PdePage2 = MI_GET_PAGE_FRAME_FROM_PTE((PMMPTE)&TempPte);
|
|
Pfn1 = MI_PFN_ELEMENT(PdePage2);
|
|
ASSERT(Pfn1->u3.e1.Modified == 1);
|
|
#endif
|
|
|
|
#endif
|
|
|
|
#if defined (_WIN64)
|
|
MiUnmapPageInHyperSpace(OldIrql2);
|
|
|
|
// Remove the page directory page (64-bit version).
|
|
Pfn1 = MI_PFN_ELEMENT(PdePage);
|
|
PpePage = Pfn1->PteFrame;
|
|
ASSERT(PpePage);
|
|
ASSERT(PpePage == MI_GET_PAGE_FRAME_FROM_PTE((PMMPTE)(&(OutProcess->Pcb.DirectoryTableBase[0]))));
|
|
|
|
PageDirectoryMap = MiMapPageInHyperSpace(PpePage, &OldIrql2);
|
|
TempPte = PageDirectoryMap[MiGetPpeOffset(MmWorkingSetList)];
|
|
ASSERT(TempPte.u.Hard.Valid == 1);
|
|
ASSERT(TempPte.u.Hard.PageFrameNumber == PdePage);
|
|
MI_MAKE_VALID_PTE_TRANSITION(TempPte, MM_READWRITE);
|
|
PageDirectoryMap[MiGetPpeOffset(MmWorkingSetList)] = TempPte;
|
|
ASSERT(Pfn1->u3.e1.Modified == 1);
|
|
MiDecrementShareCount(HyperSpacePageTable);
|
|
|
|
// Remove the top level page directory parent page.
|
|
TempPte = PageDirectoryMap[MiGetPpeOffset(PDE_TBASE)];
|
|
MI_MAKE_VALID_PTE_TRANSITION(TempPte, MM_READWRITE);
|
|
PageDirectoryMap[MiGetPpeOffset(PDE_TBASE)] = TempPte;
|
|
Pfn1 = MI_PFN_ELEMENT(PpePage);
|
|
#else
|
|
// Remove the top level page directory page.
|
|
TempPte = PageDirectoryMap[MiGetPdeOffset(PDE_BASE)];
|
|
MI_MAKE_VALID_PTE_TRANSITION(TempPte, MM_READWRITE);
|
|
PageDirectoryMap[MiGetPdeOffset(PDE_BASE)] = TempPte;
|
|
Pfn1 = MI_PFN_ELEMENT(PdePage);
|
|
#endif
|
|
|
|
MiUnmapPageInHyperSpace(OldIrql2);
|
|
|
|
// Decrement share count so the top level page directory page gets
|
|
// removed. This can cause the PteCount to equal the sharecount as the
|
|
// page directory page no longer contains itself, yet can have
|
|
// itself as a transition page.
|
|
|
|
Pfn1->u2.ShareCount -= 2;
|
|
Pfn1->PteAddress = (PMMPTE)&OutProcess->PageDirectoryPte;
|
|
OutProcess->PageDirectoryPte = TempPte.u.Flush;
|
|
|
|
#if defined (_X86PAE_)
|
|
PaeVa->PteEntry[i].u.Long = TempPte.u.Long;
|
|
#endif
|
|
|
|
if (MI_IS_PHYSICAL_ADDRESS(OutProcess)) {
|
|
ProcessPage = MI_CONVERT_PHYSICAL_TO_PFN(OutProcess);
|
|
} else {
|
|
PointerPte = MiGetPteAddress(OutProcess);
|
|
ProcessPage = MI_GET_PAGE_FRAME_FROM_PTE(PointerPte);
|
|
}
|
|
|
|
Pfn1->PteFrame = ProcessPage;
|
|
Pfn1 = MI_PFN_ELEMENT(ProcessPage);
|
|
|
|
// Increment the share count for the process page.
|
|
|
|
Pfn1->u2.ShareCount += 1;
|
|
UNLOCK_PFN(OldIrql);
|
|
|
|
LOCK_EXPANSION(OldIrql);
|
|
if (OutProcess->Vm.WorkingSetExpansionLinks.Flink > MM_IO_IN_PROGRESS) {
|
|
// The entry must be on the list.
|
|
RemoveEntryList(&OutProcess->Vm.WorkingSetExpansionLinks);
|
|
OutProcess->Vm.WorkingSetExpansionLinks.Flink = MM_WS_SWAPPED_OUT;
|
|
}
|
|
UNLOCK_EXPANSION(OldIrql);
|
|
|
|
OutProcess->WorkingSetPage = 0;
|
|
OutProcess->Vm.WorkingSetSize = 0;
|
|
#if defined(_IA64_)
|
|
// Force assignment of new PID as we have removed
|
|
// the page directory page.
|
|
// Note that a TB flush would not work here as we
|
|
// are in the wrong process context.
|
|
Process->ProcessRegion.SequenceNumber = 0;
|
|
#endif _IA64_
|
|
}
|
|
}
|
|
|
|
|
|
VOID MmInSwapProcess(IN PKPROCESS Process)
|
|
/*++
|
|
Routine Description:
|
|
This routine in swaps the specified process.
|
|
Arguments:
|
|
Process - Supplies a pointer to the process that is to be swapped into memory.
|
|
Return Value:
|
|
None.
|
|
*/
|
|
{
|
|
KIRQL OldIrql;
|
|
KIRQL OldIrql2;
|
|
PEPROCESS OutProcess;
|
|
PFN_NUMBER PdePage;
|
|
PFN_NUMBER PageDirectoryPage;
|
|
PMMPTE PageDirectoryMap;
|
|
PMMPTE PageDirectoryParentMap;
|
|
MMPTE TempPte;
|
|
MMPTE TempPte2;
|
|
PFN_NUMBER HyperSpacePageTable;
|
|
PMMPTE HyperSpacePageTableMap;
|
|
PFN_NUMBER WorkingSetPage;
|
|
PMMPFN Pfn1;
|
|
PMMPTE PointerPte;
|
|
PFN_NUMBER ProcessPage;
|
|
#if defined (_X86PAE_)
|
|
ULONG i;
|
|
PPAE_ENTRY PaeVa;
|
|
PFN_NUMBER PdePage2;
|
|
#endif
|
|
|
|
OutProcess = CONTAINING_RECORD(Process, EPROCESS, Pcb);
|
|
if (OutProcess->ProcessOutswapped == TRUE) {
|
|
// The process is out of memory, rebuild the initialized page structure.
|
|
if (MI_IS_PHYSICAL_ADDRESS(OutProcess)) {
|
|
ProcessPage = MI_CONVERT_PHYSICAL_TO_PFN(OutProcess);
|
|
} else {
|
|
PointerPte = MiGetPteAddress(OutProcess);
|
|
ProcessPage = MI_GET_PAGE_FRAME_FROM_PTE(PointerPte);
|
|
}
|
|
|
|
LOCK_PFN(OldIrql);
|
|
|
|
#if defined (_WIN64)
|
|
PdePage = MiMakeOutswappedPageResident(MiGetPteAddress(PDE_TBASE),
|
|
(PMMPTE)&OutProcess->PageDirectoryPte, 0, ProcessPage);
|
|
#else
|
|
PdePage = MiMakeOutswappedPageResident(MiGetPteAddress(PDE_BASE),
|
|
(PMMPTE)&OutProcess->PageDirectoryPte, 0, ProcessPage);
|
|
#endif
|
|
|
|
// Adjust the counts for the process page.
|
|
Pfn1 = MI_PFN_ELEMENT(ProcessPage);
|
|
Pfn1->u2.ShareCount -= 1;
|
|
|
|
ASSERT((LONG)Pfn1->u2.ShareCount >= 1);
|
|
|
|
// Adjust the counts properly for the page directory page.
|
|
Pfn1 = MI_PFN_ELEMENT(PdePage);
|
|
Pfn1->u2.ShareCount += 1;
|
|
#if !defined (_WIN64)
|
|
Pfn1->u1.Event = (PVOID)OutProcess;
|
|
#endif
|
|
Pfn1->PteFrame = PdePage;
|
|
Pfn1->PteAddress = MiGetPteAddress(PDE_BASE);
|
|
|
|
#if defined (_WIN64)
|
|
// Only the page directory parent page has really been read in above.
|
|
// Get the page directory page now also.
|
|
PageDirectoryParentMap = MiMapPageInHyperSpace(PdePage, &OldIrql2);
|
|
TempPte = PageDirectoryParentMap[MiGetPpeOffset(MmWorkingSetList)];
|
|
MiUnmapPageInHyperSpace(OldIrql2);
|
|
PageDirectoryPage = MiMakeOutswappedPageResident(
|
|
MiGetPpeAddress(MmWorkingSetList),
|
|
&TempPte,
|
|
0,
|
|
PdePage);
|
|
ASSERT(PageDirectoryPage == TempPte.u.Hard.PageFrameNumber);
|
|
ASSERT(Pfn1->u2.ShareCount >= 3);
|
|
PageDirectoryParentMap = MiMapPageInHyperSpace(PdePage, &OldIrql2);
|
|
PageDirectoryParentMap[MiGetPpeOffset(PDE_TBASE)].u.Flush = OutProcess->PageDirectoryPte;
|
|
PageDirectoryParentMap[MiGetPpeOffset(MmWorkingSetList)] = TempPte;
|
|
MiUnmapPageInHyperSpace(OldIrql2);
|
|
PdePage = PageDirectoryPage;
|
|
#endif
|
|
|
|
#if defined (_X86PAE_)
|
|
OutProcess->PaePageDirectoryPage = PdePage;
|
|
|
|
// Locate the additional page directory pages and make them resident.
|
|
PaeVa = (PPAE_ENTRY)OutProcess->PaeTop;
|
|
for (i = 0; i < PD_PER_SYSTEM - 1; i += 1) {
|
|
PageDirectoryMap = MiMapPageInHyperSpace(PdePage, &OldIrql2);
|
|
TempPte = PageDirectoryMap[i];
|
|
MiUnmapPageInHyperSpace(OldIrql2);
|
|
PdePage2 = MiMakeOutswappedPageResident(
|
|
MiGetPteAddress(PDE_BASE + (i << PAGE_SHIFT)),
|
|
&TempPte,
|
|
0,
|
|
PdePage);
|
|
|
|
ASSERT(Pfn1->u2.ShareCount >= 1);
|
|
|
|
PageDirectoryMap = MiMapPageInHyperSpace(PdePage, &OldIrql2);
|
|
PageDirectoryMap[i] = TempPte;
|
|
MiUnmapPageInHyperSpace(OldIrql2);
|
|
PaeVa->PteEntry[i].u.Long = (TempPte.u.Long & ~MM_PAE_PDPTE_MASK);
|
|
}
|
|
|
|
TempPte.u.Flush = OutProcess->PageDirectoryPte;
|
|
TempPte.u.Long &= ~MM_PAE_PDPTE_MASK;
|
|
PaeVa->PteEntry[i].u.Flush = TempPte.u.Flush;
|
|
|
|
// Locate the second page table page for hyperspace & make it resident.
|
|
PageDirectoryMap = MiMapPageInHyperSpace(PdePage, &OldIrql2);
|
|
TempPte = PageDirectoryMap[MiGetPdeOffset(HYPER_SPACE2)];
|
|
MiUnmapPageInHyperSpace(OldIrql2);
|
|
HyperSpacePageTable = MiMakeOutswappedPageResident(
|
|
MiGetPdeAddress(HYPER_SPACE2),
|
|
&TempPte,
|
|
0,
|
|
PdePage);
|
|
ASSERT(Pfn1->u2.ShareCount >= 1);
|
|
PageDirectoryMap = MiMapPageInHyperSpace(PdePage, &OldIrql2);
|
|
PageDirectoryMap[MiGetPdeOffset(HYPER_SPACE2)] = TempPte;
|
|
MiUnmapPageInHyperSpace(OldIrql2);
|
|
TempPte2 = TempPte;
|
|
#endif
|
|
|
|
// Locate the page table page for hyperspace and make it resident.
|
|
PageDirectoryMap = MiMapPageInHyperSpace(PdePage, &OldIrql2);
|
|
TempPte = PageDirectoryMap[MiGetPdeOffset(MmWorkingSetList)];
|
|
MiUnmapPageInHyperSpace(OldIrql2);
|
|
HyperSpacePageTable = MiMakeOutswappedPageResident(
|
|
MiGetPdeAddress(HYPER_SPACE),
|
|
&TempPte,
|
|
0,
|
|
PdePage);
|
|
ASSERT(Pfn1->u2.ShareCount >= 3);
|
|
PageDirectoryMap = MiMapPageInHyperSpace(PdePage, &OldIrql2);
|
|
|
|
#if !defined (_WIN64)
|
|
PageDirectoryMap[MiGetPdeOffset(PDE_BASE)].u.Flush = OutProcess->PageDirectoryPte;
|
|
#endif
|
|
|
|
PageDirectoryMap[MiGetPdeOffset(MmWorkingSetList)] = TempPte;
|
|
|
|
MiUnmapPageInHyperSpace(OldIrql2);
|
|
|
|
// Map in the hyper space page table page and retrieve the
|
|
// PTE that maps the working set list.
|
|
HyperSpacePageTableMap = MiMapPageInHyperSpace(HyperSpacePageTable, &OldIrql2);
|
|
TempPte = HyperSpacePageTableMap[MiGetPteOffset(MmWorkingSetList)];
|
|
MiUnmapPageInHyperSpace(OldIrql2);
|
|
Pfn1 = MI_PFN_ELEMENT(HyperSpacePageTable);
|
|
|
|
Pfn1->u1.WsIndex = 1;
|
|
|
|
WorkingSetPage = MiMakeOutswappedPageResident(
|
|
MiGetPteAddress(MmWorkingSetList),
|
|
&TempPte,
|
|
0,
|
|
HyperSpacePageTable);
|
|
|
|
HyperSpacePageTableMap = MiMapPageInHyperSpace(HyperSpacePageTable, &OldIrql2);
|
|
HyperSpacePageTableMap[MiGetPteOffset(MmWorkingSetList)] = TempPte;
|
|
#if defined (_X86PAE_)
|
|
HyperSpacePageTableMap[0] = TempPte2;
|
|
#endif
|
|
MiUnmapPageInHyperSpace(OldIrql2);
|
|
Pfn1 = MI_PFN_ELEMENT(WorkingSetPage);
|
|
Pfn1->u1.WsIndex = 2;
|
|
UNLOCK_PFN(OldIrql);
|
|
LOCK_EXPANSION(OldIrql);
|
|
|
|
// Allow working set trimming on this process.
|
|
OutProcess->Vm.AllowWorkingSetAdjustment = TRUE;
|
|
if (OutProcess->Vm.WorkingSetExpansionLinks.Flink == MM_WS_SWAPPED_OUT) {
|
|
InsertTailList(&MmWorkingSetExpansionHead.ListHead, &OutProcess->Vm.WorkingSetExpansionLinks);
|
|
}
|
|
UNLOCK_EXPANSION(OldIrql);
|
|
|
|
// Set up process structures.
|
|
OutProcess->WorkingSetPage = WorkingSetPage;
|
|
|
|
#if !defined (_X86PAE_)
|
|
OutProcess->Vm.WorkingSetSize = 3;
|
|
INITIALIZE_DIRECTORY_TABLE_BASE(&Process->DirectoryTableBase[0], PdePage);
|
|
INITIALIZE_DIRECTORY_TABLE_BASE(&Process->DirectoryTableBase[1], HyperSpacePageTable);
|
|
#else
|
|
// The DirectoryTableBase[0] never changes for PAE processes.
|
|
OutProcess->Vm.WorkingSetSize = 7;
|
|
Process->DirectoryTableBase[1] = HyperSpacePageTable;
|
|
#endif
|
|
OutProcess->ProcessOutswapped = FALSE;
|
|
}
|
|
|
|
if (MiHydra == TRUE && OutProcess->Vm.u.Flags.ProcessInSession == 1) {
|
|
MiSessionInSwapProcess(OutProcess);
|
|
}
|
|
|
|
OutProcess->ProcessOutswapEnabled = FALSE;
|
|
return;
|
|
}
|
|
|
|
|
|
PVOID MiCreatePebOrTeb(IN PEPROCESS TargetProcess, IN ULONG Size)
|
|
/*++
|
|
Routine Description:
|
|
This routine creates a TEB or PEB page within the target process.
|
|
Arguments:
|
|
TargetProcess - Supplies a pointer to the process in which to create the structure.
|
|
Size - Supplies the size of the structure to create a VAD for.
|
|
Return Value:
|
|
Returns the address of the base of the newly created TEB or PEB.
|
|
Environment:
|
|
Kernel mode, attached to the specified process.
|
|
*/
|
|
{
|
|
PVOID Base;
|
|
PMMVAD Vad;
|
|
|
|
// Get the address creation mutex to block multiple threads from
|
|
// creating or deleting address space at the same time and
|
|
// get the working set mutex so virtual address descriptors can
|
|
// be inserted and walked.
|
|
|
|
LOCK_WS_AND_ADDRESS_SPACE(TargetProcess);
|
|
try {
|
|
Vad = (PMMVAD)NULL;
|
|
|
|
// Find a VA for a PEB on a page-size boundary.
|
|
Base = MiFindEmptyAddressRangeDown(ROUND_TO_PAGES(Size), ((PCHAR)MM_HIGHEST_VAD_ADDRESS + 1), PAGE_SIZE);
|
|
|
|
// An unoccupied address range has been found, build the virtual
|
|
// address descriptor to describe this range.
|
|
Vad = (PMMVAD)ExAllocatePoolWithTag(NonPagedPool, sizeof(MMVAD), ' daV');
|
|
if (Vad == (PMMVAD)0) {
|
|
ExRaiseStatus(STATUS_NO_MEMORY);
|
|
}
|
|
Vad->StartingVpn = MI_VA_TO_VPN(Base);
|
|
Vad->EndingVpn = MI_VA_TO_VPN((PCHAR)Base + Size - 1);
|
|
Vad->u.LongFlags = 0;
|
|
Vad->u.VadFlags.CommitCharge = BYTES_TO_PAGES(Size);
|
|
Vad->u.VadFlags.MemCommit = 1;
|
|
Vad->u.VadFlags.PrivateMemory = 1;
|
|
Vad->u.VadFlags.Protection = MM_EXECUTE_READWRITE;
|
|
|
|
// Mark VAD as not deletable, no protection change.
|
|
Vad->u.VadFlags.NoChange = 1;
|
|
Vad->u2.LongFlags2 = 0;
|
|
Vad->u2.VadFlags2.OneSecured = 1;
|
|
Vad->u2.VadFlags2.StoredInVad = 1;
|
|
Vad->u2.VadFlags2.ReadOnly = 0;
|
|
Vad->u3.Secured.StartVpn = (ULONG_PTR)Base;
|
|
Vad->u3.Secured.EndVpn = (ULONG_PTR)MI_VPN_TO_VA_ENDING(Vad->EndingVpn);
|
|
|
|
MiInsertVad(Vad);
|
|
} except(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
// An exception has occurred. If pool was allocated, deallocate
|
|
// it and raise an exception for the caller.
|
|
if (Vad != (PMMVAD)NULL) {
|
|
ExFreePool(Vad);
|
|
}
|
|
|
|
UNLOCK_WS_AND_ADDRESS_SPACE(TargetProcess);
|
|
KeDetachProcess();
|
|
ExRaiseStatus(GetExceptionCode());
|
|
}
|
|
|
|
UNLOCK_WS_AND_ADDRESS_SPACE(TargetProcess);
|
|
|
|
return Base;
|
|
}
|
|
|
|
|
|
PTEB MmCreateTeb(IN PEPROCESS TargetProcess, IN PINITIAL_TEB InitialTeb, IN PCLIENT_ID ClientId)
|
|
/*++
|
|
Routine Description:
|
|
This routine creates a TEB page within the target process
|
|
and copies the initial TEB values into it.
|
|
Arguments:
|
|
TargetProcess - Supplies a pointer to the process in which to create and initialize the TEB.
|
|
InitialTeb - Supplies a pointer to the initial TEB to copy into the newly created TEB.
|
|
Return Value:
|
|
Returns the address of the base of the newly created TEB.
|
|
Can raise exceptions if no address space is available for the TEB or
|
|
the user has exceeded quota (non-paged, pagefile, commit).
|
|
Environment:
|
|
Kernel mode.
|
|
*/
|
|
{
|
|
PTEB TebBase;
|
|
|
|
// If the specified process is not the current process, attach to the specified process.
|
|
KeAttachProcess(&TargetProcess->Pcb);
|
|
|
|
TebBase = (PTEB)MiCreatePebOrTeb(TargetProcess, (ULONG)sizeof(TEB));
|
|
|
|
// Initialize the TEB.
|
|
#if defined(_WIN64)
|
|
TebBase->NtTib.ExceptionList = NULL;
|
|
#else
|
|
TebBase->NtTib.ExceptionList = EXCEPTION_CHAIN_END;
|
|
#endif
|
|
|
|
TebBase->NtTib.SubSystemTib = NULL;
|
|
TebBase->NtTib.Version = OS2_VERSION;
|
|
TebBase->NtTib.ArbitraryUserPointer = NULL;
|
|
TebBase->NtTib.Self = (PNT_TIB)TebBase;
|
|
TebBase->EnvironmentPointer = NULL;
|
|
TebBase->ProcessEnvironmentBlock = TargetProcess->Peb;
|
|
TebBase->ClientId = *ClientId;
|
|
TebBase->RealClientId = *ClientId;
|
|
|
|
if ((InitialTeb->OldInitialTeb.OldStackBase == NULL) &&
|
|
(InitialTeb->OldInitialTeb.OldStackLimit == NULL)) {
|
|
TebBase->NtTib.StackBase = InitialTeb->StackBase;
|
|
TebBase->NtTib.StackLimit = InitialTeb->StackLimit;
|
|
TebBase->DeallocationStack = InitialTeb->StackAllocationBase;
|
|
|
|
#if defined(_IA64_)
|
|
TebBase->BStoreLimit = InitialTeb->BStoreLimit;
|
|
TebBase->DeallocationBStore = (PCHAR)InitialTeb->StackBase
|
|
+ ((ULONG_PTR)InitialTeb->StackBase - (ULONG_PTR)InitialTeb->StackAllocationBase);
|
|
#endif
|
|
} else {
|
|
TebBase->NtTib.StackBase = InitialTeb->OldInitialTeb.OldStackBase;
|
|
TebBase->NtTib.StackLimit = InitialTeb->OldInitialTeb.OldStackLimit;
|
|
}
|
|
|
|
TebBase->StaticUnicodeString.Buffer = TebBase->StaticUnicodeBuffer;
|
|
TebBase->StaticUnicodeString.MaximumLength = (USHORT)sizeof(TebBase->StaticUnicodeBuffer);
|
|
TebBase->StaticUnicodeString.Length = (USHORT)0;
|
|
|
|
// Used for BBT of ntdll and kernel32.dll.
|
|
TebBase->ReservedForPerf = BBTBuffer;
|
|
|
|
KeDetachProcess();
|
|
return TebBase;
|
|
}
|
|
|
|
|
|
// This code is built twice on the Win64 build - once for PE32+
|
|
// and once for PE32 images.
|
|
|
|
|
|
#define MI_INIT_PEB_FROM_IMAGE(Hdrs, ImgConfig) { \
|
|
PebBase->ImageSubsystem = (Hdrs)->OptionalHeader.Subsystem; \
|
|
PebBase->ImageSubsystemMajorVersion = \
|
|
(Hdrs)->OptionalHeader.MajorSubsystemVersion; \
|
|
PebBase->ImageSubsystemMinorVersion = \
|
|
(Hdrs)->OptionalHeader.MinorSubsystemVersion; \
|
|
\
|
|
/* */ \
|
|
/* See if this image wants GetVersion to lie about who the system is */ \
|
|
/* If so, capture the lie into the PEB for the process. */ \
|
|
/* */ \
|
|
\
|
|
if ((Hdrs)->OptionalHeader.Win32VersionValue != 0) { \
|
|
PebBase->OSMajorVersion = \
|
|
(Hdrs)->OptionalHeader.Win32VersionValue & 0xFF; \
|
|
PebBase->OSMinorVersion = \
|
|
((Hdrs)->OptionalHeader.Win32VersionValue >> 8) & 0xFF; \
|
|
PebBase->OSBuildNumber = \
|
|
(USHORT)(((Hdrs)->OptionalHeader.Win32VersionValue >> 16) & 0x3FFF); \
|
|
if ((ImgConfig) != NULL && (ImgConfig)->CSDVersion != 0) { \
|
|
PebBase->OSCSDVersion = (ImgConfig)->CSDVersion; \
|
|
} \
|
|
\
|
|
/* Win32 API GetVersion returns the following bogus bit definitions */ \
|
|
/* in the high two bits: */ \
|
|
/* */ \
|
|
/* 00 - Windows NT */ \
|
|
/* 01 - reserved */ \
|
|
/* 10 - Win32s running on Windows 3.x */ \
|
|
/* 11 - Windows 95 */ \
|
|
/* */ \
|
|
/* */ \
|
|
/* Win32 API GetVersionEx returns a dwPlatformId with the following */ \
|
|
/* values defined in winbase.h */ \
|
|
/* */ \
|
|
/* 00 - VER_PLATFORM_WIN32s */ \
|
|
/* 01 - VER_PLATFORM_WIN32_WINDOWS */ \
|
|
/* 10 - VER_PLATFORM_WIN32_NT */ \
|
|
/* 11 - reserved */ \
|
|
/* */ \
|
|
/* */ \
|
|
/* So convert the former from the Win32VersionValue field into the */ \
|
|
/* OSPlatformId field. This is done by XORing with 0x2. The */ \
|
|
/* translation is symmetric so there is the same code to do the */ \
|
|
/* reverse in windows\base\client\module.c (GetVersion) */ \
|
|
/* */ \
|
|
PebBase->OSPlatformId = \
|
|
((Hdrs)->OptionalHeader.Win32VersionValue >> 30) ^ 0x2; \
|
|
} \
|
|
}
|
|
|
|
|
|
#if defined(_WIN64)
|
|
VOID MiInitializeWowPeb(IN PIMAGE_NT_HEADERS NtHeaders, IN PPEB PebBase, IN PEPROCESS TargetProcess)
|
|
/*++
|
|
Routine Description:
|
|
This routine creates a PEB32 page within the target process
|
|
and copies the initial PEB32 values into it.
|
|
Arguments:
|
|
NtHeaders - Supplies a pointer to the NT headers for the image.
|
|
PebBase - Supplies a pointer to the initial PEB to derive the PEB32 values from.
|
|
TargetProcess - Supplies a pointer to the process in which to create and initialize the PEB32.
|
|
Return Value:
|
|
Returns the address of the base of the newly created PEB.
|
|
|
|
Can raise exceptions if no address space is available for the PEB32 or
|
|
the user has exceeded quota (non-paged, pagefile, commit) or any inpage
|
|
errors happen for the user addresses, etc. If an exception is raised,
|
|
note that the process detach is performed prior to returning.
|
|
Environment:
|
|
Kernel mode.
|
|
*/
|
|
{
|
|
NTSTATUS Status;
|
|
ULONG ReturnedSize;
|
|
PPEB32 PebBase32;
|
|
ULONG ProcessAffinityMask;
|
|
PIMAGE_LOAD_CONFIG_DIRECTORY32 ImageConfigData32;
|
|
|
|
ProcessAffinityMask = 0;
|
|
ImageConfigData32 = NULL;
|
|
|
|
// Image is 32-bit.
|
|
try {
|
|
ImageConfigData32 = RtlImageDirectoryEntryToData(
|
|
PebBase->ImageBaseAddress,
|
|
TRUE,
|
|
IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG,
|
|
&ReturnedSize);
|
|
ProbeForRead((PVOID)ImageConfigData32,
|
|
sizeof(*ImageConfigData32),
|
|
sizeof(ULONG));
|
|
MI_INIT_PEB_FROM_IMAGE((PIMAGE_NT_HEADERS32)NtHeaders, ImageConfigData32);
|
|
|
|
if ((ImageConfigData32 != NULL) && (ImageConfigData32->ProcessAffinityMask != 0)) {
|
|
ProcessAffinityMask = ImageConfigData32->ProcessAffinityMask;
|
|
}
|
|
} except(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
KeDetachProcess();
|
|
ExRaiseStatus(STATUS_INVALID_IMAGE_PROTECT);
|
|
}
|
|
|
|
// Create a PEB32 for the process.
|
|
PebBase32 = (PPEB32)MiCreatePebOrTeb(TargetProcess, (ULONG)sizeof(PEB32));
|
|
|
|
// Mark the process as WOW64 by storing the 32-bit PEB pointer in the Wow64 field.
|
|
TargetProcess->Wow64Process->Wow64 = PebBase32;
|
|
|
|
// Clone the PEB into the PEB32.
|
|
PebBase32->InheritedAddressSpace = PebBase->InheritedAddressSpace;
|
|
PebBase32->Mutant = PtrToUlong(PebBase->Mutant);
|
|
PebBase32->ImageBaseAddress = PtrToUlong(PebBase->ImageBaseAddress);
|
|
PebBase32->AnsiCodePageData = PtrToUlong(PebBase->AnsiCodePageData);
|
|
PebBase32->OemCodePageData = PtrToUlong(PebBase->OemCodePageData);
|
|
PebBase32->UnicodeCaseTableData = PtrToUlong(PebBase->UnicodeCaseTableData);
|
|
PebBase32->NumberOfProcessors = PebBase->NumberOfProcessors;
|
|
PebBase32->BeingDebugged = PebBase->BeingDebugged;
|
|
PebBase32->NtGlobalFlag = PebBase->NtGlobalFlag;
|
|
PebBase32->CriticalSectionTimeout = PebBase->CriticalSectionTimeout;
|
|
|
|
if (PebBase->HeapSegmentReserve > 1024 * 1024 * 1024) { // 1gig
|
|
PebBase32->HeapSegmentReserve = 1024 * 1024; // 1meg
|
|
} else {
|
|
PebBase32->HeapSegmentReserve = (ULONG)PebBase->HeapSegmentReserve;
|
|
}
|
|
|
|
if (PebBase->HeapSegmentCommit > PebBase32->HeapSegmentReserve) {
|
|
PebBase32->HeapSegmentCommit = 2 * PAGE_SIZE;
|
|
} else {
|
|
PebBase32->HeapSegmentCommit = (ULONG)PebBase->HeapSegmentCommit;
|
|
}
|
|
|
|
PebBase32->HeapDeCommitTotalFreeThreshold = (ULONG)PebBase->HeapDeCommitTotalFreeThreshold;
|
|
PebBase32->HeapDeCommitFreeBlockThreshold = (ULONG)PebBase->HeapDeCommitFreeBlockThreshold;
|
|
PebBase32->NumberOfHeaps = PebBase->NumberOfHeaps;
|
|
PebBase32->MaximumNumberOfHeaps = (PAGE_SIZE - sizeof(PEB32)) / sizeof(ULONG);
|
|
PebBase32->ProcessHeaps = PtrToUlong(PebBase32 + 1);
|
|
PebBase32->OSMajorVersion = PebBase->OSMajorVersion;
|
|
PebBase32->OSMinorVersion = PebBase->OSMinorVersion;
|
|
PebBase32->OSBuildNumber = PebBase->OSBuildNumber;
|
|
PebBase32->OSPlatformId = PebBase->OSPlatformId;
|
|
PebBase32->OSCSDVersion = PebBase->OSCSDVersion;
|
|
PebBase32->ImageSubsystem = PebBase->ImageSubsystem;
|
|
PebBase32->ImageSubsystemMajorVersion = PebBase->ImageSubsystemMajorVersion;
|
|
PebBase32->ImageSubsystemMinorVersion = PebBase->ImageSubsystemMinorVersion;
|
|
PebBase32->SessionId = TargetProcess->SessionId;
|
|
|
|
// Leave the AffinityMask in the 32bit PEB as zero and let the
|
|
// 64bit NTDLL set the initial mask. This is to allow the
|
|
// round robin scheduling of non MP safe imageing in the
|
|
// following code to work correctly.
|
|
|
|
// Later code will set the affinity mask in the PEB32 if the
|
|
// image actually specifies one.
|
|
|
|
// Note that the AffinityMask in the PEB is simply a mechanism
|
|
// to pass affinity information from the image to the loader.
|
|
|
|
// Pass the affinity mask up to the 32 bit NTDLL via
|
|
// the PEB32. The 32 bit NTDLL will determine that the
|
|
// affinity is not zero and try to set the affinity
|
|
// mask from user-mode. This call will be intercepted
|
|
// by the wow64 thunks which will convert it
|
|
// into a 64bit affinity mask and call the kernel.
|
|
|
|
PebBase32->ImageProcessAffinityMask = ProcessAffinityMask;
|
|
}
|
|
#endif
|
|
|
|
|
|
PPEB MmCreatePeb(IN PEPROCESS TargetProcess, IN PINITIAL_PEB InitialPeb)
|
|
/*++
|
|
Routine Description:
|
|
This routine creates a PEB page within the target process
|
|
and copies the initial PEB values into it.
|
|
Arguments:
|
|
TargetProcess - Supplies a pointer to the process in which to create and initialize the PEB.
|
|
InitialPeb - Supplies a pointer to the initial PEB to copy into the newly created PEB.
|
|
Return Value:
|
|
Returns the address of the base of the newly created PEB.
|
|
|
|
Can raise exceptions if no address space is available for the PEB or
|
|
the user has exceeded quota (non-paged, pagefile, commit).
|
|
Environment:
|
|
Kernel mode.
|
|
*/
|
|
{
|
|
PPEB PebBase;
|
|
USHORT Magic;
|
|
USHORT Characteristics;
|
|
NTSTATUS Status;
|
|
PVOID ViewBase;
|
|
LARGE_INTEGER SectionOffset;
|
|
PIMAGE_NT_HEADERS NtHeaders;
|
|
SIZE_T ViewSize;
|
|
ULONG ReturnedSize;
|
|
PIMAGE_LOAD_CONFIG_DIRECTORY ImageConfigData;
|
|
ULONG ProcessAffinityMask;
|
|
|
|
ViewBase = NULL;
|
|
SectionOffset.LowPart = 0;
|
|
SectionOffset.HighPart = 0;
|
|
ViewSize = 0;
|
|
|
|
// If the specified process is not the current process, attach
|
|
// to the specified process.
|
|
KeAttachProcess(&TargetProcess->Pcb);
|
|
|
|
// Map the NLS tables into the application's address space.
|
|
Status = MmMapViewOfSection(
|
|
InitNlsSectionPointer,
|
|
TargetProcess,
|
|
&ViewBase,
|
|
0L,
|
|
0L,
|
|
&SectionOffset,
|
|
&ViewSize,
|
|
ViewShare,
|
|
MEM_TOP_DOWN | SEC_NO_CHANGE,
|
|
PAGE_READONLY
|
|
);
|
|
if (!NT_SUCCESS(Status)) {
|
|
KeDetachProcess();
|
|
ExRaiseStatus(Status);
|
|
}
|
|
|
|
PebBase = (PPEB)MiCreatePebOrTeb(TargetProcess, (ULONG)sizeof(PEB));
|
|
|
|
// Initialize the Peb.
|
|
|
|
PebBase->InheritedAddressSpace = InitialPeb->InheritedAddressSpace;
|
|
PebBase->Mutant = InitialPeb->Mutant;
|
|
PebBase->ImageBaseAddress = TargetProcess->SectionBaseAddress;
|
|
|
|
PebBase->AnsiCodePageData = (PVOID)((PUCHAR)ViewBase + InitAnsiCodePageDataOffset);
|
|
PebBase->OemCodePageData = (PVOID)((PUCHAR)ViewBase + InitOemCodePageDataOffset);
|
|
PebBase->UnicodeCaseTableData = (PVOID)((PUCHAR)ViewBase + InitUnicodeCaseTableDataOffset);
|
|
|
|
PebBase->NumberOfProcessors = KeNumberProcessors;
|
|
PebBase->BeingDebugged = (BOOLEAN)(TargetProcess->DebugPort != NULL ? TRUE : FALSE);
|
|
PebBase->NtGlobalFlag = NtGlobalFlag;
|
|
PebBase->CriticalSectionTimeout = MmCriticalSectionTimeout;
|
|
PebBase->HeapSegmentReserve = MmHeapSegmentReserve;
|
|
PebBase->HeapSegmentCommit = MmHeapSegmentCommit;
|
|
PebBase->HeapDeCommitTotalFreeThreshold = MmHeapDeCommitTotalFreeThreshold;
|
|
PebBase->HeapDeCommitFreeBlockThreshold = MmHeapDeCommitFreeBlockThreshold;
|
|
PebBase->NumberOfHeaps = 0;
|
|
PebBase->MaximumNumberOfHeaps = (PAGE_SIZE - sizeof(PEB)) / sizeof(PVOID);
|
|
PebBase->ProcessHeaps = (PVOID *)(PebBase + 1);
|
|
|
|
PebBase->OSMajorVersion = NtMajorVersion;
|
|
PebBase->OSMinorVersion = NtMinorVersion;
|
|
PebBase->OSBuildNumber = (USHORT)(NtBuildNumber & 0x3FFF);
|
|
PebBase->OSPlatformId = 2; // VER_PLATFORM_WIN32_NT from winbase.h
|
|
PebBase->OSCSDVersion = (USHORT)CmNtCSDVersion;
|
|
|
|
// Every reference to NtHeaders (including the call to RtlImageNtHeader)
|
|
// must be wrapped in try-except in case the inpage fails. The inpage
|
|
// can fail for any reason including network failures, low resources, etc.
|
|
try {
|
|
NtHeaders = RtlImageNtHeader(PebBase->ImageBaseAddress);
|
|
Magic = NtHeaders->OptionalHeader.Magic;
|
|
Characteristics = NtHeaders->FileHeader.Characteristics;
|
|
} except(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
KeDetachProcess();
|
|
ExRaiseStatus(STATUS_INVALID_IMAGE_PROTECT);
|
|
}
|
|
|
|
if (NtHeaders != NULL) {
|
|
ProcessAffinityMask = 0;
|
|
#if defined(_WIN64)
|
|
if (Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
|
|
// If this call fails, an exception will be thrown and the
|
|
// detach performed so no need to handle errors here.
|
|
MiInitializeWowPeb(NtHeaders, PebBase, TargetProcess);
|
|
} else // a PE32+ image
|
|
#endif
|
|
{
|
|
try {
|
|
ImageConfigData = RtlImageDirectoryEntryToData(
|
|
PebBase->ImageBaseAddress,
|
|
TRUE,
|
|
IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG,
|
|
&ReturnedSize);
|
|
ProbeForRead((PVOID)ImageConfigData,
|
|
sizeof(*ImageConfigData),
|
|
sizeof(ULONG));
|
|
MI_INIT_PEB_FROM_IMAGE(NtHeaders, ImageConfigData);
|
|
if (ImageConfigData != NULL && ImageConfigData->ProcessAffinityMask != 0) {
|
|
ProcessAffinityMask = ImageConfigData->ProcessAffinityMask;
|
|
}
|
|
} except(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
KeDetachProcess();
|
|
ExRaiseStatus(STATUS_INVALID_IMAGE_PROTECT);
|
|
}
|
|
}
|
|
|
|
// Note NT4 examined the NtHeaders->FileHeader.Characteristics
|
|
// for the IMAGE_FILE_AGGRESIVE_WS_TRIM bit, but this is not needed
|
|
// or used for NT5 and above.
|
|
|
|
// See if image wants to override the default processor affinity mask.
|
|
if (Characteristics & IMAGE_FILE_UP_SYSTEM_ONLY) {
|
|
// Image is NOT MP safe. Assign it a processor on a rotating
|
|
// basis to spread these processes around on MP systems.
|
|
do {
|
|
PebBase->ImageProcessAffinityMask = (KAFFINITY)(0x1 << MmRotatingUniprocessorNumber);
|
|
if (++MmRotatingUniprocessorNumber >= KeNumberProcessors) {
|
|
MmRotatingUniprocessorNumber = 0;
|
|
}
|
|
} while ((PebBase->ImageProcessAffinityMask & KeActiveProcessors) == 0);
|
|
} else {
|
|
if (ProcessAffinityMask != 0) {
|
|
// Pass the affinity mask from the image header
|
|
// to LdrpInitializeProcess via the PEB.
|
|
PebBase->ImageProcessAffinityMask = ProcessAffinityMask;
|
|
}
|
|
}
|
|
}
|
|
|
|
PebBase->SessionId = TargetProcess->SessionId;
|
|
|
|
KeDetachProcess();
|
|
return PebBase;
|
|
}
|
|
|
|
|
|
VOID MmDeleteTeb(IN PEPROCESS TargetProcess, IN PVOID TebBase)
|
|
/*++
|
|
Routine Description:
|
|
This routine deletes a TEB page within the target process.
|
|
Arguments:
|
|
TargetProcess - Supplies a pointer to the process in which to delete the TEB.
|
|
TebBase - Supplies the base address of the TEB to delete.
|
|
Return Value:
|
|
None.
|
|
Environment:
|
|
Kernel mode.
|
|
*/
|
|
{
|
|
PVOID EndingAddress;
|
|
PMMVAD Vad;
|
|
NTSTATUS Status;
|
|
PMMSECURE_ENTRY Secure;
|
|
|
|
EndingAddress = ((PCHAR)TebBase + ROUND_TO_PAGES(sizeof(TEB)) - 1);
|
|
|
|
// Attach to the specified process.
|
|
KeAttachProcess(&TargetProcess->Pcb);
|
|
|
|
// Get the address creation mutex to block multiple threads from
|
|
// creating or deleting address space at the same time and
|
|
// get the working set mutex so virtual address descriptors can
|
|
// be inserted and walked.
|
|
|
|
LOCK_WS_AND_ADDRESS_SPACE(TargetProcess);
|
|
|
|
Vad = MiLocateAddress(TebBase);
|
|
ASSERT(Vad != (PMMVAD)NULL);
|
|
ASSERT((Vad->StartingVpn == MI_VA_TO_VPN(TebBase)) && (Vad->EndingVpn == MI_VA_TO_VPN(EndingAddress)));
|
|
|
|
// If someone has secured the TEB (in addition to the standard securing
|
|
// that was done by memory management on creation, then don't delete it
|
|
// now - just leave it around until the entire process is deleted.
|
|
ASSERT(Vad->u.VadFlags.NoChange == 1);
|
|
if (Vad->u2.VadFlags2.OneSecured) {
|
|
Status = STATUS_SUCCESS;
|
|
} else {
|
|
ASSERT(Vad->u2.VadFlags2.MultipleSecured);
|
|
ASSERT(IsListEmpty(&Vad->u3.List) == 0);
|
|
|
|
// If there's only one entry, then that's the one we defined when we
|
|
// initially created the TEB. So TEB deletion can take place right
|
|
// now. If there's more than one entry, let the TEB sit around until
|
|
// the process goes away.
|
|
Secure = CONTAINING_RECORD(Vad->u3.List.Flink, MMSECURE_ENTRY, List);
|
|
if (Secure->List.Flink == &Vad->u3.List) {
|
|
Status = STATUS_SUCCESS;
|
|
} else {
|
|
Status = STATUS_NOT_FOUND;
|
|
}
|
|
}
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
MiRemoveVad(Vad);
|
|
ExFreePool(Vad);
|
|
MiDeleteFreeVm(TebBase, EndingAddress);
|
|
}
|
|
|
|
UNLOCK_WS_AND_ADDRESS_SPACE(TargetProcess);
|
|
KeDetachProcess();
|
|
}
|
|
|
|
|
|
VOID MmAllowWorkingSetExpansion(VOID)
|
|
/*++
|
|
Routine Description:
|
|
This routine updates the working set list head FLINK field to
|
|
indicate that working set adjustment is allowed.
|
|
|
|
NOTE: This routine may be called more than once per process.
|
|
Environment:
|
|
Kernel mode.
|
|
*/
|
|
{
|
|
PEPROCESS CurrentProcess;
|
|
KIRQL OldIrql;
|
|
|
|
// Check the current state of the working set adjustment flag in the process header.
|
|
CurrentProcess = PsGetCurrentProcess();
|
|
|
|
LOCK_EXPANSION(OldIrql);
|
|
if (!CurrentProcess->Vm.AllowWorkingSetAdjustment) {
|
|
CurrentProcess->Vm.AllowWorkingSetAdjustment = TRUE;
|
|
InsertTailList(&MmWorkingSetExpansionHead.ListHead, &CurrentProcess->Vm.WorkingSetExpansionLinks);
|
|
}
|
|
UNLOCK_EXPANSION(OldIrql);
|
|
}
|
|
|
|
#if DBG
|
|
ULONG MiDeleteLocked;
|
|
#endif
|
|
|
|
|
|
VOID MiDeleteAddressesInWorkingSet(IN PEPROCESS Process)
|
|
/*++
|
|
Routine Description:
|
|
This routine deletes all user mode addresses from the working set list.
|
|
Arguments:
|
|
Process = Pointer to the current process.
|
|
Return Value:
|
|
None.
|
|
Environment:
|
|
Kernel mode, Working Set Lock held.
|
|
*/
|
|
{
|
|
PMMWSLE Wsle;
|
|
ULONG index;
|
|
ULONG Entry;
|
|
PVOID Va;
|
|
KIRQL OldIrql;
|
|
#if DBG
|
|
PVOID SwapVa;
|
|
PMMPTE PointerPte;
|
|
PMMPFN Pfn1;
|
|
PMMWSLE LastWsle;
|
|
#endif
|
|
|
|
// Go through the working set and for any user-accessible page which is
|
|
// in it, rip it out of the working set and free the page.
|
|
|
|
index = 2;
|
|
Wsle = &MmWsle[index];
|
|
|
|
MmWorkingSetList->HashTable = NULL;
|
|
|
|
// Go through the working set list and remove all pages for user
|
|
// space addresses.
|
|
while (index <= MmWorkingSetList->LastEntry) {
|
|
if (Wsle->u1.e1.Valid == 1) {
|
|
#if defined (_WIN64)
|
|
ASSERT(MiGetPpeAddress(Wsle->u1.VirtualAddress)->u.Hard.Valid == 1);
|
|
#endif
|
|
ASSERT(MiGetPdeAddress(Wsle->u1.VirtualAddress)->u.Hard.Valid == 1);
|
|
ASSERT(MiGetPteAddress(Wsle->u1.VirtualAddress)->u.Hard.Valid == 1);
|
|
|
|
if (Wsle->u1.VirtualAddress < (PVOID)MM_HIGHEST_USER_ADDRESS) {
|
|
// This is a user mode address, for each one we remove we must
|
|
// maintain the NonDirectCount. This is because we may fault
|
|
// later for page tables and need to grow the hash table when
|
|
// updating the working set. NonDirectCount needs to be correct
|
|
// at that point.
|
|
if (Wsle->u1.e1.Direct == 0) {
|
|
Process->Vm.VmWorkingSetList->NonDirectCount -= 1;
|
|
}
|
|
|
|
// This entry is in the working set list.
|
|
Va = Wsle->u1.VirtualAddress;
|
|
|
|
MiReleaseWsle(index, &Process->Vm);
|
|
LOCK_PFN(OldIrql);
|
|
MiDeleteValidAddress(Va, Process);
|
|
UNLOCK_PFN(OldIrql);
|
|
|
|
if (index < MmWorkingSetList->FirstDynamic) {
|
|
// This entry is locked.
|
|
MmWorkingSetList->FirstDynamic -= 1;
|
|
if (index != MmWorkingSetList->FirstDynamic) {
|
|
Entry = MmWorkingSetList->FirstDynamic;
|
|
#if DBG
|
|
MiDeleteLocked += 1;
|
|
SwapVa = MmWsle[MmWorkingSetList->FirstDynamic].u1.VirtualAddress;
|
|
SwapVa = PAGE_ALIGN(SwapVa);
|
|
|
|
PointerPte = MiGetPteAddress(SwapVa);
|
|
Pfn1 = MI_PFN_ELEMENT(PointerPte->u.Hard.PageFrameNumber);
|
|
|
|
ASSERT(Entry == MiLocateWsle(SwapVa, MmWorkingSetList, Pfn1->u1.WsIndex));
|
|
#endif
|
|
MiSwapWslEntries(Entry, index, &Process->Vm);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
index += 1;
|
|
Wsle += 1;
|
|
}
|
|
|
|
#if DBG
|
|
Wsle = &MmWsle[2];
|
|
LastWsle = &MmWsle[MmWorkingSetList->LastInitializedWsle];
|
|
while (Wsle <= LastWsle) {
|
|
if (Wsle->u1.e1.Valid == 1) {
|
|
#if defined (_WIN64)
|
|
ASSERT(MiGetPpeAddress(Wsle->u1.VirtualAddress)->u.Hard.Valid == 1);
|
|
#endif
|
|
ASSERT(MiGetPdeAddress(Wsle->u1.VirtualAddress)->u.Hard.Valid == 1);
|
|
ASSERT(MiGetPteAddress(Wsle->u1.VirtualAddress)->u.Hard.Valid == 1);
|
|
}
|
|
Wsle += 1;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
VOID MiDeleteValidAddress(IN PVOID Va, IN PEPROCESS CurrentProcess)
|
|
/*++
|
|
Routine Description:
|
|
This routine deletes the specified virtual address.
|
|
Arguments:
|
|
Va - Supplies the virtual address to delete.
|
|
CurrentProcess - Supplies the current process.
|
|
Return Value:
|
|
None.
|
|
Environment:
|
|
Kernel mode. PFN LOCK HELD.
|
|
|
|
Note since this is only called during process teardown, the write watch
|
|
bits are not updated. If this ever called from other places, code
|
|
will need to be added here to update those bits.
|
|
*/
|
|
{
|
|
PMMPTE PointerPde;
|
|
PMMPTE PointerPte;
|
|
PMMPFN Pfn1;
|
|
PMMCLONE_BLOCK CloneBlock;
|
|
PMMCLONE_DESCRIPTOR CloneDescriptor;
|
|
PFN_NUMBER PageFrameIndex;
|
|
|
|
PointerPte = MiGetPteAddress(Va);
|
|
|
|
#if defined (_WIN64)
|
|
ASSERT(MiGetPpeAddress(Va)->u.Hard.Valid == 1);
|
|
#endif
|
|
ASSERT(MiGetPdeAddress(Va)->u.Hard.Valid == 1);
|
|
ASSERT(PointerPte->u.Hard.Valid == 1);
|
|
|
|
PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE(PointerPte);
|
|
Pfn1 = MI_PFN_ELEMENT(PageFrameIndex);
|
|
CloneDescriptor = NULL;
|
|
|
|
if (Pfn1->u3.e1.PrototypePte == 1) {
|
|
CloneBlock = (PMMCLONE_BLOCK)Pfn1->PteAddress;
|
|
|
|
// Capture the state of the modified bit for this pte.
|
|
MI_CAPTURE_DIRTY_BIT_TO_PFN(PointerPte, Pfn1);
|
|
|
|
// Decrement the share and valid counts of the page table
|
|
// page which maps this PTE.
|
|
PointerPde = MiGetPteAddress(PointerPte);
|
|
MiDecrementShareAndValidCount(MI_GET_PAGE_FRAME_FROM_PTE(PointerPde));
|
|
|
|
// Decrement the share count for the physical page.
|
|
MiDecrementShareCount(PageFrameIndex);
|
|
|
|
// Check to see if this is a fork prototype PTE and if so
|
|
// update the clone descriptor address.
|
|
if (Va <= MM_HIGHEST_USER_ADDRESS) {
|
|
// Locate the clone descriptor within the clone tree.
|
|
CloneDescriptor = MiLocateCloneAddress((PVOID)CloneBlock);
|
|
}
|
|
} else {
|
|
// This PTE is a NOT a prototype PTE, delete the physical page.
|
|
|
|
// Decrement the share and valid counts of the page table
|
|
// page which maps this PTE.
|
|
MiDecrementShareAndValidCount(Pfn1->PteFrame);
|
|
|
|
MI_SET_PFN_DELETED(Pfn1);
|
|
|
|
// Decrement the share count for the physical page. As the page
|
|
// is private it will be put on the free list.
|
|
MiDecrementShareCountOnly(PageFrameIndex);
|
|
|
|
// Decrement the count for the number of private pages.
|
|
CurrentProcess->NumberOfPrivatePages -= 1;
|
|
}
|
|
|
|
// Set the pointer to PTE to be a demand zero PTE. This allows
|
|
// the page usage count to be kept properly and handles the case
|
|
// when a page table page has only valid PTEs and needs to be
|
|
// deleted later when the VADs are removed.
|
|
PointerPte->u.Long = MM_DEMAND_ZERO_WRITE_PTE;
|
|
|
|
if (CloneDescriptor != NULL) {
|
|
// Decrement the reference count for the clone block,
|
|
// note that this could release and reacquire
|
|
// the mutexes hence cannot be done until after the
|
|
// working set index has been removed.
|
|
if (MiDecrementCloneBlockReference(CloneDescriptor, CloneBlock, CurrentProcess)) {
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
PFN_NUMBER MiMakeOutswappedPageResident(IN PMMPTE ActualPteAddress,
|
|
IN OUT PMMPTE PointerTempPte,
|
|
IN ULONG Global,
|
|
IN PFN_NUMBER ContainingPage)
|
|
/*++
|
|
Routine Description:
|
|
This routine makes the specified PTE valid.
|
|
Arguments:
|
|
ActualPteAddress - Supplies the actual address that the PTE will
|
|
reside at. This is used for page coloring.
|
|
PointerTempPte - Supplies the PTE to operate on, returns a valid PTE.
|
|
Global - Supplies 1 if the resulting PTE is global.
|
|
ContainingPage - Supplies the physical page number of the page which
|
|
contains the resulting PTE. If this value is 0, no
|
|
operations on the containing page are performed.
|
|
Return Value:
|
|
Returns the physical page number that was allocated for the PTE.
|
|
Environment:
|
|
Kernel mode, PFN LOCK HELD!
|
|
*/
|
|
{
|
|
MMPTE TempPte;
|
|
KIRQL OldIrql;
|
|
PFN_NUMBER PageFrameIndex;
|
|
PMMPFN Pfn1;
|
|
PFN_NUMBER MdlHack[(sizeof(MDL) / sizeof(PFN_NUMBER)) + 2];
|
|
PMDL Mdl;
|
|
LARGE_INTEGER StartingOffset;
|
|
KEVENT Event;
|
|
IO_STATUS_BLOCK IoStatus;
|
|
PFN_NUMBER PageFileNumber;
|
|
NTSTATUS Status;
|
|
PPFN_NUMBER Page;
|
|
ULONG RefaultCount;
|
|
PVOID HyperVa;
|
|
|
|
MM_PFN_LOCK_ASSERT();
|
|
|
|
OldIrql = APC_LEVEL;
|
|
ASSERT(PointerTempPte->u.Hard.Valid == 0);
|
|
if (PointerTempPte->u.Long == MM_KERNEL_DEMAND_ZERO_PTE) {
|
|
// Any page will do.
|
|
MiEnsureAvailablePageOrWait(NULL, NULL);
|
|
PageFrameIndex = MiRemoveAnyPage(MI_GET_PAGE_COLOR_FROM_PTE(ActualPteAddress));
|
|
MI_MAKE_VALID_PTE(TempPte, PageFrameIndex, MM_READWRITE, ActualPteAddress);
|
|
MI_SET_PTE_DIRTY(TempPte);
|
|
MI_SET_GLOBAL_STATE(TempPte, Global);
|
|
|
|
MI_WRITE_VALID_PTE(PointerTempPte, TempPte);
|
|
MiInitializePfnForOtherProcess(PageFrameIndex, ActualPteAddress, ContainingPage);
|
|
} else if (PointerTempPte->u.Soft.Transition == 1) {
|
|
PageFrameIndex = MI_GET_PAGE_FRAME_FROM_TRANSITION_PTE(PointerTempPte);
|
|
PointerTempPte->u.Trans.Protection = MM_READWRITE;
|
|
Pfn1 = MI_PFN_ELEMENT(PageFrameIndex);
|
|
|
|
// PTE refers to a transition PTE.
|
|
if (Pfn1->u3.e1.PageLocation != ActiveAndValid) {
|
|
MiUnlinkPageFromList(Pfn1);
|
|
|
|
// Even though this routine is only used to bring in special
|
|
// system pages that are separately charged, a modified write
|
|
// may be in progress and if so, will have applied a systemwide
|
|
// charge against the locked pages count. This all works out nicely
|
|
// (with no code needed here) as the write completion will see
|
|
// the nonzero ShareCount and remove the charge.
|
|
ASSERT((Pfn1->u3.e2.ReferenceCount == 0) || (Pfn1->u3.e1.LockCharged == 1));
|
|
|
|
Pfn1->u3.e2.ReferenceCount += 1;
|
|
Pfn1->u3.e1.PageLocation = ActiveAndValid;
|
|
}
|
|
|
|
// Update the PFN database, the share count is now 1 and
|
|
// the reference count is incremented as the share count
|
|
// just went from zero to 1.
|
|
Pfn1->u2.ShareCount += 1;
|
|
Pfn1->u3.e1.Modified = 1;
|
|
if (Pfn1->u3.e1.WriteInProgress == 0) {
|
|
// Release the page file space for this page.
|
|
MiReleasePageFileSpace(Pfn1->OriginalPte);
|
|
Pfn1->OriginalPte.u.Long = MM_KERNEL_DEMAND_ZERO_PTE;
|
|
}
|
|
|
|
MI_MAKE_TRANSITION_PTE_VALID(TempPte, PointerTempPte);
|
|
|
|
MI_SET_PTE_DIRTY(TempPte);
|
|
MI_SET_GLOBAL_STATE(TempPte, Global);
|
|
MI_WRITE_VALID_PTE(PointerTempPte, TempPte);
|
|
} else {
|
|
// Page resides in a paging file.
|
|
// Any page will do.
|
|
PointerTempPte->u.Soft.Protection = MM_READWRITE;
|
|
MiEnsureAvailablePageOrWait(NULL, NULL);
|
|
PageFrameIndex = MiRemoveAnyPage(MI_GET_PAGE_COLOR_FROM_PTE(ActualPteAddress));
|
|
|
|
// Initialize the PFN database element, but don't
|
|
// set read in progress as collided page faults cannot occur here.
|
|
MiInitializePfnForOtherProcess(PageFrameIndex, ActualPteAddress, ContainingPage);
|
|
|
|
KeInitializeEvent(&Event, NotificationEvent, FALSE);
|
|
|
|
// Calculate the VPN for the in-page operation.
|
|
TempPte = *PointerTempPte;
|
|
PageFileNumber = GET_PAGING_FILE_NUMBER(TempPte);
|
|
|
|
StartingOffset.QuadPart = (LONGLONG)(GET_PAGING_FILE_OFFSET(TempPte)) << PAGE_SHIFT;
|
|
|
|
Pfn1 = MI_PFN_ELEMENT(PageFrameIndex);
|
|
|
|
// Build MDL for request.
|
|
Mdl = (PMDL)&MdlHack[0];
|
|
MmInitializeMdl(Mdl, MiGetVirtualAddressMappedByPte(ActualPteAddress), PAGE_SIZE);
|
|
Mdl->MdlFlags |= MDL_PAGES_LOCKED;
|
|
|
|
Page = (PPFN_NUMBER)(Mdl + 1);
|
|
*Page = PageFrameIndex;
|
|
|
|
UNLOCK_PFN(OldIrql);
|
|
|
|
#if DBG
|
|
HyperVa = MiMapPageInHyperSpace(PageFrameIndex, &OldIrql);
|
|
RtlFillMemoryUlong(HyperVa, PAGE_SIZE, 0x34785690);
|
|
MiUnmapPageInHyperSpace(OldIrql);
|
|
#endif
|
|
|
|
// Issue the read request.
|
|
RefaultCount = MiIoRetryLevel;
|
|
|
|
Refault:
|
|
Status = IoPageRead(MmPagingFile[PageFileNumber]->File, Mdl, &StartingOffset, &Event, &IoStatus);
|
|
if (Status == STATUS_PENDING) {
|
|
KeWaitForSingleObject(&Event, WrPageIn, KernelMode, FALSE, (PLARGE_INTEGER)NULL);
|
|
Status = IoStatus.Status;
|
|
}
|
|
|
|
if (Mdl->MdlFlags & MDL_MAPPED_TO_SYSTEM_VA) {
|
|
MmUnmapLockedPages(Mdl->MappedSystemVa, Mdl);
|
|
}
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
if (IoStatus.Information != PAGE_SIZE) {
|
|
KeBugCheckEx(KERNEL_STACK_INPAGE_ERROR, 2, IoStatus.Status, PageFileNumber, StartingOffset.LowPart);
|
|
}
|
|
}
|
|
|
|
if ((!NT_SUCCESS(Status)) || (!NT_SUCCESS(IoStatus.Status))) {
|
|
if (((MmIsRetryIoStatus(Status)) || (MmIsRetryIoStatus(IoStatus.Status))) && (RefaultCount != 0)) {
|
|
// Insufficient resources, delay and reissue the in page operation.
|
|
KeDelayExecutionThread(KernelMode, FALSE, &MmHalfSecond);
|
|
KeClearEvent(&Event);
|
|
RefaultCount -= 1;
|
|
goto Refault;
|
|
}
|
|
KdPrint(("MMINPAGE: status %lx io-status %lx\n", Status, IoStatus.Status));
|
|
KeBugCheckEx(KERNEL_STACK_INPAGE_ERROR, Status, IoStatus.Status, PageFileNumber, StartingOffset.LowPart);
|
|
}
|
|
|
|
LOCK_PFN(OldIrql);
|
|
|
|
// Release the page file space.
|
|
MiReleasePageFileSpace(TempPte);
|
|
Pfn1->OriginalPte.u.Long = MM_KERNEL_DEMAND_ZERO_PTE;
|
|
|
|
MI_MAKE_VALID_PTE(TempPte, PageFrameIndex, MM_READWRITE, ActualPteAddress);
|
|
MI_SET_PTE_DIRTY(TempPte);
|
|
Pfn1->u3.e1.Modified = 1;
|
|
MI_SET_GLOBAL_STATE(TempPte, Global);
|
|
|
|
MI_WRITE_VALID_PTE(PointerTempPte, TempPte);
|
|
}
|
|
|
|
return PageFrameIndex;
|
|
}
|
|
|
|
|
|
VOID MmSetMemoryPriorityProcess(IN PEPROCESS Process, IN UCHAR MemoryPriority)
|
|
/*++
|
|
Routine Description:
|
|
Sets the memory priority of a process.
|
|
Arguments:
|
|
Process - Supplies the process to update
|
|
MemoryPriority - Supplies the new memory priority of the process
|
|
Return Value:
|
|
None.
|
|
*/
|
|
{
|
|
KIRQL OldIrql;
|
|
UCHAR OldPriority;
|
|
|
|
if (MmSystemSize == MmSmallSystem && MmNumberOfPhysicalPages < ((15 * 1024 * 1024) / PAGE_SIZE)) {
|
|
// If this is a small system, make every process BACKGROUND.
|
|
MemoryPriority = MEMORY_PRIORITY_BACKGROUND;
|
|
}
|
|
|
|
LOCK_EXPANSION(OldIrql);
|
|
OldPriority = Process->Vm.MemoryPriority;
|
|
Process->Vm.MemoryPriority = MemoryPriority;
|
|
UNLOCK_EXPANSION(OldIrql);
|
|
|
|
#ifndef _MI_USE_CLAIMS_
|
|
if (OldPriority > MemoryPriority && MmAvailablePages < MmMoreThanEnoughFreePages) {
|
|
// The priority is being lowered, see if the working set should be trimmed.
|
|
|
|
PMMSUPPORT VmSupport;
|
|
ULONG i;
|
|
ULONG Trim;
|
|
LOGICAL Attached;
|
|
|
|
VmSupport = &Process->Vm;
|
|
i = VmSupport->WorkingSetSize - VmSupport->MaximumWorkingSetSize;
|
|
if ((LONG)i > 0) {
|
|
Trim = i;
|
|
if (Trim > MmWorkingSetReductionMax) {
|
|
Trim = MmWorkingSetReductionMax;
|
|
}
|
|
if (Process != PsGetCurrentProcess()) {
|
|
KeAttachProcess(&Process->Pcb);
|
|
Attached = TRUE;
|
|
} else {
|
|
Attached = FALSE;
|
|
}
|
|
LOCK_WS(Process);
|
|
|
|
Trim = MiTrimWorkingSet(Trim, VmSupport, FALSE);
|
|
|
|
MmWorkingSetList->Quota = VmSupport->WorkingSetSize;
|
|
if (MmWorkingSetList->Quota < VmSupport->MinimumWorkingSetSize) {
|
|
MmWorkingSetList->Quota = VmSupport->MinimumWorkingSetSize;
|
|
}
|
|
|
|
UNLOCK_WS(Process);
|
|
if (Attached == TRUE) {
|
|
KeDetachProcess();
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
|
|
PMMVAD MiAllocateVad(IN ULONG_PTR StartingVirtualAddress,
|
|
IN ULONG_PTR EndingVirtualAddress,
|
|
IN LOGICAL Deletable)
|
|
/*++
|
|
Routine Description:
|
|
Reserve the specified range of address space.
|
|
Arguments:
|
|
StartingVirtualAddress - Supplies the starting virtual address.
|
|
EndingVirtualAddress - Supplies the ending virtual address.
|
|
Deletable - Supplies TRUE if the VAD is to be marked as deletable, FALSE
|
|
if deletions of this VAD should be disallowed.
|
|
Return Value:
|
|
A VAD pointer on success, NULL on failure.
|
|
*/
|
|
{
|
|
PMMVAD Vad;
|
|
|
|
ASSERT(StartingVirtualAddress <= EndingVirtualAddress);
|
|
|
|
Vad = (PMMVAD)ExAllocatePoolWithTag(NonPagedPool, sizeof(MMVAD), ' daV');
|
|
if (Vad == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
// Set the starting and ending virtual page numbers of the VAD.
|
|
Vad->StartingVpn = MI_VA_TO_VPN(StartingVirtualAddress);
|
|
Vad->EndingVpn = MI_VA_TO_VPN(EndingVirtualAddress);
|
|
|
|
// Mark VAD as no commitment, private, and readonly.
|
|
Vad->u.LongFlags = 0;
|
|
Vad->u.VadFlags.CommitCharge = MM_MAX_COMMIT;
|
|
Vad->u.VadFlags.Protection = MM_READONLY;
|
|
Vad->u.VadFlags.PrivateMemory = 1;
|
|
|
|
Vad->u2.LongFlags2 = 0;
|
|
|
|
if (Deletable == TRUE) {
|
|
Vad->u.VadFlags.NoChange = 0;
|
|
Vad->u2.VadFlags2.OneSecured = 0;
|
|
Vad->u2.VadFlags2.StoredInVad = 0;
|
|
Vad->u2.VadFlags2.ReadOnly = 0;
|
|
Vad->u3.Secured.StartVpn = 0;
|
|
Vad->u3.Secured.EndVpn = 0;
|
|
} else {
|
|
Vad->u.VadFlags.NoChange = 1;
|
|
Vad->u2.VadFlags2.OneSecured = 1;
|
|
Vad->u2.VadFlags2.StoredInVad = 1;
|
|
Vad->u2.VadFlags2.ReadOnly = 1;
|
|
Vad->u3.Secured.StartVpn = StartingVirtualAddress;
|
|
Vad->u3.Secured.EndVpn = EndingVirtualAddress;
|
|
}
|
|
|
|
return Vad;
|
|
}
|
|
|
|
|
|
#if 0
|
|
VOID MiVerifyReferenceCounts(IN ULONG PdePage)
|
|
// Verify the share and valid PTE counts for page directory page.
|
|
{
|
|
PMMPFN Pfn1;
|
|
PMMPFN Pfn3;
|
|
PMMPTE Pte1;
|
|
ULONG Share = 0;
|
|
ULONG Valid = 0;
|
|
ULONG i, ix, iy;
|
|
PMMPTE PageDirectoryMap;
|
|
KIRQL OldIrql;
|
|
|
|
PageDirectoryMap = (PMMPTE)MiMapPageInHyperSpace(PdePage, &OldIrql);
|
|
Pfn1 = MI_PFN_ELEMENT(PdePage);
|
|
Pte1 = (PMMPTE)PageDirectoryMap;
|
|
|
|
// Map in the non paged portion of the system.
|
|
|
|
ix = MiGetPdeOffset(CODE_START);
|
|
|
|
for (i = 0; i < ix; i += 1) {
|
|
if (Pte1->u.Hard.Valid == 1) {
|
|
Valid += 1;
|
|
} else if ((Pte1->u.Soft.Prototype == 0) && (Pte1->u.Soft.Transition == 1)) {
|
|
Pfn3 = MI_PFN_ELEMENT(Pte1->u.Trans.PageFrameNumber);
|
|
if (Pfn3->u3.e1.PageLocation == ActiveAndValid) {
|
|
ASSERT(Pfn1->u2.ShareCount > 1);
|
|
Valid += 1;
|
|
} else {
|
|
Share += 1;
|
|
}
|
|
}
|
|
Pte1 += 1;
|
|
}
|
|
|
|
iy = MiGetPdeOffset(PTE_BASE);
|
|
Pte1 = &PageDirectoryMap[iy];
|
|
ix = MiGetPdeOffset(HYPER_SPACE_END) + 1;
|
|
|
|
for (i = iy; i < ix; i += 1) {
|
|
if (Pte1->u.Hard.Valid == 1) {
|
|
Valid += 1;
|
|
} else if ((Pte1->u.Soft.Prototype == 0) && (Pte1->u.Soft.Transition == 1)) {
|
|
Pfn3 = MI_PFN_ELEMENT(Pte1->u.Trans.PageFrameNumber);
|
|
if (Pfn3->u3.e1.PageLocation == ActiveAndValid) {
|
|
ASSERT(Pfn1->u2.ShareCount > 1);
|
|
Valid += 1;
|
|
} else {
|
|
Share += 1;
|
|
}
|
|
}
|
|
Pte1 += 1;
|
|
}
|
|
|
|
if (Pfn1->u2.ShareCount != (Share + Valid + 1)) {
|
|
DbgPrint("MMPROCSUP - PDE page %lx ShareCount %lx found %lx\n",
|
|
PdePage, Pfn1->u2.ShareCount, Valid + Share + 1);
|
|
}
|
|
|
|
MiUnmapPageInHyperSpace(OldIrql);
|
|
ASSERT(Pfn1->u2.ShareCount == (Share + Valid + 1));
|
|
return;
|
|
}
|
|
#endif //0
|
|
|
|
|
|
#if defined (_X86PAE_)
|
|
|
|
|
|
VOID MiPaeInitialize(VOID)
|
|
{
|
|
InitializeListHead(&MiFirstFreePae.PaeEntry.ListHead);
|
|
}
|
|
|
|
|
|
ULONG MiPaeAllocate(OUT PPAE_ENTRY *Va)
|
|
/*++
|
|
Routine Description:
|
|
This routine allocates the top level page directory pointer structure.
|
|
This structure will contain 4 PDPTEs.
|
|
Arguments:
|
|
Va - Supplies a place to put the virtual address this page can be accessed at.
|
|
Return Value:
|
|
Returns a virtual and physical address suitable for use as a top level page directory pointer page.
|
|
|
|
Returns 0 if no page was allocated.
|
|
|
|
Note that on success, the page returned must be below physical 4GB.
|
|
|
|
Environment:
|
|
Kernel mode. No locks may be held.
|
|
*/
|
|
{
|
|
ULONG i;
|
|
PVOID Entry;
|
|
PMMPFN Pfn1;
|
|
KIRQL OldIrql;
|
|
LOGICAL FlushedOnce;
|
|
PPAE_ENTRY Pae;
|
|
PPAE_ENTRY PaeBase;
|
|
|
|
FlushedOnce = FALSE;
|
|
|
|
ASSERT(KeGetCurrentIrql() <= APC_LEVEL);
|
|
|
|
LOCK_PFN(OldIrql);
|
|
|
|
do {
|
|
if (MiFreePaes != 0) {
|
|
ASSERT(IsListEmpty(&MiFirstFreePae.PaeEntry.ListHead) == 0);
|
|
|
|
Pae = (PPAE_ENTRY)RemoveHeadList(&MiFirstFreePae.PaeEntry.ListHead);
|
|
|
|
PaeBase = (PPAE_ENTRY)PAGE_ALIGN(Pae);
|
|
PaeBase->PaeEntry.EntriesInUse += 1;
|
|
#if DBG
|
|
RtlZeroMemory((PVOID)Pae, sizeof(PAE_ENTRY));
|
|
|
|
Pfn1 = MI_PFN_ELEMENT(PaeBase->PaeEntry.PageFrameNumber);
|
|
ASSERT(Pfn1->u2.ShareCount == 1);
|
|
ASSERT(Pfn1->u3.e2.ReferenceCount == 1);
|
|
ASSERT(Pfn1->u3.e1.PageLocation == ActiveAndValid);
|
|
#endif
|
|
|
|
MiFreePaes -= 1;
|
|
UNLOCK_PFN(OldIrql);
|
|
|
|
ASSERT(KeGetCurrentIrql() <= APC_LEVEL);
|
|
*Va = Pae;
|
|
return (PaeBase->PaeEntry.PageFrameNumber << PAGE_SHIFT) + BYTE_OFFSET(Pae);
|
|
}
|
|
|
|
UNLOCK_PFN(OldIrql);
|
|
|
|
if (FlushedOnce == TRUE) {
|
|
break;
|
|
}
|
|
|
|
// No free pages in the cachelist, replenish the list now.
|
|
Entry = MiPaeReplenishList();
|
|
if (Entry == NULL) {
|
|
InterlockedIncrement(&MiDelayPageFaults);
|
|
|
|
// Attempt to move pages to the standby list.
|
|
MiEmptyAllWorkingSets();
|
|
MiFlushAllPages();
|
|
|
|
KeDelayExecutionThread(KernelMode, FALSE, &MmHalfSecond);
|
|
|
|
InterlockedDecrement(&MiDelayPageFaults);
|
|
|
|
FlushedOnce = TRUE;
|
|
|
|
LOCK_PFN(OldIrql);
|
|
|
|
// Since all the working sets have been trimmed, check whether
|
|
// another thread has replenished our list. If not, then attempt
|
|
// to do so since the working set pain has already been absorbed.
|
|
if (MiFreePaes < MINIMUM_PAE_THRESHOLD) {
|
|
UNLOCK_PFN(OldIrql);
|
|
MiPaeReplenishList();
|
|
LOCK_PFN(OldIrql);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
LOCK_PFN(OldIrql);
|
|
} while (TRUE);
|
|
|
|
ASSERT(KeGetCurrentIrql() <= APC_LEVEL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
PVOID MiPaeFree(PPAE_ENTRY Pae)
|
|
/*++
|
|
Routine Description:
|
|
This routine releases the top level page directory pointer page.
|
|
Arguments:
|
|
PageFrameIndex - Supplies the top level page directory pointer page.
|
|
Return Value:
|
|
A non-NULL pool address for the caller to free after releasing the PFN
|
|
lock. NULL if the caller needs to take no action.
|
|
Environment:
|
|
Kernel mode. The PFN lock is held on entry.
|
|
*/
|
|
{
|
|
PMMPTE PointerPte;
|
|
PMMPFN Pfn1;
|
|
KIRQL OldIrql;
|
|
ULONG i;
|
|
PLIST_ENTRY NextEntry;
|
|
PFN_NUMBER PageFrameIndex;
|
|
PPAE_ENTRY PaeBase;
|
|
|
|
MM_PFN_LOCK_ASSERT();
|
|
|
|
if (MI_IS_PHYSICAL_ADDRESS(Pae) == 0) {
|
|
PointerPte = MiGetPteAddress(Pae);
|
|
PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE(PointerPte);
|
|
} else {
|
|
PointerPte = NULL;
|
|
PageFrameIndex = MI_CONVERT_PHYSICAL_TO_PFN(Pae);
|
|
}
|
|
|
|
Pfn1 = MI_PFN_ELEMENT(PageFrameIndex);
|
|
|
|
ASSERT(Pfn1->u2.ShareCount == 1);
|
|
ASSERT(Pfn1->u3.e2.ReferenceCount == 1);
|
|
ASSERT(Pfn1->u3.e1.PageLocation == ActiveAndValid);
|
|
|
|
// This page must be in the first 4GB of RAM.
|
|
ASSERT(PageFrameIndex <= MM_HIGHEST_PAE_PAGE);
|
|
|
|
PaeBase = (PPAE_ENTRY)PAGE_ALIGN(Pae);
|
|
PaeBase->PaeEntry.EntriesInUse -= 1;
|
|
|
|
if ((PaeBase->PaeEntry.EntriesInUse == 0) && (MiFreePaes > EXCESS_PAE_THRESHOLD)) {
|
|
// Free the entire page.
|
|
i = 1;
|
|
NextEntry = MiFirstFreePae.PaeEntry.ListHead.Flink;
|
|
while (NextEntry != &MiFirstFreePae.PaeEntry.ListHead) {
|
|
Pae = CONTAINING_RECORD(NextEntry, PAE_ENTRY, PaeEntry.ListHead);
|
|
if (PAGE_ALIGN(Pae) == PaeBase) {
|
|
RemoveEntryList(NextEntry);
|
|
i += 1;
|
|
}
|
|
NextEntry = Pae->PaeEntry.ListHead.Flink;
|
|
}
|
|
ASSERT(i == PAES_PER_PAGE - 1);
|
|
MiFreePaes -= (PAES_PER_PAGE - 1);
|
|
return (PVOID)PaeBase;
|
|
}
|
|
|
|
InsertTailList(&MiFirstFreePae.PaeEntry.ListHead, &Pae->PaeEntry.ListHead);
|
|
MiFreePaes += 1;
|
|
return NULL;
|
|
}
|
|
|
|
|
|
PVOID MiPaeReplenishList(VOID)
|
|
/*++
|
|
Routine Description:
|
|
This routine searches the PFN database for free, zeroed or standby pages to satisfy the request.
|
|
Arguments:
|
|
NumberOfPages - Supplies the number of pages desired.
|
|
Return Value:
|
|
The virtual address of the allocated page, FALSE if no page was allocated.
|
|
Environment:
|
|
Kernel mode, IRQL of APC_LEVEL or below.
|
|
*/
|
|
{
|
|
PMMPFN Pfn1;
|
|
KIRQL OldIrql;
|
|
LONG start;
|
|
ULONG i;
|
|
PFN_NUMBER count;
|
|
PFN_NUMBER Page;
|
|
PFN_NUMBER LowPage;
|
|
PFN_NUMBER HighPage;
|
|
MMLISTS PageListType;
|
|
PMMPTE PointerPte;
|
|
PVOID BaseAddress;
|
|
PPAE_ENTRY Pae;
|
|
ULONG NumberOfPages;
|
|
MMPTE TempPte;
|
|
ULONG PageColor;
|
|
|
|
if (MiNoLowMemory == TRUE) {
|
|
BaseAddress = MiAllocateLowMemory(PAGE_SIZE, 0, 0xFFFFF, 0, (PVOID)0x123, 'DeaP');
|
|
if (BaseAddress == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
Page = MI_GET_PAGE_FRAME_FROM_PTE(MiGetPteAddress(BaseAddress));
|
|
|
|
Pae = (PPAE_ENTRY)BaseAddress;
|
|
Pae->PaeEntry.EntriesInUse = 0;
|
|
Pae->PaeEntry.PageFrameNumber = Page;
|
|
Pae += 1;
|
|
|
|
LOCK_PFN(OldIrql);
|
|
for (i = 1; i < PAES_PER_PAGE; i += 1) {
|
|
InsertTailList(&MiFirstFreePae.PaeEntry.ListHead, &Pae->PaeEntry.ListHead);
|
|
Pae += 1;
|
|
MiFreePaes += 1;
|
|
}
|
|
UNLOCK_PFN(OldIrql);
|
|
return BaseAddress;
|
|
}
|
|
|
|
HighPage = MM_HIGHEST_PAE_PAGE;
|
|
NumberOfPages = 1;
|
|
TempPte = ValidKernelPte;
|
|
|
|
ExAcquireFastMutex(&MmDynamicMemoryMutex);
|
|
start = (LONG)MmPhysicalMemoryBlock->NumberOfRuns - 1;
|
|
|
|
LOCK_PFN(OldIrql);
|
|
|
|
if (MmResidentAvailablePages <= 1) {
|
|
UNLOCK_PFN(OldIrql);
|
|
ExReleaseFastMutex(&MmDynamicMemoryMutex);
|
|
return NULL;
|
|
}
|
|
|
|
MmResidentAvailablePages -= 1;
|
|
MM_BUMP_COUNTER(57, 1);
|
|
|
|
// Careful incrementing is done of the PageListType enum so the page cache
|
|
// is not prematurely cannibalized.
|
|
|
|
// Pages are scanned from high descriptors first.
|
|
PageListType = FreePageList;
|
|
|
|
do {
|
|
while (start >= 0) {
|
|
count = MmPhysicalMemoryBlock->Run[start].PageCount;
|
|
Page = MmPhysicalMemoryBlock->Run[start].BasePage;
|
|
|
|
if (count && (Page < HighPage)) {
|
|
Pfn1 = MI_PFN_ELEMENT(Page);
|
|
do {
|
|
if ((ULONG)Pfn1->u3.e1.PageLocation <= (ULONG)PageListType) {
|
|
if ((Pfn1->u1.Flink != 0) &&
|
|
(Pfn1->u2.Blink != 0) &&
|
|
(Pfn1->u3.e2.ReferenceCount == 0)) {
|
|
if (Page >= MmKseg2Frame) {
|
|
PointerPte = MiReserveSystemPtes(1, SystemPteSpace, 0, 0, FALSE);
|
|
if (PointerPte == NULL) {
|
|
goto alldone;
|
|
}
|
|
BaseAddress = MiGetVirtualAddressMappedByPte(PointerPte);
|
|
TempPte.u.Hard.PageFrameNumber = Page;
|
|
MI_WRITE_VALID_PTE(PointerPte, TempPte);
|
|
} else {
|
|
PointerPte = NULL;
|
|
BaseAddress = (PVOID)(KSEG0_BASE + (Page << PAGE_SHIFT));
|
|
}
|
|
|
|
MiChargeCommitmentCantExpand(1, TRUE);
|
|
MM_TRACK_COMMIT(MM_DBG_COMMIT_CONTIGUOUS_PAGES, 1);
|
|
|
|
MmAllocatedNonPagedPool += 1;
|
|
NonPagedPoolDescriptor.TotalBigPages += 1;
|
|
|
|
// This page is in the desired range - grab it.
|
|
if (Pfn1->u3.e1.PageLocation == StandbyPageList) {
|
|
MiUnlinkPageFromList(Pfn1);
|
|
MiRestoreTransitionPte(Page);
|
|
} else {
|
|
MiUnlinkFreeOrZeroedPage(Page);
|
|
}
|
|
|
|
Pfn1->u3.e2.ShortFlags = 0;
|
|
PageColor = MI_GET_PAGE_COLOR_FROM_VA(BaseAddress);
|
|
MI_CHECK_PAGE_ALIGNMENT(Page, PageColor & MM_COLOR_MASK);
|
|
Pfn1->u3.e1.PageColor = PageColor & MM_COLOR_MASK;
|
|
PageColor += 1;
|
|
|
|
Pfn1->u3.e2.ReferenceCount = 1;
|
|
Pfn1->u2.ShareCount = 1;
|
|
Pfn1->OriginalPte.u.Long = MM_DEMAND_ZERO_WRITE_PTE;
|
|
|
|
if (PointerPte != NULL) {
|
|
Pfn1->PteAddress = PointerPte;
|
|
Pfn1->PteFrame = MI_GET_PAGE_FRAME_FROM_PTE(MiGetPteAddress(PointerPte));
|
|
} else {
|
|
Pfn1->PteAddress = BaseAddress;
|
|
Pfn1->PteFrame = (PFN_NUMBER)-1;
|
|
}
|
|
|
|
Pfn1->u3.e1.PageLocation = ActiveAndValid;
|
|
Pfn1->u3.e1.VerifierAllocation = 0;
|
|
Pfn1->u3.e1.LargeSessionAllocation = 0;
|
|
Pfn1->u3.e1.StartOfAllocation = 1;
|
|
Pfn1->u3.e1.EndOfAllocation = 1;
|
|
|
|
Pae = (PPAE_ENTRY)BaseAddress;
|
|
Pae->PaeEntry.EntriesInUse = 0;
|
|
Pae->PaeEntry.PageFrameNumber = Page;
|
|
Pae += 1;
|
|
|
|
for (i = 1; i < PAES_PER_PAGE; i += 1) {
|
|
InsertTailList(&MiFirstFreePae.PaeEntry.ListHead, &Pae->PaeEntry.ListHead);
|
|
Pae += 1;
|
|
MiFreePaes += 1;
|
|
}
|
|
|
|
// All the pages requested are available.
|
|
UNLOCK_PFN(OldIrql);
|
|
ExReleaseFastMutex(&MmDynamicMemoryMutex);
|
|
ExInsertPoolTag('DeaP', BaseAddress, PAGE_SIZE, NonPagedPool);
|
|
return BaseAddress;
|
|
}
|
|
}
|
|
|
|
Page += 1;
|
|
Pfn1 += 1;
|
|
count -= 1;
|
|
} while (count && (Page < HighPage));
|
|
}
|
|
start -= 1;
|
|
}
|
|
|
|
PageListType += 1;
|
|
start = (LONG)MmPhysicalMemoryBlock->NumberOfRuns - 1;
|
|
} while (PageListType <= StandbyPageList);
|
|
|
|
alldone:
|
|
MmResidentAvailablePages += 1;
|
|
MM_BUMP_COUNTER(57, -1);
|
|
UNLOCK_PFN(OldIrql);
|
|
|
|
ExReleaseFastMutex(&MmDynamicMemoryMutex);
|
|
return NULL;
|
|
}
|
|
|
|
VOID ExRemovePoolTag(ULONG Tag, PVOID Va, SIZE_T NumberOfBytes);
|
|
|
|
|
|
VOID MiPaeFreeEntirePage(PVOID VirtualAddress)
|
|
/*++
|
|
Routine Description:
|
|
This routine releases a page that previously contained top level
|
|
page directory pointer pages.
|
|
Arguments:
|
|
VirtualAddress - Supplies the virtual address of the page that contained
|
|
top level page directory pointer pages.
|
|
Return Value:
|
|
None.
|
|
Environment:
|
|
Kernel mode. No locks held.
|
|
*/
|
|
{
|
|
PFN_NUMBER PageFrameIndex;
|
|
PMMPFN Pfn1;
|
|
PMMPTE PointerPte;
|
|
KIRQL OldIrql;
|
|
|
|
#if defined (_X86PAE_)
|
|
if (MiNoLowMemory == TRUE) {
|
|
if (MiFreeLowMemory(VirtualAddress, 'DeaP') == TRUE) {
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
ExRemovePoolTag('DeaP', VirtualAddress, PAGE_SIZE);
|
|
|
|
if (MI_IS_PHYSICAL_ADDRESS(VirtualAddress) == 0) {
|
|
PointerPte = MiGetPteAddress(VirtualAddress);
|
|
PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE(PointerPte);
|
|
} else {
|
|
PointerPte = NULL;
|
|
PageFrameIndex = MI_CONVERT_PHYSICAL_TO_PFN(VirtualAddress);
|
|
}
|
|
|
|
Pfn1 = MI_PFN_ELEMENT(PageFrameIndex);
|
|
|
|
LOCK_PFN(OldIrql);
|
|
|
|
ASSERT(Pfn1->u1.WsIndex == 0);
|
|
ASSERT(Pfn1->u3.e1.PageLocation == ActiveAndValid);
|
|
ASSERT(Pfn1->u3.e1.VerifierAllocation == 0);
|
|
ASSERT(Pfn1->u3.e1.LargeSessionAllocation == 0);
|
|
ASSERT(Pfn1->u3.e1.StartOfAllocation == 1);
|
|
ASSERT(Pfn1->u3.e1.EndOfAllocation == 1);
|
|
ASSERT(Pfn1->u2.ShareCount == 1);
|
|
ASSERT(Pfn1->u3.e2.ReferenceCount == 1);
|
|
|
|
Pfn1->u2.ShareCount = 0;
|
|
MI_SET_PFN_DELETED(Pfn1);
|
|
#if DBG
|
|
Pfn1->u3.e1.PageLocation = StandbyPageList;
|
|
#endif //DBG
|
|
MiDecrementReferenceCount(PageFrameIndex);
|
|
|
|
if (PointerPte != NULL) {
|
|
KeFlushSingleTb(VirtualAddress, TRUE, TRUE, (PHARDWARE_PTE)PointerPte, ZeroKernelPte.u.Flush);
|
|
}
|
|
|
|
MmResidentAvailablePages += 1;
|
|
MM_BUMP_COUNTER(57, -1);
|
|
|
|
MmAllocatedNonPagedPool -= 1;
|
|
NonPagedPoolDescriptor.TotalBigPages -= 1;
|
|
|
|
UNLOCK_PFN(OldIrql);
|
|
|
|
if (PointerPte != NULL) {
|
|
MiReleaseSystemPtes(PointerPte, 1, SystemPteSpace);
|
|
}
|
|
|
|
MiReturnCommitment(1);
|
|
}
|
|
#endif
|