/*++ Copyright (c) 1989 Microsoft Corporation Module Name: crashdmp.c Abstract: This module contains routines which provide support for writing out a crashdump on system failure. Author: Landy Wang (landyw) 04-Oct-2000 Revision History: --*/ #include "mi.h" LOGICAL MiIsAddressRangeValid ( IN PVOID VirtualAddress, IN SIZE_T Length ) { PUCHAR Va; PUCHAR EndVa; ULONG Pages; Va = PAGE_ALIGN (VirtualAddress); Pages = ADDRESS_AND_SIZE_TO_SPAN_PAGES (VirtualAddress, Length); EndVa = Va + (Pages << PAGE_SHIFT); while (Va < EndVa) { if (!MmIsAddressValid (Va)) { return FALSE; } Va += PAGE_SIZE; } return TRUE; } VOID MiRemoveFreePoolMemoryFromDump ( IN PMM_KERNEL_DUMP_CONTEXT Context ) /*++ Routine Description: Removes all memory from the nonpaged pool free page lists to reduce the size of a kernel memory dump. Because the entries in these structures are destroyed by errant drivers that modify pool after freeing it, the entries are carefully validated prior to any dereferences. Arguments: Context - Supplies the dump context pointer that must be passed to IoFreeDumpRange. Return Value: None. Environment: Kernel-mode, post-bugcheck. For use by crashdump routines ONLY. --*/ { PLIST_ENTRY Entry; PLIST_ENTRY List; PLIST_ENTRY ListEnd; PMMFREE_POOL_ENTRY PoolEntry; ULONG LargePageMapped; List = &MmNonPagedPoolFreeListHead[0]; ListEnd = List + MI_MAX_FREE_LIST_HEADS; for ( ; List < ListEnd; List += 1) { for (Entry = List->Flink; Entry != List; Entry = Entry->Flink) { PoolEntry = CONTAINING_RECORD (Entry, MMFREE_POOL_ENTRY, List); // // Check for corrupted values. // if (BYTE_OFFSET(PoolEntry) != 0) { break; } // // Check that the entry has not been corrupted. // if (MiIsAddressRangeValid (PoolEntry, sizeof (MMFREE_POOL_ENTRY)) == FALSE) { break; } if (PoolEntry->Size == 0) { break; } // // Signature is only maintained in checked builds. // ASSERT (PoolEntry->Signature == MM_FREE_POOL_SIGNATURE); // // Verify that the element's flinks and blinks are valid. // if ((!MiIsAddressRangeValid (Entry->Flink, sizeof (LIST_ENTRY))) || (!MiIsAddressRangeValid (Entry->Blink, sizeof (LIST_ENTRY))) || (Entry->Blink->Flink != Entry) || (Entry->Flink->Blink != Entry)) { break; } // // The list entry is valid, remove it from the dump. // if (MI_IS_PHYSICAL_ADDRESS (PoolEntry)) { LargePageMapped = 1; } else { LargePageMapped = 0; } Context->FreeDumpRange (Context, PoolEntry, PoolEntry->Size, LargePageMapped); } } } LOGICAL MiIsPhysicalMemoryAddress ( IN PFN_NUMBER PageFrameIndex, IN OUT PULONG Hint, IN LOGICAL PfnLockNeeded ) /*++ Routine Description: Check if a given address is backed by RAM or IO space. Arguments: PageFrameIndex - Supplies a page frame number to check. Hint - Supplies a hint at which memory run we should start searching for this pfn. The hint is updated on success and failure. PfnLockNeeded - Supplies TRUE if the caller needs this routine to acquire the PFN lock. FALSE if not (ie: the caller already holds the PFN lock or we are crashing the system and so the PFN lock may already be held by someone else). Return Value: TRUE - If the address is backed by RAM. FALSE - If the address is IO mapped memory. Environment: Kernel-mode, post-bugcheck. For use by crash dump and other Mm internal routines. --*/ { ULONG Index; KIRQL OldIrql; PPHYSICAL_MEMORY_RUN Run; PPHYSICAL_MEMORY_DESCRIPTOR PhysicalMemoryBlock; // // Initializing OldIrql is not needed for correctness, but without it // the compiler cannot compile this code W4 to check for use of // uninitialized variables. // OldIrql = PASSIVE_LEVEL; if (PfnLockNeeded) { LOCK_PFN2 (OldIrql); } PhysicalMemoryBlock = MmPhysicalMemoryBlock; if (PageFrameIndex > MmHighestPhysicalPage) { if (PfnLockNeeded) { UNLOCK_PFN2 (OldIrql); } return FALSE; } if (*Hint < PhysicalMemoryBlock->NumberOfRuns) { Run = &PhysicalMemoryBlock->Run[*Hint]; if ((PageFrameIndex >= Run->BasePage) && (PageFrameIndex < Run->BasePage + Run->PageCount)) { if (PfnLockNeeded) { UNLOCK_PFN2 (OldIrql); } return TRUE; } } for (Index = 0; Index < PhysicalMemoryBlock->NumberOfRuns; Index += 1) { Run = &PhysicalMemoryBlock->Run[Index]; if ((PageFrameIndex >= Run->BasePage) && (PageFrameIndex < Run->BasePage + Run->PageCount)) { *Hint = Index; if (PfnLockNeeded) { UNLOCK_PFN2 (OldIrql); } return TRUE; } // // Since the physical memory block is ordered by increasing // base page PFN number, if this PFN is smaller, then bail. // if (Run->BasePage + Run->PageCount > PageFrameIndex) { *Hint = Index; break; } } if (PfnLockNeeded) { UNLOCK_PFN2 (OldIrql); } return FALSE; } VOID MiAddPagesWithNoMappings ( IN PMM_KERNEL_DUMP_CONTEXT Context ) /*++ Routine Description: Add pages to a kernel memory crashdump that do not have a virtual mapping in this process context. This includes entries that are wired directly into the TB. Arguments: Context - Crashdump context pointer. Return Value: None. Environment: Kernel-mode, post-bugcheck. For use by crash dump routines ONLY. --*/ { #if defined (_X86_) ULONG LargePageMapped; PVOID Va; PHYSICAL_ADDRESS DirBase; // // Add the current page directory table page - don't use the directory // table base for the crashing process as we have switched cr3 on // stack overflow crashes, etc. // _asm { mov eax, cr3 mov DirBase.LowPart, eax } // // cr3 is always located below 4gb physical. // DirBase.HighPart = 0; Va = MmGetVirtualForPhysical (DirBase); if (MI_IS_PHYSICAL_ADDRESS (Va)) { LargePageMapped = 1; } else { LargePageMapped = 0; } Context->SetDumpRange (Context, Va, 1, LargePageMapped); #elif defined(_AMD64_) ULONG LargePageMapped; PVOID Va; PHYSICAL_ADDRESS DirBase; // // Add the current page directory table page - don't use the directory // table base for the crashing process as we have switched cr3 on // stack overflow crashes, etc. // DirBase.QuadPart = ReadCR3 (); Va = MmGetVirtualForPhysical (DirBase); if (MI_IS_PHYSICAL_ADDRESS (Va)) { LargePageMapped = 1; } else { LargePageMapped = 0; } Context->SetDumpRange (Context, Va, 1, LargePageMapped); #elif defined(_IA64_) if (MiKseg0Mapping == TRUE) { Context->SetDumpRange ( Context, MiKseg0Start, (((ULONG_PTR)MiKseg0End - (ULONG_PTR)MiKseg0Start) >> PAGE_SHIFT) + 1, 1); } #endif } LOGICAL MiAddRangeToCrashDump ( IN PMM_KERNEL_DUMP_CONTEXT Context, IN PVOID Va, IN SIZE_T NumberOfBytes ) /*++ Routine Description: Adds the specified range of memory to the crashdump. Arguments: Context - Supplies the crashdump context pointer. Va - Supplies the starting virtual address. NumberOfBytes - Supplies the number of bytes to dump. Note that for IA64, this must not cause the range to cross a region boundary. Return Value: TRUE if all valid pages were added to the crashdump, FALSE otherwise. Environment: Kernel mode, post-bugcheck. For use by crash dump routines ONLY. --*/ { LOGICAL Status; LOGICAL AddThisPage; ULONG Hint; PVOID EndingAddress; PMMPTE PointerPte; PMMPTE PointerPde; PMMPTE PointerPpe; PMMPTE PointerPxe; PFN_NUMBER PageFrameIndex; #if defined (_X86_) || defined (_AMD64_) PFN_NUMBER NumberOfPages; #endif Hint = 0; Status = TRUE; EndingAddress = (PVOID)((ULONG_PTR)Va + NumberOfBytes - 1); #if defined(_IA64_) // // IA64 has a separate page directory parent for each region and // unimplemented address bits are ignored by the processor (as // long as they are canonical), but we must watch for them // here so the incrementing PPE walk doesn't go off the end. // This is done by truncating any given region request so it does // not go past the end of the specified region. Note this // automatically will include the page maps which are sign extended // because the PPEs would just wrap anyway. // if (((ULONG_PTR)EndingAddress & ~VRN_MASK) >= MM_VA_MAPPED_BY_PPE * PDE_PER_PAGE) { EndingAddress = (PVOID)(((ULONG_PTR)EndingAddress & VRN_MASK) | ((MM_VA_MAPPED_BY_PPE * PDE_PER_PAGE) - 1)); } #endif Va = PAGE_ALIGN (Va); PointerPxe = MiGetPxeAddress (Va); PointerPpe = MiGetPpeAddress (Va); PointerPde = MiGetPdeAddress (Va); PointerPte = MiGetPteAddress (Va); do { #if (_MI_PAGING_LEVELS >= 3) restart: #endif KdCheckForDebugBreak (); #if (_MI_PAGING_LEVELS >= 4) while (PointerPxe->u.Hard.Valid == 0) { // // This extended page directory parent entry is empty, // go to the next one. // PointerPxe += 1; PointerPpe = MiGetVirtualAddressMappedByPte (PointerPxe); PointerPde = MiGetVirtualAddressMappedByPte (PointerPpe); PointerPte = MiGetVirtualAddressMappedByPte (PointerPde); Va = MiGetVirtualAddressMappedByPte (PointerPte); if ((Va > EndingAddress) || (Va == NULL)) { // // All done, return. // return Status; } } #endif ASSERT (MiGetPpeAddress(Va) == PointerPpe); #if (_MI_PAGING_LEVELS >= 3) while (PointerPpe->u.Hard.Valid == 0) { // // This page directory parent entry is empty, go to the next one. // PointerPpe += 1; PointerPde = MiGetVirtualAddressMappedByPte (PointerPpe); PointerPte = MiGetVirtualAddressMappedByPte (PointerPde); Va = MiGetVirtualAddressMappedByPte (PointerPte); if ((Va > EndingAddress) || (Va == NULL)) { // // All done, return. // return Status; } #if (_MI_PAGING_LEVELS >= 4) if (MiIsPteOnPdeBoundary (PointerPpe)) { PointerPxe += 1; ASSERT (PointerPxe == MiGetPteAddress (PointerPpe)); goto restart; } #endif } #endif while (PointerPde->u.Hard.Valid == 0) { // // This page directory entry is empty, go to the next one. // PointerPde += 1; PointerPte = MiGetVirtualAddressMappedByPte (PointerPde); Va = MiGetVirtualAddressMappedByPte (PointerPte); if ((Va > EndingAddress) || (Va == NULL)) { // // All done, return. // return Status; } #if (_MI_PAGING_LEVELS >= 3) if (MiIsPteOnPdeBoundary (PointerPde)) { PointerPpe += 1; ASSERT (PointerPpe == MiGetPteAddress (PointerPde)); PointerPxe = MiGetPteAddress (PointerPpe); goto restart; } #endif } // // A valid PDE has been located, examine each PTE. // ASSERT64 (PointerPpe->u.Hard.Valid == 1); ASSERT (PointerPde->u.Hard.Valid == 1); ASSERT (Va <= EndingAddress); #if defined (_X86_) || defined (_AMD64_) if (PointerPde->u.Hard.LargePage == 1) { // // Large pages are always backed by RAM, not mapped to // I/O space, so always add them to the dump. // NumberOfPages = (((ULONG_PTR)MiGetVirtualAddressMappedByPde (PointerPde + 1) - (ULONG_PTR)Va) / PAGE_SIZE); Status = Context->SetDumpRange (Context, Va, NumberOfPages, 1); if (!NT_SUCCESS (Status)) { #if DBG DbgPrint ("Adding large VA %p to crashdump failed\n", Va); DbgBreakPoint (); #endif Status = FALSE; } PointerPde += 1; Va = MiGetVirtualAddressMappedByPde (PointerPde); if ((Va > EndingAddress) || (Va == NULL)) { return Status; } PointerPte = MiGetPteAddress (Va); PointerPpe = MiGetPpeAddress (Va); PointerPxe = MiGetPxeAddress (Va); // // March on to the next page directory. // continue; } #endif // // Exclude memory that is mapped in the system cache. // Note the system cache starts and ends on page directory boundaries // and is never mapped with large pages. // if (MI_IS_SYSTEM_CACHE_ADDRESS (Va)) { PointerPde += 1; Va = MiGetVirtualAddressMappedByPde (PointerPde); if ((Va > EndingAddress) || (Va == NULL)) { return Status; } PointerPte = MiGetPteAddress (Va); PointerPpe = MiGetPpeAddress (Va); PointerPxe = MiGetPxeAddress (Va); // // March on to the next page directory. // continue; } do { AddThisPage = FALSE; PageFrameIndex = 0; if (PointerPte->u.Hard.Valid == 1) { PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE (PointerPte); AddThisPage = TRUE; } else if ((PointerPte->u.Soft.Prototype == 0) && (PointerPte->u.Soft.Transition == 1)) { PageFrameIndex = MI_GET_PAGE_FRAME_FROM_TRANSITION_PTE (PointerPte); AddThisPage = TRUE; } if (AddThisPage == TRUE) { // // Include only addresses that are backed by RAM, not mapped to // I/O space. // if (MiIsPhysicalMemoryAddress (PageFrameIndex, &Hint, FALSE)) { // // Add this page to the dump. // Status = Context->SetDumpRange (Context, (PVOID) PageFrameIndex, 1, 2); if (!NT_SUCCESS (Status)) { #if DBG DbgPrint ("Adding VA %p to crashdump failed\n", Va); DbgBreakPoint (); #endif Status = FALSE; } } } Va = (PVOID)((ULONG_PTR)Va + PAGE_SIZE); PointerPte += 1; ASSERT64 (PointerPpe->u.Hard.Valid == 1); ASSERT (PointerPde->u.Hard.Valid == 1); if ((Va > EndingAddress) || (Va == NULL)) { return Status; } // // If not at the end of a page table and still within the specified // range, just march directly on to the next PTE. // // Otherwise, if the virtual address is on a page directory boundary // then attempt to leap forward skipping over empty mappings // where possible. // } while (!MiIsVirtualAddressOnPdeBoundary(Va)); ASSERT (PointerPte == MiGetPteAddress (Va)); PointerPde = MiGetPdeAddress (Va); PointerPpe = MiGetPpeAddress (Va); PointerPxe = MiGetPxeAddress (Va); } while (TRUE); // NEVER REACHED } VOID MiAddActivePageDirectories ( IN PMM_KERNEL_DUMP_CONTEXT Context ) { UCHAR i; PKPRCB Prcb; PKPROCESS Process; PFN_NUMBER PageFrameIndex; #if defined (_X86PAE_) PMMPTE PointerPte; ULONG j; #endif for (i = 0; i < KeNumberProcessors; i += 1) { Prcb = KiProcessorBlock[i]; Process = Prcb->CurrentThread->ApcState.Process; #if defined (_X86PAE_) // // Note that on PAE systems, the idle and system process have // NULL initialized PaeTop fields. Thus this field must be // explicitly checked for before being referenced here. // // // Add the 4 top level page directory pages to the dump. // PointerPte = (PMMPTE) ((PEPROCESS)Process)->PaeTop; if (PointerPte == NULL) { PointerPte = &MiSystemPaeVa.PteEntry[0]; } for (j = 0; j < PD_PER_SYSTEM; j += 1) { PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE(PointerPte); PointerPte += 1; Context->SetDumpRange (Context, (PVOID) PageFrameIndex, 1, 2); } // // Add the real cr3 page to the dump, note that the value stored in the // directory table base is really a physical address (not a frame). // PageFrameIndex = Process->DirectoryTableBase[0]; PageFrameIndex = (PageFrameIndex >> PAGE_SHIFT); #else PageFrameIndex = MI_GET_DIRECTORY_FRAME_FROM_PROCESS ((PEPROCESS)(Process)); #endif // // Add this physical page to the dump. // Context->SetDumpRange (Context, (PVOID) PageFrameIndex, 1, 2); } #if defined(_IA64_) // // The first processor's PCR is mapped in region 4 which is not (and cannot) // be scanned later, so explicitly add it to the dump here. // Prcb = KiProcessorBlock[0]; Context->SetDumpRange (Context, (PVOID) Prcb->PcrPage, 1, 2); #endif } VOID MmGetKernelDumpRange ( IN PMM_KERNEL_DUMP_CONTEXT Context ) /*++ Routine Description: Add (and subtract) ranges of system memory to the crashdump. Arguments: Context - Crashdump context pointer. Return Value: None. Environment: Kernel mode, post-bugcheck. For use by crash dump routines ONLY. --*/ { PVOID Va; SIZE_T NumberOfBytes; ASSERT ((Context != NULL) && (Context->SetDumpRange != NULL) && (Context->FreeDumpRange != NULL)); MiAddActivePageDirectories (Context); #if defined(_IA64_) // // Note each IA64 region must be passed separately to MiAddRange... // Va = (PVOID) ALT4KB_PERMISSION_TABLE_START; NumberOfBytes = PDE_UTBASE + PAGE_SIZE - (ULONG_PTR) Va; MiAddRangeToCrashDump (Context, Va, NumberOfBytes); Va = (PVOID) MM_SESSION_SPACE_DEFAULT; NumberOfBytes = PDE_STBASE + PAGE_SIZE - (ULONG_PTR) Va; MiAddRangeToCrashDump (Context, Va, NumberOfBytes); Va = (PVOID) KADDRESS_BASE; NumberOfBytes = PDE_KTBASE + PAGE_SIZE - (ULONG_PTR) Va; MiAddRangeToCrashDump (Context, Va, NumberOfBytes); #elif defined(_AMD64_) Va = (PVOID) MM_SYSTEM_RANGE_START; NumberOfBytes = MM_KSEG0_BASE - (ULONG_PTR) Va; MiAddRangeToCrashDump (Context, Va, NumberOfBytes); Va = (PVOID) MM_KSEG2_BASE; NumberOfBytes = MM_SYSTEM_SPACE_START - (ULONG_PTR) Va; MiAddRangeToCrashDump (Context, Va, NumberOfBytes); Va = (PVOID) MM_PAGED_POOL_START; NumberOfBytes = MM_SYSTEM_SPACE_END - (ULONG_PTR) Va + 1; MiAddRangeToCrashDump (Context, Va, NumberOfBytes); #else Va = MmSystemRangeStart; NumberOfBytes = MM_SYSTEM_SPACE_END - (ULONG_PTR) Va + 1; MiAddRangeToCrashDump (Context, Va, NumberOfBytes); #endif // // Add any memory that is a part of the kernel space, but does not // have a virtual mapping (hence was not collected above). // MiAddPagesWithNoMappings (Context); // // Remove nonpaged pool that is not in use. // MiRemoveFreePoolMemoryFromDump (Context); }