1375 lines
41 KiB
C++
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;
|
||
|
}
|
||
|
|