Windows2000/private/ntos/mm/session.c
2020-09-30 17:12:32 +02:00

2171 lines
67 KiB
C

/*++
Copyright (c) 1997 Microsoft Corporation
Module Name:
session.c
Abstract:
This module contains the routines which implement the creation and deletion of session spaces along with associated support routines.
Author:
Landy Wang (landyw) 05-Dec-1997
--*/
#include "mi.h"
ULONG MiSessionCount;
#define MM_MAXIMUM_CONCURRENT_SESSIONS 16384
FAST_MUTEX MiSessionIdMutex;
PRTL_BITMAP MiSessionIdBitmap;
#if defined (_WIN64)
#define MI_SESSION_COMMIT_CHARGE 3
#else
#define MI_SESSION_COMMIT_CHARGE 2
#endif
VOID
MiSessionAddProcess(
PEPROCESS NewProcess
);
VOID
MiSessionRemoveProcess (
VOID
);
VOID
MiInitializeSessionIds (
VOID
);
NTSTATUS
MiSessionCreateInternal(
OUT PULONG SessionId
);
NTSTATUS
MiSessionCommitPageTables(
IN PVOID StartVa,
IN PVOID EndVa
);
BOOLEAN
MiDereferenceSession(
VOID
);
VOID
MiSessionDeletePde(
IN PMMPTE Pde,
IN BOOLEAN WorkingSetInitialized,
IN PMMPTE SelfMapPde
);
#if DBG
VOID MiCheckSessionVirtualSpace(IN PVOID VirtualAddress, IN ULONG NumberOfBytes);
#endif
#ifdef ALLOC_PRAGMA
#pragma alloc_text(INIT,MiInitializeSessionIds)
#pragma alloc_text(PAGE, MmSessionLeader)
#pragma alloc_text(PAGE, MmSessionSetUnloadAddress)
#pragma alloc_text(PAGEHYDRA, MiSessionAddProcess)
#pragma alloc_text(PAGEHYDRA, MiSessionRemoveProcess)
#pragma alloc_text(PAGEHYDRA, MiSessionCreateInternal)
#pragma alloc_text(PAGEHYDRA, MmSessionCreate)
#pragma alloc_text(PAGEHYDRA, MmSessionDelete)
#pragma alloc_text(PAGEHYDRA, MiAttachSession)
#pragma alloc_text(PAGEHYDRA, MiDetachSession)
#pragma alloc_text(PAGEHYDRA, MiSessionCommitImagePages)
#pragma alloc_text(PAGEHYDRA, MiSessionCommitPageTables)
#pragma alloc_text(PAGEHYDRA, MiSessionOutSwapProcess)
#pragma alloc_text(PAGEHYDRA, MiSessionInSwapProcess)
#pragma alloc_text(PAGEHYDRA, MiSessionDeletePde)
#if DBG
#pragma alloc_text(PAGEHYDRA, MiCheckSessionVirtualSpace)
#endif
#endif
VOID MmSessionLeader(IN PEPROCESS Process)
/*++
Routine Description:
Mark the argument process as having the ability to create or delete session spaces.
This is only granted to the session manager process.
Arguments:
Process - Supplies a pointer to the privileged process.
Environment:
Kernel mode.
--*/
{
Process->Vm.u.Flags.SessionLeader = 1;
}
VOID MmSessionSetUnloadAddress (IN PDRIVER_OBJECT pWin32KDevice)
/*++
Routine Description:
Copy the win32k.sys driver object to the session structure for use during unload.
Arguments:
NewProcess - Supplies a pointer to the win32k driver object.
Environment:
Kernel mode.
--*/
{
if (MiHydra == TRUE && PsGetCurrentProcess()->Vm.u.Flags.ProcessInSession == 1) {
ASSERT (MmIsAddressValid(MmSessionSpace) == TRUE);
RtlMoveMemory (&MmSessionSpace->Win32KDriverObject, pWin32KDevice, sizeof(DRIVER_OBJECT));
}
}
VOID MiSessionAddProcess(PEPROCESS NewProcess)
/*++
Routine Description:
Add the new process to the current session space.
Arguments:
NewProcess - Supplies a pointer to the process being created.
Environment:
Kernel mode, APCs disabled.
--*/
{
KIRQL OldIrql;
PMM_SESSION_SPACE SessionGlobal;
// If the calling process has no session, then the new process won't get one either.
if (PsGetCurrentProcess()->Vm.u.Flags.ProcessInSession == 0) {
return;
}
ASSERT (MmIsAddressValid (MmSessionSpace) == TRUE);
SessionGlobal = SESSION_GLOBAL(MmSessionSpace);
LOCK_SESSION (OldIrql);
MmSessionSpace->ReferenceCount += 1;
UNLOCK_SESSION (OldIrql);
#if defined(_IA64_)
KeAddSessionSpace(&NewProcess->Pcb, &SessionGlobal->SessionMapInfo);
#endif
// Link the process entry into the session space and WSL structures.
LOCK_EXPANSION (OldIrql);
if (IsListEmpty(&SessionGlobal->ProcessList)) {
if (MmSessionSpace->Vm.AllowWorkingSetAdjustment == FALSE) {
ASSERT (MmSessionSpace->u.Flags.WorkingSetInserted == 0);
MmSessionSpace->Vm.AllowWorkingSetAdjustment = TRUE;
InsertTailList (&MmWorkingSetExpansionHead.ListHead, &SessionGlobal->Vm.WorkingSetExpansionLinks);
MmSessionSpace->u.Flags.WorkingSetInserted = 1;
}
}
InsertTailList (&SessionGlobal->ProcessList, &NewProcess->SessionProcessLinks);
NewProcess->Vm.u.Flags.ProcessInSession = 1;
UNLOCK_EXPANSION (OldIrql);
}
VOID MiSessionRemoveProcess (VOID)
/*++
Routine Description:
This routine removes the current process from the current session space.
This may trigger a substantial round of dereferencing and resource freeing
if it is also the last process in the session, (holding the last image in the group, etc).
Environment:
Kernel mode, APC_LEVEL and below, but queueing of APCs to this thread has been permanently disabled.
This is the last thread in the process being deleted.
The caller has ensured that this process is not on the expansion list and therefore there can be no races in regards to trimming.
--*/
{
KIRQL OldIrql;
PEPROCESS CurrentProcess;
#if DBG
ULONG Found;
PEPROCESS Process;
PLIST_ENTRY NextEntry;
PMM_SESSION_SPACE SessionGlobal;
#endif
CurrentProcess = PsGetCurrentProcess();
if (CurrentProcess->Vm.u.Flags.ProcessInSession == 0) {
return;
}
ASSERT (MmIsAddressValid (MmSessionSpace) == TRUE);
// Remove this process from the list of processes in the current session.
LOCK_EXPANSION (OldIrql);
#if DBG
SessionGlobal = SESSION_GLOBAL(MmSessionSpace);
Found = 0;
NextEntry = SessionGlobal->ProcessList.Flink;
while (NextEntry != &SessionGlobal->ProcessList)
{
Process = CONTAINING_RECORD (NextEntry, EPROCESS, SessionProcessLinks);
if (Process == CurrentProcess) {
Found = 1;
}
NextEntry = NextEntry->Flink;
}
ASSERT (Found == 1);
#endif
RemoveEntryList (&CurrentProcess->SessionProcessLinks);
UNLOCK_EXPANSION (OldIrql);
// Decrement this process' reference count to the session.
// If this is the last reference, then the entire session will be destroyed upon return.
// This includes unloading drivers, unmapping pools, freeing page tables, etc.
MiDereferenceSession ();
}
VOID MiInitializeSessionIds (VOID)
/*++
Routine Description:
This routine creates and initializes session ID allocation/deallocation.
--*/
{
// If this ever grows beyond the size of a page, both the allocation and deletion code will need to be updated.
ASSERT (sizeof(MM_SESSION_SPACE) <= PAGE_SIZE);
ExInitializeFastMutex (&MiSessionIdMutex);
MiCreateBitMap (&MiSessionIdBitmap, MM_MAXIMUM_CONCURRENT_SESSIONS, PagedPool);
if (MiSessionIdBitmap == NULL) {
MiCreateBitMap (&MiSessionIdBitmap, MM_MAXIMUM_CONCURRENT_SESSIONS, NonPagedPoolMustSucceed);
}
RtlClearAllBits (MiSessionIdBitmap);
}
NTSTATUS MiSessionCreateInternal(OUT PULONG SessionId)
/*++
Routine Description:
This routine creates the data structure that describes and maintains the session space.
It resides at the beginning of the session space.
Carefully construct the first page mapping to bootstrap the fault handler which relies on the session space data structure being present and valid.
In 32-bit NT, this initial mapping for the portion of session space
mapped by the first PDE will automatically be inherited by all child processes when the system copies the system portion of the page directory for new address spaces.
Additional entries are faulted in by the session space fault handler, which references this structure.
For 64-bit NT, everything is automatically inherited.
This routine commits virtual memory within the current session space with backing pages.
The virtual addresses within session space are allocated with a separate facility in the image management facility.
This is because images must be at a unique system wide virtual address.
Arguments:
SessionId - Supplies a pointer to place the new session ID into.
Return Value:
STATUS_SUCCESS if all went well, various failure status codes if the session was not created.
Environment:
Kernel mode, no mutexes held.
--*/
{
KIRQL OldIrql;
PMMPTE PointerPpe;
PMMPTE PointerPde;
PMMPTE PointerPte;
NTSTATUS Status;
PMM_SESSION_SPACE SessionSpace;
PMM_SESSION_SPACE SessionGlobal;
PFN_NUMBER ResidentPages;
BOOLEAN GotCommit;
BOOLEAN GotPages;
PMMPFN Pfn1;
MMPTE TempPte;
MMPTE AliasPte;
MMPTE PreviousPte;
ULONG_PTR Va;
PFN_NUMBER DataPage;
PFN_NUMBER PageTablePage;
PFN_NUMBER PageDirectoryPage;
ULONG PageColor;
SessionSpace = NULL;
GotCommit = FALSE;
GotPages = FALSE;
ASSERT (MmIsAddressValid(SessionSpace) == FALSE);
ExAcquireFastMutex (&MiSessionIdMutex);
*SessionId = RtlFindClearBitsAndSet (MiSessionIdBitmap, 1, 0);
ExReleaseFastMutex (&MiSessionIdMutex);
if (*SessionId == 0xFFFFFFFF) {
#if DBG
DbgPrint("MiSessionCreateInternal: No session IDs available\n");
#endif
MM_BUMP_SESSION_FAILURES (MM_SESSION_FAILURE_NO_IDS);
return STATUS_NO_MEMORY;
}
ResidentPages = MI_SESSION_COMMIT_CHARGE;
if (MiChargeCommitment (ResidentPages, NULL) == FALSE) {
#if DBG
if (MmDebug & MM_DBG_SESSIONS) {
DbgPrint("MiSessionCreateInternal: No commit for %d pages\n",
ResidentPages);
}
#endif
MM_BUMP_SESSION_FAILURES (MM_SESSION_FAILURE_NO_COMMIT);
goto Failure;
}
GotCommit = TRUE;
MM_TRACK_COMMIT (MM_DBG_COMMIT_SESSION_CREATE, ResidentPages);
LOCK_PFN (OldIrql);
// Check to make sure the physical pages are available.
if ((SPFN_NUMBER)(ResidentPages + MI_SESSION_SPACE_WORKING_SET_MINIMUM) > MI_NONPAGABLE_MEMORY_AVAILABLE()) {
#if DBG
if (MmDebug & MM_DBG_SESSIONS) {
DbgPrint("MiSessionCreateInternal: No Resident Pages %d, Need %d\n",
MmResidentAvailablePages,
ResidentPages + MI_SESSION_SPACE_WORKING_SET_MINIMUM);
}
#endif
UNLOCK_PFN (OldIrql);
MM_BUMP_SESSION_FAILURES (MM_SESSION_FAILURE_NO_RESIDENT);
goto Failure;
}
GotPages = TRUE;
MmResidentAvailablePages -= (ResidentPages + MI_SESSION_SPACE_WORKING_SET_MINIMUM);
MM_BUMP_COUNTER(40, ResidentPages + MI_SESSION_SPACE_WORKING_SET_MINIMUM);
#if defined (_WIN64)
// Initialize the page directory page.
MiEnsureAvailablePageOrWait (NULL, NULL);
PageColor = MI_GET_PAGE_COLOR_FROM_VA (NULL);
PageDirectoryPage = MiRemoveZeroPageIfAny (PageColor);
if (PageDirectoryPage == 0) {
PageDirectoryPage = MiRemoveAnyPage (PageColor);
UNLOCK_PFN (OldIrql);
MiZeroPhysicalPage (PageDirectoryPage, PageColor);
LOCK_PFN (OldIrql);
}
// The global bit is masked off since we need to make sure the TB entry
// is flushed when we switch to a process in a different session space.
TempPte.u.Long = ValidKernelPdeLocal.u.Long;
TempPte.u.Hard.PageFrameNumber = PageDirectoryPage;
PointerPpe = MiGetPpeAddress ((PVOID)MmSessionSpace);
// Another thread may have gotten here before us, check for that now.
if (PointerPpe->u.Long != 0) {
#if DBG
ASSERT (PointerPpe->u.Hard.Valid == 1);
DbgPrint("MiSessionCreateInternal: Detected PPE %p race\n",
PointerPpe->u.Long);
DbgBreakPoint ();
#endif //DBG
Pfn1 = MI_PFN_ELEMENT (PageDirectoryPage);
ASSERT (Pfn1->u2.ShareCount == 0);
ASSERT (Pfn1->u3.e2.ReferenceCount == 0);
Pfn1->u3.e2.ReferenceCount = 1;
Pfn1->OriginalPte.u.Long = MM_DEMAND_ZERO_WRITE_PTE;
MI_SET_PFN_DELETED (Pfn1);
#if DBG
Pfn1->u3.e1.PageLocation = StandbyPageList;
#endif
MiDecrementReferenceCount (PageDirectoryPage);
MmResidentAvailablePages += (ResidentPages + MI_SESSION_SPACE_WORKING_SET_MINIMUM);
MM_BUMP_COUNTER(46, ResidentPages + MI_SESSION_SPACE_WORKING_SET_MINIMUM);
MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_INITIAL_PAGETABLE_FREE_RACE, 1);
GotPages = FALSE;
UNLOCK_PFN (OldIrql);
MM_BUMP_SESSION_FAILURES (MM_SESSION_FAILURE_RACE_DETECTED);
goto Failure;
}
*PointerPpe = TempPte;
MiInitializePfnForOtherProcess (PageDirectoryPage, PointerPpe, 0);
Pfn1 = MI_PFN_ELEMENT (PageDirectoryPage);
Pfn1->PteFrame = 0;
ASSERT (MI_PFN_ELEMENT(PageDirectoryPage)->u1.WsIndex == 0);
KeFillEntryTb ((PHARDWARE_PTE) PointerPpe,
MiGetVirtualAddressMappedByPte (PointerPpe),
FALSE);
#endif
// Initialize the page table page.
MiEnsureAvailablePageOrWait (NULL, NULL);
PageColor = MI_GET_PAGE_COLOR_FROM_VA (NULL);
PageTablePage = MiRemoveZeroPageIfAny (PageColor);
if (PageTablePage == 0) {
PageTablePage = MiRemoveAnyPage (PageColor);
UNLOCK_PFN (OldIrql);
MiZeroPhysicalPage (PageTablePage, PageColor);
LOCK_PFN (OldIrql);
}
// The global bit is masked off since we need to make sure the TB entry
// is flushed when we switch to a process in a different session space.
TempPte.u.Long = ValidKernelPdeLocal.u.Long;
TempPte.u.Hard.PageFrameNumber = PageTablePage;
PointerPde = MiGetPdeAddress ((PVOID)MmSessionSpace);
#if !defined (_WIN64)
// Another thread may have gotten here before us, check for that now.
if (PointerPde->u.Long != 0) {
#if DBG
ASSERT (PointerPde->u.Hard.Valid == 1);
DbgPrint("MiSessionCreateInternal: Detected PDE %p race\n",
PointerPde->u.Long);
DbgBreakPoint ();
#endif //DBG
Pfn1 = MI_PFN_ELEMENT (PageTablePage);
ASSERT (Pfn1->u2.ShareCount == 0);
ASSERT (Pfn1->u3.e2.ReferenceCount == 0);
Pfn1->u3.e2.ReferenceCount = 1;
Pfn1->OriginalPte.u.Long = MM_DEMAND_ZERO_WRITE_PTE;
MI_SET_PFN_DELETED (Pfn1);
#if DBG
Pfn1->u3.e1.PageLocation = StandbyPageList;
#endif
MiDecrementReferenceCount (PageTablePage);
MmResidentAvailablePages += (ResidentPages + MI_SESSION_SPACE_WORKING_SET_MINIMUM);
MM_BUMP_COUNTER(46, ResidentPages + MI_SESSION_SPACE_WORKING_SET_MINIMUM);
MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_INITIAL_PAGETABLE_FREE_RACE, 1);
GotPages = FALSE;
UNLOCK_PFN (OldIrql);
MM_BUMP_SESSION_FAILURES (MM_SESSION_FAILURE_RACE_DETECTED);
goto Failure;
}
#endif
MI_WRITE_VALID_PTE (PointerPde, TempPte);
// This page frame references itself instead of the current (SMSS.EXE)
// page directory as its PteFrame. This allows the current process to
// appear more normal (at least on 32-bit NT). It just means we have
// to treat this page specially during teardown.
MiInitializePfnForOtherProcess (PageTablePage, PointerPde, PageTablePage);
// This page is never paged, ensure that its WsIndex stays clear so the
// release of the page is handled correctly.
ASSERT (MI_PFN_ELEMENT(PageTablePage)->u1.WsIndex == 0);
Va = (ULONG_PTR)MiGetPteAddress (MmSessionSpace);
KeFillEntryTb ((PHARDWARE_PTE) PointerPde, (PMMPTE)Va, FALSE);
MiEnsureAvailablePageOrWait (NULL, NULL);
PageColor = MI_GET_PAGE_COLOR_FROM_VA (NULL);
DataPage = MiRemoveZeroPageIfAny (PageColor);
if (DataPage == 0) {
DataPage = MiRemoveAnyPage (PageColor);
UNLOCK_PFN (OldIrql);
MiZeroPhysicalPage (DataPage, PageColor);
LOCK_PFN (OldIrql);
}
// The global bit is masked off since we need to make sure the TB entry
// is flushed when we switch to a process in a different session space.
TempPte.u.Long = ValidKernelPteLocal.u.Long;
TempPte.u.Hard.PageFrameNumber = DataPage;
PointerPte = MiGetPteAddress (MmSessionSpace);
MI_WRITE_VALID_PTE (PointerPte, TempPte);
MiInitializePfn (DataPage, PointerPte, 1);
ASSERT (MI_PFN_ELEMENT(DataPage)->u1.WsIndex == 0);
UNLOCK_PFN (OldIrql);
KeFillEntryTb ((PHARDWARE_PTE) PointerPte, (PMMPTE)MmSessionSpace, FALSE);
AliasPte = *PointerPte;
// Initialize the new session space data structure.
SessionSpace = MmSessionSpace;
MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_INITIAL_PAGETABLE_ALLOC, 1);
MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_INITIAL_PAGE_ALLOC, 1);
SessionSpace->ReferenceCount = 1;
SessionSpace->u.LongFlags = 0;
SessionSpace->SessionId = *SessionId;
SessionSpace->SessionPageDirectoryIndex = PageTablePage;
SessionSpace->Color = PageColor;
// Track the page table page and the data page.
SessionSpace->NonPagablePages = ResidentPages;
SessionSpace->CommittedPages = ResidentPages;
#if defined (_WIN64)
// Initialize the session data page directory entry so trimmers can attach.
PointerPpe = MiGetPpeAddress ((PVOID)MmSessionSpace);
SessionSpace->PageDirectory = *PointerPpe;
#else
// Load the session data page table entry so that other processes
// can fault in the mapping.
SessionSpace->PageTables[PointerPde - MiGetPdeAddress (MmSessionBase)] = *PointerPde;
#endif
// Reserve a global system PTE and map the data page into it.
SessionSpace->GlobalPteEntry = MiReserveSystemPtes (1,
SystemPteSpace,
0,
0,
FALSE
);
if (SessionSpace->GlobalPteEntry == NULL) {
#if DBG
if (MmDebug & MM_DBG_SESSIONS) {
DbgPrint("MiSessionCreateInternal: No memory for session self-map\n");
}
#endif
MM_BUMP_SESSION_FAILURES (MM_SESSION_FAILURE_NO_SYSPTES);
goto Failure;
}
*(SessionSpace->GlobalPteEntry) = AliasPte;
SessionSpace->GlobalVirtualAddress = MiGetVirtualAddressMappedByPte (SessionSpace->GlobalPteEntry);
SessionGlobal = SESSION_GLOBAL(SessionSpace);
// This list entry is only referenced while within the
// session space and has session space (not global) addresses.
InitializeListHead (&SessionSpace->ImageList);
// Initialize the session space pool.
Status = MiInitializeSessionPool ();
if (!NT_SUCCESS(Status)) {
#if DBG
if (MmDebug & MM_DBG_SESSIONS) {
DbgPrint("MiSessionCreateInternal: No memory for session pool\n");
}
#endif
goto Failure;
}
// Initialize the view mapping support - note this must happen after
// initializing session pool.
if (MiInitializeSystemSpaceMap (&SessionGlobal->Session) == FALSE) {
#if DBG
if (MmDebug & MM_DBG_SESSIONS) {
DbgPrint("MiSessionCreateInternal: No memory for view mapping\n");
}
#endif
goto Failure;
}
// Use the global virtual address rather than the session space virtual
// address to set up fields that need to be globally accessible.
InitializeListHead (&SessionGlobal->WsListEntry);
InitializeListHead (&SessionGlobal->ProcessList);
KeInitializeSpinLock (&SessionGlobal->SpinLock);
#if defined(_IA64_)
KeEnableSessionSharing(&SessionGlobal->SessionMapInfo);
#endif
return STATUS_SUCCESS;
Failure:
if (GotCommit == TRUE) {
MiReturnCommitment (ResidentPages);
MM_TRACK_COMMIT (MM_DBG_COMMIT_RETURN_SESSION_CREATE_FAILURE1,
ResidentPages);
}
if (GotPages == TRUE) {
#if defined (_WIN64)
PointerPpe = MiGetPpeAddress (MI_SESSION_POOL);
ASSERT (PointerPpe->u.Hard.Valid != 0);
#endif
PointerPde = MiGetPdeAddress (MI_SESSION_POOL);
ASSERT (PointerPde->u.Hard.Valid != 0);
// Note that this call will acquire the session space lock, so the
// data page better be mapped.
MiFreeSessionSpaceMap ();
// Free the initial page table page that was allocated for the
// paged pool range (if it has been allocated at this point).
MiFreeSessionPoolBitMaps ();
if (SessionSpace->GlobalPteEntry) {
MiReleaseSystemPtes (SessionSpace->GlobalPteEntry,
1,
SystemPteSpace);
SessionSpace->GlobalPteEntry = (PMMPTE)0;
}
LOCK_PFN (OldIrql);
// Free the session data structure page.
Pfn1 = MI_PFN_ELEMENT (DataPage);
MiDecrementShareAndValidCount (Pfn1->PteFrame);
MI_SET_PFN_DELETED (Pfn1);
MiDecrementShareCountOnly (DataPage);
MI_FLUSH_SINGLE_SESSION_TB (MmSessionSpace,
TRUE,
TRUE,
(PHARDWARE_PTE)PointerPte,
ZeroKernelPte.u.Flush,
PreviousPte);
MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_INITIAL_PAGE_FREE_FAIL1, 1);
// Free the page table page.
Pfn1 = MI_PFN_ELEMENT (PageTablePage);
ASSERT (PageTablePage == Pfn1->PteFrame);
ASSERT (Pfn1->u2.ShareCount == 2);
Pfn1->u2.ShareCount -= 1;
MI_SET_PFN_DELETED (Pfn1);
MiDecrementShareCountOnly (PageTablePage);
MI_FLUSH_SINGLE_SESSION_TB (MiGetVirtualAddressMappedByPte(PointerPde),
TRUE,
TRUE,
(PHARDWARE_PTE)PointerPde,
ZeroKernelPte.u.Flush,
PreviousPte);
#if defined (_WIN64)
// Free the page directory page.
Pfn1 = MI_PFN_ELEMENT (PageDirectoryPage);
ASSERT (PageDirectoryPage == Pfn1->PteFrame);
ASSERT (Pfn1->u2.ShareCount == 1);
ASSERT (Pfn1->u3.e2.ReferenceCount == 1);
MI_SET_PFN_DELETED (Pfn1);
MiDecrementShareCountOnly (PageDirectoryPage);
MI_FLUSH_SINGLE_SESSION_TB (MiGetVirtualAddressMappedByPte(PointerPpe),
TRUE,
TRUE,
(PHARDWARE_PTE)PointerPpe,
ZeroKernelPte.u.Flush,
PreviousPte);
#endif
// Now that the PDE has been zeroed, another thread in this process could attempt a session create so be careful not to count on any anything in this particular session.
MmResidentAvailablePages += (ResidentPages + MI_SESSION_SPACE_WORKING_SET_MINIMUM);
MM_BUMP_COUNTER (49, ResidentPages + MI_SESSION_SPACE_WORKING_SET_MINIMUM);
MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_INITIAL_PAGETABLE_FREE_FAIL1, 1);
UNLOCK_PFN (OldIrql);
}
ExAcquireFastMutex (&MiSessionIdMutex);
ASSERT (RtlCheckBit (MiSessionIdBitmap, *SessionId));
RtlClearBits (MiSessionIdBitmap, *SessionId, 1);
ExReleaseFastMutex (&MiSessionIdMutex);
return STATUS_NO_MEMORY;
}
NTSTATUS MmSessionCreate(OUT PULONG SessionId)
/*++
Routine Description:
Called from NtSetSystemInformation() to create a session space in the calling process with the specified SessionId.
An error is returned if the calling process already has a session Space.
Arguments:
SessionId - Supplies a pointer to place the resulting session id in.
Return Value:
Various NTSTATUS error codes.
Environment:
Kernel mode, no mutexes held.
--*/
{
KIRQL OldIrql;
NTSTATUS Status;
PEPROCESS CurrentProcess;
#if DBG
PMMPTE StartPde;
PMMPTE EndPde;
#endif
if (MiHydra == FALSE) {
return STATUS_INVALID_SYSTEM_SERVICE;
}
CurrentProcess = PsGetCurrentProcess();
// A simple check to see if the calling process already has a session space.
// No need to go through all this if it does. Creation races are caught
// below and recovered from regardless.
if (CurrentProcess->Vm.u.Flags.ProcessInSession == 1) {
return STATUS_ALREADY_COMMITTED;
}
if (CurrentProcess->Vm.u.Flags.SessionLeader == 0) {
// Only the session manager can create a session.
return STATUS_INVALID_SYSTEM_SERVICE;
}
#if DBG
ASSERT (MmIsAddressValid(MmSessionSpace) == FALSE);
#if defined (_WIN64)
ASSERT ((MiGetPpeAddress(MmSessionBase))->u.Long == ZeroKernelPte.u.Long);
#else
StartPde = MiGetPdeAddress (MmSessionBase);
EndPde = MiGetPdeAddress (MI_SESSION_SPACE_END);
while (StartPde < EndPde) {
ASSERT (StartPde->u.Long == ZeroKernelPte.u.Long);
StartPde += 1;
}
#endif
#endif
KeEnterCriticalRegion();
Status = MiSessionCreateInternal (SessionId);
if (!NT_SUCCESS(Status)) {
KeLeaveCriticalRegion();
return Status;
}
LOCK_EXPANSION (OldIrql);
MiSessionCount += 1;
UNLOCK_EXPANSION (OldIrql);
// Add the session space to the working set list.
Status = MiSessionInitializeWorkingSetList ();
if (!NT_SUCCESS(Status)) {
MiDereferenceSession ();
KeLeaveCriticalRegion();
return Status;
}
KeLeaveCriticalRegion();
MmSessionSpace->u.Flags.Initialized = 1;
LOCK_EXPANSION (OldIrql);
CurrentProcess->Vm.u.Flags.ProcessInSession = 1;
UNLOCK_EXPANSION (OldIrql);
return Status;
}
NTSTATUS MmSessionDelete(ULONG SessionId)
/*++
Routine Description:
Called from NtSetSystemInformation() to detach from an existing session space in the calling process.
An error is returned if the calling process has no session Space.
Arguments:
SessionId - Supplies the session id to delete.
Return Value:
STATUS_SUCCESS on success, STATUS_UNABLE_TO_FREE_VM on failure.
This process will not be able to access session space anymore upon a successful return.
If this is the last process in the session then the entire session is torn down.
Environment:
Kernel mode, no mutexes held.
--*/
{
PEPROCESS CurrentProcess;
if (MiHydra == FALSE) {
return STATUS_INVALID_SYSTEM_SERVICE;
}
CurrentProcess = PsGetCurrentProcess();
// See if the calling process has a session space - this must be
// checked since we can be called via a system service.
if (CurrentProcess->Vm.u.Flags.ProcessInSession == 0) {
#if DBG
DbgPrint ("MmSessionDelete: Process %p not in a session\n",
PsGetCurrentProcess());
DbgBreakPoint();
#endif
return STATUS_UNABLE_TO_FREE_VM;
}
if (CurrentProcess->Vm.u.Flags.SessionLeader == 0) {
// Only the session manager can delete a session. This is because
// it affects the virtual mappings for all threads within the process
// when this address space is deleted. This is different from normal
// VAD clearing because win32k and other drivers rely on this space.
return STATUS_UNABLE_TO_FREE_VM;
}
ASSERT (MmIsAddressValid(MmSessionSpace) == TRUE);
if (MmSessionSpace->SessionId != SessionId) {
#if DBG
DbgPrint("MmSessionDelete: Wrong SessionId! Own %d, Ask %d\n",
MmSessionSpace->SessionId,
SessionId);
DbgBreakPoint();
#endif
return STATUS_UNABLE_TO_FREE_VM;
}
KeEnterCriticalRegion ();
MiDereferenceSession ();
KeLeaveCriticalRegion ();
return STATUS_SUCCESS;
}
VOID MiAttachSession(IN PMM_SESSION_SPACE SessionGlobal)
/*++
Routine Description:
Attaches to the specified session space.
Arguments:
SessionGlobal - Supplies a pointer to the session to attach to.
Environment:
Kernel mode.
No locks held.
Current process must not have a session space - ie: the caller should be the system process or smss.exe.
--*/
{
PMMPTE PointerPde;
#if DBG
PMMPTE EndPde;
ASSERT (MiHydra == TRUE);
ASSERT (PsGetCurrentProcess()->Vm.u.Flags.ProcessInSession == 0);
#if defined (_WIN64)
PointerPde = MiGetPpeAddress (MmSessionBase);
ASSERT (PointerPde->u.Long == ZeroKernelPte.u.Long);
#else
PointerPde = MiGetPdeAddress (MmSessionBase);
EndPde = MiGetPdeAddress (MI_SESSION_SPACE_END);
while (PointerPde < EndPde) {
ASSERT (PointerPde->u.Long == ZeroKernelPte.u.Long);
PointerPde += 1;
}
#endif
#endif
#if defined (_WIN64)
PointerPde = MiGetPpeAddress (MmSessionBase);
MI_WRITE_VALID_PTE (PointerPde, SessionGlobal->PageDirectory);
#else
PointerPde = MiGetPdeAddress (MmSessionBase);
RtlCopyMemory (PointerPde,
&SessionGlobal->PageTables[0],
MI_SESSION_SPACE_PAGE_TABLES * sizeof (MMPTE));
#endif
#if defined(_IA64_)
KeAttachSessionSpace(&SessionGlobal->SessionMapInfo);
#endif
}
VOID MiDetachSession(VOID)
/*++
Routine Description:
Detaches from the specified session space.
Environment:
Kernel mode. No locks held. Current process must not have a session
space to return to - ie: this should be the system process.
--*/
{
PMMPTE PointerPde;
PMMPTE EndPde;
KIRQL OldIrql;
ASSERT (MiHydra == TRUE);
ASSERT (PsGetCurrentProcess()->Vm.u.Flags.ProcessInSession == 0);
ASSERT (MmIsAddressValid(MmSessionSpace) == TRUE);
#if defined (_WIN64)
PointerPde = MiGetPpeAddress (MmSessionBase);
PointerPde->u.Long = ZeroKernelPte.u.Long;
#else
PointerPde = MiGetPdeAddress (MmSessionBase);
EndPde = MiGetPdeAddress (MI_SESSION_SPACE_END);
RtlZeroMemory (PointerPde, MI_SESSION_SPACE_PAGE_TABLES * sizeof (MMPTE));
#endif
MI_FLUSH_SESSION_TB (OldIrql);
#if defined(_IA64_)
KeDetachSessionSpace();
#endif
}
#if DBG
VOID MiCheckSessionVirtualSpace(IN PVOID VirtualAddress, IN ULONG NumberOfBytes)
/*++
Routine Description:
Used to verify that no drivers fail to clean up their session allocations.
Arguments:
VirtualAddress - Supplies the starting virtual address to check.
NumberOfBytes - Supplies the number of bytes to check.
Return Value:
TRUE if all the PTEs have been freed, FALSE if not.
Environment:
Kernel mode. APCs disabled.
--*/
{
PMMPTE StartPde;
PMMPTE EndPde;
PMMPTE StartPte;
PMMPTE EndPte;
ULONG Index;
// Check the specified region. Everything should have been cleaned up already.
#if defined (_WIN64)
ASSERT (MiGetPpeAddress (VirtualAddress)->u.Hard.Valid == 1);
#endif
StartPde = MiGetPdeAddress (VirtualAddress);
EndPde = MiGetPdeAddress ((PVOID)((PCHAR)VirtualAddress + NumberOfBytes - 1));
StartPte = MiGetPteAddress (VirtualAddress);
EndPte = MiGetPteAddress ((PVOID)((PCHAR)VirtualAddress + NumberOfBytes - 1));
Index = (ULONG)(StartPde - MiGetPdeAddress (MmSessionBase));
#if defined (_WIN64)
while (StartPde <= EndPde && StartPde->u.Long == 0)
#else
while (StartPde <= EndPde && MmSessionSpace->PageTables[Index].u.Long == 0)
#endif
{
StartPde += 1;
Index += 1;
StartPte = MiGetVirtualAddressMappedByPte (StartPde);
}
while (StartPte <= EndPte)
{
if (MiIsPteOnPdeBoundary(StartPte)) {
StartPde = MiGetPteAddress (StartPte);
Index = (ULONG)(StartPde - MiGetPdeAddress (MmSessionBase));
#if defined (_WIN64)
while (StartPde <= EndPde && StartPde->u.Long == 0)
#else
while (StartPde <= EndPde && MmSessionSpace->PageTables[Index].u.Long == 0)
#endif
{
Index += 1;
StartPde += 1;
StartPte = MiGetVirtualAddressMappedByPte (StartPde);
}
if (StartPde > EndPde) {
break;
}
}
if (StartPte->u.Long != 0 && StartPte->u.Long != MM_KERNEL_NOACCESS_PTE) {
DbgPrint("MiCheckSessionVirtualSpace: StartPte 0x%p is still valid! 0x%p, VA 0x%p\n",
StartPte,
StartPte->u.Long,
MiGetVirtualAddressMappedByPte(StartPte));
DbgBreakPoint();
}
StartPte += 1;
}
}
#endif
VOID MiSessionDeletePde(IN PMMPTE Pde, IN BOOLEAN WorkingSetInitialized, IN PMMPTE SelfMapPde)
/*++
Routine Description:
Used to delete a page directory entry from a session space.
Arguments:
Pde - Supplies the page directory entry to delete.
WorkingSetInitialized - Supplies TRUE if the working set has been initialized.
SelfMapPde - Supplies the page directory entry that contains the self map session page.
Environment:
Kernel mode. PFN lock held.
--*/
{
PMMPFN Pfn1;
PMMPFN Pfn2;
KIRQL OldIrql2;
PFN_NUMBER PageFrameIndex;
BOOLEAN SelfMapPage;
#if DBG
ULONG i;
PMMPTE PointerPte;
#endif
if (Pde->u.Long == ZeroKernelPte.u.Long) {
return;
}
SelfMapPage = (Pde == SelfMapPde ? TRUE : FALSE);
ASSERT (Pde->u.Hard.Valid == 1);
PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE (Pde);
Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
#if DBG
ASSERT (PageFrameIndex <= MmHighestPhysicalPage);
if (WorkingSetInitialized == TRUE) {
ASSERT (Pfn1->u1.WsIndex);
}
ASSERT (Pfn1->u3.e1.PrototypePte == 0);
ASSERT (Pfn1->u3.e2.ReferenceCount == 1);
ASSERT (Pfn1->PteFrame <= MmHighestPhysicalPage);
Pfn2 = MI_PFN_ELEMENT (Pfn1->PteFrame);
// Verify the containing page table is still the page
// table page mapping the session data structure.
if (SelfMapPage == FALSE) {
// Note these ASSERTs will fail if win32k leaks pool.
ASSERT (Pfn1->u2.ShareCount == 1);
#if !defined (_WIN64)
// 32-bit NT points the additional page tables at the master.
// 64-bit NT doesn't need to use this trick as there is always
// an additional hierarchy level.
ASSERT (Pfn1->PteFrame == MI_GET_PAGE_FRAME_FROM_PTE (SelfMapPde));
#endif
ASSERT (Pfn2->u2.ShareCount > 2);
}
else {
ASSERT (Pfn1 == Pfn2);
ASSERT (Pfn1->u2.ShareCount == 2);
}
// Make sure the page table page is zeroed. It should always be zero
// unless win32k leaks pool or random bits get set.
PointerPte = (PMMPTE)MiMapPageInHyperSpace (PageFrameIndex, &OldIrql2);
i = 0;
while (i < PTE_PER_PAGE) {
if (PointerPte->u.Long != 0 && PointerPte->u.Long != MM_KERNEL_NOACCESS_PTE) {
DbgPrint("MM: Deleting session page table: Index %d PTE %p is not zero! %p\n",
i,
PointerPte,
PointerPte->u.Long);
DbgBreakPoint();
}
PointerPte += 1;
i += 1;
}
MiUnmapPageInHyperSpace (OldIrql2);
#endif // DBG
if (SelfMapPage == FALSE) {
MiDecrementShareAndValidCount (Pfn1->PteFrame);
}
else {
ASSERT (Pfn1 == Pfn2);
ASSERT (Pfn1->u2.ShareCount == 2);
Pfn1->u2.ShareCount -= 1;
}
MI_SET_PFN_DELETED (Pfn1);
MiDecrementShareCountOnly (PageFrameIndex);
}
BOOLEAN MiDereferenceSession(VOID)
/*++
Routine Description:
Decrement this process' reference count to the session, unmapping access to the session for the current process.
If this is the last process reference to this session, then the entire session will be destroyed upon return.
This includes unloading drivers, unmapping pools, freeing page tables, etc.
Return Value:
TRUE if the session was deleted, FALSE if only this process' access to the session was deleted.
Environment:
Kernel mode, no mutexes held, APCs disabled.
--*/
{
KIRQL OldIrql;
ULONG Index;
ULONG CountReleased;
ULONG SessionId;
PFN_NUMBER PageFrameIndex;
ULONG SessionDataPdeIndex;
KEVENT Event;
MMPTE PreviousPte;
PMMPFN Pfn1;
PMMPTE PointerPpe;
PMMPTE PointerPte;
PMMPTE EndPte;
PMMPTE GlobalPteEntrySave;
PMMPTE StartPde;
PMMPTE EndPde;
PMM_SESSION_SPACE SessionGlobal;
MMPTE SavePageTables[MI_SESSION_SPACE_PAGE_TABLES];
BOOLEAN WorkingSetWasInitialized;
ULONG AttachCount;
PEPROCESS Process;
ASSERT ((PsGetCurrentProcess()->Vm.u.Flags.ProcessInSession == 1) ||
((MmSessionSpace->u.Flags.Initialized == 0) && (PsGetCurrentProcess()->Vm.u.Flags.SessionLeader == 1) && (MmSessionSpace->ReferenceCount == 1)));
SessionId = MmSessionSpace->SessionId;
ASSERT (RtlCheckBit (MiSessionIdBitmap, SessionId));
LOCK_SESSION (OldIrql);
if (MmSessionSpace->ReferenceCount > 1) {
MmSessionSpace->ReferenceCount -= 1;
UNLOCK_SESSION (OldIrql);
Process = PsGetCurrentProcess();
LOCK_EXPANSION (OldIrql);
Process->Vm.u.Flags.ProcessInSession = 0;
UNLOCK_EXPANSION (OldIrql);
// Don't delete any non-smss session space mappings here. Let them
// live on through process death. This handles the case where
// MmDispatchWin32Callout picks csrss - csrss has exited as it's not
// the last process (smss is). smss is simultaneously detaching from
// the session and since it is the last process, it's waiting on
// the AttachCount below. The dispatch callout ends up in csrss but
// has no way to synchronize against csrss exiting through this path
// as the object reference count doesn't stop it. So leave the
// session space mappings alive so the callout can execute through
// the remains of csrss.
// Note that when smss detaches, the address space must get cleared
// here so that subsequent session creations by smss will succeed.
if (Process->Vm.u.Flags.SessionLeader == 1) {
#if defined (_WIN64)
StartPde = MiGetPpeAddress (MmSessionBase);
StartPde->u.Long = ZeroKernelPte.u.Long;
#else
StartPde = MiGetPdeAddress (MmSessionBase);
EndPde = MiGetPdeAddress (MI_SESSION_SPACE_END);
RtlZeroMemory (StartPde, (EndPde - StartPde) * sizeof(MMPTE));
#endif
MI_FLUSH_SESSION_TB (OldIrql);// Flush the session space TB entries.
}
#if defined(_IA64_)
KeDetachSessionSpace();
#endif
return FALSE;
}
MmSessionSpace->u.Flags.BeingDeleted = 1;// Mark it as being deleted.
// This is the final dereference.
// We could be any process including SMSS when a session space load fails.
// Note also that processes can terminate in any order as well.
UNLOCK_SESSION (OldIrql);
SessionGlobal = SESSION_GLOBAL (MmSessionSpace);
LOCK_EXPANSION (OldIrql);
// Wait for any cross-session process attaches to detach.
// Refuse subsequent attempts to cross-session attach so the address invalidation code doesn't surprise an ongoing or subsequent attachee.
ASSERT (MmSessionSpace->u.Flags.DeletePending == 0);
MmSessionSpace->u.Flags.DeletePending = 1;
AttachCount = MmSessionSpace->AttachCount;
if (AttachCount) {
KeInitializeEvent (&MmSessionSpace->AttachEvent, NotificationEvent, FALSE);
UNLOCK_EXPANSION (OldIrql);
KeWaitForSingleObject( &MmSessionSpace->AttachEvent, WrVirtualMemory, KernelMode, FALSE, (PLARGE_INTEGER)NULL);
LOCK_EXPANSION (OldIrql);
ASSERT (MmSessionSpace->u.Flags.DeletePending == 1);
ASSERT (MmSessionSpace->AttachCount == 0);
}
if (MmSessionSpace->Vm.u.Flags.BeingTrimmed) {
// Initialize an event and put the event address in the VmSupport.
// When the trimming is complete, this event will be set.
KeInitializeEvent(&Event, NotificationEvent, FALSE);
MmSessionSpace->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();
LOCK_EXPANSION (OldIrql);
} else if (MmSessionSpace->u.Flags.WorkingSetInserted == 1) {
// Remove this session from the session list and the working set list.
RemoveEntryList (&SessionGlobal->Vm.WorkingSetExpansionLinks);
MmSessionSpace->u.Flags.WorkingSetInserted = 0;
}
if (MmSessionSpace->u.Flags.SessionListInserted == 1) {
RemoveEntryList (&SessionGlobal->WsListEntry);
MmSessionSpace->u.Flags.SessionListInserted = 0;
}
MiSessionCount -= 1;
UNLOCK_EXPANSION (OldIrql);
#if DBG
if (PsGetCurrentProcess()->Vm.u.Flags.SessionLeader == 0) {
ASSERT (MmSessionSpace->ProcessOutSwapCount == 0);
ASSERT (MmSessionSpace->ReferenceCount == 1);
}
#endif
MM_SNAP_SESS_MEMORY_COUNTERS(0);
// If an unload function has been registered for WIN32K.SYS,
// call it now before we force an unload on any modules. WIN32K.SYS
// is responsible for calling any other loaded modules that have
// unload routines to be run. Another option is to have the other
// session drivers register a DLL initialize/uninitialize pair on load.
if (MmSessionSpace->Win32KDriverObject.DriverUnload ) {
MmSessionSpace->Win32KDriverObject.DriverUnload (&MmSessionSpace->Win32KDriverObject);
}
// Now that all modules have had their unload routine(s) called, check for pool leaks before unloading the images.
MiCheckSessionPoolAllocations ();
ASSERT (MmSessionSpace->ReferenceCount == 1);
MM_SNAP_SESS_MEMORY_COUNTERS(1);
// Destroy the view mapping structures.
MiFreeSessionSpaceMap ();
MM_SNAP_SESS_MEMORY_COUNTERS(2);
// Walk down the list of modules we have loaded dereferencing them.
// This allows us to force an unload of any kernel images loaded by
// the session so we do not have any virtual space and paging
// file leaks.
MiSessionUnloadAllImages ();
MM_SNAP_SESS_MEMORY_COUNTERS(3);
// Destroy the session space bitmap structure
MiFreeSessionPoolBitMaps ();
MM_SNAP_SESS_MEMORY_COUNTERS(4);
// Reference the session space structure using its global
// kernel PTE based address. This is to avoid deleting it out
// from underneath ourselves.
GlobalPteEntrySave = MmSessionSpace->GlobalPteEntry;
// Sweep the individual regions in their proper order.
#if DBG
// Check the executable image region.
// All images should have been unloaded by the image handler.
MiCheckSessionVirtualSpace ((PVOID)MI_SESSION_IMAGE_START, MI_SESSION_IMAGE_SIZE);
#endif
MM_SNAP_SESS_MEMORY_COUNTERS(5);
#if DBG
// Check the view region. All views should have been cleaned up already.
MiCheckSessionVirtualSpace ((PVOID)MI_SESSION_VIEW_START, MI_SESSION_VIEW_SIZE);
#endif
// Save the page tables in the session space structure since we are going to tear it down.
#if defined (_WIN64)
RtlCopyMemory (SavePageTables, MiGetPdeAddress (MmSessionBase), MI_SESSION_SPACE_PAGE_TABLES * sizeof (MMPTE));
#else
RtlCopyMemory (SavePageTables, MmSessionSpace->PageTables, MI_SESSION_SPACE_PAGE_TABLES * sizeof (MMPTE));
#endif
MM_SNAP_SESS_MEMORY_COUNTERS(6);
#if DBG
// Check everything possible before the remaining virtual address space is torn down.
// In this way if anything is amiss, the data can be more easily examined.
Pfn1 = MI_PFN_ELEMENT (MmSessionSpace->SessionPageDirectoryIndex);
// This should be greater than 1 because working set page tables are using this as their parent as well.
ASSERT (Pfn1->u2.ShareCount > 1);
#endif
CountReleased = 0;
if (MmSessionSpace->u.Flags.HasWsLock == 1) {
PointerPte = MiGetPteAddress (MI_SESSION_SPACE_WS);
EndPte = MiGetPteAddress (MmSessionSpace->Vm.VmWorkingSetList->HighestPermittedHashAddress);
for ( ; PointerPte < EndPte; PointerPte += 1)
{
if (PointerPte->u.Long) {
ASSERT (PointerPte->u.Hard.Valid == 1);
MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_WS_PAGE_FREE, 1);
MmSessionSpace->CommittedPages -= 1;
MmSessionSpace->NonPagablePages -= 1;
CountReleased += 1;
}
}
WorkingSetWasInitialized = TRUE;
MmSessionSpace->u.Flags.HasWsLock = 0;
} else {
WorkingSetWasInitialized = FALSE;
}
// Account for the session data structure data page.
// For NT64, the page directory page is also accounted for here.
#if defined (_WIN64)
MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_INITIAL_PAGE_FREE, 2);
MmSessionSpace->CommittedPages -= 2;
MmSessionSpace->NonPagablePages -= 2;
CountReleased += 2;
#else
MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_INITIAL_PAGE_FREE, 1);
MmSessionSpace->CommittedPages -= 1;
MmSessionSpace->NonPagablePages -= 1;
CountReleased += 1;
#endif
// Account for any needed session space page tables.
for (Index = 0; Index < MI_SESSION_SPACE_PAGE_TABLES; Index += 1)
{
StartPde = &SavePageTables[Index];
if (StartPde->u.Long != ZeroKernelPte.u.Long) {
MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_PAGETABLE_FREE, 1);
MmSessionSpace->CommittedPages -= 1;
MmSessionSpace->NonPagablePages -= 1;
CountReleased += 1;
}
}
ASSERT (MmSessionSpace->NonPagablePages == 0);
// Note that whenever win32k or drivers loaded by it leak pool, the
// ASSERT below will be triggered.
ASSERT (MmSessionSpace->CommittedPages == 0);
MiReturnCommitment (CountReleased);
MM_TRACK_COMMIT (MM_DBG_COMMIT_RETURN_SESSION_DEREFERENCE, CountReleased);
// Sweep the working set entries.
// No more accesses to the working set or its lock are allowed.
if (WorkingSetWasInitialized == TRUE) {
ExDeleteResource (&SessionGlobal->WsLock);
PointerPte = MiGetPteAddress (MI_SESSION_SPACE_WS);
EndPte = MiGetPteAddress (MmSessionSpace->Vm.VmWorkingSetList->HighestPermittedHashAddress);
for ( ; PointerPte < EndPte; PointerPte += 1)
{
if (PointerPte->u.Long) {
ASSERT (PointerPte->u.Hard.Valid == 1);
// Delete the page.
PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE (PointerPte);
Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
// Each page should still be locked in the session working set.
LOCK_PFN (OldIrql);
ASSERT (Pfn1->u3.e2.ReferenceCount == 1);
MiDecrementShareAndValidCount (Pfn1->PteFrame);
MI_SET_PFN_DELETED (Pfn1);
MiDecrementShareCountOnly (PageFrameIndex);
MI_WRITE_INVALID_PTE (PointerPte, ZeroKernelPte);
MmResidentAvailablePages += 1;
MM_BUMP_COUNTER(52, 1);
UNLOCK_PFN (OldIrql);
}
}
}
#if defined(_IA64_)
KeDisableSessionSharing(&SessionGlobal->SessionMapInfo);
#endif
// Now delete the session space structure itself.
// No more accesses to MmSessionSpace after this.
PointerPte = MiGetPteAddress (MmSessionSpace);
// Delete the page.
PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE (PointerPte);
Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
ASSERT (Pfn1->u1.WsIndex == 0);// Make sure this page is still locked.
LOCK_PFN (OldIrql);
ASSERT (Pfn1->u3.e2.ReferenceCount == 1);
ASSERT (Pfn1->PteFrame == MmSessionSpace->SessionPageDirectoryIndex);
MiDecrementShareAndValidCount (Pfn1->PteFrame);
MI_SET_PFN_DELETED (Pfn1);
MiDecrementShareCountOnly (PageFrameIndex);
MI_WRITE_INVALID_PTE (PointerPte, ZeroKernelPte);
MmResidentAvailablePages += CountReleased;
MM_BUMP_COUNTER(53, CountReleased);
MmResidentAvailablePages += MI_SESSION_SPACE_WORKING_SET_MINIMUM;
MM_BUMP_COUNTER(56, MI_SESSION_SPACE_WORKING_SET_MINIMUM);
UNLOCK_PFN (OldIrql);
StartPde = MiGetPdeAddress (MmSessionBase);
EndPde = MiGetPdeAddress (MI_SESSION_SPACE_END);
RtlZeroMemory (StartPde, (EndPde - StartPde) * sizeof(MMPTE));
MI_FLUSH_SESSION_TB (OldIrql);// Flush the session space TB entries.
MiReleaseSystemPtes (GlobalPteEntrySave, 1, SystemPteSpace);// Free the self-map PTE.
// Delete page table pages.
// Note that the page table page mapping the session space data structure is done last so that we can apply various ASSERTs in the DeletePde routine.
SessionDataPdeIndex = MiGetPdeSessionIndex (MmSessionSpace);
LOCK_PFN (OldIrql);
for (Index = 0; Index < MI_SESSION_SPACE_PAGE_TABLES; Index += 1)
{
if (Index == SessionDataPdeIndex) {
continue;// The self map entry must be done last.
}
MiSessionDeletePde (&SavePageTables[Index], WorkingSetWasInitialized, &SavePageTables[SessionDataPdeIndex]);
}
MiSessionDeletePde (&SavePageTables[SessionDataPdeIndex], WorkingSetWasInitialized, &SavePageTables[SessionDataPdeIndex]);
#if defined (_WIN64)
// Delete the session page directory page.
PointerPpe = MiGetPpeAddress (MmSessionSpace);
PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE (PointerPpe);
Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
MI_SET_PFN_DELETED (Pfn1);
MiDecrementShareCountOnly (PageFrameIndex);
MI_FLUSH_SINGLE_SESSION_TB (MiGetPdeAddress (MmSessionSpace), TRUE, TRUE, (PHARDWARE_PTE)PointerPpe, ZeroKernelPte.u.Flush, PreviousPte);
#endif
UNLOCK_PFN (OldIrql);
ExAcquireFastMutex (&MiSessionIdMutex);
ASSERT (RtlCheckBit (MiSessionIdBitmap, SessionId));
RtlClearBits (MiSessionIdBitmap, SessionId, 1);
ExReleaseFastMutex (&MiSessionIdMutex);
LOCK_EXPANSION (OldIrql);
PsGetCurrentProcess()->Vm.u.Flags.ProcessInSession = 0;
UNLOCK_EXPANSION (OldIrql);
return TRUE;// The session space has been deleted and all TB flushing is complete.
}
NTSTATUS MiSessionCommitImagePages(IN PVOID VirtualAddress, IN SIZE_T NumberOfBytes)
/*++
Routine Description:
This routine commits virtual memory within the current session space with backing pages.
The virtual addresses within session space are allocated with a separate facility in the image management facility.
This is because images must be at a unique system wide virtual address.
Arguments:
VirtualAddress - Supplies the first virtual address to commit.
NumberOfBytes - Supplies the number of bytes to commit.
Return Value:
STATUS_SUCCESS if all went well, STATUS_NO_MEMORY if the current process has no session.
Environment:
Kernel mode, MmSystemLoadLock held.
--*/
{
KIRQL WsIrql;
KIRQL OldIrql;
ULONG Color;
PFN_NUMBER SizeInPages;
PMMPFN Pfn1;
ULONG_PTR AllocationStart;
PFN_NUMBER PageFrameIndex;
NTSTATUS Status;
PMMPTE StartPte, EndPte;
MMPTE TempPte;
SYSLOAD_LOCK_OWNED_BY_ME ();
if (NumberOfBytes == 0) {
return STATUS_SUCCESS;
}
if (MmIsAddressValid(MmSessionSpace) == FALSE) {
#if DBG
DbgPrint ("MiSessionCommitImagePages: No session space!\n");
#endif
return STATUS_NO_MEMORY;
}
ASSERT (((ULONG_PTR)VirtualAddress % PAGE_SIZE) == 0);
ASSERT ((NumberOfBytes % PAGE_SIZE) == 0);
SizeInPages = (PFN_NUMBER)(NumberOfBytes >> PAGE_SHIFT);
// Calculate pages needed.
AllocationStart = (ULONG_PTR)VirtualAddress;
LOCK_SESSION_SPACE_WS(WsIrql);// Lock the session space working set.
// Make sure we have page tables for the PTE entries we must fill in the session space structure.
Status = MiSessionCommitPageTables ((PVOID)AllocationStart, (PVOID)(AllocationStart + NumberOfBytes));
if (!NT_SUCCESS(Status)) {
#if DBG
if (MmDebug & MM_DBG_SESSIONS) {
DbgPrint("MiSessionCommitImagePages: Could not commit pagetables, Not enough memory! MmResidentAvailablePages %d, SizeInPages %d\n", MmResidentAvailablePages, SizeInPages);
}
#endif
UNLOCK_SESSION_SPACE_WS(WsIrql);
return STATUS_NO_MEMORY;
}
// go into loop allocating them and placing them into the page tables.
StartPte = MiGetPteAddress (AllocationStart);
EndPte = MiGetPteAddress (AllocationStart + NumberOfBytes);
if (MiChargeCommitment (SizeInPages, NULL) == FALSE) {
MM_BUMP_SESSION_FAILURES (MM_SESSION_FAILURE_NO_COMMIT);
UNLOCK_SESSION_SPACE_WS(WsIrql);
return STATUS_NO_MEMORY;
}
MM_TRACK_COMMIT (MM_DBG_COMMIT_SESSION_IMAGE_PAGES, SizeInPages);
TempPte = ValidKernelPteLocal;
LOCK_PFN (OldIrql);
// Check to make sure the physical pages are available.
if ((SPFN_NUMBER)SizeInPages > MI_NONPAGABLE_MEMORY_AVAILABLE() - 20) {
UNLOCK_PFN (OldIrql);
MiReturnCommitment (SizeInPages);
MM_TRACK_COMMIT (MM_DBG_COMMIT_RETURN_SESSION_IMAGE_FAILURE1, SizeInPages);
MM_BUMP_SESSION_FAILURES (MM_SESSION_FAILURE_NO_RESIDENT);
UNLOCK_SESSION_SPACE_WS(WsIrql);
return STATUS_NO_MEMORY;
}
MmResidentAvailablePages -= SizeInPages;
MM_BUMP_COUNTER(45, SizeInPages);
while (StartPte < EndPte)
{
ASSERT (StartPte->u.Long == ZeroKernelPte.u.Long);
MiEnsureAvailablePageOrWait (NULL, NULL);
Color = MI_GET_PAGE_COLOR_FROM_SESSION (MmSessionSpace);
PageFrameIndex = MiRemoveZeroPageIfAny (Color);
if (PageFrameIndex == 0) {
PageFrameIndex = MiRemoveAnyPage (Color);
UNLOCK_PFN (OldIrql);
MiZeroPhysicalPage (PageFrameIndex, Color);
LOCK_PFN (OldIrql);
}
TempPte.u.Hard.PageFrameNumber = PageFrameIndex;
MI_WRITE_VALID_PTE (StartPte, TempPte);
Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
ASSERT (Pfn1->u1.WsIndex == 0);
MiInitializePfn (PageFrameIndex, StartPte, 1);
KeFillEntryTb ((PHARDWARE_PTE) StartPte, (PMMPTE)AllocationStart, FALSE);
StartPte += 1;
AllocationStart += PAGE_SIZE;
}
UNLOCK_PFN (OldIrql);
MmSessionSpace->CommittedPages += SizeInPages;
MmSessionSpace->NonPagablePages += SizeInPages;
MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_DRIVER_PAGES_LOCKED, SizeInPages);
UNLOCK_SESSION_SPACE_WS(WsIrql);
return STATUS_SUCCESS;
}
NTSTATUS MiSessionCommitPageTables(IN PVOID StartVa, IN PVOID EndVa)
/*++
Routine Description:
Fill in page tables covering the specified virtual address range.
Arguments:
StartVa - Supplies a starting virtual address.
EndVa - Supplies an ending virtual address.
Return Value:
STATUS_SUCCESS on success, STATUS_NO_MEMORY on failure.
Environment:
Kernel mode. Session space working set mutex held.
--*/
{
KIRQL OldIrql;
ULONG Color;
ULONG Index;
PMMPTE StartPde, EndPde;
MMPTE TempPte;
PMMPFN Pfn1;
ULONG Entry;
ULONG SwapEntry;
PFN_NUMBER SizeInPages;
PFN_NUMBER PageTablePage;
PVOID SessionPte;
PMMWSL WorkingSetList;
CHAR SavePageTables[MI_SESSION_SPACE_PAGE_TABLES];
ASSERT (MmIsAddressValid(MmSessionSpace) == TRUE);
MM_SESSION_SPACE_WS_LOCK_ASSERT();
ASSERT (StartVa >= (PVOID)MmSessionBase);
ASSERT (EndVa < (PVOID)MI_SESSION_SPACE_END);
// Allocate the page table pages, loading them into the current process's page directory.
StartPde = MiGetPdeAddress (StartVa);
EndPde = MiGetPdeAddress (EndVa);
Index = MiGetPdeSessionIndex (StartVa);
SizeInPages = 0;
while (StartPde <= EndPde)
{
#if defined (_WIN64)
if (StartPde->u.Long == ZeroKernelPte.u.Long)
#else
if (MmSessionSpace->PageTables[Index].u.Long == ZeroKernelPte.u.Long)
#endif
{
SizeInPages += 1;
}
StartPde += 1;
Index += 1;
}
if (SizeInPages == 0) {
return STATUS_SUCCESS;
}
if (MiChargeCommitment (SizeInPages, NULL) == FALSE) {
MM_BUMP_SESSION_FAILURES (MM_SESSION_FAILURE_NO_COMMIT);
return STATUS_NO_MEMORY;
}
MM_TRACK_COMMIT (MM_DBG_COMMIT_SESSION_PAGETABLE_PAGES, SizeInPages);
StartPde = MiGetPdeAddress (StartVa);
Index = MiGetPdeSessionIndex (StartVa);
TempPte = ValidKernelPdeLocal;
LOCK_PFN (OldIrql);
// Check to make sure the physical pages are available.
if ((SPFN_NUMBER)SizeInPages > MI_NONPAGABLE_MEMORY_AVAILABLE() - 20) {
UNLOCK_PFN (OldIrql);
MiReturnCommitment (SizeInPages);
MM_TRACK_COMMIT (MM_DBG_COMMIT_RETURN_SESSION_PAGETABLE_PAGES, SizeInPages);
MM_BUMP_SESSION_FAILURES (MM_SESSION_FAILURE_NO_RESIDENT);
return STATUS_NO_MEMORY;
}
MmResidentAvailablePages -= SizeInPages;
MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_PAGETABLE_ALLOC, SizeInPages);
MM_BUMP_COUNTER(44, SizeInPages);
WorkingSetList = MmSessionSpace->Vm.VmWorkingSetList;
RtlZeroMemory (SavePageTables, sizeof (SavePageTables));
while (StartPde <= EndPde)
{
#if defined (_WIN64)
if (StartPde->u.Long == ZeroKernelPte.u.Long)
#else
if (MmSessionSpace->PageTables[Index].u.Long == ZeroKernelPte.u.Long)
#endif
{
ASSERT (StartPde->u.Hard.Valid == 0);
SavePageTables[Index] = 1;
MiEnsureAvailablePageOrWait (NULL, NULL);
Color = MI_GET_PAGE_COLOR_FROM_SESSION (MmSessionSpace);
PageTablePage = MiRemoveZeroPageIfAny (Color);
if (PageTablePage == 0) {
PageTablePage = MiRemoveAnyPage (Color);
UNLOCK_PFN (OldIrql);
MiZeroPhysicalPage (PageTablePage, Color);
LOCK_PFN (OldIrql);
}
TempPte.u.Hard.PageFrameNumber = PageTablePage;
MI_WRITE_VALID_PTE (StartPde, TempPte);
#if !defined (_WIN64)
MmSessionSpace->PageTables[Index] = TempPte;
#endif
MmSessionSpace->NonPagablePages += 1;
MmSessionSpace->CommittedPages += 1;
MiInitializePfnForOtherProcess (PageTablePage, StartPde, MmSessionSpace->SessionPageDirectoryIndex);
}
StartPde += 1;
Index += 1;
}
UNLOCK_PFN (OldIrql);
StartPde = MiGetPdeAddress (StartVa);
Index = MiGetPdeSessionIndex (StartVa);
while (StartPde <= EndPde)
{
if (SavePageTables[Index] == 1) {
ASSERT (StartPde->u.Hard.Valid == 1);
PageTablePage = MI_GET_PAGE_FRAME_FROM_PTE (StartPde);
Pfn1 = MI_PFN_ELEMENT (PageTablePage);
ASSERT (Pfn1->u1.Event == NULL);
Pfn1->u1.Event = (PVOID)PsGetCurrentThread ();
SessionPte = MiGetVirtualAddressMappedByPte (StartPde);
MiAddValidPageToWorkingSet (SessionPte, StartPde, Pfn1, 0);
Entry = MiLocateWsle (SessionPte, MmSessionSpace->Vm.VmWorkingSetList, Pfn1->u1.WsIndex);
if (Entry >= WorkingSetList->FirstDynamic) {
SwapEntry = WorkingSetList->FirstDynamic;
if (Entry != WorkingSetList->FirstDynamic) {
MiSwapWslEntries (Entry, SwapEntry, &MmSessionSpace->Vm);// Swap this entry with the one at first dynamic.
}
WorkingSetList->FirstDynamic += 1;
} else {
SwapEntry = Entry;
}
MmSessionSpace->Wsle[SwapEntry].u1.e1.LockedInWs = 1;// Indicate that the page is locked.
}
StartPde += 1;
Index += 1;
}
return STATUS_SUCCESS;
}
#if DBG
typedef struct _MISWAP {
ULONG Flag;
PEPROCESS Process;
PMM_SESSION_SPACE Session;
ULONG OutSwapCount;
} MISWAP, *PMISWAP;
ULONG MiSessionInfo[4];
MISWAP MiSessionSwap[0x100];
ULONG MiSwapIndex;
#endif
VOID MiSessionOutSwapProcess (IN PEPROCESS Process)
/*++
Routine Description:
This routine notifies the containing session that the specified process is being outswapped.
When all the processes within a session have been outswapped, the containing session undergoes a heavy trim.
Arguments:
Process - Supplies a pointer to the process that is swapped out of memory.
Environment:
Kernel mode. This routine must not enter a wait state for memory resources or the system will deadlock.
--*/
{
MMPTE TempPte;
PFN_NUMBER PdePage;
PMMPTE PageDirectoryMap;
KIRQL OldIrql;
PFN_NUMBER SessionPage;
PMM_SESSION_SPACE SessionGlobal;
#if DBG
ULONG InCount;
ULONG OutCount;
PLIST_ENTRY NextEntry;
#endif
#if defined (_X86PAE_)
ULONG i;
PPAE_ENTRY PaeVa;
#endif
ASSERT (MiHydra == TRUE && Process->Vm.u.Flags.ProcessInSession == 1);
// smss doesn't count when we swap it before it has detached from the session it is currently creating.
if (Process->Vm.u.Flags.SessionLeader == 1) {
return;
}
#if defined (_X86PAE_)
PaeVa = Process->PaeTop;
#if DBG
for (i = 0; i < PD_PER_SYSTEM; i += 1)
{
ASSERT (PaeVa->PteEntry[i].u.Hard.Valid == 1);
}
#endif
PdePage = MI_GET_PAGE_FRAME_FROM_PTE (&PaeVa->PteEntry[MiGetPdPteOffset(MmSessionSpace)]);
#else
PdePage = MI_GET_DIRECTORY_FRAME_FROM_PROCESS(Process);
#endif
PageDirectoryMap = MiMapPageInHyperSpace (PdePage, &OldIrql);
#if defined (_WIN64)
TempPte = PageDirectoryMap[MiGetPpeOffset(MmSessionSpace)];
MiUnmapPageInHyperSpace (OldIrql);
PdePage = MI_GET_PAGE_FRAME_FROM_PTE (&TempPte);
PageDirectoryMap = MiMapPageInHyperSpace (PdePage, &OldIrql);
#endif
TempPte = PageDirectoryMap[MiGetPdeOffset(MmSessionSpace)];
MiUnmapPageInHyperSpace (OldIrql);
PdePage = MI_GET_PAGE_FRAME_FROM_PTE (&TempPte);
PageDirectoryMap = MiMapPageInHyperSpace (PdePage, &OldIrql);
TempPte = PageDirectoryMap[MiGetPteOffset(MmSessionSpace)];
MiUnmapPageInHyperSpace (OldIrql);
SessionPage = MI_GET_PAGE_FRAME_FROM_PTE (&TempPte);
SessionGlobal = (PMM_SESSION_SPACE) MiMapPageInHyperSpace ( SessionPage, &OldIrql);
ASSERT (MI_GET_PAGE_FRAME_FROM_PTE (MiGetPteAddress(SessionGlobal->GlobalVirtualAddress)) == SessionPage);
SessionGlobal = SessionGlobal->GlobalVirtualAddress;
MiUnmapPageInHyperSpace (OldIrql);
LOCK_EXPANSION (OldIrql);
SessionGlobal->ProcessOutSwapCount += 1;
#if DBG
ASSERT ((LONG)SessionGlobal->ProcessOutSwapCount > 0);
InCount = 0;
OutCount = 0;
NextEntry = SessionGlobal->ProcessList.Flink;
while (NextEntry != &SessionGlobal->ProcessList)
{
Process = CONTAINING_RECORD (NextEntry, EPROCESS, SessionProcessLinks);
if (Process->ProcessOutswapEnabled == TRUE) {
OutCount += 1;
} else {
InCount += 1;
}
NextEntry = NextEntry->Flink;
}
if (InCount + OutCount > SessionGlobal->ReferenceCount) {
DbgPrint ("MiSessionOutSwapProcess : process count mismatch %p %x %x %x\n", SessionGlobal, SessionGlobal->ReferenceCount, InCount, OutCount);
DbgBreakPoint ();
}
if (SessionGlobal->ProcessOutSwapCount != OutCount) {
DbgPrint ("MiSessionOutSwapProcess : out count mismatch %p %x %x %x %x\n", SessionGlobal, SessionGlobal->ReferenceCount, SessionGlobal->ProcessOutSwapCount, InCount, OutCount);
DbgBreakPoint ();
}
ASSERT (SessionGlobal->ProcessOutSwapCount <= SessionGlobal->ReferenceCount);
MiSessionSwap[MiSwapIndex].Flag = 1;
MiSessionSwap[MiSwapIndex].Process = Process;
MiSessionSwap[MiSwapIndex].Session = SessionGlobal;
MiSessionSwap[MiSwapIndex].OutSwapCount = SessionGlobal->ProcessOutSwapCount;
MiSwapIndex += 1;
if (MiSwapIndex == 0x100) {
MiSwapIndex = 0;
}
#endif
if (SessionGlobal->ProcessOutSwapCount == SessionGlobal->ReferenceCount) {
SessionGlobal->Vm.u.Flags.TrimHard = 1;
#if DBG
if (MmDebug & MM_DBG_SESSIONS) {
DbgPrint ("Mm: Last process (%d total) just swapped out for session %d, %d pages\n",
SessionGlobal->ProcessOutSwapCount,
SessionGlobal->SessionId,
SessionGlobal->Vm.WorkingSetSize);
}
MiSessionInfo[0] += 1;
#endif
KeQuerySystemTime (&SessionGlobal->LastProcessSwappedOutTime);
}
#if DBG
else {
MiSessionInfo[1] += 1;
}
#endif
UNLOCK_EXPANSION (OldIrql);
}
VOID MiSessionInSwapProcess (IN PEPROCESS 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.
Environment:
Kernel mode. This routine must not enter a wait state for memory resources or the system will deadlock.
--*/
{
MMPTE TempPte;
PFN_NUMBER PdePage;
PMMPTE PageDirectoryMap;
KIRQL OldIrql;
PFN_NUMBER SessionPage;
PMM_SESSION_SPACE SessionGlobal;
#if DBG
ULONG InCount;
ULONG OutCount;
PLIST_ENTRY NextEntry;
#endif
#if defined (_X86PAE_)
ULONG i;
PPAE_ENTRY PaeVa;
#endif
ASSERT (MiHydra == TRUE && Process->Vm.u.Flags.ProcessInSession == 1);
// smss doesn't count when we catch it before it has detached from the session it is currently creating.
if (Process->Vm.u.Flags.SessionLeader == 1) {
return;
}
ASSERT (MiHydra == TRUE && Process->Vm.u.Flags.ProcessInSession == 1);
// smss doesn't count when we swap it before it has detached from the session it is currently creating.
if (Process->Vm.u.Flags.SessionLeader == 1) {
return;
}
#if defined (_X86PAE_)
PaeVa = Process->PaeTop;
#if DBG
for (i = 0; i < PD_PER_SYSTEM; i += 1)
{
ASSERT (PaeVa->PteEntry[i].u.Hard.Valid == 1);
}
#endif
PdePage = MI_GET_PAGE_FRAME_FROM_PTE (&PaeVa->PteEntry[MiGetPdPteOffset(MmSessionSpace)]);
#else
PdePage = MI_GET_DIRECTORY_FRAME_FROM_PROCESS(Process);
#endif
PageDirectoryMap = MiMapPageInHyperSpace (PdePage, &OldIrql);
#if defined (_WIN64)
TempPte = PageDirectoryMap[MiGetPpeOffset(MmSessionSpace)];
MiUnmapPageInHyperSpace (OldIrql);
PdePage = MI_GET_PAGE_FRAME_FROM_PTE (&TempPte);
PageDirectoryMap = MiMapPageInHyperSpace (PdePage, &OldIrql);
#endif
TempPte = PageDirectoryMap[MiGetPdeOffset(MmSessionSpace)];
MiUnmapPageInHyperSpace (OldIrql);
PdePage = MI_GET_PAGE_FRAME_FROM_PTE (&TempPte);
PageDirectoryMap = MiMapPageInHyperSpace (PdePage, &OldIrql);
TempPte = PageDirectoryMap[MiGetPteOffset(MmSessionSpace)];
MiUnmapPageInHyperSpace (OldIrql);
SessionPage = MI_GET_PAGE_FRAME_FROM_PTE (&TempPte);
SessionGlobal = (PMM_SESSION_SPACE) MiMapPageInHyperSpace (SessionPage, &OldIrql);
ASSERT (MI_GET_PAGE_FRAME_FROM_PTE (MiGetPteAddress(SessionGlobal->GlobalVirtualAddress)) == SessionPage);
SessionGlobal = SessionGlobal->GlobalVirtualAddress;
MiUnmapPageInHyperSpace (OldIrql);
LOCK_EXPANSION (OldIrql);
#if DBG
ASSERT ((LONG)SessionGlobal->ProcessOutSwapCount > 0);
InCount = 0;
OutCount = 0;
NextEntry = SessionGlobal->ProcessList.Flink;
while (NextEntry != &SessionGlobal->ProcessList)
{
Process = CONTAINING_RECORD (NextEntry, EPROCESS, SessionProcessLinks);
if (Process->ProcessOutswapEnabled == TRUE) {
OutCount += 1;
} else {
InCount += 1;
}
NextEntry = NextEntry->Flink;
}
if (InCount + OutCount > SessionGlobal->ReferenceCount) {
DbgPrint ("MiSessionInSwapProcess : count mismatch %p %x %x %x\n",
SessionGlobal,
SessionGlobal->ReferenceCount,
InCount,
OutCount);
DbgBreakPoint ();
}
if (SessionGlobal->ProcessOutSwapCount != OutCount) {
DbgPrint ("MiSessionInSwapProcess : out count mismatch %p %x %x %x %x\n",
SessionGlobal,
SessionGlobal->ReferenceCount,
SessionGlobal->ProcessOutSwapCount,
InCount,
OutCount);
DbgBreakPoint ();
}
ASSERT (SessionGlobal->ProcessOutSwapCount <= SessionGlobal->ReferenceCount);
MiSessionSwap[MiSwapIndex].Flag = 2;
MiSessionSwap[MiSwapIndex].Process = Process;
MiSessionSwap[MiSwapIndex].Session = SessionGlobal;
MiSessionSwap[MiSwapIndex].OutSwapCount = SessionGlobal->ProcessOutSwapCount;
MiSwapIndex += 1;
if (MiSwapIndex == 0x100) {
MiSwapIndex = 0;
}
#endif
if (SessionGlobal->ProcessOutSwapCount == SessionGlobal->ReferenceCount) {
#if DBG
MiSessionInfo[2] += 1;
if (MmDebug & MM_DBG_SESSIONS) {
DbgPrint ("Mm: First process (%d total) just swapped back in for session %d, %d pages\n",
SessionGlobal->ProcessOutSwapCount,
SessionGlobal->SessionId,
SessionGlobal->Vm.WorkingSetSize);
}
#endif
SessionGlobal->Vm.u.Flags.TrimHard = 0;
}
#if DBG
else {
MiSessionInfo[3] += 1;
}
#endif
SessionGlobal->ProcessOutSwapCount -= 1;
ASSERT ((LONG)SessionGlobal->ProcessOutSwapCount >= 0);
UNLOCK_EXPANSION (OldIrql);
}