/*++ Copyright (c) 1999 Microsoft Corporation Module Name: triage.c Abstract: This module contains the Phase 0 code to triage bugchecks and automatically enable various system tracing components until the guilty party is found. Author: Landy Wang 13-Jan-1999 Revision History: --*/ #include "mi.h" #include "ntiodump.h" #ifdef ALLOC_PRAGMA #pragma alloc_text(INIT,MiTriageSystem) #pragma alloc_text(INIT,MiTriageAddDrivers) #endif // // Always update this macro when adding triage support for additional bugchecks. // #define MI_CAN_TRIAGE_BUGCHECK(BugCheckCode) \ ((BugCheckCode) == PROCESS_HAS_LOCKED_PAGES || \ (BugCheckCode) == NO_MORE_SYSTEM_PTES || \ (BugCheckCode) == BAD_POOL_HEADER || \ (BugCheckCode) == DRIVER_CORRUPTED_SYSPTES || \ (BugCheckCode) == DRIVER_CORRUPTED_EXPOOL || \ (BugCheckCode) == DRIVER_CORRUPTED_MMPOOL) // // These are bugchecks that were presumably triggered by either autotriage or // the admin's registry settings - so don't apply any new rules and in addition, // keep the old ones unaltered so it can reproduce. // #define MI_HOLD_TRIAGE_BUGCHECK(BugCheckCode) \ ((BugCheckCode) == DRIVER_USED_EXCESSIVE_PTES || \ (BugCheckCode) == DRIVER_LEFT_LOCKED_PAGES_IN_PROCESS || \ (BugCheckCode) == PAGE_FAULT_IN_FREED_SPECIAL_POOL || \ (BugCheckCode) == DRIVER_PAGE_FAULT_IN_FREED_SPECIAL_POOL || \ (BugCheckCode) == PAGE_FAULT_BEYOND_END_OF_ALLOCATION || \ (BugCheckCode) == DRIVER_PAGE_FAULT_BEYOND_END_OF_ALLOCATION || \ (BugCheckCode) == DRIVER_CAUGHT_MODIFYING_FREED_POOL || \ (BugCheckCode) == SYSTEM_PTE_MISUSE) #define MI_TRACKING_LOCKED_PAGES 0x00000001 #define MI_TRACKING_PTES 0x00000002 #define MI_PROTECT_FREED_NONPAGED_POOL 0x00000004 #define MI_VERIFYING_PRENT5_DRIVERS 0x00000008 #define MI_KEEPING_PREVIOUS_SETTINGS 0x00000010 #ifdef ALLOC_DATA_PRAGMA #pragma const_seg("INITCONST") #pragma data_seg("INITDATA") #endif const PCHAR MiTriageActionStrings[] = { "Locked pages tracking", "System PTE usage tracking", "Making accesses to freed nonpaged pool cause bugchecks", "Driver Verifying Pre-Windows 2000 built drivers", "Keeping previous autotriage settings" }; #if DBG ULONG MiTriageDebug = 0; BOOLEAN MiTriageRegardless = FALSE; #endif #ifdef ALLOC_DATA_PRAGMA #pragma const_seg() #pragma data_seg() #endif // // N.B. The debugger references this. // ULONG MmTriageActionTaken; // // The Version number must be incremented whenever the MI_TRIAGE_STORAGE // structure is changed. This enables usermode programs to decode the Mm // portions of triage dumps regardless of which kernel revision created the // dump. // typedef struct _MI_TRIAGE_STORAGE { ULONG Version; ULONG Size; ULONG MmSpecialPoolTag; ULONG MiTriageActionTaken; ULONG MmVerifyDriverLevel; ULONG KernelVerifier; ULONG_PTR MmMaximumNonPagedPool; ULONG_PTR MmAllocatedNonPagedPool; ULONG_PTR PagedPoolMaximum; ULONG_PTR PagedPoolAllocated; ULONG_PTR CommittedPages; ULONG_PTR CommittedPagesPeak; ULONG_PTR CommitLimitMaximum; } MI_TRIAGE_STORAGE, *PMI_TRIAGE_STORAGE; PKLDR_DATA_TABLE_ENTRY TriageGetLoaderEntry ( IN PVOID TriageDumpBlock, IN ULONG ModuleIndex ); LOGICAL TriageActUpon( IN PVOID TriageDumpBlock ); PVOID TriageGetMmInformation ( IN PVOID TriageDumpBlock ); LOGICAL MiTriageSystem ( IN PLOADER_PARAMETER_BLOCK LoaderBlock ) /*++ Routine Description: This routine takes the information from the last bugcheck (if any) and triages it. Various debugging options are then automatically enabled. Arguments: LoaderBlock - Supplies a pointer to the system loader block. Return Value: TRUE if triaging succeeded and options were enabled. FALSE otherwise. --*/ { PVOID TriageDumpBlock; ULONG_PTR BugCheckData[5]; ULONG i; ULONG ModuleCount; NTSTATUS Status; PLIST_ENTRY NextEntry; PKLDR_DATA_TABLE_ENTRY DataTableEntry; PKLDR_DATA_TABLE_ENTRY DumpTableEntry; LOGICAL Matched; ULONG OldDrivers; ULONG OldDriversNotVerifying; PMI_TRIAGE_STORAGE TriageInformation; if (LoaderBlock->Extension == NULL) { return FALSE; } if (LoaderBlock->Extension->Size < sizeof (LOADER_PARAMETER_EXTENSION)) { return FALSE; } TriageDumpBlock = LoaderBlock->Extension->TriageDumpBlock; Status = TriageGetBugcheckData (TriageDumpBlock, (PULONG)&BugCheckData[0], (PUINT_PTR) &BugCheckData[1], (PUINT_PTR) &BugCheckData[2], (PUINT_PTR) &BugCheckData[3], (PUINT_PTR) &BugCheckData[4]); if (!NT_SUCCESS (Status)) { return FALSE; } // // Always display at least the bugcheck data from the previous crash. // DbgPrint ("MiTriageSystem: Previous bugcheck was %x %p %p %p %p\n", BugCheckData[0], BugCheckData[1], BugCheckData[2], BugCheckData[3], BugCheckData[4]); if (TriageActUpon (TriageDumpBlock) == FALSE) { DbgPrint ("MiTriageSystem: Triage disabled in registry by administrator\n"); return FALSE; } DbgPrint ("MiTriageSystem: Triage ENABLED in registry by administrator\n"); // // See if the previous bugcheck was one where action can be taken. // If not, bail now. If so, then march on and verify all the loaded // module checksums before actually taking action on the bugcheck. // if (!MI_CAN_TRIAGE_BUGCHECK(BugCheckData[0])) { return FALSE; } TriageInformation = (PMI_TRIAGE_STORAGE) TriageGetMmInformation (TriageDumpBlock); if (TriageInformation == NULL) { return FALSE; } Status = TriageGetDriverCount (TriageDumpBlock, &ModuleCount); if (!NT_SUCCESS (Status)) { return FALSE; } // // Process module information from the triage dump. // #if DBG if (MiTriageDebug & 0x1) { DbgPrint ("MiTriageSystem: printing active drivers from triage crash...\n"); } #endif OldDrivers = 0; OldDriversNotVerifying = 0; for (i = 0; i < ModuleCount; i += 1) { DumpTableEntry = TriageGetLoaderEntry (TriageDumpBlock, i); if (DumpTableEntry != NULL) { if ((DumpTableEntry->Flags & LDRP_ENTRY_NATIVE) == 0) { OldDrivers += 1; if ((DumpTableEntry->Flags & LDRP_IMAGE_VERIFYING) == 0) { // // An NT3 or NT4 driver is in the system and was not // running under the verifier. // OldDriversNotVerifying += 1; } } #if DBG if (MiTriageDebug & 0x1) { DbgPrint (" %wZ: base = %p, size = %lx, flags = %lx\n", &DumpTableEntry->BaseDllName, DumpTableEntry->DllBase, DumpTableEntry->SizeOfImage, DumpTableEntry->Flags); } #endif } } // // Ensure that every driver that is currently loaded is identical to // the one in the triage dump before proceeding. // NextEntry = LoaderBlock->LoadOrderListHead.Flink; while (NextEntry != &LoaderBlock->LoadOrderListHead) { DataTableEntry = CONTAINING_RECORD(NextEntry, KLDR_DATA_TABLE_ENTRY, InLoadOrderLinks); Matched = FALSE; for (i = 0; i < ModuleCount; i += 1) { DumpTableEntry = TriageGetLoaderEntry (TriageDumpBlock, i); if (DumpTableEntry != NULL) { if (DataTableEntry->CheckSum == DumpTableEntry->CheckSum) { Matched = TRUE; break; } } } if (Matched == FALSE) { DbgPrint ("Matching checksum for module %wZ not found in triage dump\n", &DataTableEntry->BaseDllName); #if DBG if (MiTriageRegardless == FALSE) #endif return FALSE; } NextEntry = NextEntry->Flink; } #if DBG if (MiTriageDebug & 0x1) { DbgPrint ("MiTriageSystem: OldDrivers = %u, without verification =%u\n", OldDrivers, OldDriversNotVerifying); } #endif // // All boot loaded drivers matched, take action on the triage dump now. // if (MI_HOLD_TRIAGE_BUGCHECK(BugCheckData[0])) { // // The last bugcheck was presumably triggered by either autotriage or // the admin's registry settings - so don't apply any new rules // and in addition, keep the old ones unaltered so it can reproduce. // MmTriageActionTaken = TriageInformation->MiTriageActionTaken; MmTriageActionTaken |= MI_KEEPING_PREVIOUS_SETTINGS; } else { switch (BugCheckData[0]) { case PROCESS_HAS_LOCKED_PAGES: // // Turn on locked pages tracking so this turns into bugcheck // DRIVER_LEFT_LOCKED_PAGES_IN_PROCESS which shows the name // of the driver. // MmTriageActionTaken |= MI_TRACKING_LOCKED_PAGES; break; case DRIVER_CORRUPTED_SYSPTES: // // Turn on PTE tracking to trigger a SYSTEM_PTE_MISUSE bugcheck. // MmTriageActionTaken |= MI_TRACKING_PTES; break; case NO_MORE_SYSTEM_PTES: // // Turn on PTE tracking so the driver can be identified via a // DRIVER_USED_EXCESSIVE_PTES bugcheck. // if (BugCheckData[1] == SystemPteSpace) { MmTriageActionTaken |= MI_TRACKING_PTES; } break; case BAD_POOL_HEADER: case DRIVER_CORRUPTED_EXPOOL: // // Turn on the driver verifier and/or special pool. // Start by enabling it for every driver that isn't built for NT5. // Override any specified driver verifier options so that only // special pool is enabled to minimize the performance hit. // if (OldDrivers != 0) { if (OldDriversNotVerifying != 0) { MmTriageActionTaken |= MI_VERIFYING_PRENT5_DRIVERS; } } break; case DRIVER_CORRUPTED_MMPOOL: // // Protect freed nonpaged pool if the system had less than 128mb // of nonpaged pool anyway. This is to trigger a // DRIVER_CAUGHT_MODIFYING_FREED_POOL bugcheck. // #define MB128 ((ULONG_PTR)0x80000000 >> PAGE_SHIFT) if (TriageInformation->MmMaximumNonPagedPool < MB128) { MmTriageActionTaken |= MI_PROTECT_FREED_NONPAGED_POOL; } break; case IRQL_NOT_LESS_OR_EQUAL: case DRIVER_IRQL_NOT_LESS_OR_EQUAL: default: break; } } // // For now always show if action was taken from the bugcheck // data from the crash. This print and the space for the print strings // will be enabled for checked builds only prior to shipping. // if (MmTriageActionTaken != 0) { if (MmTriageActionTaken & MI_TRACKING_LOCKED_PAGES) { MmTrackLockedPages = TRUE; } if (MmTriageActionTaken & MI_TRACKING_PTES) { MmTrackPtes |= 0x1; } if (MmTriageActionTaken & MI_VERIFYING_PRENT5_DRIVERS) { MmVerifyDriverLevel &= ~DRIVER_VERIFIER_FORCE_IRQL_CHECKING; MmVerifyDriverLevel |= DRIVER_VERIFIER_SPECIAL_POOLING; } if (MmTriageActionTaken & MI_PROTECT_FREED_NONPAGED_POOL) { MmProtectFreedNonPagedPool = TRUE; } DbgPrint ("MiTriageSystem: enabling options below to find who caused the last crash\n"); for (i = 0; i < 32; i += 1) { if (MmTriageActionTaken & (1 << i)) { DbgPrint (" %s\n", MiTriageActionStrings[i]); } } } return TRUE; } LOGICAL MiTriageAddDrivers ( IN PLOADER_PARAMETER_BLOCK LoaderBlock ) /*++ Routine Description: This routine moves the names of any drivers that autotriage has determined need verifying from the LoaderBlock into pool. Arguments: LoaderBlock - Supplies a pointer to the system loader block. Return Value: TRUE if any drivers were added, FALSE if not. --*/ { ULONG i; ULONG ModuleCount; NTSTATUS Status; PKLDR_DATA_TABLE_ENTRY DumpTableEntry; PVOID TriageDumpBlock; ULONG NameLength; LOGICAL Added; PMI_VERIFIER_DRIVER_ENTRY VerifierDriverEntry; if ((MmTriageActionTaken & MI_VERIFYING_PRENT5_DRIVERS) == 0) { return FALSE; } TriageDumpBlock = LoaderBlock->Extension->TriageDumpBlock; Status = TriageGetDriverCount (TriageDumpBlock, &ModuleCount); if (!NT_SUCCESS (Status)) { return FALSE; } Added = FALSE; for (i = 0; i < ModuleCount; i += 1) { DumpTableEntry = TriageGetLoaderEntry (TriageDumpBlock, i); if (DumpTableEntry == NULL) { continue; } if (DumpTableEntry->Flags & LDRP_ENTRY_NATIVE) { continue; } DbgPrint ("MiTriageAddDrivers: Marking %wZ for verification when it is loaded\n", &DumpTableEntry->BaseDllName); NameLength = DumpTableEntry->BaseDllName.Length; VerifierDriverEntry = (PMI_VERIFIER_DRIVER_ENTRY)ExAllocatePoolWithTag ( NonPagedPool, sizeof (MI_VERIFIER_DRIVER_ENTRY) + NameLength, 'dLmM'); if (VerifierDriverEntry == NULL) { continue; } VerifierDriverEntry->Loads = 0; VerifierDriverEntry->Unloads = 0; VerifierDriverEntry->BaseName.Buffer = (PWSTR)((PCHAR)VerifierDriverEntry + sizeof (MI_VERIFIER_DRIVER_ENTRY)); VerifierDriverEntry->BaseName.Length = (USHORT)NameLength; VerifierDriverEntry->BaseName.MaximumLength = (USHORT)NameLength; RtlCopyMemory (VerifierDriverEntry->BaseName.Buffer, DumpTableEntry->BaseDllName.Buffer, NameLength); InsertHeadList (&MiSuspectDriverList, &VerifierDriverEntry->Links); Added = TRUE; } return Added; } #define MAX_UNLOADED_NAME_LENGTH 24 typedef struct _DUMP_UNLOADED_DRIVERS { UNICODE_STRING Name; WCHAR DriverName[MAX_UNLOADED_NAME_LENGTH / sizeof (WCHAR)]; PVOID StartAddress; PVOID EndAddress; } DUMP_UNLOADED_DRIVERS, *PDUMP_UNLOADED_DRIVERS; ULONG MmSizeOfUnloadedDriverInformation ( VOID ) /*++ Routine Description: This routine returns the size of the Mm-internal unloaded driver information that is stored in the triage dump when (if?) the system crashes. Arguments: None. Return Value: Size of the Mm-internal unloaded driver information. --*/ { if (MmUnloadedDrivers == NULL) { return sizeof (ULONG_PTR); } return sizeof(ULONG_PTR) + MI_UNLOADED_DRIVERS * sizeof(DUMP_UNLOADED_DRIVERS); } VOID MmWriteUnloadedDriverInformation ( IN PVOID Destination ) /*++ Routine Description: This routine stores the Mm-internal unloaded driver information into the triage dump. Arguments: None. Return Value: None. --*/ { ULONG i; ULONG Index; PUNLOADED_DRIVERS Unloaded; PDUMP_UNLOADED_DRIVERS DumpUnloaded; if (MmUnloadedDrivers == NULL) { *(PULONG)Destination = 0; } else { DumpUnloaded = (PDUMP_UNLOADED_DRIVERS)((PULONG_PTR)Destination + 1); Unloaded = MmUnloadedDrivers; // // Write the list with the most recently unloaded driver first to the // least recently unloaded driver last. // Index = MmLastUnloadedDriver - 1; for (i = 0; i < MI_UNLOADED_DRIVERS; i += 1) { if (Index >= MI_UNLOADED_DRIVERS) { Index = MI_UNLOADED_DRIVERS - 1; } Unloaded = &MmUnloadedDrivers[Index]; DumpUnloaded->Name = Unloaded->Name; if (Unloaded->Name.Buffer == NULL) { break; } DumpUnloaded->StartAddress = Unloaded->StartAddress; DumpUnloaded->EndAddress = Unloaded->EndAddress; if (DumpUnloaded->Name.Length > MAX_UNLOADED_NAME_LENGTH) { DumpUnloaded->Name.Length = MAX_UNLOADED_NAME_LENGTH; } if (DumpUnloaded->Name.MaximumLength > MAX_UNLOADED_NAME_LENGTH) { DumpUnloaded->Name.MaximumLength = MAX_UNLOADED_NAME_LENGTH; } DumpUnloaded->Name.Buffer = DumpUnloaded->DriverName; RtlCopyMemory ((PVOID)DumpUnloaded->Name.Buffer, (PVOID)Unloaded->Name.Buffer, DumpUnloaded->Name.MaximumLength); DumpUnloaded += 1; Index -= 1; } *(PULONG)Destination = i; } } ULONG MmSizeOfTriageInformation ( VOID ) /*++ Routine Description: This routine returns the size of the Mm-internal information that is stored in the triage dump when (if?) the system crashes. Arguments: None. Return Value: Size of the Mm-internal triage information. --*/ { return sizeof (MI_TRIAGE_STORAGE); } VOID MmWriteTriageInformation ( IN PVOID Destination ) /*++ Routine Description: This routine stores the Mm-internal information into the triage dump. Arguments: None. Return Value: None. --*/ { MI_TRIAGE_STORAGE TriageInformation; TriageInformation.Version = 1; TriageInformation.Size = sizeof (MI_TRIAGE_STORAGE); TriageInformation.MmSpecialPoolTag = MmSpecialPoolTag; TriageInformation.MiTriageActionTaken = MmTriageActionTaken; TriageInformation.MmVerifyDriverLevel = MmVerifierData.Level; TriageInformation.KernelVerifier = KernelVerifier; TriageInformation.MmMaximumNonPagedPool = MmMaximumNonPagedPoolInBytes >> PAGE_SHIFT; TriageInformation.MmAllocatedNonPagedPool = MmAllocatedNonPagedPool; TriageInformation.PagedPoolMaximum = MmSizeOfPagedPoolInBytes >> PAGE_SHIFT; TriageInformation.PagedPoolAllocated = MmPagedPoolInfo.AllocatedPagedPool; TriageInformation.CommittedPages = MmTotalCommittedPages; TriageInformation.CommittedPagesPeak = MmPeakCommitment; TriageInformation.CommitLimitMaximum = MmTotalCommitLimitMaximum; RtlCopyMemory (Destination, (PVOID)&TriageInformation, sizeof (MI_TRIAGE_STORAGE)); }