///////////////////////////////////////////////////////////////////////////// // // 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 : 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; }