singrdk/base/Windows/singx86/crashdump.cpp

1375 lines
41 KiB
C++

/////////////////////////////////////////////////////////////////////////////
//
// crashdump.cpp - Extension to save a dumpfile from debugger for offline analysis
//
// Copyright Microsoft Corporation. All rights reserved.
//
#include "singx86.h"
#include "diag.h"
//
// Global states controlling the extension options
//
BOOL IncludeExecutables;
BOOL IncludeRegisterReferences;
void AddMemoryReference(UINT64 address);
BOOL FullMemoryDump;
//
// Cross-platform utility functions
//
USHORT GetTargetContextSize()
{
switch (TargetMachine) {
case IMAGE_FILE_MACHINE_AMD64:
return sizeof(X64_CONTEXT);
break;
case IMAGE_FILE_MACHINE_I386:
return sizeof(X86_CONTEXT);
break;
case IMAGE_FILE_MACHINE_ARM:
return sizeof(ARM_CONTEXT);
break;
}
ExtOut("Unknown processor architecture. Exiting\n");
return 0;
}
UINT64 GetStackPointer(VOID * threadContext)
{
switch (TargetMachine) {
case IMAGE_FILE_MACHINE_AMD64:
return ((X64_CONTEXT*)threadContext)->Rsp;
break;
case IMAGE_FILE_MACHINE_I386:
return ((X86_CONTEXT*)threadContext)->Esp;
break;
case IMAGE_FILE_MACHINE_ARM:
return ((ARM_CONTEXT*)threadContext)->Pc;
break;
}
return 0;
}
void FetchRegisterReferences(VOID * dbgCOntext)
{
if (IncludeRegisterReferences) {
switch (TargetMachine) {
case IMAGE_FILE_MACHINE_AMD64: {
X64_CONTEXT * ctx = (X64_CONTEXT *)dbgCOntext;
AddMemoryReference(ctx->Rip);
AddMemoryReference(ctx->Rsi);
AddMemoryReference(ctx->Rbx);
AddMemoryReference(ctx->Rcx);
AddMemoryReference(ctx->Rdx);
AddMemoryReference(ctx->Rax);
AddMemoryReference(ctx->Rdi);
AddMemoryReference(ctx->R8);
AddMemoryReference(ctx->R9);
AddMemoryReference(ctx->R10);
AddMemoryReference(ctx->R11);
AddMemoryReference(ctx->R12);
AddMemoryReference(ctx->R13);
AddMemoryReference(ctx->R14);
AddMemoryReference(ctx->R15);
break;
}
case IMAGE_FILE_MACHINE_I386: {
X86_CONTEXT * ctx = (X86_CONTEXT *)dbgCOntext;
AddMemoryReference(ctx->Eip);
AddMemoryReference(ctx->Esi);
AddMemoryReference(ctx->Ebx);
AddMemoryReference(ctx->Ecx);
AddMemoryReference(ctx->Edx);
AddMemoryReference(ctx->Eax);
AddMemoryReference(ctx->Edi);
break;
}
case IMAGE_FILE_MACHINE_ARM: {
ARM_CONTEXT * ctx = (ARM_CONTEXT *)dbgCOntext;
AddMemoryReference(ctx->R0);
AddMemoryReference(ctx->R1);
AddMemoryReference(ctx->R2);
AddMemoryReference(ctx->R3);
AddMemoryReference(ctx->R4);
AddMemoryReference(ctx->R5);
AddMemoryReference(ctx->R6);
AddMemoryReference(ctx->R7);
AddMemoryReference(ctx->R8);
AddMemoryReference(ctx->R9);
AddMemoryReference(ctx->R10);
AddMemoryReference(ctx->R11);
AddMemoryReference(ctx->R12);
AddMemoryReference(ctx->Lr);
AddMemoryReference(ctx->Pc);
break;
}
}
}
}
void LoadThreadContext(void * dbgContext, SpillContext * context)
{
switch (TargetMachine) {
case IMAGE_FILE_MACHINE_AMD64: {
X64_CONTEXT * ctx = (X64_CONTEXT *)dbgContext;
ctx->ContextFlags = (CONTEXT_CONTROL | CONTEXT_INTEGER);
ctx->Rdi = context->di;
ctx->Rsi = context->si;
ctx->Rbx = context->bx;
ctx->Rdx = context->dx;
ctx->Rcx = context->cx;
ctx->Rax = context->ax;
ctx->R8 = context->r8;
ctx->R9 = context->r9;
ctx->R10 = context->r10;
ctx->R11 = context->r11;
ctx->R12 = context->r12;
ctx->R13 = context->r13;
ctx->R14 = context->r14;
ctx->R15 = context->r15;
ctx->Rbp = context->bp;
ctx->Rip = context->ip;
ctx->EFlags = (ULONG32)context->fl;
ctx->Rsp = context->sp;
break;
}
case IMAGE_FILE_MACHINE_I386: {
X86_CONTEXT * ctx = (X86_CONTEXT *)dbgContext;
ctx->ContextFlags = (CONTEXT_CONTROL | CONTEXT_INTEGER);
ctx->Edi = (ULONG32)context->di;
ctx->Esi = (ULONG32)context->si;
ctx->Ebx = (ULONG32)context->bx;
ctx->Edx = (ULONG32)context->dx;
ctx->Ecx = (ULONG32)context->cx;
ctx->Eax = (ULONG32)context->ax;
ctx->Ebp = (ULONG32)context->bp;
ctx->Eip = (ULONG32)context->ip;
ctx->EFlags = (ULONG32)context->fl;
ctx->Esp = (ULONG32)context->sp;
break;
}
case IMAGE_FILE_MACHINE_ARM: {
ARM_CONTEXT * ctx = (ARM_CONTEXT *)dbgContext;
ctx->ContextFlags = (CONTEXT_CONTROL | CONTEXT_INTEGER);
ctx->R0 = (ULONG32)context->r0;
ctx->R1 = (ULONG32)context->r0;
ctx->R2 = (ULONG32)context->r0;
ctx->R3 = (ULONG32)context->r0;
ctx->R4 = (ULONG32)context->r0;
ctx->R5 = (ULONG32)context->r0;
ctx->R6 = (ULONG32)context->r0;
ctx->R7 = (ULONG32)context->r0;
ctx->R8 = (ULONG32)context->r0;
ctx->R9 = (ULONG32)context->r0;
ctx->R10 = (ULONG32)context->r0;
ctx->R11 = (ULONG32)context->r0;
ctx->R12 = (ULONG32)context->r0;
ctx->Sp = (ULONG32)context->sp;
ctx->Lr = (ULONG32)context->lr;
ctx->Pc = (ULONG32)context->pc;
ctx->Cpsr = (ULONG32)context->cpsr;
break;
}
}
}
//
// Define these for the SAL annotations in dbghelp.h
//
#define unknown
#define __out_xcount(x)
#include "dbghelp.h"
static HRESULT Usage()
{
ExtOut("Usage:\n"
" !crashdump [options]\n"
"Options:\n"
" -s <file_name> : Save the dump in the provided file\n"
" -f : Include all memory pages in the dump\n"
" -x : Include images in dumps\n"
" -r : Include register references\n"
" -t : Start tracing mode. All memory references used by compliant extensions are logged\n"
" to be available for offline usage\n"
);
return S_FALSE;
}
//
// Never include a range larger than 100 M in the dumpfile
//
#define MAX_MEMORY_RANGE 100*1024*1024
#define MEMORY_PAGE_SIZE 4096
struct _MEMORY_STREAM {
UINT32 MemoryCount;
MINIDUMP_MEMORY_DESCRIPTOR Memory[1];
};
struct CRASH_DUMP_HEADER
{
MINIDUMP_HEADER Header;
MINIDUMP_DIRECTORY Directories[5];
//
// Include the fixed size structures here to make it easier to deal with
//
MINIDUMP_SYSTEM_INFO SystemInfo;
MINIDUMP_EXCEPTION_STREAM ExceptionInfo;
};
//
// The crashdump extension accesses PEImage and process information structures
// Create the accessors for them
//
struct PEImage {
ULONG64 VirtualAddress;
ULONG64 loadedSize;
ULONG64 timeDateStamp;
ULONG64 checkSum;
};
FieldType PEImageFields[] = {
FIELD(PEImage, VirtualAddress),
FIELD(PEImage, loadedSize),
FIELD(PEImage, timeDateStamp),
FIELD(PEImage, checkSum),
FIELDEND(),
};
StructType PEImageStruct = StructType(kernel_PEImage,
sizeof(PEImage), PEImageFields);
struct ProcessInfo {
ULONG64 image;
ULONG64 imageName;
};
FieldType ProcessInfoFields[] = {
FIELD(ProcessInfo, image),
FIELD(ProcessInfo, imageName),
FIELDEND(),
};
// kernel name is potentially fixed up in StructType::Initialize
StructType ProcessInfoStruct = StructType(kernel_Process,
sizeof(ProcessInfo), ProcessInfoFields);
//
// Implementation of the MemoryRagesCollection class.
// This object is managing the arbitrary ranges that are being collected
// to be included in the crashdump at various phases.
// There is a strict requirement of the crashdump to never include overlapped
// descriptors for the memory ranges. but practically, memory collected
// from various areas may in fact partially or totally overlap
// After the collection is done, CompactItems method takes care of
// sanitazing and coalescing overlapped ranges
//
static int ItemCompare(const void * e1, const void * e2)
{
UINT64 *ptr1 = (UINT64 *) e1;
UINT64 *ptr2 = (UINT64 *) e2;
if (ptr1[0] < ptr2[0]) {
return -1;
} else if (ptr1[0] == ptr2[0]) {
return 0;
}
return 1;
}
void MemoryRagesCollection::AddRange(UINT64 startRVA, UINT64 rangeSize)
{
if (startRVA > MEMORY_PAGE_SIZE * 16) {
// ExtVerb("Adding range %p %p ->", startRVA, rangeSize);
UINT64 endRVA = ROUND_UP_TO_POWER2(startRVA + rangeSize, (UINT64)MEMORY_PAGE_SIZE);
startRVA = ROUND_DOWN_TO_POWER2(startRVA, (UINT64)MEMORY_PAGE_SIZE);
rangeSize = endRVA - startRVA;
if ((PreviousAddress == startRVA) && (PreviousSize == rangeSize)) {
// ExtVerb(" %p %p (skipped)\n", startRVA, rangeSize);
return;
}
// ExtVerb(" %p %p\n", startRVA, rangeSize);
Add(startRVA);
Add(rangeSize);
PreviousAddress = startRVA;
PreviousSize = rangeSize;
}
}
void MemoryRagesCollection::CompactItems()
{
//
// Sort all ranges by address to make easier searching for overlapped ranges
//
qsort(Array, GetRangesCount(), 2*sizeof(UINT64), ItemCompare);
int lastValidRange = 0;
UINT64 prevAddr = GetStartAddress(0);
UINT64 prevSize = GetRangeSize(0);
UINT64 prevLimit = prevAddr + prevSize;
for (int i = 1; i < GetRangesCount(); i++) {
//
// Itterate through ranges, and see if the current one
// has any non-empty intersection.
//
UINT64 addr = GetStartAddress(i);
UINT64 size = GetRangeSize(i);
UINT64 limit = addr + size;
if (addr < (prevAddr + prevSize)) {
//
// There is an overlap. If this range is completly included
// clear it completly from the array, otherwise adjust the
// previous range to capture this new limit.
//
ExtVerb("%p %p %p %p\n", prevAddr, prevLimit, addr, limit);
if (limit > prevLimit) {
prevSize += limit - prevLimit;
Array[lastValidRange * 2 + 1] = prevSize;
prevLimit = limit;
}
//
// This range has been already taken care of
// we need to exclude from the list
//
Array[i * 2] = 0;
Array[i * 2 + 1] = 0;
//
// Keep track of the discarded ranges to get an accurate value of
// the valid entries that will have to be copied
//
DiscardedRanges += 1;
} else {
//
// No overlap here, set the new current range values
//
lastValidRange = i;
prevAddr = addr;
prevSize = size;
prevLimit = limit;
}
}
}
UINT64 MemoryRagesCollection::GetRVA(UINT64 address)
{
UINT64 RVA = 0;
//
// This function is getting the coresponding RVA in the crashdump,
// from a given address
//
for (int i = 0; i < GetRangesCount(); i++) {
UINT64 addr = GetStartAddress(i);
UINT64 size = GetRangeSize(i);
UINT64 limit = addr + size;
if ((address >= addr) && (address < limit)) {
//
// Found it. return the value, including also the offset inside the
// current range
//
RVA += address - addr;
return RVA;
}
//
// Keep track of the actual RVA as we walk the ranges
//
RVA += size;
}
return 0;
}
MemoryRagesCollection * MemoryRanges;
void ResetGlobals()
{
IncludeExecutables = FALSE;
IncludeRegisterReferences = FALSE;
FullMemoryDump = FALSE;
}
//
// Functions to support memory access references to include them in minidump
// An extension would have to switch to use TraceRead instead of g_ExtData->ReadVirtual
// if the memory accessed will be required in offline analysis
//
MemoryRagesCollection * TracingStore = NULL;
//
// Define how much memory around that address needs to be captured to simplify and add more
// context to dump files
//
#define TRACE_GRANULARITY MEMORY_PAGE_SIZE
HRESULT TraceRead(UINT64 address, void * buffer, ULONG size)
{
if (TracingStore != NULL) {
if (address > TRACE_GRANULARITY) {
TracingStore->AddRange(address - TRACE_GRANULARITY, size + 2*TRACE_GRANULARITY);
}
}
return g_ExtData->ReadVirtual(address, buffer, size, NULL);
}
HRESULT TraceReadPointer(int count, UINT64 address, PULONG64 buffer)
{
if (TracingStore != NULL) {
if (address > TRACE_GRANULARITY) {
TracingStore->AddRange(address - TRACE_GRANULARITY, 2*TRACE_GRANULARITY);
}
}
return g_ExtData->ReadPointersVirtual(count, address, buffer);
}
//
// Support functions to build the module list part of the crashdump
// W/o a proper list of modules loaded, the crashdumps will be useless since no debug
// information can be loaded, nor any extension can work
//
WCHAR ImageNameBuffer[MEMORY_PAGE_SIZE];
PVOID CrtName;
UINT NameSizes[1024];
MINIDUMP_MODULE_LIST * ReadModuleInformation()
{
HRESULT status;
//
// Reset the name buffer cursor
//
CrtName = (PVOID)ImageNameBuffer;
ULONG pointerSize = (g_ExtControl->IsPointer64Bit() == S_OK) ? 8 : 4;
ULONG64 address = 0;
ULONG64 procs = 0;
ULONG type = 0;
ULONG subtype = 0;
ULONG64 module = 0;
MINIDUMP_MODULE_LIST * moduleList = NULL;
//
// Although Singularity does not maintain a list with the module loaded in the system
// this can be accessible via process enumeration, each process having
// a pointer to the image, as well as the image file name. It is also
// critical to have the actual base address of the image properly set
//
EXT_CHECK(g_ExtSymbols->GetOffsetByName(kernel_processTable, &address));
EXT_CHECK(g_ExtSymbols->GetOffsetTypeId(address, &type, &module));
EXT_CHECK(g_ExtData->ReadPointersVirtual(1, address, &procs));
ExtVerb("processTable: %p\n", procs);
CHAR name[512];
EXT_CHECK(g_ExtSymbols->GetTypeName(module, type, name, arrayof(name), NULL));
ExtVerb(" processTable type: %s\n", name);
ULONG lengthOffset = 0;
EXT_CHECK(g_ExtSymbols->GetFieldOffset(module, type, "overall_length", &lengthOffset));
ULONG valuesOffset = 0;
EXT_CHECK(g_ExtSymbols->GetFieldOffset(module, type, "values", &valuesOffset));
//
// Read the size of the table and prepare for enumeration of the entries
//
ULONG length = 0;
ULONG processCount = 0;
if (procs != 0) {
EXT_CHECK(TraceRead(procs + lengthOffset, &length, sizeof(length)));
ExtVerb("processTable: %p [maximum %d entries]\n", procs, length);
for (ULONG id = 0; id < length; id++) {
ULONG64 process = 0;
EXT_CHECK(g_ExtData->ReadPointersVirtual(1, procs + valuesOffset + id * pointerSize, &process));
if (process != 0) {
//
// Select only the valid entries in enumeration
//
processCount += 1;
}
}
}
// The number of modules in the least can be at most the number of processes we enumerated. In fact
// the number is smaller since idle, kernel don't have a coresponding file. We do reserve
// a special slot for the kernel image in (processCount + 1), not relying that we'll always have something
// in the table would represent that one
moduleList = (MINIDUMP_MODULE_LIST *)malloc(sizeof(MINIDUMP_MODULE_LIST)
+ (processCount + 1) * sizeof(MINIDUMP_MODULE));
if (moduleList == NULL) goto Exit;
moduleList->NumberOfModules = 0;
if (moduleList == NULL)
return NULL;
//
// Write first the kernel module. We fetch this from the static field of the PEImage
//
MINIDUMP_MODULE * moduleInfo = &moduleList->Modules[moduleList->NumberOfModules];
RtlZeroMemory(moduleInfo, sizeof(*moduleInfo));
UINT64 kernelImageAddress;
EXT_CHECK(g_ExtSymbols->GetOffsetByName(kernel_PEImage_kernel, &kernelImageAddress));
EXT_CHECK(g_ExtData->ReadPointersVirtual(1, kernelImageAddress, &kernelImageAddress));
ExtVerb("KernelImage %p\n", kernelImageAddress);
//
// Fill in now all fields from the structure
//
PEImage imageInfo;
PEImageStruct.Read(kernelImageAddress, &imageInfo);
ExtVerb("KernelImage info %p %p %p %p\n",
imageInfo.VirtualAddress,
imageInfo.loadedSize,
imageInfo.checkSum,
imageInfo.timeDateStamp);
moduleInfo->BaseOfImage = imageInfo.VirtualAddress;
moduleInfo->SizeOfImage = (ULONG32)imageInfo.loadedSize;
moduleInfo->CheckSum = (ULONG32)imageInfo.checkSum;
moduleInfo->TimeDateStamp = (ULONG32)imageInfo.timeDateStamp;
moduleInfo->ModuleNameRva = (ULONG32)((UINT64)CrtName - (UINT64)ImageNameBuffer);
//
// We know at this point the image base and size, we can include in the dump
// if the appropriate option has been specified. Otherwise we just include the
// first page so that the debugger can check the header information such as timestamp
// when debugged offline
//
MemoryRanges->AddRange(moduleInfo->BaseOfImage, IncludeExecutables ? moduleInfo->SizeOfImage : MEMORY_PAGE_SIZE);
//
// Prepare now to save the kernel name to the crashdump. We have prepared
// a buffer holding all names, where we copy the strings to and set RVA references
//
UINT32 * Size = (UINT32 *)CrtName;
CrtName = (PVOID)(Size + 1);
//
// For the kernel we just have a hardcoded name
//
wcscpy((WCHAR*)CrtName, L"kernel");
*Size = (UINT32)wcslen((WCHAR*)CrtName) * sizeof(WCHAR);
NameSizes[0] = sizeof(*Size) + (*Size) * sizeof(WCHAR);
//
// Move the cursor to the next empty position
// TODO: test for buffer limits
//
CrtName = (PVOID)((WCHAR*)CrtName + *Size);
moduleList->NumberOfModules += 1;
//
// Enumerate now the processes and fetch for each process the module information
//
for (ULONG id = 0; id < length; id++) {
ULONG64 process = 0;
EXT_CHECK(g_ExtData->ReadPointersVirtual(1, procs + valuesOffset + id * pointerSize, &process));
if (process != 0) {
ProcessInfo pInfo;
//
// Retrieve the process data, in order to get to the image information
//
ProcessInfoStruct.Read(process, &pInfo);
ExtVerb("Process %p %p\n", process, pInfo.image);
processCount += 1;
if (pInfo.image != NULL) {
MINIDUMP_MODULE * moduleInfo = &moduleList->Modules[moduleList->NumberOfModules];
RtlZeroMemory(moduleInfo, sizeof(*moduleInfo));
PEImage imageInfo;
PEImageStruct.Read(pInfo.image, &imageInfo);
ExtVerb("KernelImage info %p %p %p %p\n",
imageInfo.VirtualAddress,
imageInfo.loadedSize,
imageInfo.checkSum,
imageInfo.timeDateStamp);
moduleInfo->BaseOfImage = imageInfo.VirtualAddress;
moduleInfo->SizeOfImage = (ULONG32)imageInfo.loadedSize;
moduleInfo->CheckSum = (ULONG32)imageInfo.checkSum;
moduleInfo->TimeDateStamp = (ULONG32)imageInfo.timeDateStamp;
moduleInfo->ModuleNameRva = 0;
//
// Same as for the kernel, we can save the entire image in dump, or just
// the first page
//
MemoryRanges->AddRange(moduleInfo->BaseOfImage, IncludeExecutables ? moduleInfo->SizeOfImage : MEMORY_PAGE_SIZE);
//
// Deal now with the image name. We have to read the string
// from the imagename field of the process informatio
//
UINT32 * Size = (UINT32 *)CrtName;
CrtName = (PVOID)(Size + 1);
String str;
EXT_CHECK(StringStruct.Read(pInfo.imageName, &str));
EXT_CHECK(g_ExtData->ReadVirtual(pInfo.imageName + StringFields[0].offset,
CrtName,
(ULONG)str.m_stringLength * sizeof(WCHAR),
NULL));
*Size = (UINT32)str.m_stringLength * sizeof(WCHAR);
NameSizes[moduleList->NumberOfModules] = sizeof(*Size) + (*Size) * sizeof(WCHAR);
//
// We are done with this module. Move the cursors for the next available slots
//
CrtName = (PVOID)((WCHAR*)CrtName + *Size);
moduleList->NumberOfModules += 1;
}
}
}
return moduleList;
Exit:
// Error path
if (moduleList) free (moduleList);
ERRORBREAK("Invalid symbols information\n");
return NULL;
}
//
// Functions dealing with capturing the thread contexts
//
VOID * ThreadContexts = NULL;
RVA LocalOffset = 0;
ULONG64 excptThread = 0;
void AddMemoryReference(UINT64 address)
{
if (address > TRACE_GRANULARITY) {
MemoryRanges->AddRange(address - TRACE_GRANULARITY, 2*TRACE_GRANULARITY);
}
}
BOOL IsCurrentStackRange(UINT64 stackReference, UINT64 stackValue)
{
// This is some hack to recognize from the threads list
// the one that is matching the current exception context
return (stackValue > TRACE_GRANULARITY) &&
(stackReference > TRACE_GRANULARITY) &&
(stackValue > (stackReference - TRACE_GRANULARITY)) &&
(stackValue < (stackReference + TRACE_GRANULARITY));
}
MINIDUMP_THREAD_LIST * ReadThreadInformation()
{
HRESULT status;
MINIDUMP_THREAD_LIST * threadList = NULL;
ULONG pointerSize = (g_ExtControl->IsPointer64Bit() == S_OK) ? 8 : 4;
ULONG64 address = 0;
ULONG64 threads = 0;
ULONG type = 0;
ULONG subtype = 0;
ULONG64 module = 0;
excptThread = 0;
//
// Fetch the thread table from the kernel
//
EXT_CHECK(g_ExtSymbols->GetOffsetByName(kernel_threadTable, &address));
EXT_CHECK(g_ExtSymbols->GetOffsetTypeId(address, &type, &module));
EXT_CHECK(g_ExtData->ReadPointersVirtual(1, address, &threads));
ExtVerb("threadTable: %p\n", threads);
//
// Prepare to enumerate the entries inside the thread table
//
ULONG lengthOffset = 0;
EXT_CHECK(g_ExtSymbols->GetFieldOffset(module, type, "overall_length", &lengthOffset));
ULONG valuesOffset = 0;
EXT_CHECK(g_ExtSymbols->GetFieldOffset(module, type, "values", &valuesOffset));
ULONG length = 0;
EXT_CHECK(TraceRead(threads + lengthOffset, &length, sizeof(length)));
ExtVerb("threadTable: %p [maximum %d entries]\n", threads, length);
//
// Reserve one slot more to save the current debugger context, which may not match
// on win32 any of the threads that get enumerated. For consistency
// accross all platforms simulate a thread, and place it at the first index in the list
// ~0s will allow switching directly to it
//
length += 1;
threadList = (MINIDUMP_THREAD_LIST *)malloc(sizeof(MINIDUMP_THREAD_LIST) + length * sizeof(MINIDUMP_THREAD));
if (threadList == NULL) {
goto Exit;
}
threadList->NumberOfThreads = 0;
ThreadContexts = NULL;
if (GetTargetContextSize() > 0) {
ThreadContexts = malloc(length * GetTargetContextSize());
}
if (ThreadContexts == NULL) {
goto Exit;
}
//
// Capture the current debugger context
//
MINIDUMP_THREAD * threadInfo = &threadList->Threads[threadList->NumberOfThreads];
EXT_CHECK(g_ExtSymbols->GetScope(NULL, NULL, ThreadContexts, GetTargetContextSize()));
UINT64 CurrentESPContext = GetStackPointer(ThreadContexts);
threadInfo->ThreadId = 0xFFFF;
threadInfo->SuspendCount = 0;
threadInfo->PriorityClass = 0;
threadInfo->Priority = 0;
threadInfo->Teb = 0;
threadInfo->ThreadContext.DataSize = GetTargetContextSize();
threadInfo->Stack.StartOfMemoryRange = CurrentESPContext;
threadInfo->Stack.Memory.DataSize = (ULONG32)4096;
MemoryRanges->AddRange(threadInfo->Stack.StartOfMemoryRange, threadInfo->Stack.Memory.DataSize);
FetchRegisterReferences(ThreadContexts);
threadList->NumberOfThreads += 1;
MINIDUMP_THREAD * dbgContextThreadInfo = threadInfo;
//
// Walk now the rest of the rest of the threads
//
for (ULONG id = 0; id < length - 1; id++) {
ULONG64 threadAddress = 0;
//
// Fetch the pointer address from the table at the current index
//
threadInfo = &threadList->Threads[threadList->NumberOfThreads];
EXT_CHECK(g_ExtData->ReadPointersVirtual(1, threads + valuesOffset + id * pointerSize, &threadAddress));
if (threadAddress != 0) {
//
// We have a valid entry. Continue to capture the thread context
//
Thread thread;
ThreadEntry entry;
EXT_CHECK(ThreadStruct->Read(threadAddress, &thread));
EXT_CHECK(ThreadEntryStruct.Read(thread.schedulerEntry, &entry));
threadInfo->ThreadId = (ULONG32)thread.context.threadIndex;
if (threadInfo->ThreadId == 0) {
// The debugger does not accept threads IDs of 0
// so we cannot preserve the exact translation.
// Find a slot for the new thread that is not used by Singulrity
threadInfo->ThreadId += 1024;
}
threadInfo->SuspendCount = 0;
threadInfo->PriorityClass = 0;
threadInfo->Priority = 0;
threadInfo->Teb = threadAddress;
VOID * ctx = (void *)((ULONG64)ThreadContexts +
GetTargetContextSize() * threadList->NumberOfThreads);
RtlZeroMemory(ctx, GetTargetContextSize());
threadInfo->ThreadContext.DataSize = GetTargetContextSize();
//
// This appears to point to a valid thread context. Fetch one page of stack memory from
// the current pointer, and load the context registers
//
threadInfo->Stack.Memory.DataSize = (ULONG32)4096;
threadInfo->Stack.StartOfMemoryRange = thread.context.threadRecord.spill.sp;
MemoryRanges->AddRange(threadInfo->Stack.StartOfMemoryRange, threadInfo->Stack.Memory.DataSize);
threadInfo->ThreadContext.DataSize = GetTargetContextSize();
LoadThreadContext(ctx, &thread.context.threadRecord.spill);
if (IsCurrentStackRange(CurrentESPContext, thread.context.threadRecord.spill.sp)) {
excptThread = threadInfo->ThreadId;
dbgContextThreadInfo->Teb = excptThread;
}
FetchRegisterReferences(ctx);
threadList->NumberOfThreads += 1;
}
}
return threadList;
Exit:
// Error path
if (threadList) free (threadList);
ERRORBREAK("Invalid symbols information\n");
return NULL;
}
void IncludeUsedMemory()
{
HRESULT status;
ULONG64 pageTable;
ULONG64 pageCount;
ExtVerb("Scanning flat pages for in-use memory");
EXT_CHECK(FindBound("Microsoft_Singularity_Memory_FlatPages::pageTable", &pageTable));
EXT_CHECK(FindBound("Microsoft_Singularity_Memory_FlatPages::pageCount", &pageCount));
ExtOut("%p %p\n", pageTable, pageCount);
ULONG * pageTableCopy = (ULONG *)malloc((SIZE_T)pageCount * sizeof(ULONG));
if(pageTableCopy == NULL) {
ExtOut("Not enough memory to cache the page table");
return;
}
if (g_ExtData->ReadVirtual(pageTable, pageTableCopy, (ULONG)pageCount * sizeof(ULONG), NULL) != S_OK) {
ExtOut("Error reading the page table");
free(pageTableCopy);
return;
}
for (ULONG64 i = 1; i < pageCount; i++) {
if ((USHORT)(pageTableCopy[i]) != 0) {
ExtVerb("Adding pages %p %lx\n", i * MEMORY_PAGE_SIZE, MEMORY_PAGE_SIZE);
MemoryRanges->AddRange(i * MEMORY_PAGE_SIZE, MEMORY_PAGE_SIZE);
}
}
free(pageTableCopy);
return;
Exit:
// Error path
ExtOut("Invalid symbols information\n");
return;
}
void CrashDump(PCSTR fname)
{
MINIDUMP_MODULE_LIST * moduleList = NULL;
MINIDUMP_THREAD_LIST * threadList = NULL;
if (MemoryRanges == NULL) {
MemoryRanges = new MemoryRagesCollection();
}
FILE * file = fopen(fname, "wb");
if (file == NULL) {
ExtOut("Cannot open file");
return;
}
int dumpsize = sizeof(CRASH_DUMP_HEADER);
void *buffer = malloc(dumpsize);
CRASH_DUMP_HEADER *dump = (CRASH_DUMP_HEADER *) buffer;
dump->Header.Signature = MINIDUMP_SIGNATURE;
dump->Header.Version = MINIDUMP_VERSION;
dump->Header.NumberOfStreams = 4;
dump->Header.StreamDirectoryRva = offsetof(CRASH_DUMP_HEADER, Directories);
// Directory list - We use 4 directories currently:
// SystemInfo
// ThreadList
// ModuleList
// MemoryList
dump->Directories[0].StreamType = SystemInfoStream;
dump->Directories[0].Location.DataSize = sizeof(dump->SystemInfo);
dump->Directories[0].Location.Rva = offsetof(CRASH_DUMP_HEADER, SystemInfo);
//
// Fill in the system information part
//
switch (TargetMachine) {
case IMAGE_FILE_MACHINE_AMD64:
dump->SystemInfo.ProcessorArchitecture = PROCESSOR_ARCHITECTURE_AMD64;
break;
case IMAGE_FILE_MACHINE_I386:
dump->SystemInfo.ProcessorArchitecture = PROCESSOR_ARCHITECTURE_INTEL;
break;
case IMAGE_FILE_MACHINE_ARM:
dump->SystemInfo.ProcessorArchitecture = PROCESSOR_ARCHITECTURE_ARM;
break;
default:
dump->SystemInfo.ProcessorArchitecture = PROCESSOR_ARCHITECTURE_UNKNOWN;
break;
}
//
// TODO: the following fields need to be updated properly by querying the
// system
//
dump->SystemInfo.ProcessorLevel = 6;
dump->SystemInfo.ProcessorRevision = 0;
// FIXME from the target. It is not really relevant here since the minidump
// is treated as a usermode process dump
dump->SystemInfo.NumberOfProcessors = 1;
dump->SystemInfo.ProductType = VER_NT_WORKSTATION;
dump->SystemInfo.MajorVersion = 5; // WinXP
dump->SystemInfo.MinorVersion = 1;
dump->SystemInfo.BuildNumber = 0;
dump->SystemInfo.PlatformId = VER_PLATFORM_WIN32_NT;
dump->SystemInfo.CSDVersionRva = 0; // hopefully this is OK
dump->SystemInfo.Cpu.X86CpuInfo.VendorId[0] = 'Genu';
dump->SystemInfo.Cpu.X86CpuInfo.VendorId[1] = 'ineI';
dump->SystemInfo.Cpu.X86CpuInfo.VendorId[2] = 'ntel';
dump->SystemInfo.Cpu.X86CpuInfo.VersionInformation = 0; // get these right later as needed
dump->SystemInfo.Cpu.X86CpuInfo.FeatureInformation = 0;
dump->SystemInfo.Cpu.X86CpuInfo.AMDExtendedCpuFeatures = 0;
LocalOffset = sizeof(CRASH_DUMP_HEADER);
// Build the module information part of the crashdump
moduleList = ReadModuleInformation();
if (moduleList == NULL) goto Exit;
dump->Directories[3].StreamType = ModuleListStream;
dump->Directories[3].Location.DataSize = sizeof(MINIDUMP_MODULE_LIST) + (sizeof(MINIDUMP_MODULE)*moduleList->NumberOfModules);
dump->Directories[3].Location.Rva = LocalOffset;
LocalOffset += dump->Directories[3].Location.DataSize;
int namebufferLength = 0;
for (ULONG32 i = 0; i < moduleList->NumberOfModules; i++) {
moduleList->Modules[i].ModuleNameRva += LocalOffset;
LocalOffset += NameSizes[i];
namebufferLength += NameSizes[i];
}
//
// Build the thread information part of the crashdump
//
threadList = ReadThreadInformation();
if (threadList == NULL) goto Exit;
dump->Directories[1].StreamType = ThreadListStream;
dump->Directories[1].Location.DataSize = sizeof(MINIDUMP_THREAD_LIST) + (sizeof(MINIDUMP_THREAD)*threadList->NumberOfThreads );
dump->Directories[1].Location.Rva = LocalOffset;
LocalOffset += dump->Directories[1].Location.DataSize;
//
// If we collected the current context, setup also an exception directory
// and make that active con text when the crashdump is opened in debugger
//
if (excptThread != 0) {
dump->Header.NumberOfStreams = 5;
dump->Directories[4].StreamType = ExceptionStream;
dump->Directories[4].Location.DataSize = sizeof(MINIDUMP_EXCEPTION_STREAM);
dump->Directories[4].Location.Rva = offsetof(CRASH_DUMP_HEADER, ExceptionInfo);
}
for (ULONG32 i = 0; i < threadList->NumberOfThreads ; i++) {
threadList->Threads[i].ThreadContext.Rva = LocalOffset;
if (threadList->Threads[i].ThreadId == excptThread) {
//
// Update the exception information to the current thread
//
dump->ExceptionInfo.ThreadId = (ULONG32)excptThread;
dump->ExceptionInfo.ThreadContext.DataSize = threadList->Threads[i].ThreadContext.DataSize;
dump->ExceptionInfo.ThreadContext.Rva = threadList->Threads[i].ThreadContext.Rva;
}
LocalOffset += threadList->Threads[i].ThreadContext.DataSize;
}
if (FullMemoryDump) {
IncludeUsedMemory();
}
//
// The memory ranges might be overlapped. This is not allowed in a crashdump
// we need to compact and coalesce the adjacent ranges to provide a full list
// of memory ranges that will be included in the dump
//
MemoryRanges->CompactItems();
int numMemoryRanges = MemoryRanges->GetEffectiveRanges();
ULONG32 memoryStreamLength = sizeof(_MEMORY_STREAM ) + sizeof(MINIDUMP_MEMORY_DESCRIPTOR)*(numMemoryRanges - 1);
_MEMORY_STREAM * memoryStream = (_MEMORY_STREAM * )malloc(memoryStreamLength);
memoryStream->MemoryCount = numMemoryRanges;
dump->Directories[2].StreamType = MemoryListStream;
dump->Directories[2].Location.DataSize = memoryStreamLength;
dump->Directories[2].Location.Rva = LocalOffset;
LocalOffset += memoryStreamLength;
//
// Fixup the RVA memory references to the regions
//
int crtIndex = 0;
for (int i = 0; i < MemoryRanges->GetRangesCount(); i++) {
if (MemoryRanges->GetStartAddress(i)) {
memoryStream->Memory[crtIndex].StartOfMemoryRange = MemoryRanges->GetStartAddress(i);
memoryStream->Memory[crtIndex].Memory.DataSize = (ULONG32)MemoryRanges->GetRangeSize(i);
memoryStream->Memory[crtIndex].Memory.Rva = LocalOffset + (RVA)MemoryRanges->GetRVA(memoryStream->Memory[crtIndex].StartOfMemoryRange);
crtIndex += 1;
if (crtIndex >= numMemoryRanges) {
break;
}
}
}
//
// Update the RVA references for objects that might have change after the
// memory ranges coalescing.
//
for (ULONG32 i = 0; i < threadList->NumberOfThreads ; i++) {
threadList->Threads[i].Stack.Memory.Rva = LocalOffset +
(RVA)MemoryRanges->GetRVA(threadList->Threads[i].Stack.StartOfMemoryRange);
ExtVerb("Thread RVA %p\n",(UINT64)threadList->Threads[i].Stack.Memory.Rva);
}
// All directories entries for streams must be setup at this point
ExtOut("Writing crashdump ...");
fwrite(buffer, dumpsize, 1, file);
fwrite(moduleList, dump->Directories[3].Location.DataSize, 1, file);
fwrite(ImageNameBuffer, namebufferLength, 1, file);
fwrite(threadList, dump->Directories[1].Location.DataSize, 1, file);
fwrite(ThreadContexts, threadList->NumberOfThreads * GetTargetContextSize(), 1, file);
fwrite(memoryStream, dump->Directories[2].Location.DataSize, 1, file);
void * tmpBuffer = malloc (MEMORY_PAGE_SIZE);
if (tmpBuffer == NULL) goto Exit;
for (ULONG32 i = 0; i < memoryStream->MemoryCount; i++) {
ExtVerb("%p %p %p %p\n",(UINT64)memoryStream->Memory[i].StartOfMemoryRange, (UINT64)memoryStream->Memory[i].Memory.DataSize,
(UINT64)(memoryStream->Memory[i].StartOfMemoryRange + memoryStream->Memory[i].Memory.DataSize),
(UINT64)memoryStream->Memory[i].Memory.Rva );
UINT64 RemainingSize = memoryStream->Memory[i].Memory.DataSize;
UINT64 currentAddress = memoryStream->Memory[i].StartOfMemoryRange;
while (RemainingSize > 0) {
ULONG32 readSize = MEMORY_PAGE_SIZE;
if (RemainingSize < readSize) {
readSize = (ULONG32)RemainingSize;
}
g_ExtData->ReadVirtual(currentAddress, tmpBuffer, readSize, NULL);
fwrite(tmpBuffer, readSize, 1, file);
RemainingSize -= readSize;
currentAddress += readSize;
}
}
free(tmpBuffer);
ExtOut("done\n");
fclose(file);
Exit:
if (moduleList) free(moduleList);
if (threadList) free(threadList);
//
// Cleanup the global states
//
if (ThreadContexts != NULL) free(ThreadContexts);
ThreadContexts = NULL;
free(buffer);
delete MemoryRanges;
MemoryRanges = NULL;
//
// Reset the tracing store buffer
//
TracingStore = NULL;
}
EXT_DECL(crashdump) // Defines: PDEBUG_CLIENT Client, PCSTR args
{
EXT_ENTER(); // Defines: HRESULT status = S_OK;
ResetGlobals();
pointerSize = (g_ExtControl->IsPointer64Bit() == S_OK) ? 8 : 4;
SKIP_WHITESPACES(args);
if (*args == '\0')
{
Usage();
goto Exit;
}
while (*args != '\0') {
SKIP_WHITESPACES(args);
// process argument
if (*args == '-' || *args == '/') {
args++;
switch (*args++) {
case 'x':
case 'X': {
IncludeExecutables = TRUE;
}
break;
case 'f':
case 'F': {
FullMemoryDump = TRUE;
}
break;
case 'r':
case 'R': {
IncludeRegisterReferences = TRUE;
}
break;
case 's':
case 'S': {
SKIP_WHITESPACES(args);
CrashDump(args);
goto Exit;
}
break;
case 't':
case 'T': {
SKIP_WHITESPACES(args);
if (MemoryRanges != NULL) {
delete MemoryRanges;
MemoryRanges = NULL;
TracingStore = NULL;
}
MemoryRanges = new MemoryRagesCollection();
TracingStore = MemoryRanges;
goto Exit;
}
break;
SKIP_WHITESPACES(args);
}
}
else {
Usage();
break;
}
}
EXT_LEAVE(); // Macro includes: return status;
}