/*++ Copyright (c) 1994 Microsoft Corporation Module Name: dumpchk.c Abstract: This module implements the NT crashdump validation tool. Author: Wesley Witt (wesw) 6-June-1994 Environment: NT 3.5 Revision History: --*/ #include #include #include #include #include #include #include #include #include #include ULONG KiProcessors[1]; ULONG KiPcrBaseAddress; // // mips // typedef struct _HARDWARE_PTE_MIPS { ULONG Global : 1; ULONG Valid : 1; ULONG Dirty : 1; ULONG CachePolicy : 3; ULONG PageFrameNumber : 24; ULONG Write : 1; ULONG CopyOnWrite : 1; } HARDWARE_PTE_MIPS, *PHARDWARE_PTE_MIPS; // // i386 // typedef struct _HARDWARE_PTE_I386 { ULONG Valid : 1; ULONG Write : 1; ULONG Owner : 1; ULONG WriteThrough : 1; ULONG CacheDisable : 1; ULONG Accessed : 1; ULONG Dirty : 1; ULONG Rsvd : 2; ULONG CopyOnWrite : 1; ULONG Prototype : 1; ULONG Transition : 1; ULONG PageFrameNumber : 20; } HARDWARE_PTE_I386, *PHARDWARE_PTE_I386; // // alpha // typedef struct _HARDWARE_PTE_ALPHA { ULONG Valid: 1; ULONG Owner: 1; ULONG Dirty: 1; ULONG reserved: 1; ULONG Global: 1; ULONG filler2: 2; ULONG Write: 1; ULONG CopyOnWrite: 1; ULONG PageFrameNumber: 23; } HARDWARE_PTE_ALPHA, *PHARDWARE_PTE_ALPHA; // // ppc // typedef struct _HARDWARE_PTE_PPC { ULONG Dirty : 2; ULONG Valid : 1; // software ULONG GuardedStorage : 1; // software? see 6-39 of PPC 601 UserManual ULONG MemoryCoherence : 1; ULONG CacheDisable : 1; ULONG WriteThrough : 1; ULONG Change : 1; ULONG Reference : 1; ULONG Write : 1; // software ULONG CopyOnWrite : 1; // software ULONG rsvd1 : 1; ULONG PageFrameNumber : 20; } HARDWARE_PTE_PPC, *PHARDWARE_PTE_PPC; CHAR CrashDumpFile[MAX_PATH]; ULONG PageSize; BOOL Verbose; BOOL PrintOnly; BOOL QuickCheck; DWORD ErrCnt; extern PPHYSICAL_MEMORY_DESCRIPTOR DmpPhysicalMemoryBlock; extern PCHAR DmpDumpBase; extern PULONG DmpDumpBaseUlong; extern PULONG DmpPdePage; extern PDUMP_HEADER DumpHeader; extern ULONG ValidPteMask; extern ULONG TransitionMask; extern ULONG TransitionCheck; extern ULONG ValidPfnMask; extern ULONG ValidPfnShift; extern ULONG TransitionPfnMask; extern ULONG TransitionPfnShift; extern ULONG PdeShift; extern ULONG PteShift; extern ULONG PteMask; extern ULONG PhysicalAddressMask; extern ULONG PhysicalAddressStart; extern ULONG PhysicalAddressEnd; extern ULONG PageSize; extern ULONG PageShift; // // prototypes // VOID GetCommandLineArgs(VOID); VOID Usage(VOID); VOID PrintHeader(LPSTR,PDUMP_HEADER,PCONTEXT,PEXCEPTION_RECORD); DWORD ValidateDumpFile(PDUMP_HEADER); ULONG GetPfn(LPVOID); DWORD ValidateModuleList(PDUMP_HEADER); BOOL ControlCHandler(DWORD); BOOL GetCrashDumpName(LPSTR,DWORD); int _cdecl main( int argc, char * argv[] ) /*++ Routine Description: Main entry point for the crasgdump check tool. Arguments: Standard c args. Return Value: Error count. --*/ { PCONTEXT Context; PEXCEPTION_RECORD Exception; PDUMP_HEADER DmpHeader; GetCommandLineArgs(); if (!CrashDumpFile[0]) { if (!GetCrashDumpName( CrashDumpFile, sizeof(CrashDumpFile))) { fprintf( stderr, "missing dump file name\n" ); return 1; } } if (!DmpInitialize( CrashDumpFile, &Context, &Exception, &DmpHeader )) { printf( "could not initialize dump file - %s\n", CrashDumpFile ); DmpUnInitialize(); return 1; } switch (DmpHeader->MachineImageType) { case IMAGE_FILE_MACHINE_I386: PageSize = 4096; break; case IMAGE_FILE_MACHINE_R4000: PageSize = 4096; break; case IMAGE_FILE_MACHINE_ALPHA: PageSize = 8192; break; } PrintHeader( CrashDumpFile, DmpHeader, Context, Exception ); if (!PrintOnly) { SetConsoleCtrlHandler( ControlCHandler, TRUE ); ValidateModuleList( DmpHeader ); ValidateDumpFile( DmpHeader ); if (ErrCnt) { printf( "\n\nTotal errors = %d\n", ErrCnt ); } else { printf( "**************\n" ); printf( "**************--> This dump file is good!\n" ); printf( "**************\n" ); } } DmpUnInitialize(); return ErrCnt; } ULONG GetPfn( LPVOID PdePage ) /*++ Routine Description: This routine extracts a pfn from a hardware pte. This routine handles the machine dependencies associated with hardware ptes. Arguments: PdePage - Pointer to a hardware pte Return Value: Page frame number. --*/ { ULONG pfn = 0; switch (DumpHeader->MachineImageType) { case IMAGE_FILE_MACHINE_I386: pfn = ((PHARDWARE_PTE_I386)PdePage)->PageFrameNumber; break; case IMAGE_FILE_MACHINE_R4000: pfn = ((PHARDWARE_PTE_MIPS)PdePage)->PageFrameNumber; break; case IMAGE_FILE_MACHINE_ALPHA: pfn = ((PHARDWARE_PTE_ALPHA)PdePage)->PageFrameNumber; break; case IMAGE_FILE_MACHINE_POWERPC: pfn = ((PHARDWARE_PTE_PPC)PdePage)->PageFrameNumber; break; } return pfn; } DWORD PrintError( ULONG va, ULONG pfn ) /*++ Routine Description: This routine discovers what type of error exists with the supplied virtual address and print the error text. Arguments: va - Virtual address pfn - pfn for the virtual address Return Value: 1 - An error was printed. 0 - An error was NOT printed. --*/ { CHAR errbuf[256]; LPSTR p; BOOL verr; ULONG loc; PVOID VaPage; ULONG PhyPage; ULONG PdeOffset; ULONG PteOffset; PULONG PtePage; DWORD rval = 0; p = errbuf; verr = FALSE; p += sprintf( p, "Error: memory read failed 0x%08x ", va ); if (pfn) { p += sprintf( p, "pfn=%x ", pfn ); } loc = (ULONG)VaToLocation( (LPVOID)va ); if ((va >= PhysicalAddressStart) && (va < PhysicalAddressEnd)) { PhyPage = GetPhysicalPage( (LPVOID)va ); VaPage = PageToLocation( PhyPage ); loc = (ULONG)((PCHAR)VaPage + (va & (PageSize - 1))) - (ULONG)DmpDumpBase; p += sprintf( p, "phypage=%x vapage=%x, loc=%x\n", PhyPage, VaPage, loc ); } else { PdeOffset = va >> PdeShift; PteOffset = (va >> PteShift) & PteMask; p += sprintf( p, "pde=%x, pte=%x ", PdeOffset, PteOffset ); if (DmpPdePage[PdeOffset] & ValidPteMask) { PtePage = PageToLocation(PteToPfn(DmpPdePage[PdeOffset])); p += sprintf( p, "ptepage=%x ", PtePage ); if (PtePage == NULL) { p += sprintf( p, "*** invalid ptepage\n" ); } else { VaPage = PageToLocation(PteToPfn (PtePage[PteOffset])); p += sprintf( p, "vapage=%x ", VaPage ); if (VaPage == NULL) { p += sprintf( p, "*** invalid vapage\n" ); verr = TRUE; } else { loc = (ULONG)((PCHAR)VaPage + ((ULONG)va & (PageSize - 1))) - (ULONG)DmpDumpBase; p += sprintf( p, "loc=%x\n", loc ); } } } else { p += sprintf( p, "*** invalid pde\n" ); } } if (verr) { if (Verbose) { printf( errbuf ); rval = 1; } } else { printf( errbuf ); rval = 1; } return rval; } DWORD ValidateDumpFile( PDUMP_HEADER DmpHeader ) /*++ Routine Description: This routine attempts to validate a crashdump file by enumerating all possible virtual addresses one page at a time and then reading memory at the virtual address. There are 2 classes of errors that are detected: swapped out memory and all others. If the error is dues to the memory being swapped out then the user can suppress the reporting of these errors thru the verbose flag. Arguments: DmpHeader - Supplies the crashdump header structure Return Value: Count of errors detected. --*/ { ULONG i; ULONG j; ULONG k; ULONG va1; ULONG va2; ULONG page; LPBYTE PageBuf; PULONG PtePage; ULONG addr; ULONG endaddr; ULONG len; // // allocate a buffer big enough to read one page // if (QuickCheck) { PageBuf = NULL; } else { PageBuf = malloc( PageSize ); } if (!PageBuf) { printf( "**************\n" ); printf( "**************--> Performing a quick check (^C to end)\n" ); printf( "**************\n" ); } else { printf( "**************\n" ); printf( "**************--> Performing a complete check (^C to end)\n" ); printf( "**************\n" ); } // // first enumerate all physical addresses // printf( "**************\n" ); printf( "**************--> Validating all physical addresses\n" ); printf( "**************\n" ); addr = PhysicalAddressStart; endaddr = PhysicalAddressEnd; while (addr < endaddr ) { len = min( PageSize, endaddr - addr ); page = GetPhysicalPage( (LPVOID)addr ); // // verify that there is a physical page in the // dump file fpr this address // for (k=0; kNumberOfRuns; k++) { if ((page >= DmpPhysicalMemoryBlock->Run[k].BasePage) && (page < (DmpPhysicalMemoryBlock->Run[k].BasePage + DmpPhysicalMemoryBlock->Run[k].PageCount))) { // // found it // break; } } if (k == DmpPhysicalMemoryBlock->NumberOfRuns) { // // there is not a physical page present so loop // back and process the next pte. this will happen // for memory that is mapped (like video) but is // not written to the crash dump // addr += len; continue; } if (!DmpReadMemory( (LPVOID)addr, PageBuf, len )) { ErrCnt += PrintError( addr, 0 ); } addr += len; } printf( "**************\n" ); printf( "**************--> Validating all virtual addresses\n" ); printf( "**************\n" ); // // enumerate all of the system pdes // for (i=0; iNumberOfRuns; k++) { if ((page >= DmpPhysicalMemoryBlock->Run[k].BasePage) && (page < (DmpPhysicalMemoryBlock->Run[k].BasePage + DmpPhysicalMemoryBlock->Run[k].PageCount))) { // // found it // break; } } if (k == DmpPhysicalMemoryBlock->NumberOfRuns) { // // there is not a physical page present so loop // back and process the next pte. this will happen // for memory that is mapped (like video) but is // not written to the crash dump // continue; } // // now we try to read a page of memory at this // newly formed virtual address // if (!DmpReadMemory( (LPVOID)va2, PageBuf, PageSize )) { // // failure, the crashdump file is either bad // or the memory is simply swapped out // ErrCnt += PrintError( va2, GetPfn( &DmpPdePage[i] ) ); } } } } // // free the page buffer // if (PageBuf) { free( PageBuf ); } return ErrCnt; } DWORD ValidateModuleList( PDUMP_HEADER DmpHeader ) { LIST_ENTRY List; PLIST_ENTRY Next; ULONG len = 0; ULONG cb; PLDR_DATA_TABLE_ENTRY DataTable; LDR_DATA_TABLE_ENTRY DataTableBuffer; CHAR AnsiBuffer[512]; WCHAR UnicodeBuffer[512]; UNICODE_STRING BaseName; printf( "**************\n" ); printf( "**************--> Validating the integrity of the PsLoadedModuleList\n" ); printf( "**************\n" ); if (!DmpReadMemory( DmpHeader->PsLoadedModuleList, (PVOID)&List, sizeof(LIST_ENTRY))) { printf( "Error: could not read base of PsLoadedModuleList\n\n" ); return ++ErrCnt; } Next = List.Flink; if (Next == NULL) { printf( "Error: PsLoadedModuleList is empty\n" ); ErrCnt += PrintError( (ULONG)DmpHeader->PsLoadedModuleList, 0 ); printf( "\n" ); return ErrCnt; } while ((ULONG)Next != (ULONG)DmpHeader->PsLoadedModuleList) { DataTable = CONTAINING_RECORD( Next, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks ); if (!DmpReadMemory( (PVOID)DataTable, (PVOID)&DataTableBuffer, sizeof(LDR_DATA_TABLE_ENTRY))) { printf( "Error: memory read failed addr=0x%08x\n\n", (DWORD)DataTable ); return ++ErrCnt; } Next = DataTableBuffer.InLoadOrderLinks.Flink; // // Get the base DLL name. // if (DataTableBuffer.BaseDllName.Length != 0 && DataTableBuffer.BaseDllName.Buffer != NULL ) { BaseName = DataTableBuffer.BaseDllName; } else if (DataTableBuffer.FullDllName.Length != 0 && DataTableBuffer.FullDllName.Buffer != NULL ) { BaseName = DataTableBuffer.FullDllName; } else { continue; } if (BaseName.Length > sizeof(UnicodeBuffer)) { printf( "Error: unicode buffer is too small\n" ); ErrCnt++; continue; } cb = DmpReadMemory( (PVOID)BaseName.Buffer, (PVOID)UnicodeBuffer, BaseName.Length ); if (!cb) { printf( "Error: memory read failed addr=0x%08x\n\n", (DWORD)BaseName.Buffer ); return ++ErrCnt; } RtlZeroMemory(&((PUCHAR)&UnicodeBuffer[0])[cb],(sizeof(WCHAR)*512)-cb); RtlZeroMemory(&AnsiBuffer[0],512); if (!WideCharToMultiByte( CP_ACP, 0, UnicodeBuffer, cb, AnsiBuffer, sizeof(AnsiBuffer), NULL, NULL )) { printf( "Error: could not convert module name to ansi\n\n" ); return ++ErrCnt; } if (Verbose) { printf( "validating %-16s 0x%08x 0x%08x\n", AnsiBuffer, DataTableBuffer.DllBase, DataTableBuffer.SizeOfImage ); } } printf( "\n" ); return ErrCnt; } VOID PrintHeader( LPSTR CrashDumpFile, PDUMP_HEADER DmpHeader, PCONTEXT Context, PEXCEPTION_RECORD Exception ) /*++ Routine Description: This routine prints each field in the crashdump header. Arguments: DmpHeader - Supplies the crashdump header structure Return Value: Nothing. --*/ { CHAR buf[16]; DWORD i; printf( "\n" ); printf( "Filename . . . . . . .%s\n", CrashDumpFile ); *(PULONG)buf = DmpHeader->Signature; buf[4] = 0; printf( "Signature. . . . . . .%s\n", buf ); *(PULONG)buf = DmpHeader->ValidDump; buf[4] = 0; printf( "ValidDump. . . . . . .%s\n", buf ); printf( "MajorVersion . . . . ." ); if (DmpHeader->MajorVersion == 0xc) { printf( "checked system\n" ); } else if (DmpHeader->MajorVersion == 0xf) { printf( "free system\n" ); } else { printf( "%d\n", DmpHeader->MajorVersion ); } printf( "MinorVersion . . . . .%d\n", DmpHeader->MinorVersion ); printf( "DirectoryTableBase . .0x%08x\n", DmpHeader->DirectoryTableBase ); printf( "PfnDataBase. . . . . .0x%08x\n", DmpHeader->PfnDataBase ); printf( "PsLoadedModuleList . .0x%08x\n", DmpHeader->PsLoadedModuleList ); printf( "PsActiveProcessHead. .0x%08x\n", DmpHeader->PsActiveProcessHead ); printf( "MachineImageType . . ." ); switch (DmpHeader->MachineImageType) { case IMAGE_FILE_MACHINE_I386: printf( "i386\n" ); break; case IMAGE_FILE_MACHINE_R4000: printf( "mips\n" ); break; case IMAGE_FILE_MACHINE_ALPHA: printf( "alpha\n" ); break; case IMAGE_FILE_MACHINE_POWERPC: printf( "PowerPC\n" ); break; } printf( "NumberProcessors . . .%d\n", DmpHeader->NumberProcessors ); printf( "BugCheckCode . . . . .0x%08x\n", DmpHeader->BugCheckCode ); printf( "BugCheckParameter1 . .0x%08x\n", DmpHeader->BugCheckParameter1 ); printf( "BugCheckParameter2 . .0x%08x\n", DmpHeader->BugCheckParameter2 ); printf( "BugCheckParameter3 . .0x%08x\n", DmpHeader->BugCheckParameter3 ); printf( "BugCheckParameter4 . .0x%08x\n", DmpHeader->BugCheckParameter4 ); printf( "\n" ); printf( "ExceptionCode. . . . .0x%08x\n", Exception->ExceptionCode ); printf( "ExceptionFlags . . . .0x%08x\n", Exception->ExceptionFlags ); printf( "ExceptionAddress . . .0x%08x\n", Exception->ExceptionAddress ); for (i=0; iNumberParameters; i++) { printf( "ExceptionParam#%d . .0x%08x\n", Exception->ExceptionInformation[i] ); } printf( "\n" ); printf( "NumberOfRuns . . . . .0x%x\n", DmpPhysicalMemoryBlock->NumberOfRuns ); printf( "NumberOfPages. . . . .0x%x\n", DmpPhysicalMemoryBlock->NumberOfPages ); for (i=0; iNumberOfRuns; i++) { printf( "Run #%d\n", i+1 ); printf( " BasePage . . . . . .0x%x\n", DmpPhysicalMemoryBlock->Run[i].BasePage ); printf( " PageCount. . . . . .0x%x\n", DmpPhysicalMemoryBlock->Run[i].PageCount ); } printf( "\n\n" ); } VOID GetCommandLineArgs( VOID ) /*++ Routine Description: Obtains the command line options for this tool. Arguments: None. Return Value: None. --*/ { char *lpstrCmd = GetCommandLine(); UCHAR ch; DWORD i = 0; // skip over program name do { ch = *lpstrCmd++; } while (ch != ' ' && ch != '\t' && ch != '\0'); // skip over any following white space while (ch == ' ' || ch == '\t') { ch = *lpstrCmd++; } if (!*lpstrCmd) { // // no args so we default the dump file // to what's in the registry // Verbose = TRUE; return; } // process each switch character '-' as encountered while (ch == '-' || ch == '/') { ch = tolower(*lpstrCmd++); // process multiple switch characters as needed do { switch (ch) { case 'v': Verbose = TRUE; ch = *lpstrCmd++; break; case 'p': PrintOnly = TRUE; ch = *lpstrCmd++; break; case 'q': QuickCheck = TRUE; ch = *lpstrCmd++; break; case '?': Usage(); ch = *lpstrCmd++; break; default: return; } } while (ch != ' ' && ch != '\t' && ch != '\0'); while (ch == ' ' || ch == '\t') { ch = *lpstrCmd++; } } // // get the crashdump file name // i=0; while (ch != ' ' && ch != '\0') { CrashDumpFile[i++] = ch; ch = *lpstrCmd++; } CrashDumpFile[i] = 0; return; } VOID Usage( VOID ) /*++ Routine Description: Prints usage text for this tool. Arguments: None. Return Value: None. --*/ { fprintf( stderr, "Microsoft (R) Windows NT (TM) Version 3.51 DUMPCHK\n" ); fprintf( stderr, "Copyright (C) 1995 Microsoft Corp. All rights reserved\n\n" ); fprintf( stderr, "usage: DUMPCHK [options] CrashDumpFile\n" ); fprintf( stderr, " [-?] Display this message\n" ); fprintf( stderr, " [-v] Verbose mode\n" ); fprintf( stderr, " [-p] Print header only, NO validation\n" ); fprintf( stderr, " [-q] Perform a quick test\n" ); ExitProcess(0); } BOOL ControlCHandler( DWORD dwCtrlType ) /*++ Routine Description: Handles Control-C events so that we can print an error summary before ending. Arguments: dwCtrlType - Event type Return Value: FALSE --*/ { if (dwCtrlType == CTRL_C_EVENT) { if (ErrCnt) { printf( "\n\nTotal errors = %d\n", ErrCnt ); } else { printf( "**************\n" ); printf( "**************--> This dump file is good (so far)!\n" ); printf( "**************\n" ); } } return FALSE; } BOOL GetCrashDumpName( LPSTR DumpName, DWORD Length ) { DWORD DataSize; DWORD DataType; CHAR Data[128]; LONG rc; HKEY hKey; if (RegOpenKeyEx( HKEY_LOCAL_MACHINE, "System\\CurrentControlSet\\Control\\CrashControl", 0, KEY_READ, &hKey ) != NO_ERROR) { // // unknown, possibly crashdumps not enabled // return FALSE; } DataSize = sizeof(Data); rc = RegQueryValueEx( hKey, "DumpFile", 0, &DataType, Data, &DataSize ); RegCloseKey( hKey ); if ((rc == NO_ERROR) && (DataType == REG_EXPAND_SZ)) { if (ExpandEnvironmentStrings( Data, DumpName, Length )) { return TRUE; } } return FALSE; }