/*++ Copyright (c) 1989 Microsoft Corporation Module Name: dmpaddr.c Abstract: Routines to examine pages and addresses. Author: Lou Perazzoli (loup) 20-Mar-1989 Landy Wang (landyw) 02-Jun-1997 Environment: Kernel Mode. Revision History: --*/ #include "mi.h" #if DBG LOGICAL MiFlushUnusedSectionInternal ( IN PCONTROL_AREA ControlArea ); #endif #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE,MmPerfSnapShotValidPhysicalMemory) #endif extern PFN_NUMBER MiStartOfInitialPoolFrame; extern PFN_NUMBER MiEndOfInitialPoolFrame; extern PMMPTE MmSystemPtesEnd[MaximumPtePoolTypes]; #if DBG PFN_NUMBER MiIdentifyFrame = (PFN_NUMBER)-1; ULONG MiIdentifyCounters[64]; #define MI_INCREMENT_IDENTIFY_COUNTER(x) { \ ASSERT (x < 64); \ MiIdentifyCounters[x] += 1; \ } #else #define MI_INCREMENT_IDENTIFY_COUNTER(x) #endif #if DBG VOID MiDumpValidAddresses ( ) { ULONG_PTR va; ULONG i; ULONG j; PMMPTE PointerPde; PMMPTE PointerPte; va = 0; PointerPde = MiGetPdeAddress ((PVOID)va); for (i = 0; i < PDE_PER_PAGE; i += 1) { if (PointerPde->u.Hard.Valid) { DbgPrint(" **valid PDE, element %ld %lx %lx\n",i,i, PointerPde->u.Long); PointerPte = MiGetPteAddress ((PVOID)va); for (j = 0 ; j < PTE_PER_PAGE; j += 1) { if (PointerPte->u.Hard.Valid) { DbgPrint("Valid address at %p PTE %p\n", (ULONG)va, PointerPte->u.Long); } va += PAGE_SIZE; PointerPte += 1; } } else { va += (ULONG_PTR)PDE_PER_PAGE * (ULONG_PTR)PAGE_SIZE; } PointerPde += 1; } return; } VOID MiFormatPte ( IN PMMPTE PointerPte ) { PMMPTE proto_pte; PSUBSECTION subsect; if (MmIsAddressValid (PointerPte) == FALSE) { DbgPrint(" cannot dump PTE %p - it's not valid\n\n", PointerPte); return; } DbgPrint("***DumpPTE at %p contains %p\n", PointerPte, PointerPte->u.Long); proto_pte = MiPteToProto(PointerPte); subsect = MiGetSubsectionAddress(PointerPte); DbgPrint(" protoaddr %p subsectaddr %p\n\n", proto_pte, (ULONG_PTR)subsect); return; } VOID MiDumpWsl ( VOID ) { ULONG i; PMMWSLE wsle; PEPROCESS CurrentProcess; CurrentProcess = PsGetCurrentProcess(); DbgPrint("***WSLE cursize %lx frstfree %lx Min %lx Max %lx\n", CurrentProcess->Vm.WorkingSetSize, MmWorkingSetList->FirstFree, CurrentProcess->Vm.MinimumWorkingSetSize, CurrentProcess->Vm.MaximumWorkingSetSize); DbgPrint(" firstdyn %lx last ent %lx next slot %lx\n", MmWorkingSetList->FirstDynamic, MmWorkingSetList->LastEntry, MmWorkingSetList->NextSlot); wsle = MmWsle; for (i = 0; i < MmWorkingSetList->LastEntry; i += 1) { DbgPrint(" index %lx %p\n",i,wsle->u1.Long); wsle += 1; } return; } #define ALLOC_SIZE ((ULONG)8*1024) #define MM_SAVED_CONTROL 64 // // Note these are deliberately sign-extended so they will always be greater // than the highest user address. // #define MM_NONPAGED_POOL_MARK ((PUCHAR)(LONG_PTR)0xfffff123) #define MM_PAGED_POOL_MARK ((PUCHAR)(LONG_PTR)0xfffff124) #define MM_KERNEL_STACK_MARK ((PUCHAR)(LONG_PTR)0xfffff125) #define MM_PAGEFILE_BACKED_SHMEM_MARK ((PUCHAR)(LONG_PTR)0xfffff126) #define MM_DUMP_ONLY_VALID_PAGES 1 typedef struct _KERN_MAP { PVOID StartVa; PVOID EndVa; PKLDR_DATA_TABLE_ENTRY Entry; } KERN_MAP, *PKERN_MAP; ULONG MiBuildKernelMap ( OUT PKERN_MAP *KernelMapOut ); LOGICAL MiIsAddressRangeValid ( IN PVOID VirtualAddress, IN SIZE_T Length ); NTSTATUS MmMemoryUsage ( IN PVOID Buffer, IN ULONG Size, IN ULONG Type, OUT PULONG OutLength ) /*++ Routine Description: This routine (debugging only) dumps the current memory usage by walking the PFN database. Arguments: Buffer - Supplies a *USER SPACE* buffer in which to copy the data. Size - Supplies the size of the buffer. Type - Supplies a value of 0 to dump everything, a value of 1 to dump only valid pages. OutLength - Returns how much data was written into the buffer. Return Value: NTSTATUS. --*/ { ULONG i; MMPFN_IDENTITY PfnId; PMMPFN LastPfn; PMMPFN Pfn1; KIRQL OldIrql; PSYSTEM_MEMORY_INFORMATION MemInfo; PSYSTEM_MEMORY_INFO Info; PSYSTEM_MEMORY_INFO InfoStart; PSYSTEM_MEMORY_INFO InfoEnd; PUCHAR String; PUCHAR Master; PCONTROL_AREA ControlArea; NTSTATUS status; ULONG Length; PEPROCESS Process; PUCHAR End; PCONTROL_AREA SavedControl[MM_SAVED_CONTROL]; PSYSTEM_MEMORY_INFO SavedInfo[MM_SAVED_CONTROL]; ULONG j; ULONG ControlCount; UCHAR PageFileMappedString[] = "PageFile Mapped"; UCHAR MetaFileString[] = "Fs Meta File"; UCHAR NoNameString[] = "No File Name"; UCHAR NonPagedPoolString[] = "NonPagedPool"; UCHAR PagedPoolString[] = "PagedPool"; UCHAR KernelStackString[] = "Kernel Stack"; PUCHAR NameString; PKERN_MAP KernMap; ULONG KernSize; PVOID VirtualAddress; PSUBSECTION Subsection; PKLDR_DATA_TABLE_ENTRY DataTableEntry; String = NULL; ControlCount = 0; Master = NULL; status = STATUS_SUCCESS; KernSize = MiBuildKernelMap (&KernMap); if (KernSize == 0) { return STATUS_INSUFFICIENT_RESOURCES; } MemInfo = ExAllocatePoolWithTag (NonPagedPool, (SIZE_T) Size, 'lMmM'); if (MemInfo == NULL) { ExFreePool (KernMap); return STATUS_INSUFFICIENT_RESOURCES; } InfoStart = &MemInfo->Memory[0]; InfoEnd = InfoStart; End = (PUCHAR)MemInfo + Size; // // Walk through the ranges identifying pages. // LOCK_PFN (OldIrql); for (i = 0; i < MmPhysicalMemoryBlock->NumberOfRuns; i += 1) { Pfn1 = MI_PFN_ELEMENT (MmPhysicalMemoryBlock->Run[i].BasePage); LastPfn = Pfn1 + MmPhysicalMemoryBlock->Run[i].PageCount; for ( ; Pfn1 < LastPfn; Pfn1 += 1) { RtlZeroMemory (&PfnId, sizeof(PfnId)); MiIdentifyPfn (Pfn1, &PfnId); if ((PfnId.u1.e1.ListDescription == FreePageList) || (PfnId.u1.e1.ListDescription == ZeroedPageList) || (PfnId.u1.e1.ListDescription == BadPageList) || (PfnId.u1.e1.ListDescription == TransitionPage)) { continue; } if (PfnId.u1.e1.ListDescription != ActiveAndValid) { if (Type == MM_DUMP_ONLY_VALID_PAGES) { continue; } } if (PfnId.u1.e1.UseDescription == MMPFNUSE_PAGEFILEMAPPED) { // // This page belongs to a pagefile-backed shared memory section. // Master = MM_PAGEFILE_BACKED_SHMEM_MARK; } else if ((PfnId.u1.e1.UseDescription == MMPFNUSE_FILE) || (PfnId.u1.e1.UseDescription == MMPFNUSE_METAFILE)) { // // This shared page maps a file or file metadata. // Subsection = MiGetSubsectionAddress (&Pfn1->OriginalPte); ControlArea = Subsection->ControlArea; Master = (PUCHAR) ControlArea; } else if (PfnId.u1.e1.UseDescription == MMPFNUSE_NONPAGEDPOOL) { // // This is nonpaged pool, put it in the nonpaged pool cell. // Master = MM_NONPAGED_POOL_MARK; } else if (PfnId.u1.e1.UseDescription == MMPFNUSE_PAGEDPOOL) { // // This is paged pool, put it in the paged pool cell. // Master = MM_PAGED_POOL_MARK; } else if (PfnId.u1.e1.UseDescription == MMPFNUSE_SESSIONPRIVATE) { // // Call this paged pool for now. // Master = MM_PAGED_POOL_MARK; } else if (PfnId.u1.e1.UseDescription == MMPFNUSE_DRIVERLOCKPAGE) { // // Call this nonpaged pool for now. // Master = MM_NONPAGED_POOL_MARK; } else if (PfnId.u1.e1.UseDescription == MMPFNUSE_AWEPAGE) { // // Call this nonpaged pool for now. // Master = MM_NONPAGED_POOL_MARK; } else { // // See if the page is part of the kernel or a driver image. // If not but it's in system PTEs, call it a kernel thread // stack. // // If neither of the above, then see if the page belongs to // a user address or a session pagetable page. // VirtualAddress = PfnId.u2.VirtualAddress; for (j = 0; j < KernSize; j += 1) { if ((VirtualAddress >= KernMap[j].StartVa) && (VirtualAddress < KernMap[j].EndVa)) { Master = (PUCHAR)&KernMap[j]; break; } } if (j == KernSize) { if (PfnId.u1.e1.UseDescription == MMPFNUSE_SYSTEMPTE) { Master = MM_KERNEL_STACK_MARK; } else if (MI_IS_SESSION_PTE (VirtualAddress)) { Master = MM_NONPAGED_POOL_MARK; } else { ASSERT ((PfnId.u1.e1.UseDescription == MMPFNUSE_PROCESSPRIVATE) || (PfnId.u1.e1.UseDescription == MMPFNUSE_PAGETABLE)); Master = (PUCHAR) (ULONG_PTR) PfnId.u1.e3.PageDirectoryBase; } } } // // The page has been identified. // See if there is already a bucket allocated for it. // for (Info = InfoStart; Info < InfoEnd; Info += 1) { if (Info->StringOffset == Master) { break; } } if (Info == InfoEnd) { InfoEnd += 1; if ((PUCHAR)InfoEnd > End) { status = STATUS_DATA_OVERRUN; goto Done; } RtlZeroMemory (Info, sizeof(*Info)); Info->StringOffset = Master; } if (PfnId.u1.e1.ListDescription == ActiveAndValid) { Info->ValidCount += 1; } else if ((PfnId.u1.e1.ListDescription == StandbyPageList) || (PfnId.u1.e1.ListDescription == TransitionPage)) { Info->TransitionCount += 1; } else if ((PfnId.u1.e1.ListDescription == ModifiedPageList) || (PfnId.u1.e1.ListDescription == ModifiedNoWritePageList)) { Info->ModifiedCount += 1; } if (PfnId.u1.e1.UseDescription == MMPFNUSE_PAGETABLE) { Info->PageTableCount += 1; } } } MemInfo->StringStart = (ULONG_PTR)Buffer + (ULONG_PTR)InfoEnd - (ULONG_PTR)MemInfo; String = (PUCHAR)InfoEnd; // // Process the buckets ... // for (Info = InfoStart; Info < InfoEnd; Info += 1) { ControlArea = NULL; if (Info->StringOffset == MM_PAGEFILE_BACKED_SHMEM_MARK) { Length = 16; NameString = PageFileMappedString; } else if (Info->StringOffset == MM_NONPAGED_POOL_MARK) { Length = 14; NameString = NonPagedPoolString; } else if (Info->StringOffset == MM_PAGED_POOL_MARK) { Length = 14; NameString = PagedPoolString; } else if (Info->StringOffset == MM_KERNEL_STACK_MARK) { Length = 14; NameString = KernelStackString; } else if (((PUCHAR)Info->StringOffset >= (PUCHAR)&KernMap[0]) && ((PUCHAR)Info->StringOffset <= (PUCHAR)&KernMap[KernSize])) { DataTableEntry = ((PKERN_MAP)Info->StringOffset)->Entry; NameString = (PUCHAR)DataTableEntry->BaseDllName.Buffer; Length = DataTableEntry->BaseDllName.Length; } else if (Info->StringOffset > (PUCHAR)MM_HIGHEST_USER_ADDRESS) { // // This points to a control area - get the file name. // ControlArea = (PCONTROL_AREA)(Info->StringOffset); NameString = (PUCHAR)&ControlArea->FilePointer->FileName.Buffer[0]; Length = ControlArea->FilePointer->FileName.Length; if (Length == 0) { if (ControlArea->u.Flags.NoModifiedWriting) { NameString = MetaFileString; Length = 14; } else if (ControlArea->u.Flags.File == 0) { NameString = PageFileMappedString; Length = 16; } else { NameString = NoNameString; Length = 14; } } } else { // // This is a process (or session) top-level page directory. // Pfn1 = MI_PFN_ELEMENT (PtrToUlong(Info->StringOffset)); ASSERT (Pfn1->u4.PteFrame == MI_PFN_ELEMENT_TO_INDEX (Pfn1)); Process = (PEPROCESS)Pfn1->u1.Event; NameString = &Process->ImageFileName[0]; Length = 16; } if ((String+Length+2) >= End) { status = STATUS_DATA_OVERRUN; Info->StringOffset = NULL; goto Done; } if ((ControlArea == NULL) || (MiIsAddressRangeValid (NameString, Length))) { RtlCopyMemory (String, NameString, Length); Info->StringOffset = (PUCHAR)Buffer + ((PUCHAR)String - (PUCHAR)MemInfo); String[Length] = 0; String[Length + 1] = 0; String += Length + 2; } else { if (!(ControlArea->u.Flags.BeingCreated || ControlArea->u.Flags.BeingDeleted) && (ControlCount < MM_SAVED_CONTROL)) { SavedControl[ControlCount] = ControlArea; SavedInfo[ControlCount] = Info; ControlArea->NumberOfSectionReferences += 1; ControlCount += 1; } Info->StringOffset = NULL; } } Done: UNLOCK_PFN (OldIrql); ExFreePool (KernMap); while (ControlCount != 0) { // // Process all the pagable name strings. // ControlCount -= 1; ControlArea = SavedControl[ControlCount]; Info = SavedInfo[ControlCount]; NameString = (PUCHAR)&ControlArea->FilePointer->FileName.Buffer[0]; Length = ControlArea->FilePointer->FileName.Length; if (Length == 0) { if (ControlArea->u.Flags.NoModifiedWriting) { Length = 12; NameString = MetaFileString; } else if (ControlArea->u.Flags.File == 0) { NameString = PageFileMappedString; Length = 16; } else { NameString = NoNameString; Length = 12; } } if ((String+Length+2) >= End) { status = STATUS_DATA_OVERRUN; } if (status != STATUS_DATA_OVERRUN) { RtlCopyMemory (String, NameString, Length); Info->StringOffset = (PUCHAR)Buffer + ((PUCHAR)String - (PUCHAR)MemInfo); String[Length] = 0; String[Length + 1] = 0; String += Length + 2; } LOCK_PFN (OldIrql); ControlArea->NumberOfSectionReferences -= 1; MiCheckForControlAreaDeletion (ControlArea); UNLOCK_PFN (OldIrql); } *OutLength = (ULONG)((PUCHAR)String - (PUCHAR)MemInfo); // // Carefully copy the results to the user buffer. // try { RtlCopyMemory (Buffer, MemInfo, (ULONG_PTR)String - (ULONG_PTR)MemInfo); } except (EXCEPTION_EXECUTE_HANDLER) { status = GetExceptionCode(); } ExFreePool (MemInfo); return status; } ULONG MiBuildKernelMap ( OUT PKERN_MAP *KernelMapOut ) { PKTHREAD CurrentThread; PLIST_ENTRY NextEntry; PKLDR_DATA_TABLE_ENTRY DataTableEntry; PKERN_MAP KernelMap; ULONG i; i = 0; CurrentThread = KeGetCurrentThread (); KeEnterCriticalRegionThread (CurrentThread); ExAcquireResourceShared (&PsLoadedModuleResource, TRUE); // // The caller wants us to allocate the return result buffer. Size it // by allocating the maximum possibly needed as this should not be // very big (relatively). It is the caller's responsibility to free // this. Obviously this option can only be requested after pool has // been initialized. // NextEntry = PsLoadedModuleList.Flink; while (NextEntry != &PsLoadedModuleList) { i += 1; NextEntry = NextEntry->Flink; } KernelMap = ExAllocatePoolWithTag (NonPagedPool, i * sizeof(KERN_MAP), 'lMmM'); if (KernelMap == NULL) { return 0; } *KernelMapOut = KernelMap; i = 0; NextEntry = PsLoadedModuleList.Flink; while (NextEntry != &PsLoadedModuleList) { DataTableEntry = CONTAINING_RECORD (NextEntry, KLDR_DATA_TABLE_ENTRY, InLoadOrderLinks); KernelMap[i].Entry = DataTableEntry; KernelMap[i].StartVa = DataTableEntry->DllBase; KernelMap[i].EndVa = (PVOID)((ULONG_PTR)KernelMap[i].StartVa + DataTableEntry->SizeOfImage); i += 1; NextEntry = NextEntry->Flink; } ExReleaseResourceLite(&PsLoadedModuleResource); KeLeaveCriticalRegionThread (CurrentThread); return i; } VOID MiDumpReferencedPages ( VOID ) /*++ Routine Description: This routine (debugging only) dumps all PFN entries which appear to be locked in memory for i/o. Arguments: None. Return Value: None. --*/ { KIRQL OldIrql; PMMPFN Pfn1; PMMPFN PfnLast; LOCK_PFN (OldIrql); Pfn1 = MI_PFN_ELEMENT (MmLowestPhysicalPage); PfnLast = MI_PFN_ELEMENT (MmHighestPhysicalPage); while (Pfn1 <= PfnLast) { if (MI_IS_PFN (MI_PFN_ELEMENT_TO_INDEX (Pfn1))) { if ((Pfn1->u2.ShareCount == 0) && (Pfn1->u3.e2.ReferenceCount != 0)) { MiFormatPfn (Pfn1); } if (Pfn1->u3.e2.ReferenceCount > 1) { MiFormatPfn (Pfn1); } } Pfn1 += 1; } UNLOCK_PFN (OldIrql); return; } #else //DBG NTSTATUS MmMemoryUsage ( IN PVOID Buffer, IN ULONG Size, IN ULONG Type, OUT PULONG OutLength ) { UNREFERENCED_PARAMETER (Buffer); UNREFERENCED_PARAMETER (Size); UNREFERENCED_PARAMETER (Type); UNREFERENCED_PARAMETER (OutLength); return STATUS_NOT_IMPLEMENTED; } #endif //DBG // // One benefit of using run length maximums of less than 4GB is that even // frame numbers above 4GB are handled properly despite the 32-bit limitations // of the bitmap routines. // #define MI_MAXIMUM_PFNID_RUN 4096 NTSTATUS MmPerfSnapShotValidPhysicalMemory ( VOID ) /*++ Routine Description: This routine logs the PFN numbers of all ActiveAndValid pages. Arguments: None. Return Value: NTSTATUS. Environment: Kernel mode. PASSIVE level. No locks held. --*/ { ULONG i; PFN_NUMBER StartPage; PFN_NUMBER EndPage; ULONG_PTR MemSnapLocal[(sizeof(MMPFN_MEMSNAP_INFORMATION)/sizeof(ULONG_PTR)) + (MI_MAXIMUM_PFNID_RUN / (8*sizeof(ULONG_PTR))) ]; PMMPFN_MEMSNAP_INFORMATION MemSnap; PMMPFN Pfn1; PMMPFN FirstPfn; PMMPFN LastPfn; PMMPFN MaxPfn; PMMPFN InitialPfn; RTL_BITMAP BitMap; PULONG ActualBits; ASSERT (KeGetCurrentIrql() == PASSIVE_LEVEL); ASSERT ((MI_MAXIMUM_PFNID_RUN % (8 * sizeof(ULONG_PTR))) == 0); MemSnap = (PMMPFN_MEMSNAP_INFORMATION) MemSnapLocal; ActualBits = (PULONG)(MemSnap + 1); RtlInitializeBitMap (&BitMap, ActualBits, MI_MAXIMUM_PFNID_RUN); MemSnap->Count = 0; RtlClearAllBits (&BitMap); KeAcquireGuardedMutex (&MmDynamicMemoryMutex); for (i = 0; i < MmPhysicalMemoryBlock->NumberOfRuns; i += 1) { StartPage = MmPhysicalMemoryBlock->Run[i].BasePage; EndPage = StartPage + MmPhysicalMemoryBlock->Run[i].PageCount; FirstPfn = MI_PFN_ELEMENT (StartPage); LastPfn = MI_PFN_ELEMENT (EndPage); // // Find the first valid PFN and start the run there. // for (Pfn1 = FirstPfn; Pfn1 < LastPfn; Pfn1 += 1) { if (Pfn1->u3.e1.PageLocation == ActiveAndValid) { break; } } if (Pfn1 == LastPfn) { // // No valid PFNs in this block, move on to the next block. // continue; } MaxPfn = LastPfn; InitialPfn = NULL; do { if (Pfn1->u3.e1.PageLocation == ActiveAndValid) { if (InitialPfn == NULL) { MemSnap->InitialPageFrameIndex = MI_PFN_ELEMENT_TO_INDEX (Pfn1); InitialPfn = Pfn1; MaxPfn = InitialPfn + MI_MAXIMUM_PFNID_RUN; } RtlSetBit (&BitMap, (ULONG) (Pfn1 - InitialPfn)); } Pfn1 += 1; if ((Pfn1 >= MaxPfn) && (InitialPfn != NULL)) { // // Log the bitmap as we're at then end of it. // ASSERT ((Pfn1 - InitialPfn) == MI_MAXIMUM_PFNID_RUN); MemSnap->Count = MI_MAXIMUM_PFNID_RUN; PerfInfoLogBytes (PERFINFO_LOG_TYPE_MEMORYSNAPLITE, MemSnap, sizeof(MemSnapLocal)); InitialPfn = NULL; MaxPfn = LastPfn; RtlClearAllBits (&BitMap); } } while (Pfn1 < LastPfn); // // Dump any straggling bitmap entries now as this range is finished. // if (InitialPfn != NULL) { ASSERT (Pfn1 == LastPfn); ASSERT (Pfn1 < MaxPfn); ASSERT (Pfn1 > InitialPfn); MemSnap->Count = Pfn1 - InitialPfn; PerfInfoLogBytes (PERFINFO_LOG_TYPE_MEMORYSNAPLITE, MemSnap, sizeof(MMPFN_MEMSNAP_INFORMATION) + (ULONG) ((MemSnap->Count + 8) / 8)); RtlClearAllBits (&BitMap); } } KeReleaseGuardedMutex (&MmDynamicMemoryMutex); return STATUS_SUCCESS; } #define PFN_ID_BUFFERS 128 NTSTATUS MmIdentifyPhysicalMemory ( VOID ) /*++ Routine Description: This routine calls the pfn id code for each page. Because the logging can't handle very large amounts of data in a burst (limited buffering), the data is broken into page size chunks. Arguments: None. Return Value: NTSTATUS. Environment: Kernel mode. PASSIVE level. No locks held. --*/ { ULONG i; KIRQL OldIrql; PMMPFN Pfn1; PMMPFN EndPfn; PFN_NUMBER PageFrameIndex; MMPFN_IDENTITY PfnIdBuffer[PFN_ID_BUFFERS]; PMMPFN_IDENTITY BufferPointer; PMMPFN_IDENTITY BufferLast; ASSERT (KeGetCurrentIrql() == PASSIVE_LEVEL); BufferPointer = &PfnIdBuffer[0]; BufferLast = BufferPointer + PFN_ID_BUFFERS; RtlZeroMemory (PfnIdBuffer, sizeof(PfnIdBuffer)); KeAcquireGuardedMutex (&MmDynamicMemoryMutex); // // Walk through the ranges and identify pages until // the buffer is full or we've run out of pages. // for (i = 0; i < MmPhysicalMemoryBlock->NumberOfRuns; i += 1) { PageFrameIndex = MmPhysicalMemoryBlock->Run[i].BasePage; Pfn1 = MI_PFN_ELEMENT (PageFrameIndex); EndPfn = Pfn1 + MmPhysicalMemoryBlock->Run[i].PageCount; LOCK_PFN (OldIrql); while (Pfn1 < EndPfn) { MiIdentifyPfn (Pfn1, BufferPointer); BufferPointer += 1; if (BufferPointer == BufferLast) { // // Release and reacquire the PFN lock so it's not held so long. // UNLOCK_PFN (OldIrql); // // Log the buffered entries. // BufferPointer = &PfnIdBuffer[0]; do { PerfInfoLogBytes (PERFINFO_LOG_TYPE_PAGEINMEMORY, BufferPointer, sizeof(PfnIdBuffer[0])); BufferPointer += 1; } while (BufferPointer < BufferLast); // // Reset the buffer to the beginning and zero it. // BufferPointer = &PfnIdBuffer[0]; RtlZeroMemory (PfnIdBuffer, sizeof(PfnIdBuffer)); LOCK_PFN (OldIrql); } Pfn1 += 1; } UNLOCK_PFN (OldIrql); } // // Note that releasing this mutex here means the last entry can be // inserted out of order if we are preempted and another thread starts // the same operation (or if we're on an MP machine). The PERF module // must handle this properly as any synchronization provided by this // routine is purely a side effect not deliberate. // KeReleaseGuardedMutex (&MmDynamicMemoryMutex); if (BufferPointer != &PfnIdBuffer[0]) { BufferLast = BufferPointer; BufferPointer = &PfnIdBuffer[0]; do { PerfInfoLogBytes (PERFINFO_LOG_TYPE_PAGEINMEMORY, BufferPointer, sizeof(PfnIdBuffer[0])); BufferPointer += 1; } while (BufferPointer < BufferLast); } return STATUS_SUCCESS; } VOID FASTCALL MiIdentifyPfn ( IN PMMPFN Pfn1, OUT PMMPFN_IDENTITY PfnIdentity ) /*++ Routine Description: This routine captures relevant information for the argument page frame. Arguments: Pfn1 - Supplies the PFN element of the page frame number being queried. PfnIdentity - Receives the structure to fill in with the information. Return Value: None. Environment: Kernel mode. PFN lock held. --*/ { ULONG i; PMMPTE PteAddress; PSUBSECTION Subsection; PCONTROL_AREA ControlArea; PVOID VirtualAddress; PFILE_OBJECT FilePointer; PFN_NUMBER PageFrameIndex; MI_INCREMENT_IDENTIFY_COUNTER (8); ASSERT (PfnIdentity->u2.VirtualAddress == 0); ASSERT (PfnIdentity->u1.e1.ListDescription == 0); ASSERT (PfnIdentity->u1.e1.UseDescription == 0); ASSERT (PfnIdentity->u1.e1.Pinned == 0); ASSERT (PfnIdentity->u1.e2.Offset == 0); MM_PFN_LOCK_ASSERT(); PageFrameIndex = MI_PFN_ELEMENT_TO_INDEX (Pfn1); PfnIdentity->PageFrameIndex = PageFrameIndex; PfnIdentity->u1.e1.ListDescription = Pfn1->u3.e1.PageLocation; #if DBG if (PageFrameIndex == MiIdentifyFrame) { DbgPrint ("MmIdentifyPfn: requested PFN %p\n", PageFrameIndex); DbgBreakPoint (); } #endif MI_INCREMENT_IDENTIFY_COUNTER (Pfn1->u3.e1.PageLocation); switch (Pfn1->u3.e1.PageLocation) { case ZeroedPageList: case FreePageList: case BadPageList: return; case ActiveAndValid: // // It's too much work to determine if the page is locked // in a working set due to cross-process WSL references, etc. // So don't bother for now. // ASSERT (PfnIdentity->u1.e1.ListDescription == MMPFNLIST_ACTIVE); if (Pfn1->u1.WsIndex == 0) { MI_INCREMENT_IDENTIFY_COUNTER (9); PfnIdentity->u1.e1.Pinned = 1; } else if (Pfn1->u3.e2.ReferenceCount > 1) { // // This page is pinned, presumably for an ongoing I/O. // PfnIdentity->u1.e1.Pinned = 1; MI_INCREMENT_IDENTIFY_COUNTER (10); } break; case StandbyPageList: case ModifiedPageList: case ModifiedNoWritePageList: if (Pfn1->u3.e2.ReferenceCount >= 1) { // // This page is pinned, presumably for an ongoing I/O. // PfnIdentity->u1.e1.Pinned = 1; MI_INCREMENT_IDENTIFY_COUNTER (11); } if ((Pfn1->u3.e1.PageLocation == ModifiedPageList) && (MI_IS_PFN_DELETED (Pfn1)) && (Pfn1->u2.ShareCount == 0)) { // // This page may be a modified write completing in the // context of the modified writer thread. If the // address space was deleted while the I/O was in // progress, the frame will be released now. More // importantly, the frame's containing frame is // meaningless as it may have already been freed // and reused. // // We can't tell what this page was being used for // since its address space is gone, so just call it // process private for now. // MI_INCREMENT_IDENTIFY_COUNTER (40); PfnIdentity->u1.e1.UseDescription = MMPFNUSE_PROCESSPRIVATE; return; } break; case TransitionPage: // // This page is pinned due to a straggling I/O - the virtual // address has been deleted but an I/O referencing it has not // completed. // PfnIdentity->u1.e1.Pinned = 1; MI_INCREMENT_IDENTIFY_COUNTER (11); PfnIdentity->u1.e1.UseDescription = MMPFNUSE_PROCESSPRIVATE; return; default: #if DBG DbgPrint ("MmIdentifyPfn: unknown PFN %p %x\n", Pfn1, Pfn1->u3.e1.PageLocation); DbgBreakPoint (); #endif break; } // // Capture differing information based on the type of page being examined. // // // General purpose stress shows 40% of the pages are prototypes so // for speed, check for these first. // if (Pfn1->u3.e1.PrototypePte == 1) { MI_INCREMENT_IDENTIFY_COUNTER (12); if (Pfn1->OriginalPte.u.Soft.Prototype == 0) { // // Demand zero or (equivalently) pagefile backed. // // There are some hard problems here preventing more indepth // identification of these pages: // // 1. The PFN contains a backpointer to the prototype PTE - but // there is no definitive way to get to the SEGMENT or // CONTROL_AREA from this. // // 2. The prototype PTE pointer itself may be paged out and // the PFN lock is held right now. // MI_INCREMENT_IDENTIFY_COUNTER (13); #if 0 PfnIdentity->u2.FileObject = (PVOID) ControlArea->Segment->u1.CreatingProcess; PfnIdentity->u1.e2.Offset = (((ULONG_PTR)ControlArea->Segment->u2.FirstMappedVa) >> MMSECTOR_SHIFT); #endif PfnIdentity->u1.e1.UseDescription = MMPFNUSE_PAGEFILEMAPPED; return; } MI_INCREMENT_IDENTIFY_COUNTER (14); // // Backed by a mapped file. // Subsection = MiGetSubsectionAddress (&Pfn1->OriginalPte); ControlArea = Subsection->ControlArea; ASSERT (ControlArea->u.Flags.File == 1); FilePointer = ControlArea->FilePointer; ASSERT (FilePointer != NULL); PfnIdentity->u2.FileObject = FilePointer; if (Subsection->SubsectionBase != NULL) { PfnIdentity->u1.e2.Offset = (MiStartingOffset (Subsection, Pfn1->PteAddress) >> MMSECTOR_SHIFT); } else { // // The only time we should be here (a valid PFN with no subsection) // is if we are the segment dereference thread putting pages into // the freelist. At this point the PFN lock is held and the // control area/subsection/PFN structures are not yet consistent // so just treat this as an offset of 0 as it should be rare. // ASSERT (PsGetCurrentThread()->StartAddress == (PVOID)(ULONG_PTR)MiDereferenceSegmentThread); } // // Check for nomodwrite sections - typically this is filesystem // metadata although it could also be registry data (which is named). // if (ControlArea->u.Flags.NoModifiedWriting) { MI_INCREMENT_IDENTIFY_COUNTER (15); PfnIdentity->u1.e1.UseDescription = MMPFNUSE_METAFILE; return; } if (FilePointer->FileName.Length != 0) { // // This mapped file has a name. // MI_INCREMENT_IDENTIFY_COUNTER (16); PfnIdentity->u1.e1.UseDescription = MMPFNUSE_FILE; return; } // // No name - this file must be in the midst of a purge, but it // still *was* a mapped file of some sort. // MI_INCREMENT_IDENTIFY_COUNTER (17); PfnIdentity->u1.e1.UseDescription = MMPFNUSE_FILE; return; } if ((PageFrameIndex >= MiStartOfInitialPoolFrame) && (PageFrameIndex <= MiEndOfInitialPoolFrame)) { // // This is initial nonpaged pool. // MI_INCREMENT_IDENTIFY_COUNTER (18); PfnIdentity->u1.e1.UseDescription = MMPFNUSE_NONPAGEDPOOL; VirtualAddress = (PVOID)((ULONG_PTR)MmNonPagedPoolStart + ((PageFrameIndex - MiStartOfInitialPoolFrame) << PAGE_SHIFT)); PfnIdentity->u2.VirtualAddress = VirtualAddress; return; } PteAddress = Pfn1->PteAddress; VirtualAddress = MiGetVirtualAddressMappedByPte (PteAddress); PfnIdentity->u2.VirtualAddress = VirtualAddress; if (MI_IS_SESSION_ADDRESS(VirtualAddress)) { // // Note session addresses that map images (or views) that haven't // undergone a copy-on-write split were already treated as prototype // PTEs above. This clause handles session pool and copy-on-written // pages. // MI_INCREMENT_IDENTIFY_COUNTER (19); PfnIdentity->u1.e1.UseDescription = MMPFNUSE_SESSIONPRIVATE; return; } if ((VirtualAddress >= MmPagedPoolStart) && (VirtualAddress <= MmPagedPoolEnd)) { // // This is paged pool. // MI_INCREMENT_IDENTIFY_COUNTER (20); PfnIdentity->u1.e1.UseDescription = MMPFNUSE_PAGEDPOOL; return; } if ((VirtualAddress >= MmNonPagedPoolExpansionStart) && (VirtualAddress < MmNonPagedPoolEnd)) { // // This is expansion nonpaged pool. // MI_INCREMENT_IDENTIFY_COUNTER (21); PfnIdentity->u1.e1.UseDescription = MMPFNUSE_NONPAGEDPOOL; return; } if ((VirtualAddress >= MmNonPagedSystemStart) && (PteAddress <= MmSystemPtesEnd[SystemPteSpace])) { // // This is driver space, kernel stack, special pool or other // system PTE mappings. // MI_INCREMENT_IDENTIFY_COUNTER (22); PfnIdentity->u1.e1.UseDescription = MMPFNUSE_SYSTEMPTE; return; } #if defined (_X86_) // // 2 other ranges of system PTEs can exist on x86. // if (((MiNumberOfExtraSystemPdes != 0) && (VirtualAddress >= (PVOID)MiExtraResourceStart) && (VirtualAddress < (PVOID)MiExtraResourceEnd)) || ((MiUseMaximumSystemSpace != 0) && (VirtualAddress >= (PVOID)MiUseMaximumSystemSpace) && (VirtualAddress < (PVOID)MiUseMaximumSystemSpaceEnd))) { // // This is driver space, kernel stack, special pool or other // system PTE mappings. // MI_INCREMENT_IDENTIFY_COUNTER (23); PfnIdentity->u1.e1.UseDescription = MMPFNUSE_SYSTEMPTE; return; } #endif if (Pfn1->u4.PteFrame == MI_MAGIC_AWE_PTEFRAME) { MI_INCREMENT_IDENTIFY_COUNTER (24); // // Carefully check here as this could be a legitimate frame as well. // if ((Pfn1->u3.e1.StartOfAllocation == 1) && (Pfn1->u3.e1.EndOfAllocation == 1) && (Pfn1->u3.e1.PageLocation == ActiveAndValid)) { if (MI_IS_PFN_DELETED (Pfn1)) { MI_INCREMENT_IDENTIFY_COUNTER (25); PfnIdentity->u1.e1.UseDescription = MMPFNUSE_DRIVERLOCKPAGE; } else { MI_INCREMENT_IDENTIFY_COUNTER (26); PfnIdentity->u1.e1.UseDescription = MMPFNUSE_AWEPAGE; } return; } } #if DBG // // In checked kernels, AWE frames get their containing frame decremented // when the AWE frame is freed. // if (Pfn1->u4.PteFrame == MI_MAGIC_AWE_PTEFRAME - 1) { MI_INCREMENT_IDENTIFY_COUNTER (24); // // Carefully check here as this could be a legitimate frame as well. // if ((Pfn1->u3.e1.StartOfAllocation == 0) && (Pfn1->u3.e1.EndOfAllocation == 0) && (Pfn1->u3.e1.PageLocation == StandbyPageList)) { MI_INCREMENT_IDENTIFY_COUNTER (26); PfnIdentity->u1.e1.UseDescription = MMPFNUSE_AWEPAGE; return; } } #endif // // Check the PFN working set index carefully here. This must be done // before walking back through the containing frames because if this page // is not in a working set, the containing frame may not be meaningful and // dereferencing it can crash the system and/or yield incorrect walks. // This is because if a page will never be trimmable there is no need to // have a containing frame initialized. This also covers the case of // data pages mapped via large page directory entries as these have no // containing page table frame. // if (Pfn1->u3.e1.PageLocation == ActiveAndValid) { if (Pfn1->u1.WsIndex == 0) { // // Default to calling these allocations nonpaged pool because even // when they technically are not, from a usage standpoint they are. // Note the default is overridden for specific cases where the usage // is not in fact nonpaged. // PfnIdentity->u1.e1.UseDescription = MMPFNUSE_NONPAGEDPOOL; ASSERT (PfnIdentity->u1.e1.Pinned == 1); MI_INCREMENT_IDENTIFY_COUNTER (27); return; } } // // Must be a process private page // // OR // // a page table, page directory, parent or extended parent. // i = 0; while (Pfn1->u4.PteFrame != PageFrameIndex) { // // The only way the PTE address will go out of bounds is if this is // a top level page directory page for a process that has been // swapped out but is still waiting for the transition/modified // page table pages to be reclaimed. ie: until that happens, the // page directory is marked Active, but the PteAddress & containing // page are pointing at the EPROCESS pool page. // #if defined(_IA64_) if (((Pfn1->PteAddress >= (PMMPTE) PTE_BASE) && (Pfn1->PteAddress <= (PMMPTE) PTE_TOP)) || ((Pfn1->PteAddress >= (PMMPTE) PTE_KBASE) && (Pfn1->PteAddress <= (PMMPTE) PTE_KTOP)) || ((Pfn1->PteAddress >= (PMMPTE) PTE_SBASE) && (Pfn1->PteAddress <= (PMMPTE) PTE_STOP)) || ((Pfn1->PteAddress >= (PMMPTE) PDE_BASE) && (Pfn1->PteAddress <= (PMMPTE) PDE_TOP)) || ((Pfn1->PteAddress >= (PMMPTE) PDE_KBASE) && (Pfn1->PteAddress <= (PMMPTE) PDE_KTOP)) || ((Pfn1->PteAddress >= (PMMPTE) PDE_SBASE) && (Pfn1->PteAddress <= (PMMPTE) PDE_STOP)) || ((Pfn1->PteAddress >= (PMMPTE) PDE_TBASE) && (Pfn1->PteAddress <= (PMMPTE) ((ULONG_PTR)PDE_TBASE + PAGE_SIZE -1))) || ((Pfn1->PteAddress >= (PMMPTE) PDE_KTBASE) && (Pfn1->PteAddress <= (PMMPTE) ((ULONG_PTR)PDE_KTBASE + PAGE_SIZE -1))) || ((Pfn1->PteAddress >= (PMMPTE) PDE_STBASE) && (Pfn1->PteAddress <= (PMMPTE) ((ULONG_PTR)PDE_STBASE + PAGE_SIZE -1))) ) #else if ((Pfn1->PteAddress >= (PMMPTE) PTE_BASE) && (Pfn1->PteAddress <= (PMMPTE) PTE_TOP)) #endif { PageFrameIndex = Pfn1->u4.PteFrame; Pfn1 = MI_PFN_ELEMENT (PageFrameIndex); i += 1; } else { MI_INCREMENT_IDENTIFY_COUNTER (41); break; } } MI_INCREMENT_IDENTIFY_COUNTER (31+i); PfnIdentity->u1.e3.PageDirectoryBase = PageFrameIndex; #if defined(_X86PAE_) // // PAE is unique because the 3rd level is not defined as only a mini // 4 entry 3rd level is in use. Check for that explicitly, noting that // it takes one extra walk to get to the top. Top level PAE pages (the // ones that contain only the 4 PDPTE pointers) are treated above as // active pinned pages, not as pagetable pages because each one is shared // across 127 processes and resides in the system global space. // if (i == _MI_PAGING_LEVELS + 1) { // // Had to walk all the way to the top. Must be a data page. // MI_INCREMENT_IDENTIFY_COUNTER (29); PfnIdentity->u1.e1.UseDescription = MMPFNUSE_PROCESSPRIVATE; return; } #else if (i == _MI_PAGING_LEVELS) { // // Had to walk all the way to the top. Must be a data page. // MI_INCREMENT_IDENTIFY_COUNTER (29); PfnIdentity->u1.e1.UseDescription = MMPFNUSE_PROCESSPRIVATE; return; } #endif // // Must have been a page in the hierarchy (not a data page) as we arrived // at the top early. // MI_INCREMENT_IDENTIFY_COUNTER (30); PfnIdentity->u1.e1.UseDescription = MMPFNUSE_PAGETABLE; return; }