windows-nt/Source/XPSP1/NT/sdktools/debuggers/dumpconv/lib/convert.cpp
2020-09-26 16:20:57 +08:00

746 lines
22 KiB
C++

//-----------------------------------------------------------------------
// @doc
//
// @module convert crash dump to triage dump for crash dump utilities
//
// Copyright 1999 Microsoft Corporation. All Rights Reserved
//
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <tchar.h>
#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <windows.h>
#include <triage.h>
#include <crash.h>
#include <ntiodump.h>
#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;
}