//---------------------------------------------------------------------------- // // Dump file writing. // // Copyright (c) Microsoft Corporation. All rights reserved. // //---------------------------------------------------------------------------- #include "ntsdp.hpp" #include #include #include #define GENERIC_FORMATS \ (DEBUG_FORMAT_WRITE_CAB | \ DEBUG_FORMAT_CAB_SECONDARY_FILES | \ DEBUG_FORMAT_NO_OVERWRITE) #define UMINI_FORMATS \ (DEBUG_FORMAT_USER_SMALL_FULL_MEMORY | \ DEBUG_FORMAT_USER_SMALL_HANDLE_DATA | \ DEBUG_FORMAT_USER_SMALL_UNLOADED_MODULES | \ DEBUG_FORMAT_USER_SMALL_INDIRECT_MEMORY | \ DEBUG_FORMAT_USER_SMALL_DATA_SEGMENTS | \ DEBUG_FORMAT_USER_SMALL_FILTER_MEMORY | \ DEBUG_FORMAT_USER_SMALL_FILTER_PATHS | \ DEBUG_FORMAT_USER_SMALL_PROCESS_THREAD_DATA | \ DEBUG_FORMAT_USER_SMALL_PRIVATE_READ_WRITE_MEMORY | \ DEBUG_FORMAT_USER_SMALL_NO_OPTIONAL_DATA | \ DEBUG_FORMAT_USER_SMALL_FULL_MEMORY_INFO | \ DEBUG_FORMAT_USER_SMALL_THREAD_INFO | \ DEBUG_FORMAT_USER_SMALL_CODE_SEGMENTS) // Internal format flags for testing of microdumps. #define UMINI_MICRO 0x00000001 #define UMINI_FORCE_DBG 0x00000002 #define UMINI_WRITE_KMINI 0x00000004 HRESULT DumpTargetInfo::DumpWriteFile(HANDLE File, PVOID Buffer, ULONG Size) { HRESULT Status = S_OK; ULONG Done; if (!WriteFile(File, Buffer, Size, &Done, NULL)) { Status = WIN32_LAST_STATUS(); } else if (Done != Size) { Status = HRESULT_FROM_WIN32(ERROR_DISK_FULL); } if (Status != S_OK) { ErrOut(_T("Error writing to dump file - %s\n \"%s\"\n"), FormatStatusCode(Status), FormatStatusArgs(Status, NULL)); } return Status; } //---------------------------------------------------------------------------- // // UserFullDumpTargetInfo::Write. // //---------------------------------------------------------------------------- #define USER_DUMP_MEMORY_BUFFER 65536 struct CREATE_USER_DUMP_STATE { ThreadInfo* Thread; ImageInfo* Image; ULONG64 MemHandle; HANDLE DumpFileHandle; MEMORY_BASIC_INFORMATION64 MemInfo; MEMORY_BASIC_INFORMATION32 MemInfo32; ULONG64 MemBufDone; ULONG64 TotalMemQueried; ULONG64 TotalMemData; CROSS_PLATFORM_CONTEXT TargetContext; DEBUG_EVENT Event; CRASH_THREAD CrashThread; ULONG64 MemBuf[USER_DUMP_MEMORY_BUFFER / sizeof(ULONG64)]; }; BOOL WINAPI CreateUserDumpCallback(ULONG DataType, PVOID* Data, PULONG DataLength, PVOID UserData) { CREATE_USER_DUMP_STATE* State = (CREATE_USER_DUMP_STATE*)UserData; ThreadInfo* Thread; switch(DataType) { case DMP_DUMP_FILE_HANDLE: *Data = State->DumpFileHandle; *DataLength = sizeof(HANDLE); break; case DMP_DEBUG_EVENT: ADDR PcAddr; // // Fake up an exception event for the current thread. // ZeroMemory(&State->Event, sizeof(State->Event)); g_Machine->GetPC(&PcAddr); State->Event.dwDebugEventCode = EXCEPTION_DEBUG_EVENT; State->Event.dwProcessId = g_Process->m_SystemId; if (g_LastEventType == DEBUG_EVENT_EXCEPTION) { // Use the exception record from the last exception. State->Event.dwThreadId = g_EventThread->m_SystemId; ExceptionRecord64To(&g_LastEventInfo.Exception.ExceptionRecord, &State->Event.u.Exception.ExceptionRecord); State->Event.u.Exception.dwFirstChance = g_LastEventInfo.Exception.FirstChance; } else { // Fake a breakpoint exception. State->Event.dwThreadId = g_Thread->m_SystemId; State->Event.u.Exception.ExceptionRecord.ExceptionCode = STATUS_BREAKPOINT; State->Event.u.Exception.ExceptionRecord.ExceptionAddress = (PVOID)(ULONG_PTR)Flat(PcAddr); State->Event.u.Exception.dwFirstChance = TRUE; } *Data = &State->Event; *DataLength = sizeof(State->Event); break; case DMP_THREAD_STATE: ULONG64 Teb64; if (State->Thread == NULL) { Thread = g_Process->m_ThreadHead; } else { Thread = State->Thread->m_Next; } State->Thread = Thread; if (Thread == NULL) { return FALSE; } ZeroMemory(&State->CrashThread, sizeof(State->CrashThread)); State->CrashThread.ThreadId = Thread->m_SystemId; State->CrashThread.SuspendCount = Thread->m_SuspendCount; Thread->GetBasicInfo(DEBUG_TBINFO_PRIORITY_CLASS | DEBUG_TBINFO_PRIORITY); if (Thread->m_BasicInfo.Valid & DEBUG_TBINFO_PRIORITY_CLASS) { State->CrashThread.PriorityClass = Thread->m_BasicInfo.PriorityClass; } else { State->CrashThread.PriorityClass = NORMAL_PRIORITY_CLASS; } if (Thread->m_BasicInfo.Valid & DEBUG_TBINFO_PRIORITY) { State->CrashThread.Priority = Thread->m_BasicInfo.Priority; } else { State->CrashThread.Priority = THREAD_PRIORITY_NORMAL; } if (g_Target->GetThreadInfoDataOffset(Thread, NULL, &Teb64) != S_OK) { Teb64 = 0; } State->CrashThread.Teb = (ULONG_PTR)Teb64; *Data = &State->CrashThread; *DataLength = sizeof(State->CrashThread); break; case DMP_MEMORY_BASIC_INFORMATION: for (;;) { if (g_Target->QueryMemoryRegion(g_Process, &State->MemHandle, &State->MemInfo) != S_OK) { State->MemHandle = 0; State->MemInfo.RegionSize = 0; return FALSE; } State->MemHandle = State->MemInfo.BaseAddress + State->MemInfo.RegionSize; if (!((State->MemInfo.Protect & PAGE_GUARD) || (State->MemInfo.Protect & PAGE_NOACCESS) || (State->MemInfo.State & MEM_FREE) || (State->MemInfo.State & MEM_RESERVE))) { break; } } State->TotalMemQueried += State->MemInfo.RegionSize; #ifdef _WIN64 *Data = &State->MemInfo; *DataLength = sizeof(State->MemInfo); #else State->MemInfo32.BaseAddress = (ULONG)State->MemInfo.BaseAddress; State->MemInfo32.AllocationBase = (ULONG)State->MemInfo.AllocationBase; State->MemInfo32.AllocationProtect = State->MemInfo.AllocationProtect; State->MemInfo32.RegionSize = (ULONG)State->MemInfo.RegionSize; State->MemInfo32.State = State->MemInfo.State; State->MemInfo32.Protect = State->MemInfo.Protect; State->MemInfo32.Type = State->MemInfo.Type; *Data = &State->MemInfo32; *DataLength = sizeof(State->MemInfo32); #endif break; case DMP_THREAD_CONTEXT: if (State->Thread == NULL) { Thread = g_Process->m_ThreadHead; } else { Thread = State->Thread->m_Next; } State->Thread = Thread; if (Thread == NULL) { g_Target->ChangeRegContext(g_Thread); return FALSE; } g_Target->ChangeRegContext(Thread); if (g_Machine->GetContextState(MCTX_CONTEXT) != S_OK || g_Machine-> ConvertCanonContextToTarget(&g_Machine->m_Context, g_Target->m_TypeInfo.SizeTargetContext, &State->TargetContext) != S_OK) { ErrOut(_T("Unable to retrieve context for thread %d. ") _T("Dump may be corrupt."), Thread->m_UserId); return FALSE; } *Data = &State->TargetContext; *DataLength = g_Target->m_TypeInfo.SizeTargetContext; break; case DMP_MODULE: ImageInfo* Image; PCRASH_MODULE Module; if (State->Image == NULL) { Image = g_Process->m_ImageHead; } else { Image = State->Image->m_Next; } // Skip over modules that are marked for unload // as they don't have valid memory and thus // will confuse the loaded module list validation // when the dump is loaded. for (;;) { State->Image = Image; if (!Image) { return FALSE; } if (!Image->m_Unloaded) { break; } Image = State->Image->m_Next; } Module = (PCRASH_MODULE)State->MemBuf; Module->BaseOfImage = (ULONG_PTR)Image->m_BaseOfImage; Module->SizeOfImage = Image->m_SizeOfImage; Module->ImageNameLength = _tcslen(Image->m_ImagePath) + 1; #ifndef UNICODE CopyString(Module->ImageName, Image->m_ImagePath, USER_DUMP_MEMORY_BUFFER - sizeof(*Module)); #else FillStringBufferTA(Image->m_ImagePath, 0, Module->ImageName, USER_DUMP_MEMORY_BUFFER - sizeof(*Module), NULL); #endif *Data = Module; *DataLength = sizeof(*Module) + Module->ImageNameLength; break; case DMP_MEMORY_DATA: ULONG64 Left; Left = State->MemInfo.RegionSize - State->MemBufDone; if (Left == 0) { State->MemBufDone = 0; for (;;) { if (g_Target->QueryMemoryRegion(g_Process, &State->MemHandle, &State->MemInfo) != S_OK) { State->MemHandle = 0; State->MemInfo.RegionSize = 0; // Sanity check that we wrote out as much data // as we stored in the MEMORY_BASIC phase. if (State->TotalMemQueried != State->TotalMemData) { ErrOut(_T("Queried %s bytes of memory but wrote %s ") _T("bytes of memory data.\n") _T("Dump may be corrupt.\n"), FormatDisp64(State->TotalMemQueried), FormatDisp64(State->TotalMemData)); } return FALSE; } State->MemHandle = State->MemInfo.BaseAddress + State->MemInfo.RegionSize; if (!((State->MemInfo.Protect & PAGE_GUARD) || (State->MemInfo.Protect & PAGE_NOACCESS) || (State->MemInfo.State & MEM_FREE) || (State->MemInfo.State & MEM_RESERVE))) { break; } } Left = State->MemInfo.RegionSize; State->TotalMemData += State->MemInfo.RegionSize; } if (Left > USER_DUMP_MEMORY_BUFFER) { Left = USER_DUMP_MEMORY_BUFFER; } if (CurReadAllVirtual(State->MemInfo.BaseAddress + State->MemBufDone, State->MemBuf, (ULONG)Left) != S_OK) { ErrOut(_T("ReadVirtual at %s failed. Dump may be corrupt.\n"), FormatAddr64(State->MemInfo.BaseAddress + State->MemBufDone)); return FALSE; } State->MemBufDone += Left; *Data = State->MemBuf; *DataLength = (ULONG)Left; break; } return TRUE; } HRESULT UserFullDumpTargetInfo::Write(HANDLE hFile, DUMP_WRITE_ARGS* Args) { dprintf(_T("user full dump\n")); FlushCallbacks(); if (!IS_LIVE_USER_TARGET(g_Target)) { ErrOut(_T("User full dumps can only be written in ") _T("live user-mode sessions\n")); return E_UNEXPECTED; } if (Args->CommentA != NULL || Args->CommentW != NULL) { ErrOut(_T("User full dumps do not support comments\n")); return E_INVALIDARG; } CREATE_USER_DUMP_STATE* State; State = (CREATE_USER_DUMP_STATE*)calloc(1, sizeof(*State)); if (State == NULL) { ErrOut(_T("Unable to allocate memory for dump state\n")); return E_OUTOFMEMORY; } State->DumpFileHandle = hFile; HRESULT Status; if (!DbgHelpCreateUserDump(NULL, CreateUserDumpCallback, State)) { Status = WIN32_LAST_STATUS(); ErrOut(_T("Dump creation failed, %s\n \"%s\"\n"), FormatStatusCode(Status), FormatStatus(Status)); } else { Status = S_OK; } free(State); return Status; } //---------------------------------------------------------------------------- // // UserMiniDumpTargetInfo::Write. // //---------------------------------------------------------------------------- class DbgSystemProvider : public MiniDumpSystemProvider { public: DbgSystemProvider(void); ~DbgSystemProvider(void); virtual void Release(void); virtual HRESULT GetCurrentTimeDate(OUT PULONG TimeDate); virtual HRESULT GetCpuType(OUT PULONG Type, OUT PBOOL BackingStore); virtual HRESULT GetCpuInfo(OUT PUSHORT Architecture, OUT PUSHORT Level, OUT PUSHORT Revision, OUT PUCHAR NumberOfProcessors, OUT PCPU_INFORMATION Info); virtual void GetContextSizes(OUT PULONG Size, OUT PULONG RegScanStart, OUT PULONG RegScanCount); virtual void GetPointerSize(OUT PULONG Size); virtual void GetPageSize(OUT PULONG Size); virtual void GetFunctionTableSizes(OUT PULONG TableSize, OUT PULONG EntrySize); virtual void GetInstructionWindowSize(OUT PULONG Size); virtual HRESULT GetOsInfo(OUT PULONG PlatformId, OUT PULONG Major, OUT PULONG Minor, OUT PULONG BuildNumber, OUT PUSHORT ProductType, OUT PUSHORT SuiteMask); virtual HRESULT GetOsCsdString(OUT PWSTR Buffer, IN ULONG BufferChars); virtual HRESULT OpenMapping(IN PCWSTR FilePath, OUT PULONG Size, OUT PWSTR LongPath, IN ULONG LongPathChars, OUT PVOID* Mapping); virtual void CloseMapping(PVOID Mapping); virtual HRESULT GetImageHeaderInfo(IN HANDLE Process, IN PCWSTR FilePath, IN ULONG64 ImageBase, OUT PULONG MachineType, OUT PULONG Size, OUT PULONG CheckSum, OUT PULONG TimeDateStamp, OUT PULONG TlsDirRva); virtual HRESULT GetImageVersionInfo(IN HANDLE Process, IN PCWSTR FilePath, IN ULONG64 ImageBase, OUT VS_FIXEDFILEINFO* Info); virtual HRESULT GetImageDebugRecord(IN HANDLE Process, IN PCWSTR FilePath, IN ULONG64 ImageBase, IN ULONG RecordType, OUT OPTIONAL PVOID Data, IN OUT PULONG DataLen); virtual HRESULT EnumImageSections(IN HANDLE Process, IN PCWSTR FilePath, IN ULONG64 ImageBase, IN ULONG WriteFlags, IN MiniDumpProviderCallbacks* Callback); virtual HRESULT OpenThread(IN ULONG DesiredAccess, IN BOOL InheritHandle, IN ULONG ThreadId, OUT PHANDLE Handle); virtual void CloseThread(IN HANDLE Handle); virtual ULONG GetCurrentThreadId(void); virtual ULONG SuspendThread(IN HANDLE Thread); virtual ULONG ResumeThread(IN HANDLE Thread); virtual HRESULT GetThreadContext(IN HANDLE Thread, OUT PVOID Context, IN ULONG ContextSize, OUT PULONG64 CurrentPc, OUT PULONG64 CurrentStack, OUT PULONG64 CurrentStore); virtual HRESULT GetTeb(IN HANDLE Thread, OUT PULONG64 Offset, OUT PULONG Size); virtual HRESULT GetThreadTebInfo(IN HANDLE Process, IN HANDLE Thread, OUT PULONG64 Teb, OUT PULONG SizeOfTeb, OUT PULONG64 StackBase, OUT PULONG64 StackLimit, OUT PULONG64 StoreBase, OUT PULONG64 StoreLimit, OUT PULONG64 StaticTlsPointer); virtual HRESULT GetThreadOsInfo(IN HANDLE Process, IN HANDLE Thread, IN OUT PUMINIPROV_THREAD_INFO Info); virtual HRESULT GetPeb(IN HANDLE Process, OUT PULONG64 Offset, OUT PULONG Size); virtual HRESULT GetProcessTimes(IN HANDLE Process, OUT LPFILETIME Create, OUT LPFILETIME User, OUT LPFILETIME Kernel); virtual HRESULT ReadVirtual(IN HANDLE Process, IN ULONG64 Offset, OUT PVOID Buffer, IN ULONG Request, OUT PULONG Done); virtual HRESULT ReadAllVirtual(IN HANDLE Process, IN ULONG64 Offset, OUT PVOID Buffer, IN ULONG Request); virtual HRESULT QueryVirtual(IN HANDLE Process, IN ULONG64 Offset, OUT PMINIDUMP_MEMORY_INFO Info); virtual HRESULT GetValidVirtualRange(IN HANDLE Process, IN ULONG64 Start, IN ULONG Size, OUT PULONG64 ValidStart, OUT PULONG ValidSize); virtual HRESULT StartProcessEnum(IN HANDLE Process, IN ULONG ProcessId); virtual HRESULT EnumThreads(OUT PULONG ThreadId); virtual HRESULT EnumModules(OUT PULONG64 Base, OUT PWSTR Path, IN ULONG PathChars); virtual HRESULT EnumFunctionTables(OUT PULONG64 MinAddress, OUT PULONG64 MaxAddress, OUT PULONG64 BaseAddress, OUT PULONG EntryCount, OUT PVOID RawTable, IN ULONG RawTableSize, OUT PVOID* RawEntryHandle); virtual HRESULT EnumFunctionTableEntries(IN PVOID RawTable, IN ULONG RawTableSize, IN PVOID RawEntryHandle, OUT PVOID RawEntries, IN ULONG RawEntriesSize); virtual HRESULT EnumFunctionTableEntryMemory(IN ULONG64 TableBase, IN PVOID RawEntries, IN ULONG Index, OUT PULONG64 Start, OUT PULONG Size); virtual HRESULT EnumUnloadedModules(OUT PWSTR Path, IN ULONG PathChars, OUT PULONG64 BaseOfModule, OUT PULONG SizeOfModule, OUT PULONG CheckSum, OUT PULONG TimeDateStamp); virtual void FinishProcessEnum(void); virtual HRESULT StartHandleEnum(IN HANDLE Process, IN ULONG ProcessId, OUT PULONG Count); virtual HRESULT EnumHandles(OUT PULONG64 Handle, OUT PULONG Attributes, OUT PULONG GrantedAccess, OUT PULONG HandleCount, OUT PULONG PointerCount, OUT PWSTR TypeName, IN ULONG TypeNameChars, OUT PWSTR ObjectName, IN ULONG ObjectNameChars); virtual void FinishHandleEnum(void); virtual HRESULT EnumPebMemory(IN HANDLE Process, IN ULONG64 PebOffset, IN ULONG PebSize, IN MiniDumpProviderCallbacks* Callback); virtual HRESULT EnumTebMemory(IN HANDLE Process, IN HANDLE Thread, IN ULONG64 TebOffset, IN ULONG TebSize, IN MiniDumpProviderCallbacks* Callback); virtual HRESULT GetClrEnum(IN PWSTR DacDllName, IN struct ICLRDataTarget* Target, OUT struct ICLRDataEnumMemoryRegions** Enum); virtual void ReleaseClrEnum(IN struct ICLRDataEnumMemoryRegions* Enum); virtual HRESULT WriteKernelMinidump(IN HANDLE File, IN HANDLE Process, IN ULONG NumThreads, IN HANDLE* ThreadHandles, IN ULONG BugCheckCode, IN ULONG Flags); protected: ThreadInfo* m_Thread; ImageInfo* m_Image; UnloadedModuleInfo* m_UnlEnum; ULONG m_Handle; ULONG64 m_FuncTableStart; ULONG64 m_FuncTableHandle; }; DbgSystemProvider::DbgSystemProvider(void) { } DbgSystemProvider::~DbgSystemProvider(void) { } void DbgSystemProvider::Release(void) { delete this; } HRESULT DbgSystemProvider::GetCurrentTimeDate(OUT PULONG TimeDate) { *TimeDate = FileTimeToTimeDateStamp(g_Target->GetCurrentTimeDateN()); return S_OK; } HRESULT DbgSystemProvider::GetCpuType(OUT PULONG Type, OUT PBOOL BackingStore) { *Type = g_Target->m_MachineType; *BackingStore = *Type == IMAGE_FILE_MACHINE_IA64; return S_OK; } HRESULT DbgSystemProvider::GetCpuInfo(OUT PUSHORT Architecture, OUT PUSHORT Level, OUT PUSHORT Revision, OUT PUCHAR NumberOfProcessors, OUT PCPU_INFORMATION Info) { DEBUG_PROCESSOR_IDENTIFICATION_ALL ProcId; ULONG64 ProcFeatures[4]; ULONG NumVals; *Architecture = (USHORT)ImageMachineToProcArch(g_Target->m_MachineType); *NumberOfProcessors = (UCHAR)g_Target->m_NumProcessors; // // We've set the basic processor type so that the dump // can be interpreted correctly. Any other failures should // not be considered fatal. // *Level = 0; *Revision = 0; ZeroMemory(Info, sizeof(*Info)); if (g_Target->GetProcessorId(0, &ProcId) != S_OK) { return S_OK; } switch(g_Target->m_MachineType) { case IMAGE_FILE_MACHINE_I386: *Level = (USHORT)ProcId.X86.Family; *Revision = ((USHORT)ProcId.X86.Model << 8) | (USHORT)ProcId.X86.Stepping; memcpy(Info->X86CpuInfo.VendorId, ProcId.X86.VendorString, sizeof(Info->X86CpuInfo.VendorId)); if (SUCCEEDED(g_Target-> GetSpecificProcessorFeatures(0, ProcFeatures, DIMA(ProcFeatures), &NumVals)) && NumVals >= 2) { Info->X86CpuInfo.VersionInformation = (ULONG32)ProcFeatures[0]; Info->X86CpuInfo.FeatureInformation = (ULONG32)ProcFeatures[1]; if (NumVals >= 3) { Info->X86CpuInfo.AMDExtendedCpuFeatures = (ULONG32)ProcFeatures[2]; } } break; case IMAGE_FILE_MACHINE_IA64: *Level = (USHORT)ProcId.Ia64.Model; *Revision = (USHORT)ProcId.Ia64.Revision; break; case IMAGE_FILE_MACHINE_AMD64: *Level = (USHORT)ProcId.Amd64.Family; *Revision = ((USHORT)ProcId.Amd64.Model << 8) | (USHORT)ProcId.Amd64.Stepping; break; } if (g_Target->m_MachineType != IMAGE_FILE_MACHINE_I386 && SUCCEEDED(g_Target-> GetGenericProcessorFeatures(0, ProcFeatures, DIMA(ProcFeatures), &NumVals))) { C_ASSERT(sizeof(Info->OtherCpuInfo.ProcessorFeatures) <= sizeof(ProcFeatures)); if (NumVals < DIMA(ProcFeatures)) { ZeroMemory(ProcFeatures + NumVals, (DIMA(ProcFeatures) - NumVals) * sizeof(ProcFeatures[0])); } memcpy(Info->OtherCpuInfo.ProcessorFeatures, ProcFeatures, sizeof(Info->OtherCpuInfo.ProcessorFeatures)); } return S_OK; } void DbgSystemProvider::GetContextSizes(OUT PULONG Size, OUT PULONG RegScanOffset, OUT PULONG RegScanCount) { *Size = g_Target->m_TypeInfo.SizeTargetContext; // Default reg scan. *RegScanOffset = -1; *RegScanCount = -1; } void DbgSystemProvider::GetPointerSize(OUT PULONG Size) { *Size = g_Machine->m_Ptr64 ? 8 : 4; } void DbgSystemProvider::GetPageSize(OUT PULONG Size) { *Size = g_Machine->m_PageSize; } void DbgSystemProvider::GetFunctionTableSizes(OUT PULONG TableSize, OUT PULONG EntrySize) { *TableSize = g_Target->m_TypeInfo.SizeDynamicFunctionTable; *EntrySize = g_Target->m_TypeInfo.SizeRuntimeFunction; } void DbgSystemProvider::GetInstructionWindowSize(OUT PULONG Size) { // Default window. *Size = -1; } HRESULT DbgSystemProvider::GetOsInfo(OUT PULONG PlatformId, OUT PULONG Major, OUT PULONG Minor, OUT PULONG BuildNumber, OUT PUSHORT ProductType, OUT PUSHORT SuiteMask) { *PlatformId = g_Target->m_PlatformId; *Major = g_Target->m_Win32Major; *Minor = g_Target->m_Win32Minor; *BuildNumber = g_Target->m_BuildNumber; *ProductType = (USHORT)g_Target->m_ProductType; *SuiteMask = (USHORT)g_Target->m_SuiteMask; return S_OK; } HRESULT DbgSystemProvider::GetOsCsdString(OUT PWSTR Buffer, IN ULONG BufferChars) { return CopyStringW(Buffer, g_Target->m_ServicePackString, BufferChars) ? S_OK : E_INVALIDARG; } HRESULT DbgSystemProvider::OpenMapping(IN PCWSTR FilePath, OUT PULONG Size, OUT PWSTR LongPath, IN ULONG LongPathChars, OUT PVOID* ViewRet) { // We could potentially support this via image file // location but the minidump code is deliberately // written to not rely to mappings. return E_NOTIMPL; } void DbgSystemProvider::CloseMapping(PVOID Mapping) { // No mapping support. DBG_ASSERT(!Mapping); } HRESULT DbgSystemProvider::GetImageHeaderInfo(IN HANDLE _Process, IN PCWSTR FilePath, IN ULONG64 ImageBase, OUT PULONG MachineType, OUT PULONG Size, OUT PULONG CheckSum, OUT PULONG TimeDateStamp, OUT PULONG TlsDirRva) { ProcessInfo* Process = (ProcessInfo*)_Process; ImageInfo* Image = Process-> FindImageByOffset(ImageBase, FALSE); if (!Image) { return E_NOINTERFACE; } Image->UpdateHeaderInfo(IMINFO_BASE_HEADER_ALL); *MachineType = Image->m_HeaderInfo.MachineType; *Size = Image->m_SizeOfImage; *CheckSum = Image->m_HeaderInfo.CheckSum; *TimeDateStamp = Image->m_HeaderInfo.TimeDateStamp; IMAGE_NT_HEADERS64 Hdrs; if (Process->m_Target-> ReadImageNtHeaders(Process, ImageBase, &Hdrs) == S_OK && Hdrs.OptionalHeader.NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_TLS && Hdrs.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS].Size > 0) { *TlsDirRva = Hdrs.OptionalHeader. DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS].VirtualAddress; } else { *TlsDirRva = 0; } return S_OK; } HRESULT DbgSystemProvider::GetImageVersionInfo(IN HANDLE Process, IN PCWSTR FilePath, IN ULONG64 ImageBase, OUT VS_FIXEDFILEINFO* Info) { return g_Target-> GetImageVersionInformation((ProcessInfo*)Process, FilePath, ImageBase, L"\\", Info, sizeof(*Info), NULL); } HRESULT DbgSystemProvider::GetImageDebugRecord(IN HANDLE Process, IN PCWSTR FilePath, IN ULONG64 ImageBase, IN ULONG RecordType, IN OUT OPTIONAL PVOID Data, OUT PULONG DataLen) { // We can rely on the default processing. return E_NOINTERFACE; } HRESULT DbgSystemProvider::EnumImageSections(IN HANDLE Process, IN PCWSTR FilePath, IN ULONG64 ImageBase, IN ULONG WriteFlags, IN MiniDumpProviderCallbacks* Callback) { // We can rely on the default processing. return E_NOINTERFACE; } HRESULT DbgSystemProvider::OpenThread(IN ULONG DesiredAccess, IN BOOL InheritHandle, IN ULONG ThreadId, OUT PHANDLE Handle) { // Just use the thread pointer as the "handle". *Handle = g_Process->FindThreadBySystemId(ThreadId); return *Handle ? S_OK : E_NOINTERFACE; } void DbgSystemProvider::CloseThread(IN HANDLE Handle) { // "Handle" is just a pointer so nothing to do. } ULONG DbgSystemProvider::GetCurrentThreadId(void) { // The minidump code uses the current thread ID // to avoid suspending the thread running the dump // code. That's not a problem for the debugger, // so return an ID that will never match. // SuspendThread will always be called so all // suspend counts will be set properly. return 0; } ULONG DbgSystemProvider::SuspendThread(IN HANDLE Thread) { return ((ThreadInfo*)Thread)->m_SuspendCount; } ULONG DbgSystemProvider::ResumeThread(IN HANDLE Thread) { return ((ThreadInfo*)Thread)->m_SuspendCount; } HRESULT DbgSystemProvider::GetThreadContext(IN HANDLE Thread, OUT PVOID Context, IN ULONG ContextSize, OUT PULONG64 CurrentPc, OUT PULONG64 CurrentStack, OUT PULONG64 CurrentStore) { HRESULT Status; ADDR Addr; g_Target->ChangeRegContext((ThreadInfo*)Thread); if ((Status = g_Machine-> GetContextState(MCTX_CONTEXT)) != S_OK || (Status = g_Machine-> ConvertCanonContextToTarget(&g_Machine->m_Context, g_Target->m_TypeInfo.SizeTargetContext, Context)) != S_OK) { return Status; } g_Machine->GetPC(&Addr); *CurrentPc = Flat(Addr); g_Machine->GetSP(&Addr); *CurrentStack = Flat(Addr); if (g_Target->m_MachineType == IMAGE_FILE_MACHINE_IA64) { *CurrentStore = g_Machine->m_Context.IA64Context.RsBSP; } else { *CurrentStore = 0; } return S_OK; } HRESULT DbgSystemProvider::GetTeb(IN HANDLE Thread, OUT PULONG64 Offset, OUT PULONG Size) { // Always save a whole page for the TEB. *Size = g_Machine->m_PageSize; return g_Target-> GetThreadInfoTeb((ThreadInfo*)Thread, 0, 0, Offset); } HRESULT DbgSystemProvider::GetThreadTebInfo(IN HANDLE Process, IN HANDLE Thread, OUT PULONG64 Teb, OUT PULONG SizeOfTeb, OUT PULONG64 StackBase, OUT PULONG64 StackLimit, OUT PULONG64 StoreBase, OUT PULONG64 StoreLimit, OUT PULONG64 StaticTlsPointer) { HRESULT Status; MEMORY_BASIC_INFORMATION64 MemInfo; ULONG64 MemHandle; if ((Status = g_Target-> GetThreadInfoTeb((ThreadInfo*)Thread, 0, 0, Teb)) != S_OK) { return Status; } // // Try and save a whole page for the TEB. If that's // not possible, save as much as is there. // MemHandle = *Teb; if ((Status = g_Target-> QueryMemoryRegion((ProcessInfo*)Process, &MemHandle, &MemInfo)) != S_OK) { return Status; } *SizeOfTeb = g_Machine->m_PageSize; if (*Teb + *SizeOfTeb > MemInfo.BaseAddress + MemInfo.RegionSize) { *SizeOfTeb = (ULONG) ((MemInfo.BaseAddress + MemInfo.RegionSize) - *Teb); } // // Read the TIB for TLS information. // ULONG PtrSize = g_Machine->m_Ptr64 ? 8 : 4; if ((Status = g_Target-> ReadPointer((ProcessInfo*)Process, g_Machine, *Teb + 11 * PtrSize, StaticTlsPointer)) != S_OK) { return Status; } THREAD_STACK_BOUNDS Bounds; if ((Status = g_Target-> GetThreadStackBounds((ThreadInfo*)Thread, *Teb, &Bounds)) != S_OK) { return Status; } *StackBase = Bounds.StackBase; *StackLimit = Bounds.StackLimit; *StoreBase = Bounds.StoreBase; *StoreLimit = Bounds.StoreLimit; return S_OK; } HRESULT DbgSystemProvider::GetThreadOsInfo(IN HANDLE Process, IN HANDLE _Thread, IN OUT PUMINIPROV_THREAD_INFO Info) { HRESULT Status; ThreadInfo* Thread = (ThreadInfo*)_Thread; if ((Status = Thread->GetBasicInfo(DEBUG_TBINFO_ALL)) != S_OK) { return Status; } Info->ExitStatus = Thread->m_ExitStatus; if (Thread->m_BasicInfo.Valid & DEBUG_TBINFO_PRIORITY_CLASS) { Info->PriorityClass = Thread->m_BasicInfo.PriorityClass; } if (Thread->m_BasicInfo.Valid & DEBUG_TBINFO_PRIORITY) { Info->Priority = Thread->m_BasicInfo.Priority; } if (Thread->m_BasicInfo.Valid & DEBUG_TBINFO_TIMES) { Info->CreateTime = Thread->m_BasicInfo.CreateTime; Info->ExitTime = Thread->m_BasicInfo.ExitTime; Info->KernelTime = Thread->m_BasicInfo.KernelTime; Info->UserTime = Thread->m_BasicInfo.UserTime; } if (Thread->m_BasicInfo.Valid & DEBUG_TBINFO_START_OFFSET) { Info->StartAddress = Thread->m_BasicInfo.StartOffset; } if (Thread->m_BasicInfo.Valid & DEBUG_TBINFO_AFFINITY) { Info->Affinity = Thread->m_BasicInfo.Affinity; } return S_OK; } HRESULT DbgSystemProvider::GetPeb(IN HANDLE Process, OUT PULONG64 Offset, OUT PULONG Size) { HRESULT Status; MEMORY_BASIC_INFORMATION64 MemInfo; ULONG64 MemHandle; // The passed in process isn't very useful but we know // that we're dumping the current state so always // retrieve the PEB for the current thread. if ((Status = g_Target-> GetProcessInfoPeb(g_Thread, 0, 0, Offset)) != S_OK) { return Status; } // // Try and save a whole page for the PEB. If that's // not possible, save as much as is there. // MemHandle = *Offset; if ((Status = g_Target-> QueryMemoryRegion((ProcessInfo*)Process, &MemHandle, &MemInfo)) != S_OK) { return Status; } *Size = g_Machine->m_PageSize; if (*Offset + *Size > MemInfo.BaseAddress + MemInfo.RegionSize) { *Size = (ULONG) ((MemInfo.BaseAddress + MemInfo.RegionSize) - *Offset); } return S_OK; } HRESULT DbgSystemProvider::GetProcessTimes(IN HANDLE Process, OUT LPFILETIME Create, OUT LPFILETIME User, OUT LPFILETIME Kernel) { HRESULT Status; ULONG64 Create64, Exit64, User64, Kernel64; if ((Status = g_Target->GetProcessTimes((ProcessInfo*)Process, &Create64, &Exit64, &Kernel64, &User64)) != S_OK) { return Status; } Create->dwHighDateTime = (ULONG)(Create64 >> 32); Create->dwLowDateTime = (ULONG)Create64; User->dwHighDateTime = (ULONG)(User64 >> 32); User->dwLowDateTime = (ULONG)User64; Kernel->dwHighDateTime = (ULONG)(Kernel64 >> 32); Kernel->dwLowDateTime = (ULONG)Kernel64; return S_OK; } HRESULT DbgSystemProvider::ReadVirtual(IN HANDLE Process, IN ULONG64 Offset, OUT PVOID Buffer, IN ULONG Request, OUT PULONG Done) { return g_Target->ReadVirtual((ProcessInfo*)Process, Offset, Buffer, Request, Done); } HRESULT DbgSystemProvider::ReadAllVirtual(IN HANDLE Process, IN ULONG64 Offset, OUT PVOID Buffer, IN ULONG Request) { return g_Target->ReadAllVirtual((ProcessInfo*)Process, Offset, Buffer, Request); } HRESULT DbgSystemProvider::QueryVirtual(IN HANDLE Process, IN ULONG64 Offset, OUT PMINIDUMP_MEMORY_INFO Info) { DBG_ASSERT(sizeof(MEMORY_BASIC_INFORMATION64) == sizeof(*Info)); return g_Target-> QueryMemoryRegion((ProcessInfo*)Process, &Offset, (PMEMORY_BASIC_INFORMATION64)Info); } HRESULT DbgSystemProvider::GetValidVirtualRange(IN HANDLE _Process, IN ULONG64 Start, IN ULONG Size, OUT PULONG64 ValidStart, OUT PULONG ValidSize) { ProcessInfo* Process = (ProcessInfo*)_Process; return g_Target->GetValidRegionVirtual(Process, Start, Size, ValidStart, ValidSize); } HRESULT DbgSystemProvider::StartProcessEnum(IN HANDLE Process, IN ULONG ProcessId) { m_Thread = ((ProcessInfo*)Process)->m_ThreadHead; m_Image = ((ProcessInfo*)Process)->m_ImageHead; // Unloaded modules aren't critical, so just // ignore them if the enumerator fails. if (((ProcessInfo*)Process)->m_Target-> GetUnloadedModuleInfo(FALSE, &m_UnlEnum) != S_OK || m_UnlEnum->Initialize(g_Thread, MODULE_INFO_ALL_STD) != S_OK) { m_UnlEnum = NULL; } m_FuncTableStart = 0; m_FuncTableHandle = 0; return S_OK; } HRESULT DbgSystemProvider::EnumThreads(OUT PULONG ThreadId) { if (!m_Thread) { return S_FALSE; } *ThreadId = m_Thread->m_SystemId; m_Thread = m_Thread->m_Next; return S_OK; } HRESULT DbgSystemProvider::EnumModules(OUT PULONG64 Base, OUT PWSTR Path, IN ULONG PathChars) { if (!m_Image) { return S_FALSE; } *Base = m_Image->m_BaseOfImage; #ifdef UNICODE CopyStringW(Path, m_Image->m_ImagePath, PathChars); #else if (!MultiByteToWideChar(CP_ACP, 0, m_Image->m_ImagePath, -1, Path, PathChars)) { return WIN32_LAST_STATUS(); } #endif m_Image = m_Image->m_Next; return S_OK; } HRESULT DbgSystemProvider::EnumFunctionTables(OUT PULONG64 MinAddress, OUT PULONG64 MaxAddress, OUT PULONG64 BaseAddress, OUT PULONG EntryCount, OUT PVOID RawTable, IN ULONG RawTableSize, OUT PVOID* RawEntryHandle) { HRESULT Status; CROSS_PLATFORM_DYNAMIC_FUNCTION_TABLE CpTable; if ((Status = g_Target-> EnumFunctionTables(g_Process, &m_FuncTableStart, &m_FuncTableHandle, MinAddress, MaxAddress, BaseAddress, EntryCount, &CpTable, RawEntryHandle)) != S_OK) { return Status; } memcpy(RawTable, &CpTable, RawTableSize); return S_OK; } HRESULT DbgSystemProvider::EnumFunctionTableEntries(IN PVOID RawTable, IN ULONG RawTableSize, IN PVOID RawEntryHandle, OUT PVOID RawEntries, IN ULONG RawEntriesSize) { memcpy(RawEntries, RawEntryHandle, RawEntriesSize); free(RawEntryHandle); return S_OK; } HRESULT DbgSystemProvider::EnumFunctionTableEntryMemory(IN ULONG64 TableBase, IN PVOID RawEntries, IN ULONG Index, OUT PULONG64 Start, OUT PULONG Size) { return g_Machine->GetUnwindInfoBounds(g_Process, TableBase, RawEntries, Index, Start, Size); } HRESULT DbgSystemProvider::EnumUnloadedModules(OUT PWSTR Path, IN ULONG PathChars, OUT PULONG64 BaseOfModule, OUT PULONG SizeOfModule, OUT PULONG CheckSum, OUT PULONG TimeDateStamp) { TCHAR UnlName[MAX_INFO_UNLOADED_NAME]; DEBUG_MODULE_PARAMETERS Params; if (!m_UnlEnum || m_UnlEnum->GetEntry(UnlName, &Params) != S_OK) { return S_FALSE; } #ifdef UNICODE CopyStringW(Path, UnlName, PathChars); #else if (!MultiByteToWideChar(CP_ACP, 0, UnlName, -1, Path, PathChars)) { return WIN32_LAST_STATUS(); } #endif *BaseOfModule = Params.Base; *SizeOfModule = Params.Size; *CheckSum = Params.Checksum; *TimeDateStamp = Params.TimeDateStamp; return S_OK; } void DbgSystemProvider::FinishProcessEnum(void) { // Nothing to do. } HRESULT DbgSystemProvider::StartHandleEnum(IN HANDLE Process, IN ULONG ProcessId, OUT PULONG Count) { m_Handle = 4; // If the target doesn't have handle data don't make // it a fatal error, just don't enumerate anything. if (g_Target-> ReadHandleData((ProcessInfo*)Process, 0, DEBUG_HANDLE_DATA_TYPE_HANDLE_COUNT, Count, sizeof(*Count), NULL) != S_OK) { *Count = 0; } return S_OK; } HRESULT DbgSystemProvider::EnumHandles(OUT PULONG64 Handle, OUT PULONG Attributes, OUT PULONG GrantedAccess, OUT PULONG HandleCount, OUT PULONG PointerCount, OUT PWSTR TypeName, IN ULONG TypeNameChars, OUT PWSTR ObjectName, IN ULONG ObjectNameChars) { DEBUG_HANDLE_DATA_BASIC BasicInfo; for (;;) { if (m_Handle >= (1 << 24)) { return S_FALSE; } // If we can't get the basic info and type there isn't much // point in writing anything out so skip the handle. if (g_Target-> ReadHandleData(g_Process, m_Handle, DEBUG_HANDLE_DATA_TYPE_BASIC, &BasicInfo, sizeof(BasicInfo), NULL) == S_OK && SUCCEEDED(g_Target-> ReadHandleData(g_Process, m_Handle, DEBUG_HANDLE_DATA_TYPE_TYPE_NAME_WIDE, TypeName, TypeNameChars * sizeof(*TypeName), NULL))) { break; } m_Handle += 4; } // Try and get the object name. if (FAILED(g_Target-> ReadHandleData(g_Process, m_Handle, DEBUG_HANDLE_DATA_TYPE_OBJECT_NAME_WIDE, ObjectName, ObjectNameChars * sizeof(*ObjectName), NULL))) { *ObjectName = 0; } *Handle = m_Handle; *Attributes = BasicInfo.Attributes; *GrantedAccess = BasicInfo.GrantedAccess; *HandleCount = BasicInfo.HandleCount; *PointerCount = BasicInfo.PointerCount; m_Handle += 4; return S_OK; } void DbgSystemProvider::FinishHandleEnum(void) { // Nothing to do. } HRESULT DbgSystemProvider::EnumPebMemory(IN HANDLE Process, IN ULONG64 PebOffset, IN ULONG PebSize, IN MiniDumpProviderCallbacks* Callback) { if (g_Target->m_SystemVersion <= NT_SVER_START || g_Target->m_SystemVersion >= NT_SVER_END) { // Basic Win32 doesn't have a defined PEB. return S_OK; } // XXX drewb - This requires a whole set of constants // to abstract data structure locations. Leave it // for when we really need it. return S_OK; } HRESULT DbgSystemProvider::EnumTebMemory(IN HANDLE Process, IN HANDLE Thread, IN ULONG64 TebOffset, IN ULONG TebSize, IN MiniDumpProviderCallbacks* Callback) { if (g_Target->m_SystemVersion <= NT_SVER_START || g_Target->m_SystemVersion >= NT_SVER_END) { // Basic Win32 doesn't have a defined TEB beyond // the TIB. The TIB can reference fiber data but // that's NT-specific. return S_OK; } // XXX drewb - This requires a whole set of constants // to abstract data structure locations. Leave it // for when we really need it. return S_OK; } HRESULT DbgSystemProvider::GetClrEnum(IN PWSTR DacDllName, IN struct ICLRDataTarget* Target, OUT struct ICLRDataEnumMemoryRegions** Enum) { HRESULT Status; // We're providing all of the system services to // the minidump code so we know that its state // matches what's available directly from the debugger's // state. Just ignore the given DLL name and // service interface in favor of the one the // debugger already has. if ((Status = g_Process->LoadClrDebugDll(NULL)) != S_OK || (Status = g_Process->m_ClrProcess-> QueryInterface(__uuidof(ICLRDataEnumMemoryRegions), (void**)Enum)) != S_OK) { return Status; } return S_OK; } void DbgSystemProvider::ReleaseClrEnum(IN struct ICLRDataEnumMemoryRegions* Enum) { Enum->Release(); } HRESULT DbgSystemProvider::WriteKernelMinidump(IN HANDLE File, IN HANDLE Process, IN ULONG NumThreads, IN PHANDLE ThreadHandles, IN ULONG BugCheckCode, IN ULONG Flags) { // We could support this in the live user debug case // by remoting NtSystemDebugControl via IUserDebugServices // but it's not important right now. return E_NOINTERFACE; } class DbgStatusProvider : public MiniDumpStatusProvider { public: DbgStatusProvider(ULONG Filter) { m_Filter = Filter; } virtual void Release(void); virtual void Status(ULONG Flags, PCSTR Format, ...); ULONG m_Filter; }; void DbgStatusProvider::Release(void) { delete this; } void DbgStatusProvider::Status(ULONG Flags, PCSTR Format, ...) { if (Flags & m_Filter) { ULONG Mask = 0; va_list Args; if (Flags & UMINIPROV_ERROR) { Mask |= DEBUG_OUTPUT_ERROR; } if (Flags & (UMINIPROV_WARNING | UMINIPROV_DATA_MISSING)) { Mask |= DEBUG_OUTPUT_WARNING; } if (Flags & UMINIPROV_STATUS) { Mask |= DEBUG_OUTPUT_NORMAL; } va_start(Args, Format); MaskOutVaA(Mask, Format, Args, FALSE); va_end(Args); MaskOut(Mask, _T("\n")); } } PMINIDUMP_EXCEPTION_INFORMATION64 CreateMiniExceptionInformation(DUMP_WRITE_ARGS* Args, PMINIDUMP_EXCEPTION_INFORMATION64 ExInfo, PVOID ExRecord, PCROSS_PLATFORM_CONTEXT Context) { MachineInfo* Machine; JIT_DEBUG_INFO64 Info; BOOL ExEvent = g_LastEventType == DEBUG_EVENT_EXCEPTION && g_Process == g_EventProcess; // If the last event was not an exception and // we don't have override information don't // put any exception info in the dump. if (!Args->JitDebugInfoAddr && (!Args->ExContextAddr || !Args->ExRecordAddr) && !ExEvent) { return NULL; } // // If the user provided the address of a JIT_DEBUG_INFO // use that as the exception information to carry in // the minidump. This makes it possible to take a minidump // from AeDebug with JIT_DEBUG_INFO that is the same as // if the process had been debugged and JIT_DEBUG_INFO hadn't been used. // if (Args->JitDebugInfoAddr) { if (g_Target->ReadJitDebugInfo(g_Process, Args->JitDebugInfoAddr, &Machine, &Info) != S_OK) { ErrOut(_T("Unable to read JIT_DEBUG_INFO at %s\n"), FormatAddr64(Args->JitDebugInfoAddr)); return NULL; } if (!Info.ContextRecord || !Info.ExceptionRecord) { ErrOut(_T("JIT_DEBUG_INFO is invalid\n")); return NULL; } if (Machine != g_Machine) { ErrOut(_T("JIT_DEBUG_INFO machine must ") _T("match the native machine\n")); return NULL; } } else { Info.ContextRecord = Args->ExContextAddr; Info.ExceptionRecord = Args->ExRecordAddr; Info.dwThreadID = Args->ExThreadId ? Args->ExThreadId : g_EventThreadSysId; } if (Info.ContextRecord) { if (g_Target->ReadAllVirtual(g_Process, Info.ContextRecord, Context, g_Target->m_TypeInfo. SizeTargetContext) != S_OK) { ErrOut(_T("Unable to read context record at %s\n"), FormatAddr64(Info.ContextRecord)); return NULL; } } else { g_Target->ChangeRegContext(g_EventThread); if (g_Machine->GetContextState(MCTX_CONTEXT) != S_OK) { ErrOut(_T("Unable to get event thread context\n")); return NULL; } *Context = g_Machine->m_Context; } if (Info.ExceptionRecord) { if (g_Target->ReadAllVirtual(g_Process, Info.ExceptionRecord, ExRecord, g_Machine->m_Ptr64 ? sizeof(EXCEPTION_RECORD64) : sizeof(EXCEPTION_RECORD32)) != S_OK) { ErrOut(_T("Unable to read exception record at %s\n"), FormatAddr64(Info.ExceptionRecord)); return NULL; } } else { if (g_EventMachine->m_Ptr64) { memcpy(ExRecord, &g_LastEventInfo.Exception.ExceptionRecord, sizeof(g_LastEventInfo.Exception.ExceptionRecord)); } else { ExceptionRecord64To32(&g_LastEventInfo.Exception.ExceptionRecord, (PEXCEPTION_RECORD32)ExRecord); } } ExInfo->ThreadId = Info.dwThreadID; ExInfo->ClientPointers = FALSE; ExInfo->ExceptionRecord = (LONG_PTR)ExRecord; ExInfo->ContextRecord = (LONG_PTR)Context; return ExInfo; } BOOL WINAPI UserMiniCallback(IN PVOID CallbackParam, IN CONST PMINIDUMP_CALLBACK_INPUT CallbackInput, IN OUT PMINIDUMP_CALLBACK_OUTPUT CallbackOutput) { DUMP_WRITE_ARGS* Args = (DUMP_WRITE_ARGS*)CallbackParam; switch(CallbackInput->CallbackType) { case IncludeModuleCallback: if (Args->InternalFlags & UMINI_MICRO) { // Mask off all flags other than the basic write flag. CallbackOutput->ModuleWriteFlags &= ModuleWriteModule; } break; case ModuleCallback: if (Args->InternalFlags & UMINI_MICRO) { // Eliminate all unreferenced modules. if (!(CallbackOutput->ModuleWriteFlags & ModuleReferencedByMemory)) { CallbackOutput->ModuleWriteFlags = 0; } } break; case IncludeThreadCallback: if (Args->InternalFlags & UMINI_MICRO) { if (CallbackInput->IncludeThread.ThreadId != g_EventThreadSysId) { return FALSE; } // Reduce write to the minimum of information // necessary for a stack walk. CallbackOutput->ThreadWriteFlags &= ~ThreadWriteInstructionWindow; } break; case CancelCallback: CallbackOutput->Cancel = CheckUserInterrupt(); CallbackOutput->CheckCancel = TRUE; break; case WriteKernelMinidumpCallback: if (Args->InternalFlags & UMINI_WRITE_KMINI) { Args->ExtraFileHandle = CreateFile(Args->ExtraFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (Args->ExtraFileHandle == INVALID_HANDLE_VALUE || !Args->ExtraFileHandle) { Args->ExtraFileHandle = NULL; ErrOut(_T("Unable to create kernel minidump file, %s\n"), FormatStatusCode(WIN32_LAST_STATUS())); } else { CallbackOutput->Handle = Args->ExtraFileHandle; } } break; case KernelMinidumpStatusCallback: if (Args->ExtraFileHandle) { CloseHandle(Args->ExtraFileHandle); Args->ExtraFileHandle = NULL; if (CallbackInput->Status == S_OK) { dprintf(_T("Wrote kernel minidump '%s'\n"), Args->ExtraFileName); } else { WideDeleteFile(Args->ExtraFileName); } } break; } return TRUE; } HRESULT UserMiniDumpTargetInfo::Write(HANDLE hFile, DUMP_WRITE_ARGS* Args) { if (!IS_USER_TARGET(g_Target)) { ErrOut(_T("User minidumps can only be written ") _T("in user-mode sessions\n")); return E_UNEXPECTED; } dprintf(_T("mini user dump\n")); FlushCallbacks(); // Clear interrupt status. CheckUserInterrupt(); HRESULT Status; MINIDUMP_EXCEPTION_INFORMATION64 ExInfoBuf, *ExInfo; EXCEPTION_RECORD64 ExRecord; CROSS_PLATFORM_CONTEXT Context; ULONG MiniType; MINIDUMP_USER_STREAM UserStreams[2]; MINIDUMP_USER_STREAM_INFORMATION UserStreamInfo; MINIDUMP_CALLBACK_INFORMATION CallbackBuffer; MiniType = MiniDumpNormal; if (Args->FormatFlags & DEBUG_FORMAT_USER_SMALL_FULL_MEMORY) { MiniType |= MiniDumpWithFullMemory; } if (Args->FormatFlags & DEBUG_FORMAT_USER_SMALL_HANDLE_DATA) { MiniType |= MiniDumpWithHandleData; } if (Args->FormatFlags & DEBUG_FORMAT_USER_SMALL_UNLOADED_MODULES) { MiniType |= MiniDumpWithUnloadedModules; } if (Args->FormatFlags & DEBUG_FORMAT_USER_SMALL_INDIRECT_MEMORY) { MiniType |= MiniDumpWithIndirectlyReferencedMemory; } if (Args->FormatFlags & DEBUG_FORMAT_USER_SMALL_DATA_SEGMENTS) { MiniType |= MiniDumpWithDataSegs; } if (Args->FormatFlags & DEBUG_FORMAT_USER_SMALL_FILTER_MEMORY) { MiniType |= MiniDumpFilterMemory; } if (Args->FormatFlags & DEBUG_FORMAT_USER_SMALL_FILTER_PATHS) { MiniType |= MiniDumpFilterModulePaths; } if (Args->FormatFlags & DEBUG_FORMAT_USER_SMALL_PROCESS_THREAD_DATA) { MiniType |= MiniDumpWithProcessThreadData; } if (Args->FormatFlags & DEBUG_FORMAT_USER_SMALL_PRIVATE_READ_WRITE_MEMORY) { MiniType |= MiniDumpWithPrivateReadWriteMemory; } if (Args->FormatFlags & DEBUG_FORMAT_USER_SMALL_NO_OPTIONAL_DATA) { MiniType |= MiniDumpWithoutOptionalData; } if (Args->FormatFlags & DEBUG_FORMAT_USER_SMALL_FULL_MEMORY_INFO) { MiniType |= MiniDumpWithFullMemoryInfo; } if (Args->FormatFlags & DEBUG_FORMAT_USER_SMALL_THREAD_INFO) { MiniType |= MiniDumpWithThreadInfo; } if (Args->FormatFlags & DEBUG_FORMAT_USER_SMALL_CODE_SEGMENTS) { MiniType |= MiniDumpWithCodeSegs; } UserStreamInfo.UserStreamCount = 0; UserStreamInfo.UserStreamArray = UserStreams; if (Args->CommentA != NULL) { UserStreams[UserStreamInfo.UserStreamCount].Type = CommentStreamA; UserStreams[UserStreamInfo.UserStreamCount].BufferSize = strlen(Args->CommentA) + 1; UserStreams[UserStreamInfo.UserStreamCount].Buffer = (PVOID)Args->CommentA; UserStreamInfo.UserStreamCount++; } if (Args->CommentW != NULL) { UserStreams[UserStreamInfo.UserStreamCount].Type = CommentStreamW; UserStreams[UserStreamInfo.UserStreamCount].BufferSize = (wcslen(Args->CommentW) + 1) * sizeof(WCHAR); UserStreams[UserStreamInfo.UserStreamCount].Buffer = (PVOID)Args->CommentW; UserStreamInfo.UserStreamCount++; } ExInfo = CreateMiniExceptionInformation(Args, &ExInfoBuf, &ExRecord, &Context); CallbackBuffer.CallbackRoutine = UserMiniCallback; CallbackBuffer.CallbackParam = Args; if (Args->InternalFlags & UMINI_MICRO) { // This case isn't expected to be used by users, // it's for testing of the microdump support. ExInfo = NULL; MiniType |= MiniDumpScanMemory; } HANDLE ProcHandle; MiniDumpSystemProvider* SysProv = NULL; MiniDumpOutputProvider* OutProv = NULL; MiniDumpAllocationProvider* AllocProv = NULL; DbgStatusProvider StatusProv(UMINIPROV_WARNING | UMINIPROV_ERROR); if ((Status = MiniDumpCreateLiveAllocationProvider(&AllocProv)) != S_OK || (Status = MiniDumpCreateFileOutputProvider(AllocProv, hFile, &OutProv)) != S_OK) { goto Exit; } // // If we're live we can let the official minidump // code do all the work. If not, hook up a provider // that uses debugger information. This provider // could always be used but the default live-system // provider offers slightly more information so // check and use that if possible. // if (IS_LIVE_USER_TARGET(g_Target) && ((LiveUserTargetInfo*)g_Target)->m_Local && !(Args->InternalFlags & UMINI_FORCE_DBG)) { if ((Status = MiniDumpCreateLiveSystemProvider(AllocProv, &SysProv)) != S_OK) { goto Exit; } ProcHandle = OS_HANDLE(g_Process->m_SysHandle); } else { DbgSystemProvider* DbgSysProv = new DbgSystemProvider; if (!DbgSysProv) { Status = E_OUTOFMEMORY; goto Exit; } SysProv = DbgSysProv; ProcHandle = (HANDLE)g_Process; } Status = MiniDumpProvideDump(ProcHandle, g_Process->m_SystemId, SysProv, OutProv, AllocProv, &StatusProv, MiniType, ExInfo, &UserStreamInfo, &CallbackBuffer); Exit: if (Status != S_OK) { ErrOut(_T("Dump creation failed, %s\n \"%s\"\n"), FormatStatusCode(Status), FormatStatus(Status)); } if (SysProv) { SysProv->Release(); } if (OutProv) { OutProv->Release(); } if (AllocProv) { AllocProv->Release(); } // Reset the current register context in case // it was changed at some point. g_Target->ChangeRegContext(g_Thread); return Status; } //------------------------------------------------------------------- // initialize the dump headers // #define MINIDUMP_BUGCHECK 0x10000000 void KernelDumpTargetInfo::InitDumpHeader32(PDUMP_HEADER32 Hdr, PCSTR CommentA, PCWSTR CommentW, ULONG BugCheckCodeModifier) { ULONG64 Data[4]; PULONG FillPtr = (PULONG)Hdr; while (FillPtr < (PULONG)(Hdr + 1)) { *FillPtr++ = DUMP_SIGNATURE32; } Hdr->Signature = DUMP_SIGNATURE32; Hdr->ValidDump = DUMP_VALID_DUMP32; Hdr->MajorVersion = g_Target->m_KdVersion.MajorVersion; Hdr->MinorVersion = g_Target->m_KdVersion.MinorVersion; g_Target->ReadDirectoryTableBase(Data); Hdr->DirectoryTableBase = (ULONG)Data[0]; Hdr->PfnDataBase = (ULONG)g_Target->m_KdDebuggerData.MmPfnDatabase; Hdr->PsLoadedModuleList = (ULONG)g_Target->m_KdDebuggerData.PsLoadedModuleList; Hdr->PsActiveProcessHead = (ULONG)g_Target->m_KdDebuggerData.PsActiveProcessHead; Hdr->MachineImageType = g_Target->m_KdVersion.MachineType; Hdr->NumberProcessors = g_Target->m_NumProcessors; g_Target->ReadBugCheckData(&(Hdr->BugCheckCode), Data); Hdr->BugCheckCode |= BugCheckCodeModifier; Hdr->BugCheckParameter1 = (ULONG)Data[0]; Hdr->BugCheckParameter2 = (ULONG)Data[1]; Hdr->BugCheckParameter3 = (ULONG)Data[2]; Hdr->BugCheckParameter4 = (ULONG)Data[3]; Hdr->PaeEnabled = g_Target->m_KdDebuggerData.PaeEnabled; Hdr->KdDebuggerDataBlock = (ULONG)g_Target->m_KdDebuggerDataOffset; if (IS_CONTEXT_POSSIBLE(g_Target)) { g_Machine->GetContextState(MCTX_CONTEXT); g_Machine->ConvertCanonContextToTarget(&g_Machine->m_Context, sizeof(Hdr->ContextRecord), Hdr->ContextRecord); } else { ZeroMemory(Hdr->ContextRecord, sizeof(Hdr->ContextRecord)); } if (g_LastEventType == DEBUG_EVENT_EXCEPTION) { // Use the exception record from the last event. ExceptionRecord64To32(&g_LastEventInfo.Exception.ExceptionRecord, &Hdr->Exception); } else { ADDR PcAddr; // Fake a breakpoint exception. ZeroMemory(&Hdr->Exception, sizeof(Hdr->Exception)); Hdr->Exception.ExceptionCode = STATUS_BREAKPOINT; if (IS_CONTEXT_POSSIBLE(g_Target)) { g_Machine->GetPC(&PcAddr); Hdr->Exception.ExceptionAddress = (ULONG)Flat(PcAddr); } } Hdr->RequiredDumpSpace.QuadPart = TRIAGE_DUMP_SIZE32 + DumpGetTaggedDataSize(); Hdr->SystemTime.QuadPart = g_Target->GetCurrentTimeDateN(); Hdr->SystemUpTime.QuadPart = g_Target->GetCurrentSystemUpTimeN(); if (g_Target->m_ProductType != INVALID_PRODUCT_TYPE) { Hdr->ProductType = g_Target->m_ProductType; Hdr->SuiteMask = g_Target->m_SuiteMask; } PSTR ConvComment = NULL; if (!CommentA && CommentW) { if (WideToAnsi(CommentW, &ConvComment) != S_OK) { ConvComment = NULL; } else { CommentA = ConvComment; } } if (CommentA != NULL && CommentA[0]) { CopyString(Hdr->Comment, CommentA, DIMA(Hdr->Comment)); } FreeAnsi(ConvComment); } void KernelDumpTargetInfo::InitDumpHeader64(PDUMP_HEADER64 Hdr, PCSTR CommentA, PCWSTR CommentW, ULONG BugCheckCodeModifier) { ULONG64 Data[4]; PULONG FillPtr = (PULONG)Hdr; while (FillPtr < (PULONG)(Hdr + 1)) { *FillPtr++ = DUMP_SIGNATURE32; } Hdr->Signature = DUMP_SIGNATURE64; Hdr->ValidDump = DUMP_VALID_DUMP64; Hdr->MajorVersion = g_Target->m_KdVersion.MajorVersion; Hdr->MinorVersion = g_Target->m_KdVersion.MinorVersion; // IA64 has several page directories. The defined // behavior is to put the kernel page directory // in the dump header as that's the one that can // be most useful when first initializing the dump. if (g_Target->m_EffMachineType == IMAGE_FILE_MACHINE_IA64) { ULONG Next; if (g_Machine-> SetPageDirectory(g_Thread, PAGE_DIR_KERNEL, 0, &Next) != S_OK) { ErrOut(_T("Unable to update the kernel dirbase\n")); Data[0] = 0; } else { Data[0] = g_Machine->m_PageDirectories[PAGE_DIR_KERNEL]; } } else { g_Target->ReadDirectoryTableBase(Data); } Hdr->DirectoryTableBase = Data[0]; Hdr->PfnDataBase = g_Target->m_KdDebuggerData.MmPfnDatabase; Hdr->PsLoadedModuleList = g_Target->m_KdDebuggerData.PsLoadedModuleList; Hdr->PsActiveProcessHead = g_Target->m_KdDebuggerData.PsActiveProcessHead; Hdr->MachineImageType = g_Target->m_KdVersion.MachineType; Hdr->NumberProcessors = g_Target->m_NumProcessors; g_Target->ReadBugCheckData(&(Hdr->BugCheckCode), Data); Hdr->BugCheckCode |= BugCheckCodeModifier; Hdr->BugCheckParameter1 = Data[0]; Hdr->BugCheckParameter2 = Data[1]; Hdr->BugCheckParameter3 = Data[2]; Hdr->BugCheckParameter4 = Data[3]; Hdr->KdDebuggerDataBlock = g_Target->m_KdDebuggerDataOffset; if (IS_CONTEXT_POSSIBLE(g_Target)) { g_Machine->GetContextState(MCTX_CONTEXT); g_Machine->ConvertCanonContextToTarget(&g_Machine->m_Context, sizeof(Hdr->ContextRecord), Hdr->ContextRecord); } else { ZeroMemory(Hdr->ContextRecord, sizeof(Hdr->ContextRecord)); } if (g_LastEventType == DEBUG_EVENT_EXCEPTION) { // Use the exception record from the last event. Hdr->Exception = g_LastEventInfo.Exception.ExceptionRecord; } else { ADDR PcAddr; // Fake a breakpoint exception. ZeroMemory(&Hdr->Exception, sizeof(Hdr->Exception)); Hdr->Exception.ExceptionCode = STATUS_BREAKPOINT; if (IS_CONTEXT_POSSIBLE(g_Target)) { g_Machine->GetPC(&PcAddr); Hdr->Exception.ExceptionAddress = Flat(PcAddr); } } Hdr->RequiredDumpSpace.QuadPart = TRIAGE_DUMP_SIZE64 + DumpGetTaggedDataSize(); Hdr->SystemTime.QuadPart = g_Target->GetCurrentTimeDateN(); Hdr->SystemUpTime.QuadPart = g_Target->GetCurrentSystemUpTimeN(); if (g_Target->m_ProductType != INVALID_PRODUCT_TYPE) { Hdr->ProductType = g_Target->m_ProductType; Hdr->SuiteMask = g_Target->m_SuiteMask; } PSTR ConvComment = NULL; if (!CommentA && CommentW) { if (WideToAnsi(CommentW, &ConvComment) != S_OK) { ConvComment = NULL; } else { CommentA = ConvComment; } } if (CommentA != NULL && CommentA[0]) { CopyString(Hdr->Comment, CommentA, DIMA(Hdr->Comment)); } FreeAnsi(ConvComment); } #define DUMP_BLOB_ALIGN 8 ULONG KernelDumpTargetInfo::DumpGetTaggedDataSize(void) { HRESULT Status; ULONG64 HdrOffs; DUMP_BLOB_HEADER BlobHdr; ULONG Size; if ((Status = GetFirstBlobHeaderOffset(&HdrOffs)) != S_OK) { return 0; } Size = sizeof(DUMP_BLOB_FILE_HEADER); // // Loop over every entry and accumulate the size. // We assume only 8-byte-alignment post-padding // will be necessary. // while (ReadBlobHeaderAtOffset(HdrOffs, &BlobHdr) == S_OK) { Size += sizeof(BlobHdr) + ((BlobHdr.DataSize + (DUMP_BLOB_ALIGN - 1)) & ~(DUMP_BLOB_ALIGN - 1)); HdrOffs += BlobHdr.HeaderSize + BlobHdr.PrePad + BlobHdr.DataSize + BlobHdr.PostPad; } return Size; } HRESULT KernelDumpTargetInfo::DumpWriteTaggedData(HANDLE File) { HRESULT Status; ULONG64 HdrOffs; DUMP_BLOB_FILE_HEADER FileHdr; DUMP_BLOB_HEADER BlobHdr; BYTE Buffer[8192]; C_ASSERT((sizeof(FileHdr) & (DUMP_BLOB_ALIGN - 1)) == 0); C_ASSERT((sizeof(BlobHdr) & (DUMP_BLOB_ALIGN - 1)) == 0); if ((Status = GetFirstBlobHeaderOffset(&HdrOffs)) != S_OK) { // E_NOINTERFACE indicates no tagged data, so // we trivially succeed in that case. return Status == E_NOINTERFACE ? S_OK : Status; } // // We have tagged data, so write a file header. // FileHdr.Signature1 = DUMP_BLOB_SIGNATURE1; FileHdr.Signature2 = DUMP_BLOB_SIGNATURE2; FileHdr.HeaderSize = sizeof(FileHdr); FileHdr.BuildNumber = g_Target->m_BuildNumber | (g_Target->m_CheckedBuild << 28); if ((Status = DumpWriteFile(File, &FileHdr, sizeof(FileHdr))) != S_OK) { return Status; } // // Loop over every entry and copy the data into the // dump file being written. // while (ReadBlobHeaderAtOffset(HdrOffs, &BlobHdr) == S_OK) { DUMP_BLOB_HEADER WriteBlobHdr; // // We're always going to write out data packed so the // only padding we need is a post-pad on the data to // keep headers 8-byte-aligned. // ZeroMemory(&WriteBlobHdr, sizeof(WriteBlobHdr)); memcpy(&WriteBlobHdr, &BlobHdr, BlobHdr.HeaderSize); WriteBlobHdr.HeaderSize = sizeof(WriteBlobHdr); WriteBlobHdr.PrePad = 0; WriteBlobHdr.PostPad = ((BlobHdr.DataSize + (DUMP_BLOB_ALIGN - 1)) & ~(DUMP_BLOB_ALIGN - 1)) - BlobHdr.DataSize; if ((Status = DumpWriteFile(File, &WriteBlobHdr, sizeof(WriteBlobHdr))) != S_OK) { return Status; } // // Copy the raw data. // HdrOffs += BlobHdr.HeaderSize + BlobHdr.PrePad; while (BlobHdr.DataSize) { ULONG Req = BlobHdr.DataSize < sizeof(Buffer) ? BlobHdr.DataSize : sizeof(Buffer); if ((Status = g_Target-> ReadTagged(HdrOffs, Buffer, Req)) != S_OK || (Status = DumpWriteFile(File, Buffer, Req)) != S_OK) { return Status; } BlobHdr.DataSize -= Req; HdrOffs += Req; } // // Write postpad. // if (WriteBlobHdr.PostPad) { ZeroMemory(Buffer, WriteBlobHdr.PostPad); if ((Status = DumpWriteFile(File, Buffer, WriteBlobHdr.PostPad)) != S_OK) { return Status; } } HdrOffs += BlobHdr.PostPad; } return S_OK; } //---------------------------------------------------------------------------- // // Kernel full dumps. // //---------------------------------------------------------------------------- HRESULT KernelDumpTargetInfo::DumpWritePhysicalMemoryToFile(HANDLE File, DUMP_WRITE_ARGS* Args, PVOID PhysDesc, BOOL Ptr64) { HRESULT Status; DbgKdTransport* KdTrans; HANDLE PhysMemFile = NULL; PUCHAR PageBuffer = NULL; // // Write the physical memory out to disk. // There are three sources of physical memory data: // 1. The user specified a file to open and read. The // file offsets must correspond to physical addresses. // 2. Some transports, such as 1394, directly support access // to physical memory. // 3. The target's physical memory read routine. // if (IS_CONN_KERNEL_TARGET(g_Target)) { KdTrans = ((ConnLiveKernelTargetInfo*)g_Target)->m_Transport; } else { KdTrans = NULL; } if (Args->PhysMemFileName) { PhysMemFile = CreateFileT(Args->PhysMemFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (!PhysMemFile || PhysMemFile == INVALID_HANDLE_VALUE) { PhysMemFile = NULL; Status = WIN32_LAST_STATUS(); ErrOut(_T("Unable to open physical memory file '%s' - %s\n ") _T("\"%s\"\n"), Args->PhysMemFileName, FormatStatusCode(Status), FormatStatus(Status)); goto Exit; } } PageBuffer = (PUCHAR)malloc(g_Machine->m_PageSize); if (!PageBuffer) { ErrOut(_T("Unable to allocate page buffer\n")); Status = E_OUTOFMEMORY; goto Exit; } ULONG NumRuns; ULONG64 NumPages; PPHYSICAL_MEMORY_RUN64 Run64; PPHYSICAL_MEMORY_RUN32 Run32; if (Ptr64) { PPHYSICAL_MEMORY_DESCRIPTOR64 Phys = (PPHYSICAL_MEMORY_DESCRIPTOR64)PhysDesc; NumRuns = Phys->NumberOfRuns; NumPages = Phys->NumberOfPages; Run64 = Phys->Run; } else { PPHYSICAL_MEMORY_DESCRIPTOR32 Phys = (PPHYSICAL_MEMORY_DESCRIPTOR32)PhysDesc; NumRuns = Phys->NumberOfRuns; NumPages = Phys->NumberOfPages; Run32 = Phys->Run; } ULONG64 CurrentPagesWritten = 0; ULONG Percent = 0; for (ULONG Run = 0; Run < NumRuns; Run++) { ULONG64 RunPages; ULONG64 Offset; if (Ptr64) { RunPages = Run64[Run].PageCount; Offset = Run64[Run].BasePage * g_Machine->m_PageSize; } else { RunPages = Run32[Run].PageCount; Offset = (ULONG64)Run32[Run].BasePage * g_Machine->m_PageSize; } for (ULONG64 Page = 0; Page < RunPages; Page++) { if (CheckUserInterrupt()) { ErrOut(_T("--Interrupt\n")); Status = HRESULT_FROM_NT(STATUS_CONTROL_C_EXIT); goto Exit; } if ((CurrentPagesWritten * 100) / NumPages == Percent) { dprintf(_T("Percent written %d\n"), Percent); FlushCallbacks(); if (PhysMemFile || (KdTrans && KdTrans->m_DirectPhysicalMemory)) { Percent += 5; } else { Percent += 1; } } ULONG Done; PCTSTR SourceName; if (PhysMemFile) { SourceName = _T("physical memory file"); LONG High = (LONG)(Offset >> 32); ULONG Low = (ULONG)Offset; Low = SetFilePointer(PhysMemFile, Low, &High, FILE_BEGIN); if (Low == INVALID_SET_FILE_POINTER && GetLastError()) { Status = WIN32_LAST_STATUS(); } else if (!ReadFile(PhysMemFile, PageBuffer, g_Machine->m_PageSize, &Done, NULL)) { Status = WIN32_LAST_STATUS(); } else if (Done < g_Machine->m_PageSize) { Status = HRESULT_FROM_WIN32(ERROR_READ_FAULT); } else { Status = S_OK; } } else if (KdTrans && KdTrans->m_DirectPhysicalMemory) { SourceName = _T("transport physical memory"); Status = KdTrans-> ReadTargetPhysicalMemory(Offset, PageBuffer, g_Machine->m_PageSize, &Done); if (Status == S_OK && Done < g_Machine->m_PageSize) { Status = HRESULT_FROM_WIN32(ERROR_READ_FAULT); } } else { SourceName = _T("target physical memory"); Status = g_Target->ReadPhysical(Offset, PageBuffer, g_Machine->m_PageSize, PHYS_FLAG_DEFAULT, &Done); if (Status == S_OK && Done < g_Machine->m_PageSize) { Status = HRESULT_FROM_WIN32(ERROR_READ_FAULT); } } if (Status != S_OK) { ErrOut(_T("Failed reading %s at %I64x, %s\n"), SourceName, Offset, FormatStatusCode(Status)); goto Exit; } if ((Status = DumpWriteFile(File, PageBuffer, g_Machine->m_PageSize)) != S_OK) { goto Exit; } Offset += g_Machine->m_PageSize; CurrentPagesWritten++; } } Status = S_OK; Exit: if (PhysMemFile) { CloseHandle(PhysMemFile); } free(PageBuffer); return Status; } HRESULT KernelFull64DumpTargetInfo::Write(HANDLE File, DUMP_WRITE_ARGS* Args) { HRESULT Status; PDUMP_HEADER64 DumpHeader; ULONG64 Offset; PPHYSICAL_MEMORY_DESCRIPTOR64 PhysMem; ULONG BytesRead; if (!IS_REMOTE_KERNEL_TARGET(g_Target) && !IS_KERNEL_FULL_DUMP(g_Target) && !IS_LOCAL_KERNEL_TARGET(g_Target)) { ErrOut(_T("\nKernel full dumps can only be ") _T("written when all of physical ") _T("memory is accessible - aborting now\n")); return E_INVALIDARG; } DumpHeader = (PDUMP_HEADER64)malloc(sizeof(*DumpHeader)); if (!DumpHeader) { ErrOut(_T("Unable to allocate dump header buffer\n")); return E_OUTOFMEMORY; } dprintf(_T("Full kernel dump\n")); FlushCallbacks(); KernelDumpTargetInfo::InitDumpHeader64(DumpHeader, Args->CommentA, Args->CommentW, 0); DumpHeader->DumpType = DUMP_TYPE_FULL; DumpHeader->WriterStatus = DUMP_DBGENG_SUCCESS; // // Copy the memory descriptor list to our header. // First get the pointer VA. // if ((Status = g_Target-> ReadPointer(g_Process, g_Target->m_Machine, g_Target->m_KdDebuggerData. MmPhysicalMemoryBlock, &Offset)) != S_OK || !Offset) { ErrOut(_T("Unable to read MmPhysicalMemoryBlock\n")); Status = Status != S_OK ? Status : E_INVALIDARG; goto Exit; } // // First read the memory descriptor size. // PhysMem = &DumpHeader->PhysicalMemoryBlock; if ((Status = g_Target-> ReadVirtual(g_Process, Offset, PhysMem, DMP_PHYSICAL_MEMORY_BLOCK_SIZE_64, &BytesRead)) != S_OK || BytesRead < sizeof(*PhysMem) + (sizeof(PhysMem->Run[0]) * (PhysMem->NumberOfRuns - 1))) { ErrOut(_T("Unable to read MmPhysicalMemoryBlock\n")); Status = Status != S_OK ? Status : E_INVALIDARG; goto Exit; } // // Calculate total dump file size. // DumpHeader->RequiredDumpSpace.QuadPart = sizeof(*DumpHeader) + DumpHeader->PhysicalMemoryBlock.NumberOfPages * g_Machine->m_PageSize + DumpGetTaggedDataSize(); // // Write dump header to crash dump file. // if ((Status = DumpWriteFile(File, DumpHeader, sizeof(*DumpHeader))) != S_OK) { goto Exit; } Status = DumpWritePhysicalMemoryToFile(File, Args, PhysMem, TRUE); if (Status == S_OK) { Status = DumpWriteTaggedData(File); } Exit: free(DumpHeader); return Status; } HRESULT KernelFull32DumpTargetInfo::Write(HANDLE File, DUMP_WRITE_ARGS* Args) { HRESULT Status; PDUMP_HEADER32 DumpHeader; ULONG64 Offset; PPHYSICAL_MEMORY_DESCRIPTOR32 PhysMem; ULONG BytesRead; if (!IS_REMOTE_KERNEL_TARGET(g_Target) && !IS_KERNEL_FULL_DUMP(g_Target) && !IS_LOCAL_KERNEL_TARGET(g_Target)) { ErrOut(_T("\nKernel full dumps can only be ") _T("written when all of physical ") _T("memory is accessible - aborting now\n")); return E_INVALIDARG; } DumpHeader = (PDUMP_HEADER32)malloc(sizeof(*DumpHeader)); if (!DumpHeader) { ErrOut(_T("Unable to allocate dump header buffer\n")); return E_OUTOFMEMORY; } dprintf(_T("Full kernel dump\n")); FlushCallbacks(); KernelDumpTargetInfo::InitDumpHeader32(DumpHeader, Args->CommentA, Args->CommentW, 0); DumpHeader->DumpType = DUMP_TYPE_FULL; DumpHeader->WriterStatus = DUMP_DBGENG_SUCCESS; // // Copy the memory descriptor list to our header. // First get the pointer VA. // if ((Status = g_Target-> ReadPointer(g_Process, g_Target->m_Machine, g_Target->m_KdDebuggerData. MmPhysicalMemoryBlock, &Offset)) != S_OK || !Offset) { ErrOut(_T("Unable to read MmPhysicalMemoryBlock\n")); Status = Status != S_OK ? Status : E_INVALIDARG; goto Exit; } // // First read the memory descriptor size. // PhysMem = &DumpHeader->PhysicalMemoryBlock; if ((Status = g_Target-> ReadVirtual(g_Process, Offset, PhysMem, DMP_PHYSICAL_MEMORY_BLOCK_SIZE_32, &BytesRead)) != S_OK || BytesRead < sizeof(*PhysMem) + (sizeof(PhysMem->Run[0]) * (PhysMem->NumberOfRuns - 1))) { ErrOut(_T("Unable to read MmPhysicalMemoryBlock\n")); Status = Status != S_OK ? Status : E_INVALIDARG; goto Exit; } // // Calculate total dump file size. // DumpHeader->RequiredDumpSpace.QuadPart = sizeof(*DumpHeader) + DumpHeader->PhysicalMemoryBlock.NumberOfPages * g_Machine->m_PageSize + DumpGetTaggedDataSize(); // // Write dump header to crash dump file. // if ((Status = DumpWriteFile(File, DumpHeader, sizeof(*DumpHeader))) != S_OK) { goto Exit; } Status = DumpWritePhysicalMemoryToFile(File, Args, PhysMem, FALSE); if (Status == S_OK) { Status = DumpWriteTaggedData(File); } Exit: free(DumpHeader); return Status; } enum { GNME_ENTRY, GNME_DONE, GNME_NO_NAME, GNME_CORRUPT, }; ULONG GetNextModuleEntry(ModuleInfo *ModIter, MODULE_INFO_ENTRY *ModEntry) { HRESULT Status; ModEntry->Reset(); if ((Status = ModIter->GetEntry(ModEntry)) != S_OK) { return Status == S_FALSE ? GNME_DONE : GNME_CORRUPT; } if (ModEntry->NameBytes > (MAX_IMAGE_PATH - 1) * ModEntry->NameCharBytes) { ErrOut(_T("Module list is corrupt.")); if (IS_KERNEL_TARGET(g_Target)) { ErrOut(_T(" Check your kernel symbols.\n")); } else { ErrOut(_T(" Loader list may be invalid\n")); } return GNME_CORRUPT; } // If this entry has no name just skip it. if (!ModEntry->NamePtr || !ModEntry->NameBytes) { ErrOut(_T(" Module List has empty entry in it - skipping\n")); return GNME_NO_NAME; } // If the image header information couldn't be read // we end up with placeholder values for certain entries. // The kernel writes out zeroes in this case so copy // its behavior so that there's one consistent value // for unknown. if (ModEntry->CheckSum == UNKNOWN_CHECKSUM) { ModEntry->CheckSum = 0; } if (ModEntry->TimeDateStamp == UNKNOWN_TIMESTAMP) { ModEntry->TimeDateStamp = 0; } return GNME_ENTRY; } //---------------------------------------------------------------------------- // // Shared triage writing things. // //---------------------------------------------------------------------------- #define ExtractValue(NAME, val) { \ if (!g_Target->m_KdDebuggerData.NAME) { \ val = 0; \ ErrOut(_T("KdDebuggerData.") _T(#NAME) _T(" is NULL\n")); \ } else { \ g_Target->ReadAllVirtual(g_Process, \ g_Target->m_KdDebuggerData.NAME, &(val), \ sizeof(val)); \ } \ } inline ALIGN_8(unsigned Offset) { return (Offset + 7) & 0xfffffff8; } const unsigned MAX_TRIAGE_STACK_SIZE32 = 16 * 1024; const unsigned MAX_TRIAGE_STACK_SIZE64 = 32 * 1024; const unsigned MAX_TRIAGE_BSTORE_SIZE = 16 * 4096; // as defined in ntia64.h const ULONG TRIAGE_DRIVER_NAME_SIZE_GUESS = 0x40; typedef struct _TRIAGE_PTR_DATA_BLOCK { ULONG64 MinAddress; ULONG64 MaxAddress; } TRIAGE_PTR_DATA_BLOCK, *PTRIAGE_PTR_DATA_BLOCK; // A triage dump is sixteen pages long. Some of that is // header information and at least a few other pages will // be used for basic dump information so limit the number // of extra data blocks to something less than sixteen // to save array space. #define IO_MAX_TRIAGE_DUMP_DATA_BLOCKS 64 ULONG IopNumTriageDumpDataBlocks; TRIAGE_PTR_DATA_BLOCK IopTriageDumpDataBlocks[IO_MAX_TRIAGE_DUMP_DATA_BLOCKS]; // // If space is available in a triage dump it's possible // to add "interesting" data pages referenced by runtime // information such as context registers. The following // lists are offsets into the CONTEXT structure of pointers // which usually point to interesting data. They are // in priority order. // #define IOP_LAST_CONTEXT_OFFSET 0xffff USHORT IopRunTimeContextOffsetsX86[] = { FIELD_OFFSET(X86_NT5_CONTEXT, Ebx), FIELD_OFFSET(X86_NT5_CONTEXT, Esi), FIELD_OFFSET(X86_NT5_CONTEXT, Edi), FIELD_OFFSET(X86_NT5_CONTEXT, Ecx), FIELD_OFFSET(X86_NT5_CONTEXT, Edx), FIELD_OFFSET(X86_NT5_CONTEXT, Eax), FIELD_OFFSET(X86_NT5_CONTEXT, Eip), IOP_LAST_CONTEXT_OFFSET }; USHORT IopRunTimeContextOffsetsIa64[] = { FIELD_OFFSET(IA64_CONTEXT, IntS0), FIELD_OFFSET(IA64_CONTEXT, IntS1), FIELD_OFFSET(IA64_CONTEXT, IntS2), FIELD_OFFSET(IA64_CONTEXT, IntS3), FIELD_OFFSET(IA64_CONTEXT, StIIP), IOP_LAST_CONTEXT_OFFSET }; USHORT IopRunTimeContextOffsetsAmd64[] = { FIELD_OFFSET(AMD64_CONTEXT, Rbx), FIELD_OFFSET(AMD64_CONTEXT, Rsi), FIELD_OFFSET(AMD64_CONTEXT, Rdi), FIELD_OFFSET(AMD64_CONTEXT, Rcx), FIELD_OFFSET(AMD64_CONTEXT, Rdx), FIELD_OFFSET(AMD64_CONTEXT, Rax), FIELD_OFFSET(AMD64_CONTEXT, Rip), IOP_LAST_CONTEXT_OFFSET }; USHORT IopRunTimeContextOffsetsEmpty[] = { IOP_LAST_CONTEXT_OFFSET }; BOOLEAN IopIsAddressRangeValid( IN ULONG64 VirtualAddress, IN ULONG Length ) { VirtualAddress = PAGE_ALIGN(g_Machine, VirtualAddress); Length = (Length + g_Machine->m_PageSize - 1) >> g_Machine->m_PageShift; while (Length > 0) { UCHAR Data; if (CurReadAllVirtual(VirtualAddress, &Data, sizeof(Data)) != S_OK) { return FALSE; } VirtualAddress += g_Machine->m_PageSize; Length--; } return TRUE; } BOOLEAN IoAddTriageDumpDataBlock( IN ULONG64 Address, IN ULONG Length ) { ULONG i; PTRIAGE_PTR_DATA_BLOCK Block; ULONG64 MinAddress, MaxAddress; // Check against SIZE32 for both 32 and 64-bit dumps // as no data block needs to be larger than that. if (Length >= TRIAGE_DUMP_SIZE32 || !IopIsAddressRangeValid(Address, Length)) { return FALSE; } MinAddress = Address; MaxAddress = MinAddress + Length; // // Minimize overlap between the new block and existing blocks. // Blocks cannot simply be merged as blocks are inserted in // priority order for storage in the dump. Combining a low-priority // block with a high-priority block could lead to a medium- // priority block being bumped improperly from the dump. // Block = IopTriageDumpDataBlocks; for (i = 0; i < IopNumTriageDumpDataBlocks; i++, Block++) { if (MinAddress >= Block->MaxAddress || MaxAddress <= Block->MinAddress) { // No overlap. continue; } // // Trim overlap out of the new block. If this // would split the new block into pieces don't // trim to keep things simple. Content may then // be duplicated in the dump. // if (MinAddress >= Block->MinAddress) { if (MaxAddress <= Block->MaxAddress) { // New block is completely contained. return TRUE; } // New block extends above the current block // so trim off the low-range overlap. MinAddress = Block->MaxAddress; } else if (MaxAddress <= Block->MaxAddress) { // New block extends below the current block // so trim off the high-range overlap. MaxAddress = Block->MinAddress; } } if (IopNumTriageDumpDataBlocks >= IO_MAX_TRIAGE_DUMP_DATA_BLOCKS) { return FALSE; } Block = IopTriageDumpDataBlocks + IopNumTriageDumpDataBlocks++; Block->MinAddress = MinAddress; Block->MaxAddress = MaxAddress; return TRUE; } VOID IopAddRunTimeTriageDataBlocks( IN PCROSS_PLATFORM_CONTEXT Context, IN ULONG64 StackMin, IN ULONG64 StackMax, IN ULONG64 StoreMin, IN ULONG64 StoreMax ) { PUSHORT ContextOffset; switch(g_Target->m_MachineType) { case IMAGE_FILE_MACHINE_I386: ContextOffset = IopRunTimeContextOffsetsX86; break; case IMAGE_FILE_MACHINE_IA64: ContextOffset = IopRunTimeContextOffsetsIa64; break; case IMAGE_FILE_MACHINE_AMD64: ContextOffset = IopRunTimeContextOffsetsAmd64; break; default: ContextOffset = IopRunTimeContextOffsetsEmpty; break; } while (*ContextOffset < IOP_LAST_CONTEXT_OFFSET) { ULONG64 Ptr; // // Retrieve possible pointers from the context // registers. // if (g_Machine->m_Ptr64) { Ptr = *(PULONG64)((PUCHAR)Context + *ContextOffset); } else { Ptr = EXTEND64(*(PULONG)((PUCHAR)Context + *ContextOffset)); } // Stack and backing store memory is already saved // so ignore any pointers that fall into those ranges. if ((Ptr < StackMin || Ptr >= StackMax) && (Ptr < StoreMin || Ptr >= StoreMax)) { IoAddTriageDumpDataBlock(PAGE_ALIGN(g_Machine, Ptr), g_Machine->m_PageSize); } ContextOffset++; } } void AddInMemoryTriageDataBlocks(void) { // // Look at the global data for nt!IopTriageDumpDataBlocks // and include the same data blocks so that dump conversion // preserves data blocks. // // If we don't know where IopTriageDumpDataBlocks is then // we don't have anything to do. if (!g_Target->m_KdDebuggerData.IopNumTriageDumpDataBlocks || !g_Target->m_KdDebuggerData.IopTriageDumpDataBlocks) { return; } ULONG NumBlocks; if (g_Target-> ReadAllVirtual(g_Process, g_Target-> m_KdDebuggerData.IopNumTriageDumpDataBlocks, &NumBlocks, sizeof(NumBlocks)) != S_OK) { return; } if (NumBlocks > IO_MAX_TRIAGE_DUMP_DATA_BLOCKS) { NumBlocks = IO_MAX_TRIAGE_DUMP_DATA_BLOCKS; } ULONG64 BlockDescOffs = g_Target->m_KdDebuggerData.IopTriageDumpDataBlocks; TRIAGE_PTR_DATA_BLOCK BlockDesc; ULONG i; ULONG PtrSize = g_Machine->m_Ptr64 ? 8 : 4; for (i = 0; i < NumBlocks; i++) { if (g_Target->ReadPointer(g_Process, g_Machine, BlockDescOffs, &BlockDesc.MinAddress) != S_OK || g_Target->ReadPointer(g_Process, g_Machine, BlockDescOffs + PtrSize, &BlockDesc.MaxAddress) != S_OK) { return; } BlockDescOffs += 2 * PtrSize; IoAddTriageDumpDataBlock(BlockDesc.MinAddress, (LONG)(BlockDesc.MaxAddress - BlockDesc.MinAddress)); } } ULONG IopSizeTriageDumpDataBlocks( ULONG Offset, ULONG BufferSize, PULONG StartOffset, PULONG Count ) { ULONG i; ULONG Size; PTRIAGE_PTR_DATA_BLOCK Block; *Count = 0; Block = IopTriageDumpDataBlocks; for (i = 0; i < IopNumTriageDumpDataBlocks; i++, Block++) { Size = ALIGN_8(sizeof(TRIAGE_DATA_BLOCK)) + ALIGN_8((ULONG)(Block->MaxAddress - Block->MinAddress)); if (Offset + Size >= BufferSize) { break; } if (i == 0) { *StartOffset = Offset; } Offset += Size; (*Count)++; } return Offset; } VOID IopWriteTriageDumpDataBlocks( ULONG StartOffset, ULONG Count, PUCHAR BufferAddress ) { ULONG i; PTRIAGE_PTR_DATA_BLOCK Block; PUCHAR DataBuffer; PTRIAGE_DATA_BLOCK DumpBlock; DumpBlock = (PTRIAGE_DATA_BLOCK) (BufferAddress + StartOffset); DataBuffer = (PUCHAR)(DumpBlock + Count); Block = IopTriageDumpDataBlocks; for (i = 0; i < Count; i++, Block++) { DumpBlock->Address = Block->MinAddress; DumpBlock->Offset = (ULONG)(DataBuffer - BufferAddress); DumpBlock->Size = (ULONG)(Block->MaxAddress - Block->MinAddress); CurReadAllVirtual(Block->MinAddress, DataBuffer, DumpBlock->Size); DataBuffer += DumpBlock->Size; DumpBlock++; } } void KernelTriageDumpTargetInfo::WriteDriverList32(PUCHAR DataBase, TRIAGE_DUMP32* TriageHdr) { PDUMP_DRIVER_ENTRY32 pdde; PDUMP_STRING pds; ModuleInfo* ModIter; ULONG MaxEntries = TriageHdr->DriverCount; TriageHdr->DriverCount = 0; if (((ModIter = g_Target->GetModuleInfo(FALSE)) == NULL) || ((ModIter->Initialize(g_Thread, MODULE_INFO_ALL_STD)) != S_OK)) { return; } // pointer to first driver entry to write out pdde = (PDUMP_DRIVER_ENTRY32) (DataBase + TriageHdr->DriverListOffset); // pointer to first module name to write out pds = (PDUMP_STRING) (DataBase + TriageHdr->StringPoolOffset); while ((PUCHAR)(pds + 1) < DataBase + TRIAGE_DUMP_SIZE32 && TriageHdr->DriverCount < MaxEntries) { MODULE_INFO_ENTRY ModEntry; ULONG EntryRet = GetNextModuleEntry(ModIter, &ModEntry); if (EntryRet == GNME_CORRUPT || EntryRet == GNME_DONE) { break; } else if (EntryRet == GNME_NO_NAME) { continue; } pdde->LdrEntry.DllBase = (ULONG)(ULONG_PTR)ModEntry.Base; pdde->LdrEntry.SizeOfImage = ModEntry.SizeOfImage; pdde->LdrEntry.CheckSum = ModEntry.CheckSum; pdde->LdrEntry.TimeDateStamp = ModEntry.TimeDateStamp; if (ModEntry.NameCharBytes == sizeof(WCHAR)) { // Convert length from bytes to characters. pds->Length = ModEntry.NameBytes / sizeof(WCHAR); if ((PUCHAR)pds->Buffer + pds->Length + sizeof(WCHAR) > DataBase + TRIAGE_DUMP_SIZE32) { break; } CopyMemory(pds->Buffer, ModEntry.NamePtr, ModEntry.NameBytes); } else { pds->Length = ModEntry.NameBytes; if ((PUCHAR)pds->Buffer + pds->Length + sizeof(WCHAR) > DataBase + TRIAGE_DUMP_SIZE32) { break; } MultiByteToWideChar(CP_ACP, 0, (PSTR)ModEntry.NamePtr, ModEntry.NameBytes, pds->Buffer, ModEntry.NameBytes); } // null terminate string pds->Buffer[pds->Length] = 0; pdde->DriverNameOffset = (ULONG) ((ULONG_PTR) pds - (ULONG_PTR) DataBase); // get pointer to next string pds = (PDUMP_STRING) (DataBase + ALIGN_8(pdde->DriverNameOffset + sizeof(DUMP_STRING) + sizeof(WCHAR) * (pds->Length + 1))); pdde = (PDUMP_DRIVER_ENTRY32)(((PUCHAR) pdde) + sizeof(*pdde)); TriageHdr->DriverCount++; } TriageHdr->StringPoolSize = (ULONG) ((ULONG_PTR)pds - (ULONG_PTR)(DataBase + TriageHdr->StringPoolOffset)); } PUCHAR KernelTriageDumpTargetInfo::WriteUnloadedDrivers32(PUCHAR Data) { ULONG i; ULONG Index; UNLOADED_DRIVERS32 ud; PDUMP_UNLOADED_DRIVERS32 pdud; ULONG64 pvMiUnloadedDrivers = 0; ULONG ulMiLastUnloadedDriver = 0; PUCHAR CountInData = Data; ULONG Written = 0; *((PULONG) CountInData) = 0; Data += sizeof(ULONG); // // find location of unloaded drivers // if (!g_Target->m_KdDebuggerData.MmUnloadedDrivers || !g_Target->m_KdDebuggerData.MmLastUnloadedDriver) { return Data; } if (g_Target->ReadPointer(g_Process, g_Target->m_Machine, g_Target->m_KdDebuggerData.MmUnloadedDrivers, &pvMiUnloadedDrivers) != S_OK || CurReadAllVirtual(g_Target->m_KdDebuggerData.MmLastUnloadedDriver, &ulMiLastUnloadedDriver, sizeof(ULONG)) != S_OK) { return Data; } if (pvMiUnloadedDrivers == NULL || ulMiLastUnloadedDriver == 0) { return Data; } // point to last unloaded drivers pdud = (PDUMP_UNLOADED_DRIVERS32)Data; // // Write the list with the most recently unloaded driver first to the // least recently unloaded driver last. // Index = ulMiLastUnloadedDriver - 1; for (i = 0; i < MAX_KERNEL_UNLOADED_DRIVERS; i++, Index--) { if (Index >= MAX_KERNEL_UNLOADED_DRIVERS) { Index = MAX_KERNEL_UNLOADED_DRIVERS - 1; } // read in unloaded driver if (CurReadAllVirtual(pvMiUnloadedDrivers + Index * sizeof(UNLOADED_DRIVERS32), &ud, sizeof(ud)) != S_OK) { ErrOut(_T("Can't read memory from %s\n"), FormatAddr64(pvMiUnloadedDrivers + Index * sizeof(UNLOADED_DRIVERS32))); continue; } // copy name lengths pdud->Name.MaximumLength = ud.Name.MaximumLength; pdud->Name.Length = ud.Name.Length; if (ud.Name.Buffer == NULL) { continue; } // copy start and end address pdud->StartAddress = ud.StartAddress; pdud->EndAddress = ud.EndAddress; // restrict name length and maximum name length to 12 characters if (pdud->Name.Length > MAX_UNLOADED_NAME_LENGTH) { pdud->Name.Length = MAX_UNLOADED_NAME_LENGTH; } if (pdud->Name.MaximumLength > MAX_UNLOADED_NAME_LENGTH) { pdud->Name.MaximumLength = MAX_UNLOADED_NAME_LENGTH; } // Can't store pointers in the dump so just zero it. pdud->Name.Buffer = 0; // Read in name. if (CurReadAllVirtual(EXTEND64(ud.Name.Buffer), pdud->DriverName, pdud->Name.MaximumLength) != S_OK) { ErrOut(_T("Can't read memory at address %s\n"), FormatAddr64(ud.Name.Buffer)); continue; } // move to previous driver pdud += 1; Written++; } // number of drivers in the list *((PULONG) CountInData) = Written; return (PUCHAR)pdud; } PUCHAR KernelTriageDumpTargetInfo::WriteMmTriageInformation32(PUCHAR Data) { DUMP_MM_STORAGE32 TriageInformation; ULONG64 pMmVerifierData; ULONG64 pvMmPagedPoolInfo; ULONG cbNonPagedPool; ULONG cbPagedPool; // version information TriageInformation.Version = 1; // size information TriageInformation.Size = sizeof(TriageInformation); // get special pool tag ExtractValue(MmSpecialPoolTag, TriageInformation.MmSpecialPoolTag); // get triage action taken ExtractValue(MmTriageActionTaken, TriageInformation.MiTriageActionTaken); pMmVerifierData = g_Target->m_KdDebuggerData.MmVerifierData; // read in verifier level // BUGBUG - should not read internal data structures in MM //if (pMmVerifierData) // DmpReadMemory( // (ULONG64) &((MM_DRIVER_VERIFIER_DATA *) pMmVerifierData)->Level, // &TriageInformation.MmVerifyDriverLevel, // sizeof(TriageInformation.MmVerifyDriverLevel)); //else TriageInformation.MmVerifyDriverLevel = 0; // read in verifier ExtractValue(KernelVerifier, TriageInformation.KernelVerifier); // read non paged pool info ExtractValue(MmMaximumNonPagedPoolInBytes, cbNonPagedPool); TriageInformation.MmMaximumNonPagedPool = cbNonPagedPool / g_Target->m_Machine->m_PageSize; ExtractValue(MmAllocatedNonPagedPool, TriageInformation.MmAllocatedNonPagedPool); // read paged pool info ExtractValue(MmSizeOfPagedPoolInBytes, cbPagedPool); TriageInformation.PagedPoolMaximum = cbPagedPool / g_Target->m_Machine->m_PageSize; pvMmPagedPoolInfo = g_Target->m_KdDebuggerData.MmPagedPoolInformation; // BUGBUG - should not read internal data structures in MM //if (pvMmPagedPoolInfo) // DmpReadMemory( // (ULONG64) &((MM_PAGED_POOL_INFO *) pvMmPagedPoolInfo)->AllocatedPagedPool, // &TriageInformation.PagedPoolAllocated, // sizeof(TriageInformation.PagedPoolAllocated)); //else TriageInformation.PagedPoolAllocated = 0; // read committed pages info ExtractValue(MmTotalCommittedPages, TriageInformation.CommittedPages); ExtractValue(MmPeakCommitment, TriageInformation.CommittedPagesPeak); ExtractValue(MmTotalCommitLimitMaximum, TriageInformation.CommitLimitMaximum); memcpy(Data, &TriageInformation, sizeof(TriageInformation)); return Data + sizeof(TriageInformation); } void KernelTriageDumpTargetInfo::WriteDriverList64(PUCHAR DataBase, TRIAGE_DUMP64* TriageHdr) { PDUMP_DRIVER_ENTRY64 pdde; PDUMP_STRING pds; ModuleInfo* ModIter; ULONG MaxEntries = TriageHdr->DriverCount; TriageHdr->DriverCount = 0; if (((ModIter = g_Target->GetModuleInfo(FALSE)) == NULL) || ((ModIter->Initialize(g_Thread, MODULE_INFO_ALL_STD)) != S_OK)) { return; } // pointer to first driver entry to write out pdde = (PDUMP_DRIVER_ENTRY64) (DataBase + TriageHdr->DriverListOffset); // pointer to first module name to write out pds = (PDUMP_STRING) (DataBase + TriageHdr->StringPoolOffset); while ((PUCHAR)(pds + 1) < DataBase + TRIAGE_DUMP_SIZE64 && TriageHdr->DriverCount < MaxEntries) { MODULE_INFO_ENTRY ModEntry; ULONG EntryRet = GetNextModuleEntry(ModIter, &ModEntry); if (EntryRet == GNME_CORRUPT || EntryRet == GNME_DONE) { break; } else if (EntryRet == GNME_NO_NAME) { continue; } pdde->LdrEntry.DllBase = ModEntry.Base; pdde->LdrEntry.SizeOfImage = ModEntry.SizeOfImage; pdde->LdrEntry.CheckSum = ModEntry.CheckSum; pdde->LdrEntry.TimeDateStamp = ModEntry.TimeDateStamp; if (ModEntry.NameCharBytes == sizeof(WCHAR)) { // Convert length from bytes to characters. pds->Length = ModEntry.NameBytes / sizeof(WCHAR); if ((PUCHAR)pds->Buffer + pds->Length + sizeof(WCHAR) > DataBase + TRIAGE_DUMP_SIZE64) { break; } CopyMemory(pds->Buffer, ModEntry.NamePtr, ModEntry.NameBytes); } else { pds->Length = ModEntry.NameBytes; if ((PUCHAR)pds->Buffer + pds->Length + sizeof(WCHAR) > DataBase + TRIAGE_DUMP_SIZE64) { break; } MultiByteToWideChar(CP_ACP, 0, (PSTR)ModEntry.NamePtr, ModEntry.NameBytes, pds->Buffer, ModEntry.NameBytes); } // null terminate string pds->Buffer[pds->Length] = 0; pdde->DriverNameOffset = (ULONG) ((ULONG_PTR) pds - (ULONG_PTR) DataBase); // get pointer to next string pds = (PDUMP_STRING) (DataBase + ALIGN_8(pdde->DriverNameOffset + sizeof(DUMP_STRING) + sizeof(WCHAR) * (pds->Length + 1))); pdde = (PDUMP_DRIVER_ENTRY64)(((PUCHAR) pdde) + sizeof(*pdde)); TriageHdr->DriverCount++; } TriageHdr->StringPoolSize = (ULONG) ((ULONG_PTR)pds - (ULONG_PTR)(DataBase + TriageHdr->StringPoolOffset)); } PUCHAR KernelTriageDumpTargetInfo::WriteUnloadedDrivers64(PUCHAR Data) { ULONG i; ULONG Index; UNLOADED_DRIVERS64 ud; PDUMP_UNLOADED_DRIVERS64 pdud; ULONG64 pvMiUnloadedDrivers = 0; ULONG ulMiLastUnloadedDriver = 0; PUCHAR CountInData = Data; ULONG Written = 0; *((PULONG64) CountInData) = 0; Data += sizeof(ULONG64); // // find location of unloaded drivers // if (!g_Target->m_KdDebuggerData.MmUnloadedDrivers || !g_Target->m_KdDebuggerData.MmLastUnloadedDriver) { return Data; } if (g_Target->ReadPointer(g_Process, g_Target->m_Machine, g_Target->m_KdDebuggerData.MmUnloadedDrivers, &pvMiUnloadedDrivers) != S_OK || CurReadAllVirtual(g_Target->m_KdDebuggerData.MmLastUnloadedDriver, &ulMiLastUnloadedDriver, sizeof(ULONG)) != S_OK) { return Data; } if (pvMiUnloadedDrivers == NULL || ulMiLastUnloadedDriver == 0) { return Data; } // point to last unloaded drivers pdud = (PDUMP_UNLOADED_DRIVERS64)Data; // // Write the list with the most recently unloaded driver first to the // least recently unloaded driver last. // Index = ulMiLastUnloadedDriver - 1; for (i = 0; i < MAX_KERNEL_UNLOADED_DRIVERS; i++, Index--) { if (Index >= MAX_KERNEL_UNLOADED_DRIVERS) { Index = MAX_KERNEL_UNLOADED_DRIVERS - 1; } // read in unloaded driver if (CurReadAllVirtual(pvMiUnloadedDrivers + Index * sizeof(UNLOADED_DRIVERS64), &ud, sizeof(ud)) != S_OK) { ErrOut(_T("Can't read memory from %s\n"), FormatAddr64(pvMiUnloadedDrivers + Index * sizeof(UNLOADED_DRIVERS64))); continue; } // copy name lengths pdud->Name.MaximumLength = ud.Name.MaximumLength; pdud->Name.Length = ud.Name.Length; if (ud.Name.Buffer == NULL) { continue; } // copy start and end address pdud->StartAddress = ud.StartAddress; pdud->EndAddress = ud.EndAddress; // restrict name length and maximum name length to 12 characters if (pdud->Name.Length > MAX_UNLOADED_NAME_LENGTH) { pdud->Name.Length = MAX_UNLOADED_NAME_LENGTH; } if (pdud->Name.MaximumLength > MAX_UNLOADED_NAME_LENGTH) { pdud->Name.MaximumLength = MAX_UNLOADED_NAME_LENGTH; } // Can't store pointers in the dump so just zero it. pdud->Name.Buffer = 0; // Read in name. if (CurReadAllVirtual(ud.Name.Buffer, pdud->DriverName, pdud->Name.MaximumLength) != S_OK) { ErrOut(_T("Can't read memory at address %s\n"), FormatAddr64(ud.Name.Buffer)); continue; } // move to previous driver pdud += 1; Written++; } // number of drivers in the list *((PULONG) CountInData) = Written; return (PUCHAR)pdud; } PUCHAR KernelTriageDumpTargetInfo::WriteMmTriageInformation64(PUCHAR Data) { DUMP_MM_STORAGE64 TriageInformation; ULONG64 pMmVerifierData; ULONG64 pvMmPagedPoolInfo; ULONG cbNonPagedPool; ULONG cbPagedPool; // version information TriageInformation.Version = 1; // size information TriageInformation.Size = sizeof(TriageInformation); // get special pool tag ExtractValue(MmSpecialPoolTag, TriageInformation.MmSpecialPoolTag); // get triage action taken ExtractValue(MmTriageActionTaken, TriageInformation.MiTriageActionTaken); pMmVerifierData = g_Target->m_KdDebuggerData.MmVerifierData; // read in verifier level // BUGBUG - should not read internal data structures in MM //if (pMmVerifierData) // DmpReadMemory( // (ULONG64) &((MM_DRIVER_VERIFIER_DATA *) pMmVerifierData)->Level, // &TriageInformation.MmVerifyDriverLevel, // sizeof(TriageInformation.MmVerifyDriverLevel)); //else TriageInformation.MmVerifyDriverLevel = 0; // read in verifier ExtractValue(KernelVerifier, TriageInformation.KernelVerifier); // read non paged pool info ExtractValue(MmMaximumNonPagedPoolInBytes, cbNonPagedPool); TriageInformation.MmMaximumNonPagedPool = cbNonPagedPool / g_Target->m_Machine->m_PageSize; ExtractValue(MmAllocatedNonPagedPool, TriageInformation.MmAllocatedNonPagedPool); // read paged pool info ExtractValue(MmSizeOfPagedPoolInBytes, cbPagedPool); TriageInformation.PagedPoolMaximum = cbPagedPool / g_Target->m_Machine->m_PageSize; pvMmPagedPoolInfo = g_Target->m_KdDebuggerData.MmPagedPoolInformation; // BUGBUG - should not read internal data structures in MM //if (pvMmPagedPoolInfo) // DmpReadMemory( // (ULONG64) &((MM_PAGED_POOL_INFO *) pvMmPagedPoolInfo)->AllocatedPagedPool, // &TriageInformation.PagedPoolAllocated, // sizeof(TriageInformation.PagedPoolAllocated)); //else TriageInformation.PagedPoolAllocated = 0; // read committed pages info ExtractValue(MmTotalCommittedPages, TriageInformation.CommittedPages); ExtractValue(MmPeakCommitment, TriageInformation.CommittedPagesPeak); ExtractValue(MmTotalCommitLimitMaximum, TriageInformation.CommitLimitMaximum); memcpy(Data, &TriageInformation, sizeof(TriageInformation)); return Data + sizeof(TriageInformation); } //---------------------------------------------------------------------------- // // KernelTriage32DumpTargetInfo::Write. // //---------------------------------------------------------------------------- HRESULT KernelTriage32DumpTargetInfo::Write(HANDLE File, DUMP_WRITE_ARGS* Args) { HRESULT Status; PMEMORY_DUMP32 NewHeader; PUCHAR DumpData; ULONG64 ThreadAddr, ProcAddr; BOOL ThreadAddrValid = FALSE; ULONG CodeMod = 0; ULONG BugCheckCode; ULONG64 BugCheckData[4]; ULONG64 SaveDataPage = 0; ULONG64 PrcbAddr; // Set a special marker to indicate there is no pushed context. ContextSave* PushedContext = (ContextSave*)&PushedContext; ULONG i; ULONG Done; if (!IS_KERNEL_TARGET(g_Target)) { ErrOut(_T("kernel minidumps can only be written ") _T("in kernel-mode sessions\n")); return E_UNEXPECTED; } dprintf(_T("mini kernel dump\n")); FlushCallbacks(); NewHeader = (PMEMORY_DUMP32)malloc(TRIAGE_DUMP_SIZE32); if (NewHeader == NULL) { ErrOut(_T("Unable to allocate dump buffer\n")); return E_OUTOFMEMORY; } DumpData = (PUCHAR)NewHeader; // // For some bugchecks the interesting thread is a different // thread than the current thread, so make the following code // generic so it handles any thread. // if ((Status = g_Target-> ReadBugCheckData(&BugCheckCode, BugCheckData)) != S_OK) { ErrOut(_T("Unable to read bugcheck data\n")); goto NewHeader; } if (BugCheckCode == THREAD_STUCK_IN_DEVICE_DRIVER) { CROSS_PLATFORM_CONTEXT Context; // Modify the bugcheck code to indicate this // minidump represents a special state. CodeMod = MINIDUMP_BUGCHECK; // The interesting thread is the first bugcheck parameter. ThreadAddr = BugCheckData[0]; ThreadAddrValid = TRUE; // We need to make the thread's context the current // machine context for the duration of dump generation. if ((Status = g_Target-> GetContextFromThreadStack(ThreadAddr, &Context, FALSE)) != S_OK) { ErrOut(_T("Unable to get context for thread %s\n"), FormatAddr64(ThreadAddr)); goto NewHeader; } PushedContext = g_Machine->PushContext(&Context); } else if (BugCheckCode == SYSTEM_THREAD_EXCEPTION_NOT_HANDLED) { // // System thread stores a context record as the 4th parameter. // use that. // Also save the context record in case someone needs to look // at it. // if (BugCheckData[3]) { CROSS_PLATFORM_CONTEXT TargetContext, Context; if (CurReadAllVirtual(BugCheckData[3], &TargetContext, g_Target->m_TypeInfo. SizeTargetContext) == S_OK && g_Machine-> ConvertTargetContextToCanon(g_Target-> m_TypeInfo.SizeTargetContext, &TargetContext, &Context) == S_OK) { CodeMod = MINIDUMP_BUGCHECK; PushedContext = g_Machine->PushContext(&Context); SaveDataPage = BugCheckData[3]; } } } else if (BugCheckCode == KERNEL_MODE_EXCEPTION_NOT_HANDLED) { CROSS_PLATFORM_CONTEXT Context; // // 3rd parameter is a trap frame. // // Build a context record out of that only if it's a kernel mode // failure because esp may be wrong in that case ???. // if (BugCheckData[2] && g_Machine->GetContextFromTrapFrame(BugCheckData[2], &Context, FALSE) == S_OK) { CodeMod = MINIDUMP_BUGCHECK; PushedContext = g_Machine->PushContext(&Context); SaveDataPage = BugCheckData[2]; } } else if (BugCheckCode == UNEXPECTED_KERNEL_MODE_TRAP) { CROSS_PLATFORM_CONTEXT Context; // // Double fault // // The thread is correct in this case. // Second parameter is the TSS. If we have a TSS, convert // the context and mark the bugcheck as converted. // if (BugCheckData[0] == 8 && BugCheckData[1] && g_Machine->GetContextFromTaskSegment(BugCheckData[1], &Context, FALSE) == S_OK) { CodeMod = MINIDUMP_BUGCHECK; PushedContext = g_Machine->PushContext(&Context); } } if (!ThreadAddrValid && (Status = g_Process-> GetImplicitThreadData(g_Thread, &ThreadAddr)) != S_OK) { ErrOut(_T("Unable to get current thread\n")); goto NewHeader; } if (PushedContext == (ContextSave*)&PushedContext) { // If an explicit context wasn't pushed we need // to make sure that the dump thread's context // is active as it may not be the current thread. if ((Status = GetRegSourceContext(GetDefRegSource(), TRUE, NULL, &PushedContext)) != S_OK) { goto NewHeader; } // If there wasn't a push it gets NULLed, so // translate that to our special no-push marker. if (!PushedContext) { PushedContext = (ContextSave*)&PushedContext; } } // // Set up the main header. // KernelDumpTargetInfo::InitDumpHeader32(&NewHeader->Header, Args->CommentA, Args->CommentW, CodeMod); NewHeader->Header.DumpType = DUMP_TYPE_TRIAGE; NewHeader->Header.MiniDumpFields = TRIAGE_DUMP_BASIC_INFO; NewHeader->Header.WriterStatus = DUMP_DBGENG_SUCCESS; // // Triage dump header begins after dump header. // TRIAGE_DUMP32 *TriageHdr = &NewHeader->Triage; ZeroMemory(TriageHdr, sizeof(*TriageHdr)); TriageHdr->ServicePackBuild = g_Target->m_ServicePackNumber; TriageHdr->SizeOfDump = TRIAGE_DUMP_SIZE32; TriageHdr->ContextOffset = FIELD_OFFSET(DUMP_HEADER32, ContextRecord); TriageHdr->ExceptionOffset = FIELD_OFFSET(DUMP_HEADER32, Exception); // // Starting offset in triage dump follows the triage dump header. // unsigned Offset = ALIGN_8(sizeof(DUMP_HEADER32) + sizeof(TRIAGE_DUMP32)); // // Write MM information for Win2K and above only. // if (g_Target->m_SystemVersion >= NT_SVER_W2K) { TriageHdr->MmOffset = Offset; Offset = ALIGN_8((ULONG)(WriteMmTriageInformation32(DumpData + Offset) - DumpData)); } // // Write unloaded drivers. // TriageHdr->UnloadedDriversOffset = Offset; Offset = ALIGN_8((ULONG)(WriteUnloadedDrivers32(DumpData + Offset) - DumpData)); // // Write processor control block (KPRCB). // if ((Status = g_Target-> GetProcessorSystemDataOffset(CURRENT_PROC, DEBUG_DATA_KPRCB_OFFSET, &PrcbAddr)) != S_OK || (Status = CurReadAllVirtual(PrcbAddr, DumpData + Offset, g_Target-> m_KdDebuggerData.SizePrcb)) != S_OK) { ErrOut(_T("Unable to get current PRCB\n")); goto NewHeader; } TriageHdr->PrcbOffset = Offset; Offset += ALIGN_8(g_Target->m_KdDebuggerData.SizePrcb); // // Write the thread and process data structures. // if (g_Target->ReadPointer(g_Process, g_Machine, ThreadAddr + g_Target-> m_KdDebuggerData.OffsetKThreadApcProcess, &ProcAddr) == S_OK && CurReadAllVirtual(ProcAddr, DumpData + Offset, g_Target->m_KdDebuggerData.SizeEProcess) == S_OK) { TriageHdr->ProcessOffset = Offset; Offset += ALIGN_8(g_Target->m_KdDebuggerData.SizeEProcess); } else { ProcAddr = 0; } if (CurReadAllVirtual(ThreadAddr, DumpData + Offset, g_Target->m_KdDebuggerData.SizeEThread) == S_OK) { TriageHdr->ThreadOffset = Offset; Offset += ALIGN_8(g_Target->m_KdDebuggerData.SizeEThread); } // // Write the call stack. // ADDR StackPtr; ULONG64 StackBase; BOOL MustReadAll = TRUE; g_Machine->GetSP(&StackPtr); TriageHdr->TopOfStack = (ULONG)(ULONG_PTR)Flat(StackPtr); if (g_Target-> ReadPointer(g_Process, g_Machine, g_Target->m_KdDebuggerData.OffsetKThreadInitialStack + ThreadAddr, &StackBase) != S_OK) { StackBase = Flat(StackPtr) + MAX_TRIAGE_STACK_SIZE32; MustReadAll = FALSE; } // // There may have been a stack switch (DPCs, double-faults, etc.) // so the current stack pointer may not be within the // stack bounds. If that's the case just try and read // as much of the stack as possible. // if (StackBase <= Flat(StackPtr) || Flat(StackPtr) < StackBase - 16 * g_Machine->m_PageSize) { TriageHdr->SizeOfCallStack = MAX_TRIAGE_STACK_SIZE32; MustReadAll = FALSE; } else { TriageHdr->SizeOfCallStack = min((ULONG)(ULONG_PTR)(StackBase - Flat(StackPtr)), MAX_TRIAGE_STACK_SIZE32); } if (TriageHdr->SizeOfCallStack) { if ((Status = CurReadVirtual(EXTEND64(TriageHdr->TopOfStack), DumpData + Offset, TriageHdr->SizeOfCallStack, &Done)) != S_OK) { ErrOut(_T("Unable to read thread stack at %s\n"), FormatAddr64(TriageHdr->TopOfStack)); goto NewHeader; } if (MustReadAll && Done < TriageHdr->SizeOfCallStack) { ErrOut(_T("Unable to read full thread stack at %s\n"), FormatAddr64(TriageHdr->TopOfStack)); Status = HRESULT_FROM_WIN32(ERROR_READ_FAULT); goto NewHeader; } TriageHdr->CallStackOffset = Offset; TriageHdr->SizeOfCallStack = Done; Offset += ALIGN_8(TriageHdr->SizeOfCallStack); } // // Write debugger data. // if (g_Target->m_SystemVersion >= NT_SVER_XP && g_Target->m_KdDebuggerDataOffset && (!IS_KERNEL_TRIAGE_DUMP(g_Target) || ((KernelTriageDumpTargetInfo*)g_Target)->m_HasDebuggerData) && Offset + ALIGN_8(sizeof(g_Target->m_KdDebuggerData)) < TRIAGE_DUMP_SIZE32) { NewHeader->Header.MiniDumpFields |= TRIAGE_DUMP_DEBUGGER_DATA; TriageHdr->DebuggerDataSize = sizeof(g_Target->m_KdDebuggerData); memcpy(DumpData + Offset, &g_Target->m_KdDebuggerData, sizeof(g_Target->m_KdDebuggerData)); TriageHdr->DebuggerDataOffset = Offset; Offset += ALIGN_8(sizeof(g_Target->m_KdDebuggerData)); } // // Write loaded driver list. // ModuleInfo* ModIter; ULONG MaxEntries; // Use a heuristic to guess how many entries we // can pack into the remaining space. MaxEntries = (TRIAGE_DUMP_SIZE32 - Offset) / (sizeof(DUMP_DRIVER_ENTRY32) + TRIAGE_DRIVER_NAME_SIZE_GUESS); TriageHdr->DriverCount = 0; if (ModIter = g_Target->GetModuleInfo(FALSE)) { if ((Status = ModIter-> Initialize(g_Thread, MODULE_INFO_ALL_STD)) == S_OK) { while (TriageHdr->DriverCount < MaxEntries) { MODULE_INFO_ENTRY ModEntry; ULONG EntryRet = GetNextModuleEntry(ModIter, &ModEntry); if (EntryRet == GNME_CORRUPT || EntryRet == GNME_DONE) { if (EntryRet == GNME_CORRUPT) { NewHeader->Header.WriterStatus = DUMP_DBGENG_CORRUPT_MODULE_LIST; } break; } else if (EntryRet == GNME_NO_NAME) { continue; } TriageHdr->DriverCount++; } } else { NewHeader->Header.WriterStatus = DUMP_DBGENG_NO_MODULE_LIST; } } TriageHdr->DriverListOffset = Offset; Offset += ALIGN_8(TriageHdr->DriverCount * sizeof(DUMP_DRIVER_ENTRY32)); TriageHdr->StringPoolOffset = Offset; TriageHdr->BrokenDriverOffset = 0; WriteDriverList32(DumpData, TriageHdr); Offset = TriageHdr->StringPoolOffset + TriageHdr->StringPoolSize; Offset = ALIGN_8(Offset); // // For XP and above add in any additional data pages and write out // whatever fits. // if (g_Target->m_SystemVersion >= NT_SVER_XP) { if (SaveDataPage) { IoAddTriageDumpDataBlock(PAGE_ALIGN(g_Machine, SaveDataPage), g_Machine->m_PageSize); } // If there are other interesting data pages, such as // alternate stacks for DPCs and such, pick them up. if (PrcbAddr) { ADDR_RANGE AltData[MAX_ALT_ADDR_RANGES]; ZeroMemory(AltData, sizeof(AltData)); if (g_Machine->GetAlternateTriageDumpDataRanges(PrcbAddr, ThreadAddr, AltData) == S_OK) { for (i = 0; i < MAX_ALT_ADDR_RANGES; i++) { if (AltData[i].Base) { IoAddTriageDumpDataBlock(AltData[i].Base, AltData[i].Size); } } } } // Add any data blocks that were registered // in the debuggee. AddInMemoryTriageDataBlocks(); // Add data blocks which might be referred to by // the context or other runtime state. IopAddRunTimeTriageDataBlocks(&g_Machine->m_Context, EXTEND64(TriageHdr->TopOfStack), EXTEND64(TriageHdr->TopOfStack + TriageHdr->SizeOfCallStack), 0, 0); // Check which data blocks fit and write them. Offset = IopSizeTriageDumpDataBlocks(Offset, TRIAGE_DUMP_SIZE32, &TriageHdr->DataBlocksOffset, &TriageHdr->DataBlocksCount); Offset = ALIGN_8(Offset); if (TriageHdr->DataBlocksCount) { NewHeader->Header.MiniDumpFields |= TRIAGE_DUMP_DATA_BLOCKS; IopWriteTriageDumpDataBlocks(TriageHdr->DataBlocksOffset, TriageHdr->DataBlocksCount, DumpData); } } // // All options are enabled. // TriageHdr->TriageOptions = 0xffffffff; // // End of triage dump validated. // TriageHdr->ValidOffset = TRIAGE_DUMP_SIZE32 - sizeof(ULONG); *(PULONG)(DumpData + TriageHdr->ValidOffset) = TRIAGE_DUMP_VALID; // // Write it out to the file. // if ((Status = DumpWriteFile(File, DumpData, TRIAGE_DUMP_SIZE32)) == S_OK) { Status = DumpWriteTaggedData(File); } NewHeader: if (PushedContext != (ContextSave*)&PushedContext) { g_Machine->PopContext(PushedContext); } free(NewHeader); return Status; } //---------------------------------------------------------------------------- // // KernelTriage64DumpTargetInfo::Write. // //---------------------------------------------------------------------------- HRESULT KernelTriage64DumpTargetInfo::Write(HANDLE File, DUMP_WRITE_ARGS* Args) { HRESULT Status; PMEMORY_DUMP64 NewHeader; PUCHAR DumpData; ULONG64 ThreadAddr, ProcAddr; BOOL ThreadAddrValid = FALSE; ULONG CodeMod = 0; ULONG BugCheckCode; ULONG64 BugCheckData[4]; ULONG64 SaveDataPage = 0; ULONG64 BStoreBase = 0; ULONG BStoreSize = 0; ULONG64 BStoreLimit; ULONG64 PrcbAddr; // Set a special marker to indicate there is no pushed context. ContextSave* PushedContext = (ContextSave*)&PushedContext; ULONG i; ULONG Done; if (!IS_KERNEL_TARGET(g_Target)) { ErrOut(_T("kernel minidumps can only be written ") _T("in kernel-mode sessions\n")); return E_UNEXPECTED; } dprintf(_T("mini kernel dump\n")); FlushCallbacks(); NewHeader = (PMEMORY_DUMP64)malloc(TRIAGE_DUMP_SIZE64); if (NewHeader == NULL) { ErrOut(_T("Unable to allocate dump buffer\n")); return E_OUTOFMEMORY; } DumpData = (PUCHAR)NewHeader; // // For some bugchecks the interesting thread is a different // thread than the current thread, so make the following code // generic so it handles any thread. // if ((Status = g_Target-> ReadBugCheckData(&BugCheckCode, BugCheckData)) != S_OK) { ErrOut(_T("Unable to read bugcheck data\n")); goto NewHeader; } if (BugCheckCode == THREAD_STUCK_IN_DEVICE_DRIVER) { CROSS_PLATFORM_CONTEXT Context; // Modify the bugcheck code to indicate this // minidump represents a special state. CodeMod = MINIDUMP_BUGCHECK; // The interesting thread is the first bugcheck parameter. ThreadAddr = BugCheckData[0]; ThreadAddrValid = TRUE; // We need to make the thread's context the current // machine context for the duration of dump generation. if ((Status = g_Target-> GetContextFromThreadStack(ThreadAddr, &Context, FALSE)) != S_OK) { ErrOut(_T("Unable to get context for thread %s\n"), FormatAddr64(ThreadAddr)); goto NewHeader; } PushedContext = g_Machine->PushContext(&Context); } else if (BugCheckCode == SYSTEM_THREAD_EXCEPTION_NOT_HANDLED) { // // System thread stores a context record as the 4th parameter. // use that. // Also save the context record in case someone needs to look // at it. // if (BugCheckData[3]) { CROSS_PLATFORM_CONTEXT TargetContext, Context; if (CurReadAllVirtual(BugCheckData[3], &TargetContext, g_Target-> m_TypeInfo.SizeTargetContext) == S_OK && g_Machine-> ConvertTargetContextToCanon(g_Target-> m_TypeInfo.SizeTargetContext, &TargetContext, &Context) == S_OK) { CodeMod = MINIDUMP_BUGCHECK; PushedContext = g_Machine->PushContext(&Context); SaveDataPage = BugCheckData[3]; } } } else if (BugCheckCode == KERNEL_MODE_EXCEPTION_NOT_HANDLED) { CROSS_PLATFORM_CONTEXT Context; // // 3rd parameter is a trap frame. // // Build a context record out of that only if it's a kernel mode // failure because esp may be wrong in that case ???. // if (BugCheckData[2] && g_Machine->GetContextFromTrapFrame(BugCheckData[2], &Context, FALSE) == S_OK) { CodeMod = MINIDUMP_BUGCHECK; PushedContext = g_Machine->PushContext(&Context); SaveDataPage = BugCheckData[2]; } } if (!ThreadAddrValid && (Status = g_Process-> GetImplicitThreadData(g_Thread, &ThreadAddr)) != S_OK) { ErrOut(_T("Unable to get current thread\n")); goto NewHeader; } if (PushedContext == (ContextSave*)&PushedContext) { // If an explicit context wasn't pushed we need // to make sure that the dump thread's context // is active as it may not be the current thread. if ((Status = GetRegSourceContext(GetDefRegSource(), TRUE, NULL, &PushedContext)) != S_OK) { goto NewHeader; } // If there wasn't a push it gets NULLed, so // translate that to our special no-push marker. if (!PushedContext) { PushedContext = (ContextSave*)&PushedContext; } } // // Set up the main header. // KernelDumpTargetInfo::InitDumpHeader64(&NewHeader->Header, Args->CommentA, Args->CommentW, CodeMod); NewHeader->Header.DumpType = DUMP_TYPE_TRIAGE; NewHeader->Header.MiniDumpFields = TRIAGE_DUMP_BASIC_INFO; NewHeader->Header.WriterStatus = DUMP_DBGENG_SUCCESS; // // Triage dump header begins after dump header. // TRIAGE_DUMP64 *TriageHdr = &NewHeader->Triage; ZeroMemory(TriageHdr, sizeof(*TriageHdr)); TriageHdr->ServicePackBuild = g_Target->m_ServicePackNumber; TriageHdr->SizeOfDump = TRIAGE_DUMP_SIZE64; TriageHdr->ContextOffset = FIELD_OFFSET(DUMP_HEADER64, ContextRecord); TriageHdr->ExceptionOffset = FIELD_OFFSET(DUMP_HEADER64, Exception); // // Starting offset in triage dump follows the triage dump header. // unsigned Offset = ALIGN_8(sizeof(DUMP_HEADER64) + sizeof(TRIAGE_DUMP64)); // // Write MM information. // TriageHdr->MmOffset = Offset; Offset = ALIGN_8((ULONG)(WriteMmTriageInformation64(DumpData + Offset) - DumpData)); // // Write unloaded drivers. // TriageHdr->UnloadedDriversOffset = Offset; Offset = ALIGN_8((ULONG)(WriteUnloadedDrivers64(DumpData + Offset) - DumpData)); // // Write processor control block (KPRCB). // if ((Status = g_Target-> GetProcessorSystemDataOffset(CURRENT_PROC, DEBUG_DATA_KPRCB_OFFSET, &PrcbAddr)) != S_OK || (Status = CurReadAllVirtual(PrcbAddr, DumpData + Offset, g_Target-> m_KdDebuggerData.SizePrcb)) != S_OK) { ErrOut(_T("Unable to get current PRCB\n")); goto NewHeader; } TriageHdr->PrcbOffset = Offset; Offset += ALIGN_8(g_Target->m_KdDebuggerData.SizePrcb); // // Write the thread and process data structures. // if (g_Target->ReadPointer(g_Process, g_Machine, ThreadAddr + g_Target-> m_KdDebuggerData.OffsetKThreadApcProcess, &ProcAddr) == S_OK && CurReadAllVirtual(ProcAddr, DumpData + Offset, g_Target->m_KdDebuggerData.SizeEProcess) == S_OK) { TriageHdr->ProcessOffset = Offset; Offset += ALIGN_8(g_Target->m_KdDebuggerData.SizeEProcess); } else { ProcAddr = 0; } if (CurReadAllVirtual(ThreadAddr, DumpData + Offset, g_Target->m_KdDebuggerData.SizeEThread) == S_OK) { TriageHdr->ThreadOffset = Offset; Offset += ALIGN_8(g_Target->m_KdDebuggerData.SizeEThread); } // // Write the call stack. // ADDR StackPtr; ULONG64 StackBase; BOOL MustReadAll = TRUE; g_Machine->GetSP(&StackPtr); TriageHdr->TopOfStack = Flat(StackPtr); if (g_Target-> ReadPointer(g_Process, g_Machine, g_Target->m_KdDebuggerData.OffsetKThreadInitialStack + ThreadAddr, &StackBase) != S_OK) { StackBase = Flat(StackPtr) + MAX_TRIAGE_STACK_SIZE64; MustReadAll = FALSE; } // // There may have been a stack switch (DPCs, double-faults, etc.) // so the current stack pointer may not be within the // stack bounds. If that's the case just try and read // as much of the stack as possible. // if (StackBase <= Flat(StackPtr) || Flat(StackPtr) < StackBase - 16 * g_Machine->m_PageSize) { TriageHdr->SizeOfCallStack = MAX_TRIAGE_STACK_SIZE64; MustReadAll = FALSE; } else { TriageHdr->SizeOfCallStack = min((ULONG)(ULONG_PTR)(StackBase - Flat(StackPtr)), MAX_TRIAGE_STACK_SIZE64); } if (TriageHdr->SizeOfCallStack) { if ((Status = CurReadVirtual(TriageHdr->TopOfStack, DumpData + Offset, TriageHdr->SizeOfCallStack, &Done)) != S_OK) { ErrOut(_T("Unable to read thread stack at %s\n"), FormatAddr64(TriageHdr->TopOfStack)); goto NewHeader; } if (MustReadAll && Done < TriageHdr->SizeOfCallStack) { ErrOut(_T("Unable to read full thread stack at %s\n"), FormatAddr64(TriageHdr->TopOfStack)); Status = HRESULT_FROM_WIN32(ERROR_READ_FAULT); goto NewHeader; } TriageHdr->CallStackOffset = Offset; TriageHdr->SizeOfCallStack = Done; Offset += ALIGN_8(TriageHdr->SizeOfCallStack); } // // The IA64 contains two call stacks. The first is the normal // call stack, and the second is a scratch region where // the processor can spill registers. It is this latter stack, // the backing-store, that we now save. // if (g_Target->m_MachineType == IMAGE_FILE_MACHINE_IA64 && g_Target->ReadPointer(g_Process, g_Machine, ThreadAddr + g_Target->m_KdDebuggerData.OffsetKThreadBStore, &BStoreBase) == S_OK && g_Target->ReadPointer(g_Process, g_Machine, ThreadAddr + g_Target-> m_KdDebuggerData.OffsetKThreadBStoreLimit, &BStoreLimit) == S_OK) { BStoreSize = min((ULONG)(BStoreLimit - BStoreBase), MAX_TRIAGE_BSTORE_SIZE); if (BStoreSize && CurReadAllVirtual(BStoreBase, DumpData + Offset, BStoreSize) == S_OK) { TriageHdr->ArchitectureSpecific.Ia64.BStoreOffset = Offset; TriageHdr->ArchitectureSpecific.Ia64.LimitOfBStore = BStoreLimit; TriageHdr->ArchitectureSpecific.Ia64.SizeOfBStore = BStoreSize; Offset += ALIGN_8(BStoreSize); } } // // Write debugger data. // if (g_Target->m_SystemVersion >= NT_SVER_XP && g_Target->m_KdDebuggerDataOffset && (!IS_KERNEL_TRIAGE_DUMP(g_Target) || ((KernelTriageDumpTargetInfo*)g_Target)->m_HasDebuggerData) && Offset + ALIGN_8(sizeof(g_Target->m_KdDebuggerData)) < TRIAGE_DUMP_SIZE64) { NewHeader->Header.MiniDumpFields |= TRIAGE_DUMP_DEBUGGER_DATA; TriageHdr->DebuggerDataSize = sizeof(g_Target->m_KdDebuggerData); memcpy(DumpData + Offset, &g_Target->m_KdDebuggerData, sizeof(g_Target->m_KdDebuggerData)); TriageHdr->DebuggerDataOffset = Offset; Offset += ALIGN_8(sizeof(g_Target->m_KdDebuggerData)); } // // Write loaded driver list. // ModuleInfo* ModIter; ULONG MaxEntries; // Use a heuristic to guess how many entries we // can pack into the remaining space. MaxEntries = (TRIAGE_DUMP_SIZE64 - Offset) / (sizeof(DUMP_DRIVER_ENTRY64) + TRIAGE_DRIVER_NAME_SIZE_GUESS); TriageHdr->DriverCount = 0; if (ModIter = g_Target->GetModuleInfo(FALSE)) { if ((Status = ModIter-> Initialize(g_Thread, MODULE_INFO_ALL_STD)) == S_OK) { while (TriageHdr->DriverCount < MaxEntries) { MODULE_INFO_ENTRY ModEntry; ULONG EntryRet = GetNextModuleEntry(ModIter, &ModEntry); if (EntryRet == GNME_CORRUPT || EntryRet == GNME_DONE) { if (EntryRet == GNME_CORRUPT) { NewHeader->Header.WriterStatus = DUMP_DBGENG_CORRUPT_MODULE_LIST; } break; } else if (EntryRet == GNME_NO_NAME) { continue; } TriageHdr->DriverCount++; } } else { NewHeader->Header.WriterStatus = DUMP_DBGENG_NO_MODULE_LIST; } } TriageHdr->DriverListOffset = Offset; Offset += ALIGN_8(TriageHdr->DriverCount * sizeof(DUMP_DRIVER_ENTRY64)); TriageHdr->StringPoolOffset = Offset; TriageHdr->BrokenDriverOffset = 0; WriteDriverList64(DumpData, TriageHdr); Offset = TriageHdr->StringPoolOffset + TriageHdr->StringPoolSize; Offset = ALIGN_8(Offset); // // For XP and above add in any additional data pages and write out // whatever fits. // if (g_Target->m_SystemVersion >= NT_SVER_XP) { if (SaveDataPage) { IoAddTriageDumpDataBlock(PAGE_ALIGN(g_Machine, SaveDataPage), g_Machine->m_PageSize); } // If there are other interesting data pages, such as // alternate stacks for DPCs and such, pick them up. if (PrcbAddr) { ADDR_RANGE AltData[MAX_ALT_ADDR_RANGES]; ZeroMemory(AltData, sizeof(AltData)); if (g_Machine->GetAlternateTriageDumpDataRanges(PrcbAddr, ThreadAddr, AltData) == S_OK) { for (i = 0; i < MAX_ALT_ADDR_RANGES; i++) { if (AltData[i].Base) { IoAddTriageDumpDataBlock(AltData[i].Base, AltData[i].Size); } } } } // Add any data blocks that were registered // in the debuggee. AddInMemoryTriageDataBlocks(); // Add data blocks which might be referred to by // the context or other runtime state. IopAddRunTimeTriageDataBlocks(&g_Machine->m_Context, TriageHdr->TopOfStack, TriageHdr->TopOfStack + TriageHdr->SizeOfCallStack, BStoreBase, BStoreSize); // Check which data blocks fit and write them. Offset = IopSizeTriageDumpDataBlocks(Offset, TRIAGE_DUMP_SIZE64, &TriageHdr->DataBlocksOffset, &TriageHdr->DataBlocksCount); Offset = ALIGN_8(Offset); if (TriageHdr->DataBlocksCount) { NewHeader->Header.MiniDumpFields |= TRIAGE_DUMP_DATA_BLOCKS; IopWriteTriageDumpDataBlocks(TriageHdr->DataBlocksOffset, TriageHdr->DataBlocksCount, (PUCHAR)NewHeader); } } // // All options are enabled. // TriageHdr->TriageOptions = 0xffffffff; // // End of triage dump validated. // TriageHdr->ValidOffset = TRIAGE_DUMP_SIZE64 - sizeof(ULONG); *(PULONG)(DumpData + TriageHdr->ValidOffset) = TRIAGE_DUMP_VALID; // // Write it out to the file. // if ((Status = DumpWriteFile(File, DumpData, TRIAGE_DUMP_SIZE64)) == S_OK) { Status = DumpWriteTaggedData(File); } NewHeader: if (PushedContext != (ContextSave*)&PushedContext) { g_Machine->PopContext(PushedContext); } free(NewHeader); return Status; } //---------------------------------------------------------------------------- // // Functions. // //---------------------------------------------------------------------------- HRESULT WriteDumpFile(PCWSTR FileName, ULONG64 FileHandle, DUMP_WRITE_ARGS* Args) { ULONG DumpType = DTYPE_COUNT; DumpTargetInfo* WriteTarget; HRESULT Status; ULONG OldMachine; WCHAR TempFile[2 * MAX_PATH]; PCWSTR DumpWriteFile; HANDLE DumpWriteHandle; if (!IS_CUR_MACHINE_ACCESSIBLE()) { return E_UNEXPECTED; } if (IS_KERNEL_TARGET(g_Target)) { DbgKdTransport* KdTrans; if (Args->FormatFlags & ~GENERIC_FORMATS) { return E_INVALIDARG; } // // not much we can do without the processor block // or at least the PRCB for the current process in a minidump. // if (!g_Target->m_KdDebuggerData.KiProcessorBlock && IS_DUMP_TARGET(g_Target) && !((KernelDumpTargetInfo*)g_Target)->m_KiProcessors[CURRENT_PROC]) { ErrOut(_T("Cannot find KiProcessorBlock - ") _T("can not create dump file\n")); return E_FAIL; } if (IS_CONN_KERNEL_TARGET(g_Target)) { KdTrans = ((ConnLiveKernelTargetInfo*)g_Target)->m_Transport; } else { KdTrans = NULL; } switch(Args->Qualifier) { case DEBUG_KERNEL_SMALL_DUMP: DumpType = g_Target->m_Machine->m_Ptr64 ? DTYPE_KERNEL_TRIAGE64 : DTYPE_KERNEL_TRIAGE32; break; case DEBUG_KERNEL_FULL_DUMP: if (KdTrans != NULL && KdTrans->m_DirectPhysicalMemory == FALSE) { WarnOut(_T("Creating a full kernel dump over the COM port is ") _T("a VERY VERY slow operation.\n") _T("This command may take many HOURS to complete. ") _T("Ctrl-C if you want to terminate the command.\n")); } DumpType = g_Target->m_Machine->m_Ptr64 ? DTYPE_KERNEL_FULL64 : DTYPE_KERNEL_FULL32; break; default: // Other formats are not supported. return E_INVALIDARG; } } else { DBG_ASSERT(IS_USER_TARGET(g_Target)); switch(Args->Qualifier) { case DEBUG_USER_WINDOWS_SMALL_DUMP: if (Args->FormatFlags & ~(GENERIC_FORMATS | UMINI_FORMATS)) { return E_INVALIDARG; } DumpType = (Args->FormatFlags & DEBUG_FORMAT_USER_SMALL_FULL_MEMORY) ? DTYPE_USER_MINI_FULL : DTYPE_USER_MINI_PARTIAL; break; case DEBUG_USER_WINDOWS_DUMP: if (Args->FormatFlags & ~GENERIC_FORMATS) { return E_INVALIDARG; } DumpType = g_Target->m_Machine->m_Ptr64 ? DTYPE_USER_FULL64 : DTYPE_USER_FULL32; break; default: // Other formats are not supported. return E_INVALIDARG; } } WriteTarget = NewDumpTargetInfo(DumpType); if (WriteTarget == NULL) { ErrOut(_T("Unable to create dump write target\n")); return E_OUTOFMEMORY; } // Ensure that the dump is always written according to the // target machine type and not any emulated machine. OldMachine = g_Target->m_EffMachineType; g_Target->SetEffMachine(g_Target->m_MachineType, FALSE); // Flush context first so that the minidump reads the // same register values the debugger has. g_Target->FlushRegContext(); // // If we're producing a CAB put the dump in a temp file. // if (Args->FormatFlags & DEBUG_FORMAT_WRITE_CAB) { if (FileHandle) { Status = E_INVALIDARG; goto Exit; } if (!GetTempPathW(DIMA(TempFile), TempFile)) { wcscpy(TempFile, L".\\"); } // Use the CAB name as the dump file name so the // name in the CAB will match. CatStringW(TempFile, PathTailW(FileName), DIMA(TempFile)); CatStringW(TempFile, L".dmp", DIMA(TempFile)); DumpWriteFile = TempFile; Args->FormatFlags &= ~DEBUG_FORMAT_NO_OVERWRITE; } else { DumpWriteFile = FileName; if (!DumpWriteFile) { DumpWriteFile = L""; } } if (FileHandle) { DumpWriteHandle = OS_HANDLE(FileHandle); if (!DumpWriteHandle || DumpWriteHandle == INVALID_HANDLE_VALUE) { Status = E_INVALIDARG; } else { Status = S_OK; } } else { if (g_SymOptions & SYMOPT_SECURE) { ErrOut(_T("SECURE: Dump writing disallowed\n")); return E_ACCESSDENIED; } // Dumps are almost always written sequentially so // add that hint to the file flags. DumpWriteHandle = WideCreateFile(DumpWriteFile, GENERIC_READ | GENERIC_WRITE, 0, NULL, (Args->FormatFlags & DEBUG_FORMAT_NO_OVERWRITE) ? CREATE_NEW : CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL); if (!DumpWriteHandle || DumpWriteHandle == INVALID_HANDLE_VALUE) { Status = WIN32_LAST_STATUS(); ErrOut(_T("Unable to create file '%ws' - %s\n \"%s\"\n"), DumpWriteFile, FormatStatusCode(Status), FormatStatus(Status)); } else { Status = S_OK; } } if (Status == S_OK) { dprintf(_T("Creating %ws - "), DumpWriteFile); Status = WriteTarget->Write(DumpWriteHandle, Args); if (Status == S_OK) { dprintf(_T("Dump successfully written\n")); } if (!FileHandle) { CloseHandle(DumpWriteHandle); if (Status != S_OK) { WideDeleteFile(DumpWriteFile); } } } if (Status == S_OK && (Args->FormatFlags & DEBUG_FORMAT_WRITE_CAB)) { GCONV_WA(DumpWriteFile, MAX_PATH, TRUE, Status = E_OUTOFMEMORY; goto Exit); GCONV_WA(FileName, MAX_PATH, TRUE, Status = E_OUTOFMEMORY; goto Exit); Status = CreateCabFromDump(DumpWriteFileA, FileNameA, Args->FormatFlags); WideDeleteFile(TempFile); } Exit: g_Target->SetEffMachine(OldMachine, FALSE); delete WriteTarget; return Status; } void DotDump(PDOT_COMMAND Cmd, DebugClient* Client) { BOOL Usage = FALSE; DUMP_WRITE_ARGS ArgsBuffer, *Args = &ArgsBuffer; ZeroMemory(Args, sizeof(*Args)); // // Default to minidumps // if (IS_KERNEL_TARGET(g_Target)) { Args->Qualifier = DEBUG_KERNEL_SMALL_DUMP; } else { Args->Qualifier = DEBUG_USER_WINDOWS_SMALL_DUMP; } Args->FormatFlags = DEBUG_FORMAT_DEFAULT | DEBUG_FORMAT_NO_OVERWRITE; // // Scan for options. // TCHAR Save; PTSTR FileName; BOOL SubLoop; PTSTR CommentEnd = NULL; PTSTR PhysMemFileNameEnd = NULL; BOOL Unique = FALSE; ProcessInfo* DumpProcess = g_Process; ULONG64 Addr; for (;;) { if (PeekChar() == '-' || *g_CurCmd == '/') { SubLoop = TRUE; g_CurCmd++; switch(*g_CurCmd) { case 'a': DumpProcess = NULL; g_CurCmd++; break; case 'b': Args->FormatFlags |= DEBUG_FORMAT_WRITE_CAB; g_CurCmd++; if (*g_CurCmd == 'a') { Args->FormatFlags |= DEBUG_FORMAT_CAB_SECONDARY_FILES; g_CurCmd++; } break; case 'c': g_CurCmd++; #ifdef UNICODE Args->CommentW = StringValue(STRV_SPACE_IS_SEPARATOR | STRV_TRIM_TRAILING_SPACE, &Save); #else Args->CommentA = StringValue(STRV_SPACE_IS_SEPARATOR | STRV_TRIM_TRAILING_SPACE, &Save); #endif *g_CurCmd = Save; CommentEnd = g_CurCmd; break; case 'f': if (IS_KERNEL_TARGET(g_Target)) { Args->Qualifier = DEBUG_KERNEL_FULL_DUMP; } else { Args->Qualifier = DEBUG_USER_WINDOWS_DUMP; ErrOut(_T("*****************************************************************************\n")); ErrOut(_T("* .dump /ma is the recommend method of creating a complete memory dump *\n")); ErrOut(_T("* of a user mode process. *\n")); ErrOut(_T("*****************************************************************************\n")); } g_CurCmd++; break; case 'j': g_CurCmd++; Args->JitDebugInfoAddr = GetTermExpression(_T("JIT_DEBUG_INFO address ") _T("missing from")); break; case 'k': if (g_CurCmd[1] == 'p' && g_CurCmd[2] == 'm' && g_CurCmd[3] == 'f' && IsNzCmdSep(g_CurCmd[4])) { g_CurCmd += 4; Args->PhysMemFileName = StringValue(STRV_SPACE_IS_SEPARATOR | STRV_TRIM_TRAILING_SPACE, &Save); *g_CurCmd = Save; PhysMemFileNameEnd = g_CurCmd; } else { goto UnknownOption; } break; case 'm': g_CurCmd++; if (IS_KERNEL_TARGET(g_Target)) { Args->Qualifier = DEBUG_KERNEL_SMALL_DUMP; if (*g_CurCmd != '/' && *g_CurCmd != '-' && !IsSpaceT(*g_CurCmd)) { Usage = TRUE; break; } } else { Args->Qualifier = DEBUG_USER_WINDOWS_SMALL_DUMP; for (;;) { switch(*g_CurCmd) { case 'a': // Synthetic flag meaning "save the // maximum amount of data." Args->FormatFlags |= DEBUG_FORMAT_USER_SMALL_FULL_MEMORY | DEBUG_FORMAT_USER_SMALL_HANDLE_DATA | DEBUG_FORMAT_USER_SMALL_UNLOADED_MODULES | DEBUG_FORMAT_USER_SMALL_FULL_MEMORY_INFO | DEBUG_FORMAT_USER_SMALL_THREAD_INFO; break; case 'c': Args->FormatFlags |= DEBUG_FORMAT_USER_SMALL_CODE_SEGMENTS; break; case 'C': // Flag to test microdump code. Args->InternalFlags |= UMINI_MICRO; break; case 'd': Args->FormatFlags |= DEBUG_FORMAT_USER_SMALL_DATA_SEGMENTS; break; case 'D': // Flag to test debugger provider. Args->InternalFlags |= UMINI_FORCE_DBG; break; case 'f': Args->FormatFlags |= DEBUG_FORMAT_USER_SMALL_FULL_MEMORY; break; case 'F': Args->FormatFlags |= DEBUG_FORMAT_USER_SMALL_FULL_MEMORY_INFO; break; case 'h': Args->FormatFlags |= DEBUG_FORMAT_USER_SMALL_HANDLE_DATA; break; case 'i': Args->FormatFlags |= DEBUG_FORMAT_USER_SMALL_INDIRECT_MEMORY; break; case 'k': g_CurCmd++; SubLoop = FALSE; Args->InternalFlags |= UMINI_WRITE_KMINI; Args->ExtraFileName = StringValue(STRV_REQUIRE_QUOTES, &Save); // We required an opening quote so we should // be past a close quote, which we can just // leave zeroed. g_CurCmd++; break; case 'p': Args->FormatFlags |= DEBUG_FORMAT_USER_SMALL_PROCESS_THREAD_DATA; break; case 'r': Args->FormatFlags |= DEBUG_FORMAT_USER_SMALL_FILTER_MEMORY; break; case 'R': Args->FormatFlags |= DEBUG_FORMAT_USER_SMALL_FILTER_PATHS; break; case 't': Args->FormatFlags |= DEBUG_FORMAT_USER_SMALL_THREAD_INFO; break; case 'u': Args->FormatFlags |= DEBUG_FORMAT_USER_SMALL_UNLOADED_MODULES; break; case 'w': Args->FormatFlags |= DEBUG_FORMAT_USER_SMALL_PRIVATE_READ_WRITE_MEMORY; break; default: SubLoop = FALSE; break; } if (SubLoop) { g_CurCmd++; } else { break; } } } break; case 'o': Args->FormatFlags &= ~DEBUG_FORMAT_NO_OVERWRITE; g_CurCmd++; break; case 'u': Unique = TRUE; g_CurCmd++; break; case 'x': g_CurCmd++; switch(*g_CurCmd) { case 'c': g_CurCmd++; Args->ExContextAddr = GetTermExpression(_T("Context record address ") _T("missing from")); break; case 'p': g_CurCmd++; Addr = GetTermExpression(_T("Exception pointers address ") _T("missing from")); if (g_Target->ReadPointer(g_Process, g_Machine, Addr, &Args->ExRecordAddr) != S_OK) { error(MEMORY); } Addr += g_Machine->m_Ptr64 ? 8 : 4; if (g_Target->ReadPointer(g_Process, g_Machine, Addr, &Args->ExContextAddr) != S_OK) { error(MEMORY); } break; case 'r': g_CurCmd++; Args->ExRecordAddr = GetTermExpression(_T("Exception record address ") _T("missing from")); break; case 't': g_CurCmd++; Args->ExThreadId = (ULONG) GetTermExpression(_T("Thread ID ") _T("missing from")); break; default: error(SYNTAX); } break; case '?': Usage = TRUE; g_CurCmd++; break; default: UnknownOption: ErrOut(_T("Unknown option '%c'\n"), *g_CurCmd); Usage = TRUE; g_CurCmd++; break; } } else { FileName = StringValue(STRV_TRIM_TRAILING_SPACE, &Save); if (*FileName) { break; } else { *g_CurCmd = Save; Usage = TRUE; } } if (Usage) { break; } } if (DumpProcess == NULL && !Unique) { Usage = TRUE; } if (Usage) { ErrOut(_T("Usage: .dump [options] filename\n")); ErrOut(_T("Options are:\n")); ErrOut(_T(" /a - Create dumps for all processes (requires -u)\n")); ErrOut(_T(" /b[a] - Package dump in a CAB and delete dump\n")); ErrOut(_T(" /c - Add a comment ") _T("(not supported in all formats)\n")); ErrOut(_T(" /j - Provide a JIT_DEBUG_INFO address\n")); if (IS_KERNEL_TARGET(g_Target)) { ErrOut(_T(" /f - Create a full dump\n")); ErrOut(_T(" /m - Create a minidump (default)\n")); } else { ErrOut(_T(" /f - Create a legacy style full dump\n")); ErrOut(_T(" /m[acdfFhiprRtuw] - Create a minidump (default)\n")); } ErrOut(_T(" /o - Overwrite any existing file\n")); ErrOut(_T(" /u - Append unique identifier to dump name\n")); ErrOut(_T("\nUse \".hh .dump\" or open debugger.chm in the ") _T("debuggers directory to get\n") _T("detailed documentation on this command.\n\n")); return; } if (IS_LOCAL_KERNEL_TARGET(g_Target)) { // It's possible to allow lkd to capture dump files // but because the system is running during the capture // the dump state will be a mishmash of different data. // We could mark the dump headers and warn when such // dumps are opened but it still seems too risky. // Leave this disabled for now. #ifdef ALLOW_LOCAL_KD_DUMP if (Args->Qualifier != DEBUG_KERNEL_FULL_DUMP) { error(SESSIONNOTSUP); } WarnOut(_T("***************************************") _T("***************************************\n")); WarnOut(_T("WARNING: System state is changing as ") _T("local kd is running.\n")); WarnOut(_T(" Dump state will not be consistent.\n")); WarnOut(_T("***************************************") _T("***************************************\n")); #else error(SESSIONNOTSUP); #endif } if (CommentEnd) { *CommentEnd = 0; } if (PhysMemFileNameEnd) { *PhysMemFileNameEnd = 0; } ThreadInfo* OldThread = g_Thread; TargetInfo* Target; ProcessInfo* Process; ForAllLayersToProcess() { PTSTR DumpFileName; TCHAR UniqueName[2 * MAX_PATH]; if (DumpProcess != NULL && Process != DumpProcess) { continue; } if (Process != g_Process) { SetCurrentThread(Process->m_ThreadHead, TRUE); } if (Unique) { MakeFileNameUnique(FileName, UniqueName, DIMA(UniqueName), TRUE, g_Process); DumpFileName = UniqueName; } else { DumpFileName = FileName; } #ifdef UNICODE WriteDumpFile(DumpFileName, 0, Args); #else PWSTR WideName; if (AnsiToWide(DumpFileName, &WideName) == S_OK) { WriteDumpFile(WideName, 0, Args); FreeWide(WideName); } else { ErrOut(_T("Unable to convert dump filename\n")); } #endif } if (!OldThread || OldThread->m_Process != g_Process) { SetCurrentThread(OldThread, TRUE); } *g_CurCmd = Save; } BOOL DumpCabAdd(PCSTR File) { HRESULT Status; dprintf(_T(" Adding %hs - "), File); FlushCallbacks(); if ((Status = AddToDumpCab(File)) != S_OK) { ErrOut(_T("%s\n"), FormatStatusCode(Status)); } else { dprintf(_T("added\n")); } if (CheckUserInterrupt()) { return FALSE; } FlushCallbacks(); return TRUE; } HRESULT CreateCabFromDump(PCSTR DumpFile, PCSTR CabFile, ULONG Flags) { HRESULT Status; if ((Status = CreateDumpCab(CabFile)) != S_OK) { ErrOut(_T("Unable to create CAB, %s\n"), FormatStatusCode(Status)); return Status; } WarnOut(_T("Creating a cab file can take a VERY VERY long time\n.") _T("Ctrl-C can only interrupt the command after a file ") _T("has been added to the cab.\n")); // // First add all base dump files. // if (!DumpFile) { DumpTargetInfo* Dump = (DumpTargetInfo*)g_Target; ULONG i; for (i = DUMP_INFO_DUMP; i < DUMP_INFO_COUNT; i++) { if (Dump->m_InfoFiles[i].m_File) { if (!DumpCabAdd(Dump->m_InfoFiles[i].m_FileNameA)) { Status = E_UNEXPECTED; goto Exit; } } } } else { if (!DumpCabAdd(DumpFile)) { Status = E_UNEXPECTED; goto Exit; } } if (Flags & DEBUG_FORMAT_CAB_SECONDARY_FILES) { ImageInfo* Image; // // Add all symbols and images. // if (g_Process->m_ClrDebugDll) { char ClrPath[MAX_PATH]; if (GetModuleFileNameA(g_Process->m_ClrDebugDll, ClrPath, DIMA(ClrPath))) { if (!DumpCabAdd(ClrPath)) { Status = E_UNEXPECTED; goto Exit; } } } for (Image = g_Process->m_ImageHead; Image; Image = Image->m_Next) { if (Image->m_MappedImagePath[0]) { PTSTR MapPath = Image->m_MappedImagePath; GCONV_TA(MapPath, MAX_IMAGE_PATH, TRUE, Status = E_OUTOFMEMORY; break); if (!DumpCabAdd(MapPathA)) { Status = E_UNEXPECTED; break; } } IMAGEHLP_MODULE64 ModInfo; ModInfo.SizeOfStruct = sizeof(ModInfo); if (SymGetModuleInfo64(g_Process->m_SymHandle, Image->m_BaseOfImage, &ModInfo)) { ULONG Len; PTSTR Str; // The loaded image name often refers directly to the // image. Only save the loaded image file if it // refers to a .dbg file. if (ModInfo.LoadedImageName[0] && (Len = _tcslen(ModInfo.LoadedImageName)) > 4 && !_tcsicmp(ModInfo.LoadedImageName + (Len - 4), _T(".dbg"))) { Str = ModInfo.LoadedImageName; GCONV_TA(Str, MAX_PATH, TRUE, Status = E_OUTOFMEMORY; break); if (!DumpCabAdd(StrA)) { Status = E_UNEXPECTED; break; } } // Save any PDB that was opened. if (ModInfo.LoadedPdbName[0]) { Str = ModInfo.LoadedPdbName; GCONV_TA(Str, MAX_PATH, TRUE, Status = E_OUTOFMEMORY; break); if (!DumpCabAdd(StrA)) { Status = E_UNEXPECTED; break; } } } } } Exit: CloseDumpCab(); if (Status == S_OK) { dprintf(_T("Wrote %hs\n"), CabFile); } return Status; }