//----------------------------------------------------------------------- // @doc // // @module convert crash dump to triage dump for crash dump utilities // // Copyright 1999 Microsoft Corporation. All Rights Reserved // #include #include #include #include #include #include #include #include #include #include #include #include #define NOEXTAPI #include "wdbgexts.h" #include "ntdbg.h" inline ALIGN_8(unsigned offset) { return (offset + 7) & 0xfffffff8; } 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; KDDEBUGGER_DATA64 g_DebuggerData; extern PKDDEBUGGER_DATA64 blocks[]; #define ExtractValue(NAME, val) { \ if (!g_DebuggerData.NAME) { \ val = 0; \ printf("g_DebuggerData.NAME is NULL"); \ } else { \ DmpReadMemory(g_DebuggerData.NAME, &(val), sizeof(val)); \ } \ } //BUGBUG #define PAGE_SHIFT 12 #define PAGE_SIZE 0x1000 #define ALIGN_DOWN_POINTER(address, type) \ ((PVOID)((ULONG_PTR)(address) & ~((ULONG_PTR)sizeof(type) - 1))) #define ALIGN_UP_POINTER(address, type) \ (ALIGN_DOWN_POINTER(((ULONG_PTR)(address) + sizeof(type) - 1), type)) const MAX_UNLOADED_NAME_LENGTH = 12; typedef struct _DUMP_UNLOADED_DRIVERS { UNICODE_STRING Name; WCHAR DriverName[MAX_UNLOADED_NAME_LENGTH]; PVOID StartAddress; PVOID EndAddress; } DUMP_UNLOADED_DRIVERS, *PDUMP_UNLOADED_DRIVERS; class CCrashDumpWrapper { public: // @cmember constructor CCrashDumpWrapper() { } ~CCrashDumpWrapper() {} unsigned GetCallStackSize(); unsigned GetDriverCount(PULONG cbNames); void WriteDriverList(BYTE *pb, unsigned offset, unsigned stringOffset); void WriteCurrentProcess(BYTE *pb, ULONG offset); void WriteUnloadedDrivers(BYTE *pb, ULONG offset); void WriteMmTriageInformation(BYTE *pb, ULONG offset); }; BOOL g_fVerbose; PDUMP_HEADER m_pHeader; PX86_CONTEXT m_pcontext; PEXCEPTION_RECORD m_pexception; const unsigned MAX_TRIAGE_STACK_SIZE = 16 * 1024; extern "C" { // processor block. We fill this in and it is accessed by crashlib PVOID KiProcessors[MAXIMUM_PROCESSORS]; // ignored but needed for linking ULONG KiPcrBaseAddress = 0; } #define MI_UNLOADED_DRIVERS 50 typedef struct _UNLOADED_DRIVERS { UNICODE_STRING Name; PVOID StartAddress; PVOID EndAddress; LARGE_INTEGER CurrentTime; } UNLOADED_DRIVERS, *PUNLOADED_DRIVERS; VOID GetCurrentThread(LPBYTE pthread) { // get current processor unsigned iProcessor = DmpGetCurrentProcessor(); // get KPCRB for current processor PX86_PARTIAL_KPRCB pkprcb = (PX86_PARTIAL_KPRCB)(KiProcessors[iProcessor]); ULONG64 threadAddr = 0; // read current thread pointer from KPCRB DmpReadMemory((ULONG64) &pkprcb->CurrentThread, &threadAddr, sizeof(ULONG)); // read current thread DmpReadMemory(threadAddr, pthread, X86_ETHREAD_SIZE); } unsigned CCrashDumpWrapper::GetDriverCount(PULONG cbNames) { LIST_ENTRY le; PLIST_ENTRY pleNext; PLDR_DATA_TABLE_ENTRY pdte; LDR_DATA_TABLE_ENTRY dte; unsigned cModules = 0; *cbNames = 0; // first entry is pointed to by the the PsLoadedModuleList field in the dump header LIST_ENTRY *pleHead = (PLIST_ENTRY) m_pHeader->PsLoadedModuleList; // read list element if (!DmpReadMemory((ULONG64) pleHead, (PVOID) &le, sizeof(LIST_ENTRY))) { printf("Could not read base of PsLoadedModuleList"); } // obtain pointer to next list element pleNext = le.Flink; if (pleNext == NULL) { printf("PsLoadedModuleList is empty"); } // while next list element is not pointer to headed while(pleNext != pleHead) { // obtain pointerr to loader entry pdte = CONTAINING_RECORD ( pleNext, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks ); // read loader entry if (!DmpReadMemory((ULONG64) pdte, (PVOID) &dte, sizeof(dte))) { printf("memory read failed addr=0x%08x", (ULONG)(ULONG_PTR) pdte); } // compute length of module name *cbNames += ALIGN_8((dte.BaseDllName.Length + 1) * sizeof(WCHAR) + sizeof(DUMP_STRING)); // get pointer to next loader entry pleNext = dte.InLoadOrderLinks.Flink; // if name is not null then this is a valid entry if (dte.BaseDllName.Length >= 0 && dte.BaseDllName.Buffer != NULL) cModules++; if (cModules > 10000) { printf("PsLoadedModuleList is empty"); exit(-1); } } // return # of modules return cModules; } unsigned CCrashDumpWrapper::GetCallStackSize() { BYTE thread[X86_ETHREAD_SIZE]; // get current thread GetCurrentThread(thread); // if kernel stack is not resident, then we can't do anything //if (!thread.Tcb.KernelStackResident) // return 0; // obtain stack base from thread ULONG StackBase = ((X86_THREAD *)(&thread[0]))->InitialStack; // obtain top of stack from register ESP ULONG_PTR StackPtr = m_pcontext->Esp; // make sure pointers make sense if (StackBase < StackPtr) { printf("Stack base pointer is invalid StackBase = %08x, esp=%08x", StackBase, StackPtr); return MAX_TRIAGE_STACK_SIZE; } // return stack size limited by max triage stack size (16K) return min((ULONG) StackBase - (ULONG) StackPtr, MAX_TRIAGE_STACK_SIZE); } void CCrashDumpWrapper::WriteDriverList( BYTE *pb, unsigned offset, unsigned stringOffset ) { PLIST_ENTRY pleNext; PLDR_DATA_TABLE_ENTRY pdte; PDUMP_DRIVER_ENTRY pdde; PDUMP_STRING pds; PDUMP_STRING pdsInitial; LIST_ENTRY le; LDR_DATA_TABLE_ENTRY dte; ULONG i = 0; // pointer to first driver entry to write out pdde = (PDUMP_DRIVER_ENTRY) (pb + offset); // pointer to first module name to write out pds = (PDUMP_STRING) (pb + stringOffset); pdsInitial = pds; // obtain pointer to list head from dump header PLIST_ENTRY pleHead = (PLIST_ENTRY) m_pHeader->PsLoadedModuleList; // read in list head if (!DmpReadMemory((ULONG64) pleHead, &le, sizeof(le))) { printf("Could not read base of the PsModuleList"); } // get pointer to first link pleNext = le.Flink; while (pleNext != pleHead) { // obtain pointer to loader entry pdte = CONTAINING_RECORD(pleNext, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks ); // read in loader entry if (!DmpReadMemory((ULONG64) pdte, (PVOID) &dte, sizeof(dte))) { printf("memory read failed addr=0x%08x", (DWORD)(ULONG_PTR) pdte); } // Build the entry in the string pool. We guarantee all strings are // NULL terminated as well as length prefixed. pds->Length = dte.BaseDllName.Length / 2; if (!DmpReadMemory((ULONG64) dte.BaseDllName.Buffer, pds->Buffer, pds->Length * sizeof (WCHAR))) { printf("memory read failed addr=0x%08x", (DWORD)(ULONG_PTR) dte.BaseDllName.Buffer); } // null terminate string pds->Buffer[pds->Length] = '\0'; // read in loader entry memcpy(&pdde->LdrEntry, &dte, sizeof(pdde->LdrEntry)); // replace pointer to string pdde->DriverNameOffset = (ULONG)((ULONG_PTR) pds - (ULONG_PTR) pb); // get pointer to next string pds = (PDUMP_STRING) ALIGN_UP_POINTER(((LPBYTE) pds) + sizeof(DUMP_STRING) + sizeof(WCHAR) * (pds->Length + 1), ULONGLONG); // extract timestamp and image size IMAGE_DOS_HEADER hdr; IMAGE_NT_HEADERS nthdr; unsigned cb; cb = DmpReadMemory((ULONG64) dte.DllBase, &hdr, sizeof(hdr)); if (cb == sizeof(IMAGE_DOS_HEADER) && hdr.e_magic == IMAGE_DOS_SIGNATURE && (hdr.e_lfanew & 3) == 0) { cb = DmpReadMemory((ULONG64) dte.DllBase + hdr.e_lfanew, &nthdr, sizeof(nthdr)); if (cb == sizeof(IMAGE_NT_HEADERS) && nthdr.Signature == IMAGE_NT_SIGNATURE) { // repoace next link with link date timestap and image size pdde->LdrEntry.TimeDateStamp = nthdr.FileHeader.TimeDateStamp; pdde->LdrEntry.SizeOfImage = nthdr.OptionalHeader.SizeOfImage; } } pleNext = dte.InLoadOrderLinks.Flink; pdde = (PDUMP_DRIVER_ENTRY)(((PUCHAR) pdde) + sizeof(*pdde)); } } void CCrashDumpWrapper::WriteCurrentProcess(BYTE *pb, ULONG offset) { BYTE thread[X86_ETHREAD_SIZE]; // get current htread GetCurrentThread(thread); // read process from pointer from thread DmpReadMemory((DWORD) ((X86_THREAD *)(&thread[0]))->ApcState.Process, pb + offset, X86_NT5_EPROCESS_SIZE); // validate type of object //if (process.Pcb.Header.Type != ProcessObject) //{ // printf("Current process object type is incorrect. The symbols are probably wrong."); //} } void CCrashDumpWrapper::WriteUnloadedDrivers(BYTE *pb, ULONG offset) { ULONG64 addr; ULONG i; ULONG Index; UNLOADED_DRIVERS *pud; UNLOADED_DRIVERS ud; PDUMP_UNLOADED_DRIVERS pdud; PVOID pvMiUnloadedDrivers; ULONG ulMiLastUnloadedDriver; // find location of unloaded drivers if (!(addr = g_DebuggerData.MmUnloadedDrivers)) { // if can't be found then no unloaded drivers *(PULONG) (pb + offset) = 0; return; } else // read in pointer to start of unloaded drivers DmpReadMemory(addr, &pvMiUnloadedDrivers, sizeof(PVOID)); // try finding symbol indicating offset of last unloaded driver if (!(addr = g_DebuggerData.MmLastUnloadedDriver)) { // if not found, then no unloaded drivers *(PULONG) (pb + offset) = 0; return; } else // read in offset of last unloaded driver DmpReadMemory(addr, &ulMiLastUnloadedDriver, sizeof(ULONG)); if (pvMiUnloadedDrivers == NULL) { // if unloaded driver pointer is null, then no unloaded drivers *(PULONG)(pb + offset) = 0; return; } // point to last unloaded drivers pdud = (PDUMP_UNLOADED_DRIVERS)((PULONG)(pb + offset) + 1); PUNLOADED_DRIVERS rgud = (PUNLOADED_DRIVERS) pvMiUnloadedDrivers; // // Write the list with the most recently unloaded driver first to the // least recently unloaded driver last. // Index = ulMiLastUnloadedDriver - 1; for (i = 0; i < MI_UNLOADED_DRIVERS; i += 1) { if (Index >= MI_UNLOADED_DRIVERS) Index = MI_UNLOADED_DRIVERS - 1; // read in unloaded driver if (!DmpReadMemory((ULONG64) &rgud[Index], &ud, sizeof(ud))) { printf("can't read memory from %08x", (ULONG)(ULONG_PTR)(&rgud[Index])); } // copy name lengths pdud->Name.MaximumLength = ud.Name.MaximumLength; pdud->Name.Length = ud.Name.Length; if (ud.Name.Buffer == NULL) break; // copy start and end address pdud->StartAddress = ud.StartAddress; pdud->EndAddress = ud.EndAddress; // restrict name length and maximum name length to 12 characters if (pdud->Name.Length > MAX_UNLOADED_NAME_LENGTH * 2) pdud->Name.Length = MAX_UNLOADED_NAME_LENGTH * 2; if (pdud->Name.MaximumLength > MAX_UNLOADED_NAME_LENGTH * 2) pdud->Name.MaximumLength = MAX_UNLOADED_NAME_LENGTH * 2; // setup pointer to driver name and read it in pdud->Name.Buffer = pdud->DriverName; if (!DmpReadMemory((ULONG64) ud.Name.Buffer, pdud->Name.Buffer, pdud->Name.MaximumLength)) { printf("cannot read memory at address %08x", (ULONG)(ULONG64)(ud.Name.Buffer)); } // move to previous driver pdud += 1; Index -= 1; } // number of drivers in the list *(PULONG) (pb + offset) = i; } void CCrashDumpWrapper::WriteMmTriageInformation(BYTE *pb, ULONG offset) { MI_TRIAGE_STORAGE TriageInformation; ULONG64 pMmVerifierData; ULONG64 pvMmPagedPoolInfo; ULONG_PTR cbNonPagedPool; ULONG_PTR cbPagedPool; // version information TriageInformation.Version = 1; // size information TriageInformation.Size = sizeof(MI_TRIAGE_STORAGE); // get special pool tag ExtractValue(MmSpecialPoolTag, TriageInformation.MmSpecialPoolTag); // get triage action taken ExtractValue(MmTriageActionTaken, TriageInformation.MiTriageActionTaken); pMmVerifierData = g_DebuggerData.MmVerifierData; // read in verifier level // BUGBUG - should not read internal data structures in MM //if (pMmVerifierData) // DmpReadMemory( // (ULONG64) &((MM_DRIVER_VERIFIER_DATA *) pMmVerifierData)->Level, // &TriageInformation.MmVerifyDriverLevel, // sizeof(TriageInformation.MmVerifyDriverLevel)); //else TriageInformation.MmVerifyDriverLevel = 0; // read in verifier ExtractValue(KernelVerifier, TriageInformation.KernelVerifier); // read non paged pool info ExtractValue(MmMaximumNonPagedPoolInBytes, cbNonPagedPool); TriageInformation.MmMaximumNonPagedPool = cbNonPagedPool >> PAGE_SHIFT; ExtractValue(MmAllocatedNonPagedPool, TriageInformation.MmAllocatedNonPagedPool); // read paged pool info ExtractValue(MmSizeOfPagedPoolInBytes, cbPagedPool); TriageInformation.PagedPoolMaximum = cbPagedPool >> PAGE_SHIFT; pvMmPagedPoolInfo = g_DebuggerData.MmPagedPoolInformation; // BUGBUG - should not read internal data structures in MM //if (pvMmPagedPoolInfo) // DmpReadMemory( // (ULONG64) &((MM_PAGED_POOL_INFO *) pvMmPagedPoolInfo)->AllocatedPagedPool, // &TriageInformation.PagedPoolAllocated, // sizeof(TriageInformation.PagedPoolAllocated)); //else TriageInformation.PagedPoolAllocated = 0; // read committed pages info ExtractValue(MmTotalCommittedPages, TriageInformation.CommittedPages); ExtractValue(MmPeakCommitment, TriageInformation.CommittedPagesPeak); ExtractValue(MmTotalCommitLimitMaximum, TriageInformation.CommitLimitMaximum); memcpy(pb + offset, &TriageInformation, sizeof(TriageInformation)); } //------------------------------------------------------------------- // @mfunc initialize the triage dump header from a full or kernel // dump // void InitTriageDumpHeader( TRIAGE_DUMP *ptdh, // out | triage dump header CCrashDumpWrapper &wrapper // in | wrapper for dump extraction functions ) { ULONG cbNames; // copy build number ExtractValue(CmNtCSDVersion, ptdh->ServicePackBuild); // set size of dump to 64K ptdh->SizeOfDump = TRIAGE_DUMP_SIZE; // valid offset is last DWORD in tiage dump ptdh->ValidOffset = TRIAGE_DUMP_SIZE - sizeof(ULONG); // context offset is fixed position on first page ptdh->ContextOffset = FIELD_OFFSET (DUMP_HEADER, ContextRecord); // exception offset is fixed position on first page ptdh->ExceptionOffset = FIELD_OFFSET (DUMP_HEADER, Exception); // starting offset in triage dump follows the triage dump header unsigned offset = ALIGN_8(PAGE_SIZE + sizeof(TRIAGE_DUMP)); // mm information is first ptdh->MmOffset = offset; // mm information is fixed size structure offset += ALIGN_8(sizeof(MI_TRIAGE_STORAGE)); // unloaded module list is next ptdh->UnloadedDriversOffset = offset; offset += sizeof(ULONG) + MI_UNLOADED_DRIVERS * sizeof(DUMP_UNLOADED_DRIVERS); // processor control block is next ptdh->PrcbOffset = offset; offset += ALIGN_8(X86_NT5_KPRCB_SIZE); // current process is next ptdh->ProcessOffset = offset; offset += ALIGN_8(X86_NT5_EPROCESS_SIZE); // current thread is next ptdh->ThreadOffset = offset; offset += ALIGN_8(X86_ETHREAD_SIZE); // call stack is next ptdh->CallStackOffset = offset; ptdh->SizeOfCallStack = wrapper.GetCallStackSize(); ptdh->TopOfStack = m_pcontext->Esp; offset += ALIGN_8(ptdh->SizeOfCallStack); // Offset of Driver List // loaded driver list is next ptdh->DriverListOffset = offset; ptdh->DriverCount = wrapper.GetDriverCount(&cbNames); offset += ALIGN_8(ptdh->DriverCount * sizeof(DUMP_DRIVER_ENTRY)); ptdh->StringPoolOffset = offset; ptdh->StringPoolSize = (ULONG) cbNames; ptdh->BrokenDriverOffset = 0; // all options are enabled ptdh->TriageOptions = 0xffffffff; } //------------------------------------------------------------------------ // @func convert a full or kernel dump to a triage dump // extern "C" BOOL DoConversion( LPSTR szInputDumpFile, // full or kernel dump HANDLE OutputDumpFile // triage dump file ) { PDUMP_HEADER pNewHeader; ULONG64 addr; ULONG i; // // Open the full dump files // crash dump wrapper has extraction functions for full dump // if (!DmpInitialize(szInputDumpFile, (PCONTEXT *)&m_pcontext, &m_pexception, (PVOID *)&m_pHeader)) { return 0; } // // Lets determine what version of the dump file we are looking at. // Read the appropriate data block based on that. // if (!m_pHeader) { return FALSE; } if ((m_pHeader->KdDebuggerDataBlock) && (m_pHeader->KdDebuggerDataBlock != 'EGAP')) { DmpReadMemory((ULONG64)(m_pHeader->KdDebuggerDataBlock), &g_DebuggerData, sizeof(g_DebuggerData)); } else { for (i=0; i<32; i++) { if (blocks[i]->PsLoadedModuleList == m_pHeader->PsLoadedModuleList) { g_DebuggerData = *(blocks[i]); break; } } if (i == 32) { return 0; } } CCrashDumpWrapper wrapper; if (addr = g_DebuggerData.KiProcessorBlock) { DmpReadMemory(addr, KiProcessors, sizeof(PVOID) * MAXIMUM_PROCESSORS); // validate dump file and throw if invalid DmpValidateDumpFile(1); // allocate block to hold triage dump pNewHeader = (PDUMP_HEADER) malloc(TRIAGE_DUMP_SIZE); if (pNewHeader) { // copy in first page (common between all dumps) memcpy(pNewHeader, m_pHeader, PAGE_SIZE); // set dump type to triage dump pNewHeader->DumpType = DUMP_TYPE_TRIAGE; // triage dump header begins on second page TRIAGE_DUMP *ptdh = (TRIAGE_DUMP *) ((BYTE *) pNewHeader + PAGE_SIZE); // setup triage dump header InitTriageDumpHeader(ptdh, wrapper); // write unloaded drivers wrapper.WriteUnloadedDrivers((PBYTE)pNewHeader, ptdh->UnloadedDriversOffset); // write mm information wrapper.WriteMmTriageInformation((PBYTE)pNewHeader, ptdh->MmOffset); // write stack if (ptdh->SizeOfCallStack > 0) DmpReadMemory(ptdh->TopOfStack, ((PBYTE)pNewHeader) + ptdh->CallStackOffset, ptdh->SizeOfCallStack); // write thread GetCurrentThread((PBYTE)pNewHeader + ptdh->ThreadOffset); // write process wrapper.WriteCurrentProcess((PBYTE)pNewHeader, ptdh->ProcessOffset); // write processor control block (KPRCB) DmpReadMemory((ULONG64) KiProcessors[DmpGetCurrentProcessor()], ((PBYTE)pNewHeader) + ptdh->PrcbOffset, X86_NT5_KPRCB_SIZE); // write loaded driver list wrapper.WriteDriverList((PBYTE)pNewHeader, ptdh->DriverListOffset, ptdh->StringPoolOffset); // end of triage dump validated ((ULONG *) pNewHeader)[TRIAGE_DUMP_SIZE/sizeof(ULONG) - 1] = TRIAGE_DUMP_VALID; ULONG cbWritten; if (!WriteFile(OutputDumpFile, pNewHeader, TRIAGE_DUMP_SIZE, &cbWritten, NULL )) { printf("Write to minidump file failed for reason %08x.\n", GetLastError()); return 0; } if (cbWritten != TRIAGE_DUMP_SIZE) { printf("Write to minidump failed because disk is full.\n"); return 0; } } } else { // not much we can do without the processor block printf("Cannot load KiProcessorBlock"); } DmpUnInitialize(); return 1; }