5679 lines
178 KiB
C++
5679 lines
178 KiB
C++
//----------------------------------------------------------------------------
|
|
//
|
|
// Dump file writing.
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
#include "ntsdp.hpp"
|
|
|
|
#include <uminiprov.hpp>
|
|
#include <dbgver.h>
|
|
#include <bugcodes.h>
|
|
|
|
#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"<HandleOnly>";
|
|
}
|
|
}
|
|
|
|
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 <comment> - Add a comment ")
|
|
_T("(not supported in all formats)\n"));
|
|
ErrOut(_T(" /j <addr> - 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;
|
|
}
|