diff options
Diffstat (limited to 'src/ToolBox/SOS/Strike')
57 files changed, 77664 insertions, 0 deletions
diff --git a/src/ToolBox/SOS/Strike/.gitmirror b/src/ToolBox/SOS/Strike/.gitmirror new file mode 100644 index 0000000000..f507630f94 --- /dev/null +++ b/src/ToolBox/SOS/Strike/.gitmirror @@ -0,0 +1 @@ +Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror.
\ No newline at end of file diff --git a/src/ToolBox/SOS/Strike/ApolloNative.rc b/src/ToolBox/SOS/Strike/ApolloNative.rc new file mode 100644 index 0000000000..ba320af911 --- /dev/null +++ b/src/ToolBox/SOS/Strike/ApolloNative.rc @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#define FX_VER_FILEDESCRIPTION_STR "Microsoft NTSD extension for .NET Runtime\0" + +#include <fxver.h> +#include <fxver.rc> + +DOCUMENTATION TEXT DISCARDABLE "apollososdocs.txt" diff --git a/src/ToolBox/SOS/Strike/CMakeLists.txt b/src/ToolBox/SOS/Strike/CMakeLists.txt new file mode 100644 index 0000000000..5d25865780 --- /dev/null +++ b/src/ToolBox/SOS/Strike/CMakeLists.txt @@ -0,0 +1,201 @@ +# Set the RPATH of sos so that it can find dependencies without needing to set LD_LIBRARY_PATH +# For more information: http://www.cmake.org/Wiki/CMake_RPATH_handling. +if (CORECLR_SET_RPATH) + set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) + if(CLR_CMAKE_PLATFORM_DARWIN) + set(CMAKE_INSTALL_RPATH "@loader_path") + else() + set(CMAKE_INSTALL_RPATH "\$ORIGIN") + endif(CLR_CMAKE_PLATFORM_DARWIN) +endif (CORECLR_SET_RPATH) + +if(CLR_CMAKE_PLATFORM_ARCH_AMD64) + add_definitions(-DSOS_TARGET_AMD64=1) + add_definitions(-D_TARGET_WIN64_=1) + add_definitions(-DDBG_TARGET_64BIT) + add_definitions(-DDBG_TARGET_WIN64=1) + if(WIN32) + add_definitions(-DSOS_TARGET_ARM64=1) + endif(WIN32) + remove_definitions(-D_TARGET_ARM64_=1) + add_definitions(-D_TARGET_AMD64_) + add_definitions(-DDBG_TARGET_AMD64) +elseif(CLR_CMAKE_PLATFORM_ARCH_I386) + add_definitions(-DSOS_TARGET_X86=1) + add_definitions(-D_TARGET_X86_=1) + add_definitions(-DDBG_TARGET_32BIT) + add_definitions(-DSOS_TARGET_ARM=1) +elseif(CLR_CMAKE_PLATFORM_ARCH_ARM) + add_definitions(-DSOS_TARGET_ARM=1) + add_definitions(-D_TARGET_WIN32_=1) + add_definitions(-D_TARGET_ARM_=1) + add_definitions(-DDBG_TARGET_32BIT) +elseif(CLR_CMAKE_PLATFORM_ARCH_ARM64) + add_definitions(-DSOS_TARGET_ARM64=1) + add_definitions(-D_TARGET_WIN64_=1) + add_definitions(-DDBG_TARGET_64BIT) + add_definitions(-DDBG_TARGET_WIN64=1) +endif() +add_definitions(-DSTRIKE) + +remove_definitions(-DUNICODE) +remove_definitions(-D_UNICODE) + +include_directories(BEFORE ${VM_DIR}) +include_directories(${CLR_DIR}/src/gcdump) +include_directories(${CLR_DIR}/src/debug/shim) +include_directories(${CLR_DIR}/src/coreclr/hosts/unixcoreruncommon) +include_directories(${CLR_DIR}/src/coreclr/hosts/inc) + +if(WIN32) + include_directories(inc) + include_directories("$ENV{VSInstallDir}/DIA SDK/include") + + add_definitions(-DUSE_STL) + + #use static crt + add_definitions(-MT) + + set(SOS_SOURCES + disasm.cpp + dllsext.cpp + eeheap.cpp + EventCallbacks.cpp + ExpressionNode.cpp + exts.cpp + gchist.cpp + gcroot.cpp + metadata.cpp + sildasm.cpp + sos.cpp + stressLogDump.cpp + strike.cpp + util.cpp + vm.cpp + WatchCmd.cpp + Native.rc + ) + + add_definitions(-DFX_VER_INTERNALNAME_STR=SOS.dll) + + #Preprocess exports definition file + preprocess_def_file(${CMAKE_CURRENT_SOURCE_DIR}/sos.def ${CMAKE_CURRENT_BINARY_DIR}/sos.def) + list(APPEND SOS_SOURCES ${CMAKE_CURRENT_BINARY_DIR}/sos.def) + + set(SOS_LIBRARY + corguids + debugshim + dbgutil + ${STATIC_MT_CRT_LIB} + ${STATIC_MT_CPP_LIB} + ${STATIC_MT_VCRT_LIB} + kernel32.lib + user32.lib + ole32.lib + oleaut32.lib + dbghelp.lib + uuid.lib + version.lib + dbgeng.lib + advapi32.lib + psapi.lib + ntdll.lib + ) +else(WIN32) + add_definitions(-DFEATURE_ENABLE_HARDWARE_EXCEPTIONS) + add_definitions(-DPAL_STDCPP_COMPAT=1) + add_compile_options(-Wno-null-arithmetic) + add_compile_options(-Wno-format) + + include_directories(BEFORE xplat) + include_directories(BEFORE ../lldbplugin/inc) + + add_compile_options(-fPIC) + + set(SOS_SOURCES + disasm.cpp + datatarget.cpp + eeheap.cpp + exts.cpp + gchist.cpp + gcroot.cpp + metadata.cpp + sildasm.cpp + stressLogDump.cpp + strike.cpp + sos.cpp + util.cpp + ../../../coreclr/hosts/unixcoreruncommon/coreruncommon.cpp + ) + + set(SOS_LIBRARY + corguids + debugshim + dbgutil + # share the PAL in the dac module + mscordaccore + palrt + ) + + set(DEF_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/sos_unixexports.src) + set(EXPORTS_FILE ${CMAKE_CURRENT_BINARY_DIR}/sos.exports) + generate_exports_file(${DEF_SOURCES} ${EXPORTS_FILE}) +endif(WIN32) + +if(CLR_CMAKE_PLATFORM_ARCH_AMD64) + set(SOS_SOURCES_ARCH + disasmX86.cpp + ) + if(WIN32) + list(APPEND + SOS_SOURCES_ARCH + disasmARM64.cpp + ) + endif(WIN32) +elseif(CLR_CMAKE_PLATFORM_ARCH_I386) + set(SOS_SOURCES_ARCH + disasmX86.cpp + disasmARM.cpp + ) +elseif(CLR_CMAKE_PLATFORM_ARCH_ARM) + set(SOS_SOURCES_ARCH + disasmARM.cpp + ) +elseif(CLR_CMAKE_PLATFORM_ARCH_ARM64) + set(SOS_SOURCES_ARCH + disasmARM64.cpp + ) +endif() + +list(APPEND SOS_SOURCES ${SOS_SOURCES_ARCH}) + +if(CLR_CMAKE_PLATFORM_LINUX OR CLR_CMAKE_PLATFORM_FREEBSD OR CLR_CMAKE_PLATFORM_NETBSD) + # Add linker exports file option + set(EXPORTS_LINKER_OPTION -Wl,--version-script=${EXPORTS_FILE}) +endif(CLR_CMAKE_PLATFORM_LINUX OR CLR_CMAKE_PLATFORM_FREEBSD OR CLR_CMAKE_PLATFORM_NETBSD) + +if(CLR_CMAKE_PLATFORM_DARWIN) + # Add linker exports file option + set(EXPORTS_LINKER_OPTION -Wl,-exported_symbols_list,${EXPORTS_FILE}) +endif(CLR_CMAKE_PLATFORM_DARWIN) + +add_library_clr(sos SHARED ${SOS_SOURCES}) + +if(CLR_CMAKE_PLATFORM_UNIX) + add_custom_target(sos_exports DEPENDS ${EXPORTS_FILE}) + add_dependencies(sos sos_exports) + + set_property(TARGET sos APPEND_STRING PROPERTY LINK_FLAGS ${EXPORTS_LINKER_OPTION}) + set_property(TARGET sos APPEND_STRING PROPERTY LINK_DEPENDS ${EXPORTS_FILE}) + + add_dependencies(sos mscordaccore) +endif(CLR_CMAKE_PLATFORM_UNIX) + +target_link_libraries(sos ${SOS_LIBRARY}) + +# add the install targets +install_clr(sos) + +if(NOT WIN32) + install(FILES sosdocsunix.txt DESTINATION .) +endif(NOT WIN32) diff --git a/src/ToolBox/SOS/Strike/EventCallbacks.cpp b/src/ToolBox/SOS/Strike/EventCallbacks.cpp new file mode 100644 index 0000000000..0066dfa1e8 --- /dev/null +++ b/src/ToolBox/SOS/Strike/EventCallbacks.cpp @@ -0,0 +1,160 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#include "EventCallbacks.h" + +EventCallbacks::EventCallbacks(IDebugClient* pDebugClient) : m_refCount(1), m_pDebugClient(pDebugClient) +{ +} + +EventCallbacks::~EventCallbacks() +{ + if(m_pDebugClient != NULL) + m_pDebugClient->Release(); +} + + // IUnknown implementation +HRESULT __stdcall EventCallbacks::QueryInterface(REFIID riid, VOID** ppInterface) +{ + if(riid == __uuidof(IDebugEventCallbacks)) + { + *ppInterface = static_cast<IDebugEventCallbacks*>(this); + AddRef(); + return S_OK; + } + else if(riid == __uuidof(IUnknown)) + { + *ppInterface = static_cast<IUnknown*>(this); + AddRef(); + return S_OK; + } + else + { + return E_NOINTERFACE; + } +} + +ULONG __stdcall EventCallbacks::AddRef() +{ + return InterlockedIncrement((volatile LONG *) &m_refCount); +} + +ULONG __stdcall EventCallbacks::Release() +{ + ULONG count = InterlockedDecrement((volatile LONG *) &m_refCount); + if(count == 0) + { + delete this; + } + return count; +} + +// IDebugEventCallbacks implementation +HRESULT __stdcall EventCallbacks::Breakpoint(PDEBUG_BREAKPOINT bp) +{ + return DEBUG_STATUS_NO_CHANGE; +} + +HRESULT __stdcall EventCallbacks::ChangeDebuggeeState(ULONG Flags, ULONG64 Argument) +{ + return DEBUG_STATUS_NO_CHANGE; +} + +HRESULT __stdcall EventCallbacks::ChangeEngineState(ULONG Flags, ULONG64 Argument) +{ + return DEBUG_STATUS_NO_CHANGE; +} +HRESULT __stdcall EventCallbacks::ChangeSymbolState(ULONG Flags, ULONG64 Argument) +{ + return DEBUG_STATUS_NO_CHANGE; +} +HRESULT __stdcall EventCallbacks::CreateProcess(ULONG64 ImageFileHandle, + ULONG64 Handle, + ULONG64 BaseOffset, + ULONG ModuleSize, + PCSTR ModuleName, + PCSTR ImageName, + ULONG CheckSum, + ULONG TimeDateStamp, + ULONG64 InitialThreadHandle, + ULONG64 ThreadDataOffset, + ULONG64 StartOffset) +{ + return DEBUG_STATUS_NO_CHANGE; +} + +HRESULT __stdcall EventCallbacks::CreateThread(ULONG64 Handle, + ULONG64 DataOffset, + ULONG64 StartOffset) +{ + return DEBUG_STATUS_NO_CHANGE; +} + +HRESULT __stdcall EventCallbacks::Exception(PEXCEPTION_RECORD64 Exception, ULONG FirstChance) +{ + return DEBUG_STATUS_NO_CHANGE; +} + +HRESULT __stdcall EventCallbacks::ExitProcess(ULONG ExitCode) +{ + UninitCorDebugInterface(); + return DEBUG_STATUS_NO_CHANGE; +} + +HRESULT __stdcall EventCallbacks::ExitThread(ULONG ExitCode) +{ + return DEBUG_STATUS_NO_CHANGE; +} + +HRESULT __stdcall EventCallbacks::GetInterestMask(PULONG Mask) +{ + *Mask = DEBUG_EVENT_LOAD_MODULE | DEBUG_EVENT_EXIT_PROCESS; + return S_OK; +} + +extern BOOL g_fAllowJitOptimization; + +HRESULT __stdcall EventCallbacks::LoadModule(ULONG64 ImageFileHandle, + ULONG64 BaseOffset, + ULONG ModuleSize, + PCSTR ModuleName, + PCSTR ImageName, + ULONG CheckSum, + ULONG TimeDateStamp) +{ + HRESULT handleEventStatus = DEBUG_STATUS_NO_CHANGE; + ExtQuery(m_pDebugClient); + + if (ModuleName != NULL && _stricmp(ModuleName, MAIN_CLR_MODULE_NAME_A) == 0) + { + // if we don't want the JIT to optimize, we should also disable optimized NGEN images + if(!g_fAllowJitOptimization) + { + // if we aren't succesful SetNGENCompilerFlags will print relevant error messages + // and then we need to stop the debugger so the user can intervene if desired + if(FAILED(SetNGENCompilerFlags(CORDEBUG_JIT_DISABLE_OPTIMIZATION))) + { + handleEventStatus = DEBUG_STATUS_BREAK; + } + } + } + + ExtRelease(); + return handleEventStatus; +} + +HRESULT __stdcall EventCallbacks::SessionStatus(ULONG Status) +{ + return DEBUG_STATUS_NO_CHANGE; +} + +HRESULT __stdcall EventCallbacks::SystemError(ULONG Error, ULONG Level) +{ + return DEBUG_STATUS_NO_CHANGE; +} + +HRESULT __stdcall EventCallbacks::UnloadModule(PCSTR ImageBaseName, ULONG64 BaseOffset) +{ + return DEBUG_STATUS_NO_CHANGE; +} diff --git a/src/ToolBox/SOS/Strike/EventCallbacks.h b/src/ToolBox/SOS/Strike/EventCallbacks.h new file mode 100644 index 0000000000..acd5e412d1 --- /dev/null +++ b/src/ToolBox/SOS/Strike/EventCallbacks.h @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#ifndef __EventCallbacks__ +#define __EventCallbacks__ + +#include "exts.h" + +// A set of callbacks that are registered with windbg whenever SOS is loaded +// Right now these callbacks only act on the module load event for CLR, but +// feel free to add other event hooks as needed +// +// TODO: we should probably be using these callbacks to hook clrnotify exceptions +// rather than attaching a user handler on the clrn event. That handler is both +// visible to the user and could be accidentally erased by them. +class EventCallbacks : IDebugEventCallbacks +{ +public: + EventCallbacks(IDebugClient* pDebugClient); + ~EventCallbacks(); + + // IUnknown implementation + HRESULT __stdcall QueryInterface(REFIID riid, VOID** ppInterface); + ULONG __stdcall AddRef(); + ULONG __stdcall Release(); + + // IDebugEventCallbacks implementation + HRESULT __stdcall Breakpoint(PDEBUG_BREAKPOINT bp); + HRESULT __stdcall ChangeDebuggeeState(ULONG Flags, ULONG64 Argument); + HRESULT __stdcall ChangeEngineState(ULONG Flags, ULONG64 Argument); + HRESULT __stdcall ChangeSymbolState(ULONG Flags, ULONG64 Argument); + HRESULT __stdcall CreateProcess(ULONG64 ImageFileHandle, + ULONG64 Handle, + ULONG64 BaseOffset, + ULONG ModuleSize, + PCSTR ModuleName, + PCSTR ImageName, + ULONG CheckSum, + ULONG TimeDateStamp, + ULONG64 InitialThreadHandle, + ULONG64 ThreadDataOffset, + ULONG64 StartOffset); + HRESULT __stdcall CreateThread(ULONG64 Handle, + ULONG64 DataOffset, + ULONG64 StartOffset); + HRESULT __stdcall Exception(PEXCEPTION_RECORD64 Exception, ULONG FirstChance); + HRESULT __stdcall ExitProcess(ULONG ExitCode); + HRESULT __stdcall ExitThread(ULONG ExitCode); + HRESULT __stdcall GetInterestMask(PULONG Mask); + HRESULT __stdcall LoadModule(ULONG64 ImageFileHandle, + ULONG64 BaseOffset, + ULONG ModuleSize, + PCSTR ModuleName, + PCSTR ImageName, + ULONG CheckSum, + ULONG TimeDateStamp); + HRESULT __stdcall SessionStatus(ULONG Status); + HRESULT __stdcall SystemError(ULONG Error, ULONG Level); + HRESULT __stdcall UnloadModule(PCSTR ImageBaseName, ULONG64 BaseOffset); + + +private: + volatile ULONG m_refCount; + IDebugClient* m_pDebugClient; +}; + +#endif diff --git a/src/ToolBox/SOS/Strike/ExpressionNode.cpp b/src/ToolBox/SOS/Strike/ExpressionNode.cpp new file mode 100644 index 0000000000..6516823aaa --- /dev/null +++ b/src/ToolBox/SOS/Strike/ExpressionNode.cpp @@ -0,0 +1,2178 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#include "ExpressionNode.h" + + +#ifndef IfFailRet +#define IfFailRet(EXPR) do { Status = (EXPR); if(FAILED(Status)) { return (Status); } } while (0) +#endif + +// Returns the complete expression being evaluated to get the value for this node +// The returned pointer is a string interior to this object - once you release +// all references to this object the string is invalid. +WCHAR* ExpressionNode::GetAbsoluteExpression() { return pAbsoluteExpression; } + +// Returns the sub expression that logically indicates how the parent expression +// was built upon to reach this node. This relative value has no purpose other +// than an identifier and to convey UI meaning to the user. At present typical values +// are the name of type, a local, a parameter, a field, an array index, or '<basetype>' +// for a baseclass casting operation +// The returned pointer is a string interior to this object - once you release +// all references to this object the string is invalid. +WCHAR* ExpressionNode::GetRelativeExpression() { return pRelativeExpression; } + +// Returns a text representation of the type of value that this node refers to +// It is possible this node doesn't evaluate to anything and therefore has no +// type +// The returned pointer is a string interior to this object - once you release +// all references to this object the string is invalid. +WCHAR* ExpressionNode::GetTypeName() { PopulateType(); return pTypeName; } + +// Returns a text representation of the value for this node. It is possible that +// this node doesn't evaluate to anything and therefore has no value text. +// The returned pointer is a string interior to this object - once you release +// all references to this object the string is invalid. +WCHAR* ExpressionNode::GetTextValue() { PopulateTextValue(); return pTextValue; } + +// If there is any error during the evaluation of this node's expression, it is +// returned here. +// The returned pointer is a string interior to this object - once you release +// all references to this object the string is invalid. +WCHAR* ExpressionNode::GetErrorMessage() { return pErrorMessage; } + +// Factory function for creating the expression node at the root of a tree +HRESULT ExpressionNode::CreateExpressionNode(__in_z WCHAR* pExpression, ExpressionNode** ppExpressionNode) +{ + *ppExpressionNode = NULL; + HRESULT Status = CreateExpressionNodeHelper(pExpression, + pExpression, + 0, + NULL, + NULL, + NULL, + 0, + NULL, + ppExpressionNode); + if(FAILED(Status) && *ppExpressionNode == NULL) + { + + WCHAR pErrorMessage[MAX_ERROR]; + _snwprintf_s(pErrorMessage, MAX_ERROR, _TRUNCATE, L"Error 0x%x while parsing expression", Status); + *ppExpressionNode = new ExpressionNode(pExpression, pErrorMessage); + Status = S_OK; + if(*ppExpressionNode == NULL) + Status = E_OUTOFMEMORY; + } + return Status; +} + +// Performs recursive expansion within the tree for nodes that are along the path to varToExpand. +// Expansion involves calulating a set of child expressions from the current expression via +// field dereferencing, array index dereferencing, or casting to a base type. +// For example if a tree was rooted with expression 'foo.bar' and varToExpand is '(Baz)foo.bar[9]' +// then 'foo.bar', 'foo.bar[9]', and '(Baz)foo.bar[9]' nodes would all be expanded. +HRESULT ExpressionNode::Expand(__in_z WCHAR* varToExpand) +{ + if(!ShouldExpandVariable(varToExpand)) + return S_FALSE; + if(pValue == NULL && pTypeCast == NULL) + return S_OK; + + // if the node evaluates to a type, then the children are static fields of the type + if(pValue == NULL) + return ExpandFields(NULL, varToExpand); + + // If the value is a null reference there is nothing to expand + HRESULT Status = S_OK; + BOOL isNull = TRUE; + ToRelease<ICorDebugValue> pInnerValue; + IfFailRet(DereferenceAndUnboxValue(pValue, &pInnerValue, &isNull)); + if(isNull) + { + return S_OK; + } + + + CorElementType corElemType; + IfFailRet(pValue->GetType(&corElemType)); + if (corElemType == ELEMENT_TYPE_SZARRAY) + { + //If its an array, add children representing the indexed elements + return ExpandSzArray(pInnerValue, varToExpand); + } + else if(corElemType == ELEMENT_TYPE_CLASS || corElemType == ELEMENT_TYPE_VALUETYPE) + { + // If its a class or struct (not counting string, array, or object) then add children representing + // the fields. + return ExpandFields(pInnerValue, varToExpand); + } + else + { + // nothing else expands + return S_OK; + } +} + +// Standard depth first search tree traversal pattern with a callback +VOID ExpressionNode::DFSVisit(ExpressionNodeVisitorCallback pFunc, VOID* pUserData, int depth) +{ + pFunc(this, depth, pUserData); + ExpressionNode* pCurChild = pChild; + while(pCurChild != NULL) + { + pCurChild->DFSVisit(pFunc, pUserData, depth+1); + pCurChild = pCurChild->pNextSibling; + } +} + + +// Creates a new expression with a given debuggee value and frame +ExpressionNode::ExpressionNode(__in_z WCHAR* pExpression, ICorDebugValue* pValue, ICorDebugILFrame* pFrame) +{ + Init(pValue, NULL, pFrame); + _snwprintf_s(pAbsoluteExpression, MAX_EXPRESSION, _TRUNCATE, L"%s", pExpression); + _snwprintf_s(pRelativeExpression, MAX_EXPRESSION, _TRUNCATE, L"%s", pExpression); +} + +// Creates a new expression that has an error and no value +ExpressionNode::ExpressionNode(__in_z WCHAR* pExpression, __in_z WCHAR* pErrorMessage) +{ + Init(NULL, NULL, NULL); + _snwprintf_s(pAbsoluteExpression, MAX_EXPRESSION, _TRUNCATE, L"%s", pExpression); + _snwprintf_s(pRelativeExpression, MAX_EXPRESSION, _TRUNCATE, L"%s", pExpression); + _snwprintf_s(this->pErrorMessage, MAX_ERROR, _TRUNCATE, L"%s", pErrorMessage); +} + +// Creates a new child expression +ExpressionNode::ExpressionNode(__in_z WCHAR* pParentExpression, ChildKind ck, __in_z WCHAR* pRelativeExpression, ICorDebugValue* pValue, ICorDebugType* pType, ICorDebugILFrame* pFrame, UVCP_CONSTANT pDefaultValue, ULONG cchDefaultValue) +{ + Init(pValue, pType, pFrame); + if(ck == ChildKind_BaseClass) + { + _snwprintf_s(pAbsoluteExpression, MAX_EXPRESSION, _TRUNCATE, L"%s", pParentExpression); + } + else + { + _snwprintf_s(pAbsoluteExpression, MAX_EXPRESSION, _TRUNCATE, ck == ChildKind_Field ? L"%s.%s" : L"%s[%s]", pParentExpression, pRelativeExpression); + } + _snwprintf_s(this->pRelativeExpression, MAX_EXPRESSION, _TRUNCATE, ck == ChildKind_Index ? L"[%s]" : L"%s", pRelativeExpression); + this->pDefaultValue = pDefaultValue; + this->cchDefaultValue = cchDefaultValue; +} + +// Common member initialization for the constructors +VOID ExpressionNode::Init(ICorDebugValue* pValue, ICorDebugType* pTypeCast, ICorDebugILFrame* pFrame) +{ + this->pValue = pValue; + this->pTypeCast = pTypeCast; + this->pILFrame = pFrame; + pChild = NULL; + pNextSibling = NULL; + pTextValue[0] = 0; + pErrorMessage[0] = 0; + pAbsoluteExpression[0] = 0; + pRelativeExpression[0] = 0; + pTypeName[0] = 0; + + pDefaultValue = NULL; + cchDefaultValue = 0; + + // The ToRelease holders don't automatically AddRef + if(pILFrame != NULL) + pILFrame->AddRef(); + if(pTypeCast != NULL) + pTypeCast->AddRef(); + if(pValue != NULL) + { + pValue->AddRef(); + PopulateMetaDataImport(); + } +} + +// Retreves the correct IMetaDataImport for the type represented in this node and stores it +// in pMD. +HRESULT ExpressionNode::PopulateMetaDataImport() +{ + if(pMD != NULL) + return S_OK; + + HRESULT Status = S_OK; + ToRelease<ICorDebugType> pType; + if(pTypeCast != NULL) + { + pType = pTypeCast; + pType->AddRef(); + } + else + { + BOOL isNull; + ToRelease<ICorDebugValue> pInnerValue; + IfFailRet(DereferenceAndUnboxValue(pValue, &pInnerValue, &isNull)); + ToRelease<ICorDebugValue2> pValue2; + IfFailRet(pInnerValue->QueryInterface(IID_ICorDebugValue2, (LPVOID *) &pValue2)); + + IfFailRet(pValue2->GetExactType(&pType)); + + // for array, pointer, and byref types we can't directly get a class, we must unwrap first + CorElementType et; + IfFailRet(pType->GetType(&et)); + while(et == ELEMENT_TYPE_ARRAY || et == ELEMENT_TYPE_SZARRAY || et == ELEMENT_TYPE_BYREF || et == ELEMENT_TYPE_PTR) + { + pType->GetFirstTypeParameter(&pType); + IfFailRet(pType->GetType(&et)); + } + } + ToRelease<ICorDebugClass> pClass; + IfFailRet(pType->GetClass(&pClass)); + ToRelease<ICorDebugModule> pModule; + IfFailRet(pClass->GetModule(&pModule)); + ToRelease<IUnknown> pMDUnknown; + IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown)); + IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD)); + return Status; +} + +// Determines the string representation of pType and stores it in typeName +HRESULT ExpressionNode::CalculateTypeName(ICorDebugType * pType, __inout_ecount(typeNameLen) WCHAR* typeName, DWORD typeNameLen) +{ + HRESULT Status = S_OK; + + CorElementType corElemType; + IfFailRet(pType->GetType(&corElemType)); + + switch (corElemType) + { + //List of unsupported CorElementTypes: + //ELEMENT_TYPE_END = 0x0, + //ELEMENT_TYPE_VAR = 0x13, // a class type variable VAR <U1> + //ELEMENT_TYPE_GENERICINST = 0x15, // GENERICINST <generic type> <argCnt> <arg1> ... <argn> + //ELEMENT_TYPE_TYPEDBYREF = 0x16, // TYPEDREF (it takes no args) a typed referece to some other type + //ELEMENT_TYPE_MVAR = 0x1e, // a method type variable MVAR <U1> + //ELEMENT_TYPE_CMOD_REQD = 0x1F, // required C modifier : E_T_CMOD_REQD <mdTypeRef/mdTypeDef> + //ELEMENT_TYPE_CMOD_OPT = 0x20, // optional C modifier : E_T_CMOD_OPT <mdTypeRef/mdTypeDef> + //ELEMENT_TYPE_INTERNAL = 0x21, // INTERNAL <typehandle> + //ELEMENT_TYPE_MAX = 0x22, // first invalid element type + //ELEMENT_TYPE_MODIFIER = 0x40, + //ELEMENT_TYPE_SENTINEL = 0x01 | ELEMENT_TYPE_MODIFIER, // sentinel for varargs + //ELEMENT_TYPE_PINNED = 0x05 | ELEMENT_TYPE_MODIFIER, + default: + swprintf_s(typeName, typeNameLen, L"(Unhandled CorElementType: 0x%x)\0", corElemType); + break; + + case ELEMENT_TYPE_VALUETYPE: + case ELEMENT_TYPE_CLASS: + { + //Defaults in case we fail... + if(corElemType == ELEMENT_TYPE_VALUETYPE) swprintf_s(typeName, typeNameLen, L"struct\0"); + else swprintf_s(typeName, typeNameLen, L"class\0"); + + mdTypeDef typeDef; + ToRelease<ICorDebugClass> pClass; + if(SUCCEEDED(pType->GetClass(&pClass)) && SUCCEEDED(pClass->GetToken(&typeDef))) + { + ToRelease<ICorDebugModule> pModule; + IfFailRet(pClass->GetModule(&pModule)); + + ToRelease<IUnknown> pMDUnknown; + ToRelease<IMetaDataImport> pMD; + IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown)); + IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD)); + + if(SUCCEEDED(NameForToken_s(TokenFromRid(typeDef, mdtTypeDef), pMD, g_mdName, mdNameLen, false))) + swprintf_s(typeName, typeNameLen, L"%s\0", g_mdName); + } + AddGenericArgs(pType, typeName, typeNameLen); + } + break; + case ELEMENT_TYPE_VOID: + swprintf_s(typeName, typeNameLen, L"void\0"); + break; + case ELEMENT_TYPE_BOOLEAN: + swprintf_s(typeName, typeNameLen, L"bool\0"); + break; + case ELEMENT_TYPE_CHAR: + swprintf_s(typeName, typeNameLen, L"char\0"); + break; + case ELEMENT_TYPE_I1: + swprintf_s(typeName, typeNameLen, L"signed byte\0"); + break; + case ELEMENT_TYPE_U1: + swprintf_s(typeName, typeNameLen, L"byte\0"); + break; + case ELEMENT_TYPE_I2: + swprintf_s(typeName, typeNameLen, L"short\0"); + break; + case ELEMENT_TYPE_U2: + swprintf_s(typeName, typeNameLen, L"unsigned short\0"); + break; + case ELEMENT_TYPE_I4: + swprintf_s(typeName, typeNameLen, L"int\0"); + break; + case ELEMENT_TYPE_U4: + swprintf_s(typeName, typeNameLen, L"unsigned int\0"); + break; + case ELEMENT_TYPE_I8: + swprintf_s(typeName, typeNameLen, L"long\0"); + break; + case ELEMENT_TYPE_U8: + swprintf_s(typeName, typeNameLen, L"unsigned long\0"); + break; + case ELEMENT_TYPE_R4: + swprintf_s(typeName, typeNameLen, L"float\0"); + break; + case ELEMENT_TYPE_R8: + swprintf_s(typeName, typeNameLen, L"double\0"); + break; + case ELEMENT_TYPE_OBJECT: + swprintf_s(typeName, typeNameLen, L"object\0"); + break; + case ELEMENT_TYPE_STRING: + swprintf_s(typeName, typeNameLen, L"string\0"); + break; + case ELEMENT_TYPE_I: + swprintf_s(typeName, typeNameLen, L"IntPtr\0"); + break; + case ELEMENT_TYPE_U: + swprintf_s(typeName, typeNameLen, L"UIntPtr\0"); + break; + case ELEMENT_TYPE_SZARRAY: + case ELEMENT_TYPE_ARRAY: + case ELEMENT_TYPE_BYREF: + case ELEMENT_TYPE_PTR: + { + // get a name for the type we are building from + ToRelease<ICorDebugType> pFirstParameter; + if(SUCCEEDED(pType->GetFirstTypeParameter(&pFirstParameter))) + CalculateTypeName(pFirstParameter, typeName, typeNameLen); + else + swprintf_s(typeName, typeNameLen, L"<unknown>\0"); + + // append the appropriate [], *, & + switch(corElemType) + { + case ELEMENT_TYPE_SZARRAY: + wcsncat_s(typeName, typeNameLen, L"[]", typeNameLen); + return S_OK; + case ELEMENT_TYPE_ARRAY: + { + ULONG32 rank = 0; + pType->GetRank(&rank); + wcsncat_s(typeName, typeNameLen, L"[", typeNameLen); + for(ULONG32 i = 0; i < rank - 1; i++) + { + // todo- could we print out exact boundaries? + wcsncat_s(typeName, typeNameLen, L",", typeNameLen); + } + wcsncat_s(typeName, typeNameLen, L"]", typeNameLen); + } + return S_OK; + case ELEMENT_TYPE_BYREF: + wcsncat_s(typeName, typeNameLen, L"&", typeNameLen); + return S_OK; + case ELEMENT_TYPE_PTR: + wcsncat_s(typeName, typeNameLen, L"*", typeNameLen); + return S_OK; + } + } + break; + case ELEMENT_TYPE_FNPTR: + swprintf_s(typeName, typeNameLen, L"*(...)"); + break; + case ELEMENT_TYPE_TYPEDBYREF: + swprintf_s(typeName, typeNameLen, L"typedbyref"); + break; + } + return S_OK; +} + + +// Appends angle brackets and the generic argument list to a type name +HRESULT ExpressionNode::AddGenericArgs(ICorDebugType * pType, __inout_ecount(typeNameLen) WCHAR* typeName, DWORD typeNameLen) +{ + bool isFirst = true; + ToRelease<ICorDebugTypeEnum> pTypeEnum; + if(SUCCEEDED(pType->EnumerateTypeParameters(&pTypeEnum))) + { + ULONG numTypes = 0; + ToRelease<ICorDebugType> pCurrentTypeParam; + + while(SUCCEEDED(pTypeEnum->Next(1, &pCurrentTypeParam, &numTypes))) + { + if(numTypes == 0) break; + + if(isFirst) + { + isFirst = false; + wcsncat_s(typeName, typeNameLen, L"<", typeNameLen); + } + else wcsncat_s(typeName, typeNameLen, L",", typeNameLen); + + WCHAR typeParamName[mdNameLen]; + typeParamName[0] = L'\0'; + CalculateTypeName(pCurrentTypeParam, typeParamName, mdNameLen); + wcsncat_s(typeName, typeNameLen, typeParamName, typeNameLen); + } + if(!isFirst) + wcsncat_s(typeName, typeNameLen, L">", typeNameLen); + } + + return S_OK; +} + +// Determines the text name for the type of this node and caches it +HRESULT ExpressionNode::PopulateType() +{ + HRESULT Status = S_OK; + if(pTypeName[0] != 0) + return S_OK; + + //default value + swprintf_s(pTypeName, MAX_EXPRESSION, L"<unknown>"); + + // if we are displaying this type as a specific sub-type, use that + if(pTypeCast != NULL) + return CalculateTypeName(pTypeCast, pTypeName, MAX_EXPRESSION); + + // if there is no value then either we succesfully already determined the type + // name, or this node has no value or type and thus no type name. + if(pValue == NULL) + return S_OK; + + // get the type from the value and then calculate a name based on that + ToRelease<ICorDebugType> pType; + ToRelease<ICorDebugValue2> pValue2; + if(SUCCEEDED(pValue->QueryInterface(IID_ICorDebugValue2, (void**) &pValue2)) && SUCCEEDED(pValue2->GetExactType(&pType))) + return CalculateTypeName(pType, pTypeName, MAX_EXPRESSION); + + return S_OK; +} + +// Node expansion helpers + +// Inserts a new child at the end of the linked list of children +// PERF: This has O(N) insert time but these lists should never be large +VOID ExpressionNode::AddChild(ExpressionNode* pNewChild) +{ + if(pChild == NULL) + pChild = pNewChild; + else + { + ExpressionNode* pCursor = pChild; + while(pCursor->pNextSibling != NULL) + pCursor = pCursor->pNextSibling; + pCursor->pNextSibling = pNewChild; + } +} + +// Helper that determines if the current node is on the path of nodes represented by +// expression varToExpand +BOOL ExpressionNode::ShouldExpandVariable(__in_z WCHAR* varToExpand) +{ + if(pAbsoluteExpression == NULL || varToExpand == NULL) return FALSE; + + // if there is a cast operation, move past it + WCHAR* pEndCast = _wcschr(varToExpand, L')'); + varToExpand = (pEndCast == NULL) ? varToExpand : pEndCast+1; + + size_t varToExpandLen = _wcslen(varToExpand); + size_t currentExpansionLen = _wcslen(pAbsoluteExpression); + if(currentExpansionLen > varToExpandLen) return FALSE; + if(currentExpansionLen < varToExpandLen && + varToExpand[currentExpansionLen] != L'.' && + varToExpand[currentExpansionLen] != L'[') + return FALSE; + if(_wcsncmp(pAbsoluteExpression, varToExpand, currentExpansionLen) != 0) return FALSE; + + return TRUE; +} + +// Expands this array node by creating child nodes with expressions refering to individual array elements +HRESULT ExpressionNode::ExpandSzArray(ICorDebugValue* pInnerValue, __in_z WCHAR* varToExpand) +{ + HRESULT Status = S_OK; + ToRelease<ICorDebugArrayValue> pArrayValue; + IfFailRet(pInnerValue->QueryInterface(IID_ICorDebugArrayValue, (LPVOID*) &pArrayValue)); + + ULONG32 nRank; + IfFailRet(pArrayValue->GetRank(&nRank)); + if (nRank != 1) + { + _snwprintf_s(pErrorMessage, MAX_ERROR, _TRUNCATE, L"Multi-dimensional arrays NYI"); + return E_UNEXPECTED; + } + + ULONG32 cElements; + IfFailRet(pArrayValue->GetCount(&cElements)); + + //TODO: do we really want all the elements? This could be huge! + for (ULONG32 i=0; i < cElements; i++) + { + WCHAR index[20]; + swprintf_s(index, 20, L"%d", i); + + ToRelease<ICorDebugValue> pElementValue; + IfFailRet(pArrayValue->GetElementAtPosition(i, &pElementValue)); + ExpressionNode* pExpr = new ExpressionNode(pAbsoluteExpression, ChildKind_Index, index, pElementValue, NULL, pILFrame); + AddChild(pExpr); + pExpr->Expand(varToExpand); + } + return S_OK; +} + +// Expands this struct/class node by creating child nodes with expressions refering to individual field values +// and one node for the basetype value +HRESULT ExpressionNode::ExpandFields(ICorDebugValue* pInnerValue, __in_z WCHAR* varToExpand) +{ + HRESULT Status = S_OK; + + mdTypeDef currentTypeDef; + ToRelease<ICorDebugClass> pClass; + ToRelease<ICorDebugType> pType; + ToRelease<ICorDebugModule> pModule; + if(pTypeCast == NULL) + { + ToRelease<ICorDebugValue2> pValue2; + IfFailRet(pInnerValue->QueryInterface(IID_ICorDebugValue2, (LPVOID *) &pValue2)); + IfFailRet(pValue2->GetExactType(&pType)); + } + else + { + pType = pTypeCast; + pType->AddRef(); + } + IfFailRet(pType->GetClass(&pClass)); + IfFailRet(pClass->GetModule(&pModule)); + IfFailRet(pClass->GetToken(¤tTypeDef)); + + ToRelease<IUnknown> pMDUnknown; + ToRelease<IMetaDataImport> pMD; + IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown)); + IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD)); + + // If the current type has a base type that isn't object, enum, or ValueType then add a node for the base type + WCHAR baseTypeName[mdNameLen] = L"\0"; + ToRelease<ICorDebugType> pBaseType; + ExpressionNode* pBaseTypeNode = NULL; + if(SUCCEEDED(pType->GetBase(&pBaseType)) && pBaseType != NULL && SUCCEEDED(CalculateTypeName(pBaseType, baseTypeName, mdNameLen))) + { + if(_wcsncmp(baseTypeName, L"System.Enum", 11) == 0) + return S_OK; + else if(_wcsncmp(baseTypeName, L"System.Object", 13) != 0 && _wcsncmp(baseTypeName, L"System.ValueType", 16) != 0) + { + pBaseTypeNode = new ExpressionNode(pAbsoluteExpression, ChildKind_BaseClass, L"<baseclass>", pInnerValue, pBaseType, pILFrame); + AddChild(pBaseTypeNode); + } + } + + // add nodes for all the fields in this object + ULONG numFields = 0; + HCORENUM fEnum = NULL; + mdFieldDef fieldDef; + BOOL fieldExpanded = FALSE; + while(SUCCEEDED(pMD->EnumFields(&fEnum, currentTypeDef, &fieldDef, 1, &numFields)) && numFields != 0) + { + mdTypeDef classDef = 0; + ULONG nameLen = 0; + DWORD fieldAttr = 0; + WCHAR mdName[mdNameLen]; + WCHAR typeName[mdNameLen]; + CorElementType fieldDefaultValueEt; + UVCP_CONSTANT pDefaultValue; + ULONG cchDefaultValue; + if(SUCCEEDED(pMD->GetFieldProps(fieldDef, &classDef, mdName, mdNameLen, &nameLen, &fieldAttr, NULL, NULL, (DWORD*)&fieldDefaultValueEt, &pDefaultValue, &cchDefaultValue))) + { + ToRelease<ICorDebugType> pFieldType; + ToRelease<ICorDebugValue> pFieldVal; + + // static fields (of any kind - AppDomain, thread, context, RVA) + if (fieldAttr & fdStatic) + { + pType->GetStaticFieldValue(fieldDef, pILFrame, &pFieldVal); + } + // non-static fields on an object instance + else if(pInnerValue != NULL) + { + ToRelease<ICorDebugObjectValue> pObjValue; + if (SUCCEEDED(pInnerValue->QueryInterface(IID_ICorDebugObjectValue, (LPVOID*) &pObjValue))) + pObjValue->GetFieldValue(pClass, fieldDef, &pFieldVal); + } + // skip over non-static fields on static types + else + { + continue; + } + + // we didn't get a value yet and there is default value available + // need to calculate the type because there won't be a ICorDebugValue to derive it from + if(pFieldVal == NULL && pDefaultValue != NULL) + { + FindTypeFromElementType(fieldDefaultValueEt, &pFieldType); + } + + ExpressionNode* pNewChildNode = new ExpressionNode(pAbsoluteExpression, ChildKind_Field, mdName, pFieldVal, pFieldType, pILFrame, pDefaultValue, cchDefaultValue); + AddChild(pNewChildNode); + if(pNewChildNode->Expand(varToExpand) != S_FALSE) + fieldExpanded = TRUE; + } + } + pMD->CloseEnum(fEnum); + + // Only recurse to expand the base type if all of these hold: + // 1) base type exists + // 2) no field was expanded + // 3) the non-casting portion of the varToExpand doesn't match the current expression + // OR the cast exists and doesn't match + + if(pBaseTypeNode == NULL) return Status; + if(fieldExpanded) return Status; + + WCHAR* pEndCast = _wcschr(varToExpand, L')'); + WCHAR* pNonCast = (pEndCast == NULL) ? varToExpand : pEndCast+1; + if(_wcscmp(pNonCast, pAbsoluteExpression) != 0) + { + pBaseTypeNode->Expand(varToExpand); + return Status; + } + + if(varToExpand[0] == L'(' && pEndCast != NULL) + { + int cchCastTypeName = ((int)(pEndCast-1)-(int)varToExpand)/2; + PopulateType(); + if(_wcslen(pTypeName) != (cchCastTypeName) || + _wcsncmp(varToExpand+1, pTypeName, cchCastTypeName) != 0) + { + pBaseTypeNode->Expand(varToExpand); + return Status; + } + } + + return Status; +} + + +// Value Population functions + +//Helper for unwrapping values +HRESULT ExpressionNode::DereferenceAndUnboxValue(ICorDebugValue * pInputValue, ICorDebugValue** ppOutputValue, BOOL * pIsNull) +{ + HRESULT Status = S_OK; + *ppOutputValue = NULL; + if(pIsNull != NULL) *pIsNull = FALSE; + + ToRelease<ICorDebugReferenceValue> pReferenceValue; + Status = pInputValue->QueryInterface(IID_ICorDebugReferenceValue, (LPVOID*) &pReferenceValue); + if (SUCCEEDED(Status)) + { + BOOL isNull = FALSE; + IfFailRet(pReferenceValue->IsNull(&isNull)); + if(!isNull) + { + ToRelease<ICorDebugValue> pDereferencedValue; + IfFailRet(pReferenceValue->Dereference(&pDereferencedValue)); + return DereferenceAndUnboxValue(pDereferencedValue, ppOutputValue); + } + else + { + if(pIsNull != NULL) *pIsNull = TRUE; + *ppOutputValue = pInputValue; + (*ppOutputValue)->AddRef(); + return S_OK; + } + } + + ToRelease<ICorDebugBoxValue> pBoxedValue; + Status = pInputValue->QueryInterface(IID_ICorDebugBoxValue, (LPVOID*) &pBoxedValue); + if (SUCCEEDED(Status)) + { + ToRelease<ICorDebugObjectValue> pUnboxedValue; + IfFailRet(pBoxedValue->GetObject(&pUnboxedValue)); + return DereferenceAndUnboxValue(pUnboxedValue, ppOutputValue); + } + *ppOutputValue = pInputValue; + (*ppOutputValue)->AddRef(); + return S_OK; +} + +// Returns TRUE if the value derives from System.Enum +BOOL ExpressionNode::IsEnum(ICorDebugValue * pInputValue) +{ + ToRelease<ICorDebugValue> pValue; + if(FAILED(DereferenceAndUnboxValue(pInputValue, &pValue, NULL))) return FALSE; + + WCHAR baseTypeName[mdNameLen]; + ToRelease<ICorDebugValue2> pValue2; + ToRelease<ICorDebugType> pType; + ToRelease<ICorDebugType> pBaseType; + + if(FAILED(pValue->QueryInterface(IID_ICorDebugValue2, (LPVOID *) &pValue2))) return FALSE; + if(FAILED(pValue2->GetExactType(&pType))) return FALSE; + if(FAILED(pType->GetBase(&pBaseType)) || pBaseType == NULL) return FALSE; + if(FAILED(CalculateTypeName(pBaseType, baseTypeName, mdNameLen))) return FALSE; + + return (_wcsncmp(baseTypeName, L"System.Enum", 11) == 0); +} + +// Calculates the value text for nodes that have enum values +HRESULT ExpressionNode::PopulateEnumValue(ICorDebugValue* pEnumValue, BYTE* enumValue) +{ + HRESULT Status = S_OK; + + mdTypeDef currentTypeDef; + ToRelease<ICorDebugClass> pClass; + ToRelease<ICorDebugValue2> pValue2; + ToRelease<ICorDebugType> pType; + ToRelease<ICorDebugModule> pModule; + IfFailRet(pEnumValue->QueryInterface(IID_ICorDebugValue2, (LPVOID *) &pValue2)); + IfFailRet(pValue2->GetExactType(&pType)); + IfFailRet(pType->GetClass(&pClass)); + IfFailRet(pClass->GetModule(&pModule)); + IfFailRet(pClass->GetToken(¤tTypeDef)); + + ToRelease<IUnknown> pMDUnknown; + ToRelease<IMetaDataImport> pMD; + IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown)); + IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD)); + + + //First, we need to figure out the underlying enum type so that we can correctly type cast the raw values of each enum constant + //We get that from the non-static field of the enum variable (I think the field is called __value or something similar) + ULONG numFields = 0; + HCORENUM fEnum = NULL; + mdFieldDef fieldDef; + CorElementType enumUnderlyingType = ELEMENT_TYPE_END; + while(SUCCEEDED(pMD->EnumFields(&fEnum, currentTypeDef, &fieldDef, 1, &numFields)) && numFields != 0) + { + DWORD fieldAttr = 0; + PCCOR_SIGNATURE pSignatureBlob = NULL; + ULONG sigBlobLength = 0; + if(SUCCEEDED(pMD->GetFieldProps(fieldDef, NULL, NULL, 0, NULL, &fieldAttr, &pSignatureBlob, &sigBlobLength, NULL, NULL, NULL))) + { + if((fieldAttr & fdStatic) == 0) + { + CorSigUncompressCallingConv(pSignatureBlob); + enumUnderlyingType = CorSigUncompressElementType(pSignatureBlob); + break; + } + } + } + pMD->CloseEnum(fEnum); + + + //Now that we know the underlying enum type, let's decode the enum variable into OR-ed, human readable enum contants + fEnum = NULL; + bool isFirst = true; + ULONG64 remainingValue = *((ULONG64*)enumValue); + WCHAR* pTextValueCursor = pTextValue; + DWORD cchTextValueCursor = MAX_EXPRESSION; + while(SUCCEEDED(pMD->EnumFields(&fEnum, currentTypeDef, &fieldDef, 1, &numFields)) && numFields != 0) + { + ULONG nameLen = 0; + DWORD fieldAttr = 0; + WCHAR mdName[mdNameLen]; + WCHAR typeName[mdNameLen]; + UVCP_CONSTANT pRawValue = NULL; + ULONG rawValueLength = 0; + if(SUCCEEDED(pMD->GetFieldProps(fieldDef, NULL, mdName, mdNameLen, &nameLen, &fieldAttr, NULL, NULL, NULL, &pRawValue, &rawValueLength))) + { + DWORD enumValueRequiredAttributes = fdPublic | fdStatic | fdLiteral | fdHasDefault; + if((fieldAttr & enumValueRequiredAttributes) != enumValueRequiredAttributes) + continue; + + ULONG64 currentConstValue = 0; + switch (enumUnderlyingType) + { + case ELEMENT_TYPE_CHAR: + case ELEMENT_TYPE_I1: + currentConstValue = (ULONG64)(*((CHAR*)pRawValue)); + break; + case ELEMENT_TYPE_U1: + currentConstValue = (ULONG64)(*((BYTE*)pRawValue)); + break; + case ELEMENT_TYPE_I2: + currentConstValue = (ULONG64)(*((SHORT*)pRawValue)); + break; + case ELEMENT_TYPE_U2: + currentConstValue = (ULONG64)(*((USHORT*)pRawValue)); + break; + case ELEMENT_TYPE_I4: + currentConstValue = (ULONG64)(*((INT32*)pRawValue)); + break; + case ELEMENT_TYPE_U4: + currentConstValue = (ULONG64)(*((UINT32*)pRawValue)); + break; + case ELEMENT_TYPE_I8: + currentConstValue = (ULONG64)(*((LONG*)pRawValue)); + break; + case ELEMENT_TYPE_U8: + currentConstValue = (ULONG64)(*((ULONG*)pRawValue)); + break; + case ELEMENT_TYPE_I: + currentConstValue = (ULONG64)(*((int*)pRawValue)); + break; + case ELEMENT_TYPE_U: + case ELEMENT_TYPE_R4: + case ELEMENT_TYPE_R8: + // Technically U and the floating-point ones are options in the CLI, but not in the CLS or C#, so these are NYI + default: + currentConstValue = 0; + } + + if((currentConstValue == remainingValue) || ((currentConstValue != 0) && ((currentConstValue & remainingValue) == currentConstValue))) + { + remainingValue &= ~currentConstValue; + DWORD charsCopied = 0; + if(isFirst) + { + charsCopied = _snwprintf_s(pTextValueCursor, cchTextValueCursor, _TRUNCATE, L"= %s", mdName); + isFirst = false; + } + else + charsCopied = _snwprintf_s(pTextValueCursor, cchTextValueCursor, _TRUNCATE, L" | %s", mdName); + + // if an error or truncation occurred, stop copying + if(charsCopied == -1) + { + cchTextValueCursor = 0; + pTextValueCursor = NULL; + } + else + { + // charsCopied is the number of characters copied, not counting the terminating null + // this advances the cursor to point right at the terminating null so that future copies + // will concatenate the string + pTextValueCursor += charsCopied; + cchTextValueCursor -= charsCopied; + } + } + } + } + pMD->CloseEnum(fEnum); + + return Status; +} + +// Helper that caches the textual value for nodes that evaluate to a string object +HRESULT ExpressionNode::GetDebuggeeStringValue(ICorDebugValue* pInputValue, __inout_ecount(cchBuffer) WCHAR* wszBuffer, DWORD cchBuffer) +{ + HRESULT Status; + + ToRelease<ICorDebugStringValue> pStringValue; + IfFailRet(pInputValue->QueryInterface(IID_ICorDebugStringValue, (LPVOID*) &pStringValue)); + + ULONG32 cchValueReturned; + IfFailRet(pStringValue->GetString(cchBuffer, &cchValueReturned, wszBuffer)); + + return S_OK; +} + +// Retrieves the string value for a constant +HRESULT ExpressionNode::GetConstantStringValue(__inout_ecount(cchBuffer) WCHAR* wszBuffer, DWORD cchBuffer) +{ + // The string encoded in metadata isn't null-terminated + // so we need to copy it to a null terminated buffer + DWORD copyLen = cchDefaultValue; + if(copyLen > cchBuffer-1) + copyLen = cchDefaultValue; + + wcsncpy_s(wszBuffer, cchBuffer, (WCHAR*)pDefaultValue, copyLen); + return S_OK; +} + +// Helper that caches the textual value for nodes that evaluate to array objects +HRESULT ExpressionNode::PopulateSzArrayValue(ICorDebugValue* pInputValue) +{ + HRESULT Status = S_OK; + + ToRelease<ICorDebugArrayValue> pArrayValue; + IfFailRet(pInputValue->QueryInterface(IID_ICorDebugArrayValue, (LPVOID*) &pArrayValue)); + + ULONG32 nRank; + IfFailRet(pArrayValue->GetRank(&nRank)); + if (nRank != 1) + { + _snwprintf_s(pErrorMessage, MAX_EXPRESSION, _TRUNCATE, L"Multi-dimensional arrays NYI"); + return E_UNEXPECTED; + } + + ULONG32 cElements; + IfFailRet(pArrayValue->GetCount(&cElements)); + + if (cElements == 0) + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"(empty)"); + else if (cElements == 1) + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"(1 element)"); + else + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"(%d elements)", cElements); + + return S_OK; +} + +// Helper that caches the textual value for nodes of any type +HRESULT ExpressionNode::PopulateTextValueHelper() +{ + HRESULT Status = S_OK; + + BOOL isNull = TRUE; + ToRelease<ICorDebugValue> pInnerValue; + CorElementType corElemType; + ULONG32 cbSize = 0; + if(pValue != NULL) + { + IfFailRet(DereferenceAndUnboxValue(pValue, &pInnerValue, &isNull)); + + if(isNull) + { + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= null"); + return S_OK; + } + IfFailRet(pInnerValue->GetSize(&cbSize)); + IfFailRet(pInnerValue->GetType(&corElemType)); + } + else if(pDefaultValue != NULL) + { + if(pTypeCast == NULL) + { + // this shouldn't happen, but just print nothing if it does + return S_OK; + } + // This works around an irritating issue in ICorDebug. For default values + // we have to construct the ICorDebugType ourselves, however ICorDebug + // doesn't allow type construction using the correct element types. The + // caller must past CLASS or VALUETYPE even when a more specific short + // form element type is applicable. That means that later, here, we get + // back the wrong answer. To work around this we format the type as a + // string, and check it against all the known types. That allows us determine + // everything except VALUETYPE/CLASS. Thankfully that distinction is the + // one piece of data ICorDebugType will tell us if needed. + if(FAILED(GetCanonicalElementTypeForTypeName(GetTypeName(), &corElemType))) + { + pTypeCast->GetType(&corElemType); + } + + switch(corElemType) + { + case ELEMENT_TYPE_BOOLEAN: + case ELEMENT_TYPE_I1: + case ELEMENT_TYPE_U1: + cbSize = 1; + break; + + case ELEMENT_TYPE_CHAR: + case ELEMENT_TYPE_I2: + case ELEMENT_TYPE_U2: + cbSize = 2; + break; + + case ELEMENT_TYPE_I: + case ELEMENT_TYPE_U: + case ELEMENT_TYPE_I4: + case ELEMENT_TYPE_U4: + case ELEMENT_TYPE_R4: + cbSize = 4; + break; + + case ELEMENT_TYPE_I8: + case ELEMENT_TYPE_U8: + case ELEMENT_TYPE_R8: + cbSize = 8; + break; + } + } + + if (corElemType == ELEMENT_TYPE_STRING) + { + WCHAR buffer[MAX_EXPRESSION]; + buffer[0] = L'\0'; + if(pInnerValue != NULL) + GetDebuggeeStringValue(pInnerValue, buffer, MAX_EXPRESSION); + else + GetConstantStringValue(buffer, MAX_EXPRESSION); + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= \"%s\"", buffer); + } + else if (corElemType == ELEMENT_TYPE_SZARRAY) + { + return PopulateSzArrayValue(pInnerValue); + } + + + ArrayHolder<BYTE> rgbValue = new BYTE[cbSize]; + memset(rgbValue.GetPtr(), 0, cbSize * sizeof(BYTE)); + if(pInnerValue != NULL) + { + ToRelease<ICorDebugGenericValue> pGenericValue; + IfFailRet(pInnerValue->QueryInterface(IID_ICorDebugGenericValue, (LPVOID*) &pGenericValue)); + IfFailRet(pGenericValue->GetValue((LPVOID) &(rgbValue[0]))); + } + else + { + memcpy((LPVOID) &(rgbValue[0]), pDefaultValue, cbSize); + } + + //TODO: this should really be calculated from the type + if(pInnerValue != NULL && IsEnum(pInnerValue)) + { + Status = PopulateEnumValue(pInnerValue, rgbValue); + return Status; + } + + switch (corElemType) + { + default: + _snwprintf_s(pErrorMessage, MAX_ERROR, _TRUNCATE, L"Unhandled CorElementType: 0x%x", corElemType); + Status = E_FAIL; + break; + + case ELEMENT_TYPE_PTR: + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"<pointer>"); + break; + + case ELEMENT_TYPE_FNPTR: + { + CORDB_ADDRESS addr = 0; + ToRelease<ICorDebugReferenceValue> pReferenceValue = NULL; + if(SUCCEEDED(pInnerValue->QueryInterface(IID_ICorDebugReferenceValue, (LPVOID*) &pReferenceValue))) + pReferenceValue->GetValue(&addr); + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"<function pointer 0x%x>", addr); + } + break; + + case ELEMENT_TYPE_VALUETYPE: + case ELEMENT_TYPE_CLASS: + case ELEMENT_TYPE_OBJECT: + ULONG64 pointer; + if(pInnerValue != NULL && SUCCEEDED(pInnerValue->GetAddress(&pointer))) + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"@ 0x%p", (void *) pointer); + break; + + case ELEMENT_TYPE_BOOLEAN: + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %s", rgbValue[0] == 0 ? L"false" : L"true"); + break; + + case ELEMENT_TYPE_CHAR: + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= '%C'", *(WCHAR *) &(rgbValue[0])); + break; + + case ELEMENT_TYPE_I1: + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %d", *(char*) &(rgbValue[0])); + break; + + case ELEMENT_TYPE_U1: + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %d", *(unsigned char*) &(rgbValue[0])); + break; + + case ELEMENT_TYPE_I2: + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %hd", *(short*) &(rgbValue[0])); + break; + + case ELEMENT_TYPE_U2: + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %hu", *(unsigned short*) &(rgbValue[0])); + break; + + case ELEMENT_TYPE_I: + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %d", *(int*) &(rgbValue[0])); + break; + + case ELEMENT_TYPE_U: + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %u", *(unsigned int*) &(rgbValue[0])); + break; + + case ELEMENT_TYPE_I4: + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %d", *(int*) &(rgbValue[0])); + break; + + case ELEMENT_TYPE_U4: + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %u", *(unsigned int*) &(rgbValue[0])); + break; + + case ELEMENT_TYPE_I8: + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %I64d", *(__int64*) &(rgbValue[0])); + break; + + case ELEMENT_TYPE_U8: + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %I64u", *(unsigned __int64*) &(rgbValue[0])); + break; + + case ELEMENT_TYPE_R4: + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %f", (double) *(float*) &(rgbValue[0])); + break; + + case ELEMENT_TYPE_R8: + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"%f", *(double*) &(rgbValue[0])); + break; + + // TODO: The following corElementTypes are not yet implemented here. Array + // might be interesting to add, though the others may be of rather limited use: + // ELEMENT_TYPE_ARRAY = 0x14, // MDARRAY <type> <rank> <bcount> <bound1> ... <lbcount> <lb1> ... + // + // ELEMENT_TYPE_GENERICINST = 0x15, // GENERICINST <generic type> <argCnt> <arg1> ... <argn> + } + + return Status; +} + +// Caches the textual value of this node +HRESULT ExpressionNode::PopulateTextValue() +{ + if(pErrorMessage[0] != 0) + return E_UNEXPECTED; + if(pValue == NULL && pDefaultValue == NULL) + return S_OK; + HRESULT Status = PopulateTextValueHelper(); + if(FAILED(Status) && pErrorMessage[0] == 0) + { + _snwprintf_s(pErrorMessage, MAX_ERROR, _TRUNCATE, L"Error in PopulateTextValueHelper: 0x%x", Status); + } + return Status; +} + + +// Expression parsing and search + +//Callback that searches a frame to determine if it contains a local variable or parameter of a given name +VOID ExpressionNode::EvaluateExpressionFrameScanCallback(ICorDebugFrame* pFrame, VOID* pUserData) +{ + EvaluateExpressionFrameScanData* pData = (EvaluateExpressionFrameScanData*)pUserData; + + // we already found what we were looking for, just continue + if(pData->pFoundValue != NULL) + return; + + // if any of these fail we just continue on + // querying for ILFrame will frequently fail because many frames aren't IL + ToRelease<ICorDebugILFrame> pILFrame; + HRESULT Status = pFrame->QueryInterface(IID_ICorDebugILFrame, (LPVOID*) &pILFrame); + if (FAILED(Status)) + { + return; + } + // we need to save off the first frame we find regardless of whether we find the + // local or not. We might need this frame later for static field lookup. + if(pData->pFirstFrame == NULL) + { + pData->pFirstFrame = pILFrame; + pData->pFirstFrame->AddRef(); + } + // not all IL frames map to an assembly (ex. LCG) + ToRelease<ICorDebugFunction> pFunction; + Status = pFrame->GetFunction(&pFunction); + if (FAILED(Status)) + { + return; + } + // from here down shouldn't generally fail, but just in case + mdMethodDef methodDef; + Status = pFunction->GetToken(&methodDef); + if (FAILED(Status)) + { + return; + } + ToRelease<ICorDebugModule> pModule; + Status = pFunction->GetModule(&pModule); + if (FAILED(Status)) + { + return; + } + ToRelease<IUnknown> pMDUnknown; + Status = pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown); + if (FAILED(Status)) + { + return; + } + ToRelease<IMetaDataImport> pMD; + Status = pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD); + if (FAILED(Status)) + { + return; + } + + pData->pFoundFrame = pILFrame; + pData->pFoundFrame->AddRef(); + // Enumerate all the parameters + EnumerateParameters(pMD, methodDef, pILFrame, EvaluateExpressionVariableScanCallback, pUserData); + // Enumerate all the locals + EnumerateLocals(pMD, methodDef, pILFrame, EvaluateExpressionVariableScanCallback, pUserData); + + // if we didn't find it in this frame then clear the frame back out + if(pData->pFoundValue == NULL) + { + pData->pFoundFrame = NULL; + } + + return; +} + +//Callback checks to see if a given local/parameter has name pName +VOID ExpressionNode::EvaluateExpressionVariableScanCallback(ICorDebugValue* pValue, __in_z WCHAR* pName, __out_z WCHAR* pErrorMessage, VOID* pUserData) +{ + EvaluateExpressionFrameScanData* pData = (EvaluateExpressionFrameScanData*)pUserData; + if(_wcscmp(pName, pData->pIdentifier) == 0) + { + // found it + pData->pFoundValue = pValue; + pValue->AddRef(); + } + return; +} + +//Factory method that recursively parses pExpression and create an ExpressionNode +// pExpression - the entire expression being parsed +// pExpressionRemainder - the portion of the expression that remains to be parsed in this +// recursive invocation +// charactersParsed - the number of characters that have been parsed from pExpression +// so far (todo: this is basically the difference between remainder and +// full expression, do we need it?) +// pParsedValue - A debuggee value that should be used as the context for interpreting +// pExpressionRemainder +// pParsedType - A debuggee type that should be used as the context for interpreting +// pExpressionRemainder. +// pParsedDefaultValue - A fixed value from metadata that should be used as context for +// interpretting pExpressionRemainder +// cchParsedDefaultValue- Size of pParsedDefaultValue +// pFrame - A debuggee IL frame that disambiguates the thread and context needed +// to evaluate a thread-static or context-static value +// ppExpressionNode - OUT - the resulting expression node +// +// +// Valid combinations of state comming into this method: +// The expression up to charactersParsed isn't recognized yet: +// pParsedValue = pParsedType = pParsedDefaultValue = NULL +// cchParsedDefaultValue = 0 +// The expression up to charactersParsed is a recognized type: +// pParsedType = <parsed type> +// pParsedValue = pParsedDefaultValue = NULL +// cchParsedDefaultValue = 0 +// The expression up to charactersParsed is a recognized value in the debuggee: +// pParsedValue = <parsed value> +// pParsedType = pParsedDefaultValue = NULL +// cchParsedDefaultValue = 0 +// The expression up to charactersParsed is a recognized default value stored in metadata: +// pParsedValue = NULL +// pParsedType = <type calculated from metadata> +// pParsedDefaultValue = <value from metadata> +// cchParsedDefaultValue = <size of metadata value> +// +// +// REFACTORING NOTE: This method is very similar (but not identical) to the expansion logic +// in ExpressionNode. The primary difference is that the nodes expand all +// fields/indices whereas this function only expands along a precise route. +// If the ExpressionNode code where enhanced to support expanding precisely +// large portions of this function could be disposed of. As soon as the function +// matched the initial name it could create an ExpressionNode and then use the +// ExpressionNode expansion functions to drill down to the actual node required. +// Also need to make sure the nodes manage lifetime ok when a parent is destroyed +// but a child node is still referenced. +HRESULT ExpressionNode::CreateExpressionNodeHelper(__in_z WCHAR* pExpression, + __in_z WCHAR* pExpressionParseRemainder, + DWORD charactersParsed, + ICorDebugValue* pParsedValue, + ICorDebugType* pParsedType, + UVCP_CONSTANT pParsedDefaultValue, + ULONG cchParsedDefaultValue, + ICorDebugILFrame* pFrame, + ExpressionNode** ppExpressionNode) +{ + HRESULT Status = S_OK; + WCHAR* pExpressionCursor = pExpressionParseRemainder; + DWORD currentCharsParsed = charactersParsed; + WCHAR pIdentifier[mdNameLen]; + pIdentifier[0] = 0; + BOOL isArray = FALSE; + WCHAR pResultBuffer[MAX_EXPRESSION]; + + // Get the next name from the expression string + if(FAILED(Status = ParseNextIdentifier(&pExpressionCursor, pIdentifier, mdNameLen, pResultBuffer, MAX_EXPRESSION, ¤tCharsParsed, &isArray))) + { + *ppExpressionNode = new ExpressionNode(pExpression, pResultBuffer); + if(*ppExpressionNode == NULL) + return E_OUTOFMEMORY; + else + return S_OK; + } + + // we've gone as far as we need, nothing left to parse + if(Status == S_FALSE) + { + ToRelease<ICorDebugValue> pValue; + *ppExpressionNode = new ExpressionNode(pExpression, ChildKind_BaseClass, pExpression, pParsedValue, pParsedType, pFrame, pParsedDefaultValue, cchParsedDefaultValue); + if(*ppExpressionNode == NULL) + return E_OUTOFMEMORY; + else + return S_OK; + } + // if we are just starting and have no context then we need to search locals/parameters/type names + else if(pParsedValue == NULL && pParsedType == NULL) + { + // the first identifier must be a name, not an indexing expression + if(isArray) + { + *ppExpressionNode = new ExpressionNode(pExpression, L"Expression must begin with a local variable, parameter, or fully qualified type name"); + return S_OK; + } + + // scan for root on stack + EvaluateExpressionFrameScanData data; + data.pIdentifier = pIdentifier; + data.pFoundValue = NULL; + data.pFoundFrame = NULL; + data.pFirstFrame = NULL; + data.pErrorMessage = pResultBuffer; + data.cchErrorMessage = MAX_EXPRESSION; + EnumerateFrames(EvaluateExpressionFrameScanCallback, (VOID*) &data); + + if(data.pFoundValue != NULL) + { + // found the root, now recurse along the expression + return CreateExpressionNodeHelper(pExpression, pExpressionCursor, currentCharsParsed, data.pFoundValue, NULL, NULL, 0, data.pFoundFrame, ppExpressionNode); + } + + // didn't find it - search the type table for a matching name + WCHAR pName[MAX_EXPRESSION]; + while(true) + { + wcsncpy_s(pName, MAX_EXPRESSION, pExpression, currentCharsParsed); + ToRelease<ICorDebugType> pType; + if(SUCCEEDED(FindTypeByName(pName, &pType))) + return CreateExpressionNodeHelper(pExpression, pExpressionCursor, currentCharsParsed, NULL, pType, NULL, 0, data.pFirstFrame, ppExpressionNode); + + if(FAILED(Status = ParseNextIdentifier(&pExpressionCursor, pIdentifier, mdNameLen, pResultBuffer, MAX_EXPRESSION, ¤tCharsParsed, &isArray))) + { + *ppExpressionNode = new ExpressionNode(pExpression, pResultBuffer); + return S_OK; + } + else if(Status == S_FALSE) + { + break; + } + } + + WCHAR errorMessage[MAX_ERROR]; + swprintf_s(errorMessage, MAX_ERROR, L"No expression prefix could not be matched to an existing type, parameter, or local"); + *ppExpressionNode = new ExpressionNode(pExpression, errorMessage); + return S_OK; + } + + // we've got some context from an earlier portion of the search, now just need to continue + // by dereferencing and indexing until we reach the end of the expression + + // Figure out the type, module, and metadata from our context information + ToRelease<ICorDebugType> pType; + BOOL isNull = TRUE; + ToRelease<ICorDebugValue> pInnerValue = NULL; + if(pParsedValue != NULL) + { + IfFailRet(DereferenceAndUnboxValue(pParsedValue, &pInnerValue, &isNull)); + + if(isNull) + { + WCHAR parsedExpression[MAX_EXPRESSION]; + wcsncpy_s(parsedExpression, MAX_EXPRESSION, pExpression, charactersParsed); + WCHAR errorMessage[MAX_ERROR]; + swprintf_s(errorMessage, MAX_ERROR, L"Dereferencing \'%s\' throws NullReferenceException", parsedExpression); + *ppExpressionNode = new ExpressionNode(pExpression, errorMessage); + return S_OK; + } + + ToRelease<ICorDebugValue2> pValue2; + IfFailRet(pInnerValue->QueryInterface(IID_ICorDebugValue2, (LPVOID *) &pValue2)); + IfFailRet(pValue2->GetExactType(&pType)); + CorElementType et; + IfFailRet(pType->GetType(&et)); + while(et == ELEMENT_TYPE_ARRAY || et == ELEMENT_TYPE_SZARRAY || et == ELEMENT_TYPE_BYREF || et == ELEMENT_TYPE_PTR) + { + pType->GetFirstTypeParameter(&pType); + IfFailRet(pType->GetType(&et)); + } + } + else + { + pType = pParsedType; + pType->AddRef(); + } + ToRelease<ICorDebugClass> pClass; + IfFailRet(pType->GetClass(&pClass)); + ToRelease<ICorDebugModule> pModule; + IfFailRet(pClass->GetModule(&pModule)); + mdTypeDef currentTypeDef; + IfFailRet(pClass->GetToken(¤tTypeDef)); + + ToRelease<IUnknown> pMDUnknown; + ToRelease<IMetaDataImport> pMD; + IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown)); + IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD)); + + + // if we are searching along and this is an array index dereference + if(isArray) + { + ToRelease<ICorDebugArrayValue> pArrayValue; + if(pInnerValue == NULL || FAILED(Status = pInnerValue->QueryInterface(IID_ICorDebugArrayValue, (LPVOID*) &pArrayValue))) + { + WCHAR errorMessage[MAX_ERROR]; + swprintf_s(errorMessage, MAX_ERROR, L"Index notation only supported for instances of an array type"); + *ppExpressionNode = new ExpressionNode(pExpression, errorMessage); + return S_OK; + } + + ULONG32 nRank; + IfFailRet(pArrayValue->GetRank(&nRank)); + if (nRank != 1) + { + WCHAR errorMessage[MAX_ERROR]; + swprintf_s(errorMessage, MAX_ERROR, L"Multi-dimensional arrays NYI"); + *ppExpressionNode = new ExpressionNode(pExpression, errorMessage); + return S_OK; + } + + int index = -1; + if(swscanf_s(pIdentifier, L"%d", &index) != 1) + { + WCHAR errorMessage[MAX_ERROR]; + swprintf_s(errorMessage, MAX_ERROR, L"Failed to parse expression, missing or invalid index expression at character %d", charactersParsed+1); + *ppExpressionNode = new ExpressionNode(pExpression, errorMessage); + return S_OK; + } + + ULONG32 cElements; + IfFailRet(pArrayValue->GetCount(&cElements)); + if(index < 0 || (ULONG32)index >= cElements) + { + WCHAR errorMessage[MAX_ERROR]; + swprintf_s(errorMessage, MAX_ERROR, L"Index is out of range for this array"); + *ppExpressionNode = new ExpressionNode(pExpression, errorMessage); + return S_OK; + } + + ToRelease<ICorDebugValue> pElementValue; + IfFailRet(pArrayValue->GetElementAtPosition(index, &pElementValue)); + return CreateExpressionNodeHelper(pExpression, pExpressionCursor, currentCharsParsed, pElementValue, NULL, NULL, 0, pFrame, ppExpressionNode); + } + // if we are searching along and this is field dereference + else + { + ToRelease<ICorDebugType> pBaseType = pType; + pBaseType->AddRef(); + + while(pBaseType != NULL) + { + // get the current base type class/token/MD + ToRelease<ICorDebugClass> pBaseClass; + IfFailRet(pBaseType->GetClass(&pBaseClass)); + ToRelease<ICorDebugModule> pBaseTypeModule; + IfFailRet(pBaseClass->GetModule(&pBaseTypeModule)); + mdTypeDef baseTypeDef; + IfFailRet(pBaseClass->GetToken(&baseTypeDef)); + ToRelease<IUnknown> pBaseTypeMDUnknown; + ToRelease<IMetaDataImport> pBaseTypeMD; + IfFailRet(pBaseTypeModule->GetMetaDataInterface(IID_IMetaDataImport, &pBaseTypeMDUnknown)); + IfFailRet(pBaseTypeMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pBaseTypeMD)); + + + // iterate through all fields at this level of the class hierarchy + ULONG numFields = 0; + HCORENUM fEnum = NULL; + mdFieldDef fieldDef; + while(SUCCEEDED(pMD->EnumFields(&fEnum, baseTypeDef, &fieldDef, 1, &numFields)) && numFields != 0) + { + ULONG nameLen = 0; + DWORD fieldAttr = 0; + WCHAR mdName[mdNameLen]; + WCHAR typeName[mdNameLen]; + CorElementType fieldDefaultValueEt; + UVCP_CONSTANT pDefaultValue; + ULONG cchDefaultValue; + if(SUCCEEDED(pBaseTypeMD->GetFieldProps(fieldDef, NULL, mdName, mdNameLen, &nameLen, &fieldAttr, NULL, NULL, (DWORD*)&fieldDefaultValueEt, &pDefaultValue, &cchDefaultValue)) && + _wcscmp(mdName, pIdentifier) == 0) + { + ToRelease<ICorDebugType> pFieldValType = NULL; + ToRelease<ICorDebugValue> pFieldVal; + if (fieldAttr & fdStatic) + pBaseType->GetStaticFieldValue(fieldDef, pFrame, &pFieldVal); + else if(pInnerValue != NULL) + { + ToRelease<ICorDebugObjectValue> pObjValue; + if (SUCCEEDED(pInnerValue->QueryInterface(IID_ICorDebugObjectValue, (LPVOID*) &pObjValue))) + pObjValue->GetFieldValue(pBaseClass, fieldDef, &pFieldVal); + } + + // we didn't get a value yet and there is default value available + // need to calculate the type because there won't be a ICorDebugValue to derive it from + if(pFieldVal == NULL && pDefaultValue != NULL) + { + FindTypeFromElementType(fieldDefaultValueEt, &pFieldValType); + } + else + { + // if we aren't using default value, make sure it is cleared out + pDefaultValue = NULL; + cchDefaultValue = 0; + } + + // if we still don't have a value, check if we are trying to get an instance field from a static type + if(pInnerValue == NULL && pFieldVal == NULL && pDefaultValue == NULL) + { + WCHAR pObjectTypeName[MAX_EXPRESSION]; + CalculateTypeName(pBaseType, pObjectTypeName, MAX_EXPRESSION); + WCHAR errorMessage[MAX_ERROR]; + swprintf_s(errorMessage, MAX_ERROR, L"Can not evaluate instance field \'%s\' from static type \'%s\'", pIdentifier, pObjectTypeName); + *ppExpressionNode = new ExpressionNode(pExpression, errorMessage); + return S_OK; + } + return CreateExpressionNodeHelper(pExpression, pExpressionCursor, currentCharsParsed, pFieldVal, pFieldValType, pDefaultValue, cchDefaultValue, pFrame, ppExpressionNode); + } + } + + //advance to next base type + ICorDebugType* pTemp = NULL; + pBaseType->GetBase(&pTemp); + pBaseType = pTemp; + } + + WCHAR pObjectTypeName[MAX_EXPRESSION]; + CalculateTypeName(pType, pObjectTypeName, MAX_EXPRESSION); + WCHAR errorMessage[MAX_ERROR]; + swprintf_s(errorMessage, MAX_ERROR, L"Field \'%s\' does not exist in type \'%s\'", pIdentifier, pObjectTypeName); + *ppExpressionNode = new ExpressionNode(pExpression, errorMessage); + return S_OK; + } + + return Status; +} + +// Splits apart a C#-like expression and determines the first identifier in the string and updates expression to point +// at the remaining unparsed portion +HRESULT ExpressionNode::ParseNextIdentifier(__in_z WCHAR** expression, __inout_ecount(cchIdentifierName) WCHAR* identifierName, DWORD cchIdentifierName, __inout_ecount(cchErrorMessage) WCHAR* errorMessage, DWORD cchErrorMessage, DWORD* charactersParsed, BOOL* isArrayIndex) +{ + + // This algorithm is best understood as a two stage process. The first stage splits + // the expression into two chunks an identifier and a remaining expression. The second stage + // normalizes the identifier. The splitting algorithm doesn't care if identifiers are well-formed + // at all, we do some error checking in the 2nd stage though. For the splitting stage, an identifier is + // any first character, followed by as many characters as possible that aren't a '.' or a '['. + // In the 2nd stage any '.' character is removed from the front (the only place it could be) + // and enclosing braces are removed. An error is recorded if the identifier ends 0 length or the + // opening bracket isn't matched. Here is an example showing how we would parse an expression + // which is deliberately not very well formed. Each line is the result of calling this function once... it + // takes many calls to break the entire expression down. + // + // expression 1st stage identifier 2nd stage identifier + // foo.bar[18..f[1.][2][[ + // .bar[18..f[1.][2][[ foo foo + // [18..f[1.][2][[ .bar bar + // ..f[1.][2][[ [18 error no ] + // .f[1.][2][[ . error 0-length + // [1.][2][[ .f f + // .][2][[ [1 error no ] + // [2][[ .] ] (we don't error check legal CLI identifier name characters) + // [[ [2] 2 + // [ [ error no ] + // [ error no ] + + // not an error, just the end of the expression + if(*expression == NULL || **expression == 0) + return S_FALSE; + + WCHAR* expressionStart = *expression; + DWORD currentCharsParsed = *charactersParsed; + DWORD identifierLen = (DWORD) _wcscspn(expressionStart, L".["); + // if the first character was a . or [ skip over it. Note that we don't + // do this always in case the first WCHAR was part of a surrogate pair + if(identifierLen == 0) + { + identifierLen = (DWORD) _wcscspn(expressionStart+1, L".[") + 1; + } + + *expression += identifierLen; + *charactersParsed += identifierLen; + + // done with the first stage splitting, on to 2nd stage + + // a . should be followed by field name + if(*expressionStart == L'.') + { + if(identifierLen == 1) // 0-length after . + { + swprintf_s(errorMessage, cchErrorMessage, L"Failed to parse expression, missing name after character %d", currentCharsParsed+1); + return E_FAIL; + } + if(identifierLen-1 >= cchIdentifierName) + { + swprintf_s(errorMessage, cchErrorMessage, L"Failed to parse expression, name at character %d is too long", currentCharsParsed+2); + return E_FAIL; + } + *isArrayIndex = FALSE; + wcsncpy_s(identifierName, cchIdentifierName, expressionStart+1, identifierLen-1); + return S_OK; + } + // an open bracket should be followed by a decimal value and then a closing bracket + else if(*expressionStart == L'[') + { + if(*(expressionStart+identifierLen-1) != L']') + { + swprintf_s(errorMessage, cchErrorMessage, L"Failed to parse expression, missing or invalid index expression at character %d", currentCharsParsed+1); + return E_FAIL; + } + if(identifierLen <= 2) // 0-length between [] + { + swprintf_s(errorMessage, cchErrorMessage, L"Failed to parse expression, missing index after character %d", currentCharsParsed+1); + return E_FAIL; + } + if(identifierLen-2 >= cchIdentifierName) + { + swprintf_s(errorMessage, cchErrorMessage, L"Failed to parse expression, index at character %d is too large", currentCharsParsed+2); + return E_FAIL; + } + *isArrayIndex = TRUE; + wcsncpy_s(identifierName, cchIdentifierName, expressionStart+1, identifierLen-2); + return S_OK; + } + else // no '.' or '[', this is an initial name + { + if(identifierLen == 0) // 0-length + { + swprintf_s(errorMessage, cchErrorMessage, L"Failed to parse expression, missing name after character %d", currentCharsParsed+1); + return E_FAIL; + } + if(identifierLen >= cchIdentifierName) + { + swprintf_s(errorMessage, cchErrorMessage, L"Failed to parse expression, name at character %d is too long", currentCharsParsed+1); + return E_FAIL; + } + *isArrayIndex = FALSE; + wcsncpy_s(identifierName, cchIdentifierName, expressionStart, identifierLen); + return S_OK; + } +} + + +// Iterate through all parameters in the ILFrame calling the callback function for each of them +HRESULT ExpressionNode::EnumerateParameters(IMetaDataImport * pMD, + mdMethodDef methodDef, + ICorDebugILFrame * pILFrame, + VariableEnumCallback pCallback, + VOID* pUserData) +{ + HRESULT Status = S_OK; + + ULONG cParams = 0; + ToRelease<ICorDebugValueEnum> pParamEnum; + IfFailRet(pILFrame->EnumerateArguments(&pParamEnum)); + IfFailRet(pParamEnum->GetCount(&cParams)); + DWORD methAttr = 0; + IfFailRet(pMD->GetMethodProps(methodDef, NULL, NULL, 0, NULL, &methAttr, NULL, NULL, NULL, NULL)); + for (ULONG i=0; i < cParams; i++) + { + ULONG paramNameLen = 0; + mdParamDef paramDef; + WCHAR paramName[mdNameLen] = L"\0"; + + if(i == 0 && (methAttr & mdStatic) == 0) + swprintf_s(paramName, mdNameLen, L"this\0"); + else + { + int idx = ((methAttr & mdStatic) == 0)? i : (i + 1); + if(SUCCEEDED(pMD->GetParamForMethodIndex(methodDef, idx, ¶mDef))) + pMD->GetParamProps(paramDef, NULL, NULL, paramName, mdNameLen, ¶mNameLen, NULL, NULL, NULL, NULL); + } + if(_wcslen(paramName) == 0) + swprintf_s(paramName, mdNameLen, L"param_%d\0", i); + + ToRelease<ICorDebugValue> pValue; + ULONG cArgsFetched; + WCHAR pErrorMessage[MAX_ERROR] = L"\0"; + HRESULT hr = pParamEnum->Next(1, &pValue, &cArgsFetched); + if (FAILED(hr)) + { + swprintf_s(pErrorMessage, MAX_ERROR, L" + (Error 0x%x retrieving parameter '%S')\n", hr, paramName); + } + if (hr == S_FALSE) + { + break; + } + pCallback(pValue, paramName, pErrorMessage, pUserData); + } + + return Status; +} + +// Enumerate all locals in the given ILFrame, calling the callback method for each of them +HRESULT ExpressionNode::EnumerateLocals(IMetaDataImport * pMD, + mdMethodDef methodDef, + ICorDebugILFrame * pILFrame, + VariableEnumCallback pCallback, + VOID* pUserData) +{ + HRESULT Status = S_OK; + ULONG cLocals = 0; + ToRelease<ICorDebugFunction> pFunction; + ToRelease<ICorDebugModule> pModule; + if(SUCCEEDED(pILFrame->GetFunction(&pFunction))) + { + IfFailRet(pFunction->GetModule(&pModule)); + } + ToRelease<ICorDebugValueEnum> pLocalsEnum; + IfFailRet(pILFrame->EnumerateLocalVariables(&pLocalsEnum)); + IfFailRet(pLocalsEnum->GetCount(&cLocals)); + if (cLocals > 0) + { + SymbolReader symReader; + bool symbolsAvailable = false; + if(pModule != NULL && SUCCEEDED(symReader.LoadSymbols(pMD, pModule))) + symbolsAvailable = true; + + for (ULONG i=0; i < cLocals; i++) + { + ULONG paramNameLen = 0; + WCHAR paramName[mdNameLen] = L"\0"; + WCHAR pErrorMessage[MAX_ERROR] = L"\0"; + ToRelease<ICorDebugValue> pValue; + HRESULT hr = S_OK; + if(symbolsAvailable) + hr = symReader.GetNamedLocalVariable(pILFrame, i, paramName, mdNameLen, &pValue); + else + { + ULONG cArgsFetched; + hr = pLocalsEnum->Next(1, &pValue, &cArgsFetched); + } + if(_wcslen(paramName) == 0) + swprintf_s(paramName, mdNameLen, L"local_%d\0", i); + + if (FAILED(hr)) + { + swprintf_s(pErrorMessage, MAX_ERROR, L" + (Error 0x%x retrieving local variable '%S')\n", hr, paramName); + } + else if (hr == S_FALSE) + { + break; + } + pCallback(pValue, paramName, pErrorMessage, pUserData); + } + } + + return Status; +} + +// Iterates over all frames on the current thread's stack, calling the callback function for each of them +HRESULT ExpressionNode::EnumerateFrames(FrameEnumCallback pCallback, VOID* pUserData) +{ + HRESULT Status = S_OK; + ToRelease<ICorDebugThread> pThread; + ToRelease<ICorDebugThread3> pThread3; + ToRelease<ICorDebugStackWalk> pStackWalk; + ULONG ulThreadID = 0; + g_ExtSystem->GetCurrentThreadSystemId(&ulThreadID); + + IfFailRet(g_pCorDebugProcess->GetThread(ulThreadID, &pThread)); + IfFailRet(pThread->QueryInterface(IID_ICorDebugThread3, (LPVOID *) &pThread3)); + IfFailRet(pThread3->CreateStackWalk(&pStackWalk)); + + InternalFrameManager internalFrameManager; + IfFailRet(internalFrameManager.Init(pThread3)); + + int currentFrame = -1; + + for (Status = S_OK; ; Status = pStackWalk->Next()) + { + currentFrame++; + + if (Status == CORDBG_S_AT_END_OF_STACK) + { + break; + } + IfFailRet(Status); + + if (IsInterrupt()) + { + ExtOut("<interrupted>\n"); + break; + } + + CROSS_PLATFORM_CONTEXT context; + ULONG32 cbContextActual; + if ((Status=pStackWalk->GetContext( + DT_CONTEXT_FULL, + sizeof(context), + &cbContextActual, + (BYTE *)&context))!=S_OK) + { + ExtOut("GetFrameContext failed: %lx\n",Status); + break; + } + + ToRelease<ICorDebugFrame> pFrame; + IfFailRet(pStackWalk->GetFrame(&pFrame)); + if (Status == S_FALSE) + { + Status = S_OK; + continue; + } + + pCallback(pFrame, pUserData); + } + + return Status; +} + +// Determines the corresponding ICorDebugType for a given primitive type +HRESULT ExpressionNode::FindTypeFromElementType(CorElementType et, ICorDebugType** ppType) +{ + HRESULT Status; + switch (et) + { + default: + Status = E_FAIL; + break; + + case ELEMENT_TYPE_BOOLEAN: + Status = FindTypeByName(L"System.Boolean", ppType); + break; + + case ELEMENT_TYPE_CHAR: + Status = FindTypeByName(L"System.Char", ppType); + break; + + case ELEMENT_TYPE_I1: + Status = FindTypeByName(L"System.SByte", ppType); + break; + + case ELEMENT_TYPE_U1: + Status = FindTypeByName(L"System.Byte", ppType); + break; + + case ELEMENT_TYPE_I2: + Status = FindTypeByName(L"System.Short", ppType); + break; + + case ELEMENT_TYPE_U2: + Status = FindTypeByName(L"System.UShort", ppType); + break; + + case ELEMENT_TYPE_I: + Status = FindTypeByName(L"System.Int32", ppType); + break; + + case ELEMENT_TYPE_U: + Status = FindTypeByName(L"System.UInt32", ppType); + break; + + case ELEMENT_TYPE_I4: + Status = FindTypeByName(L"System.Int32", ppType); + break; + + case ELEMENT_TYPE_U4: + Status = FindTypeByName(L"System.UInt32", ppType); + break; + + case ELEMENT_TYPE_I8: + Status = FindTypeByName(L"System.Int64", ppType); + break; + + case ELEMENT_TYPE_U8: + Status = FindTypeByName(L"System.UInt64", ppType); + break; + + case ELEMENT_TYPE_R4: + Status = FindTypeByName(L"System.Single", ppType); + break; + + case ELEMENT_TYPE_R8: + Status = FindTypeByName(L"System.Double", ppType); + break; + + case ELEMENT_TYPE_OBJECT: + Status = FindTypeByName(L"System.Object", ppType); + break; + + case ELEMENT_TYPE_STRING: + Status = FindTypeByName(L"System.String", ppType); + break; + } + return Status; +} + +// Gets the appropriate element type encoding for well-known fully qualified type names +// This doesn't work for arbitrary types, just types that have CorElementType short forms. +HRESULT ExpressionNode::GetCanonicalElementTypeForTypeName(__in_z WCHAR* pTypeName, CorElementType *et) +{ + //Sadly ICorDebug deliberately prevents creating ICorDebugType instances + //that use canonical short form element types... seems like an issue to me. + + if(_wcscmp(pTypeName, L"System.String")==0) + { + *et = ELEMENT_TYPE_STRING; + } + else if(_wcscmp(pTypeName, L"System.Object")==0) + { + *et = ELEMENT_TYPE_OBJECT; + } + else if(_wcscmp(pTypeName, L"System.Void")==0) + { + *et = ELEMENT_TYPE_VOID; + } + else if(_wcscmp(pTypeName, L"System.Boolean")==0) + { + *et = ELEMENT_TYPE_BOOLEAN; + } + else if(_wcscmp(pTypeName, L"System.Char")==0) + { + *et = ELEMENT_TYPE_CHAR; + } + else if(_wcscmp(pTypeName, L"System.Byte")==0) + { + *et = ELEMENT_TYPE_U1; + } + else if(_wcscmp(pTypeName, L"System.Sbyte")==0) + { + *et = ELEMENT_TYPE_I1; + } + else if(_wcscmp(pTypeName, L"System.Int16")==0) + { + *et = ELEMENT_TYPE_I2; + } + else if(_wcscmp(pTypeName, L"System.UInt16")==0) + { + *et = ELEMENT_TYPE_U2; + } + else if(_wcscmp(pTypeName, L"System.UInt32")==0) + { + *et = ELEMENT_TYPE_U4; + } + else if(_wcscmp(pTypeName, L"System.Int32")==0) + { + *et = ELEMENT_TYPE_I4; + } + else if(_wcscmp(pTypeName, L"System.UInt64")==0) + { + *et = ELEMENT_TYPE_U8; + } + else if(_wcscmp(pTypeName, L"System.Int64")==0) + { + *et = ELEMENT_TYPE_I8; + } + else if(_wcscmp(pTypeName, L"System.Single")==0) + { + *et = ELEMENT_TYPE_R4; + } + else if(_wcscmp(pTypeName, L"System.Double")==0) + { + *et = ELEMENT_TYPE_R8; + } + else if(_wcscmp(pTypeName, L"System.IntPtr")==0) + { + *et = ELEMENT_TYPE_U; + } + else if(_wcscmp(pTypeName, L"System.UIntPtr")==0) + { + *et = ELEMENT_TYPE_I; + } + else if(_wcscmp(pTypeName, L"System.TypedReference")==0) + { + *et = ELEMENT_TYPE_TYPEDBYREF; + } + else + { + return E_FAIL; // can't tell from a name whether it should be valuetype or class + } + return S_OK; +} + +// Searches the debuggee for any ICorDebugType that matches the given fully qualified name +// This will search across all AppDomains and Assemblies +HRESULT ExpressionNode::FindTypeByName(__in_z WCHAR* pTypeName, ICorDebugType** ppType) +{ + HRESULT Status = S_OK; + ToRelease<ICorDebugAppDomainEnum> pAppDomainEnum; + IfFailRet(g_pCorDebugProcess->EnumerateAppDomains(&pAppDomainEnum)); + DWORD count; + IfFailRet(pAppDomainEnum->GetCount(&count)); + for(DWORD i = 0; i < count; i++) + { + ToRelease<ICorDebugAppDomain> pAppDomain; + DWORD countFetched = 0; + IfFailRet(pAppDomainEnum->Next(1, &pAppDomain, &countFetched)); + Status = FindTypeByName(pAppDomain, pTypeName, ppType); + if(SUCCEEDED(Status)) + break; + } + + return Status; +} + +// Searches the debuggee for any ICorDebugType that matches the given fully qualified name +// This will search across all Assemblies in the given AppDomain +HRESULT ExpressionNode::FindTypeByName(ICorDebugAppDomain* pAppDomain, __in_z WCHAR* pTypeName, ICorDebugType** ppType) +{ + HRESULT Status = S_OK; + ToRelease<ICorDebugAssemblyEnum> pAssemblyEnum; + IfFailRet(pAppDomain->EnumerateAssemblies(&pAssemblyEnum)); + DWORD count; + IfFailRet(pAssemblyEnum->GetCount(&count)); + for(DWORD i = 0; i < count; i++) + { + ToRelease<ICorDebugAssembly> pAssembly; + DWORD countFetched = 0; + IfFailRet(pAssemblyEnum->Next(1, &pAssembly, &countFetched)); + Status = FindTypeByName(pAssembly, pTypeName, ppType); + if(SUCCEEDED(Status)) + break; + } + + return Status; +} + +// Searches the assembly for any ICorDebugType that matches the given fully qualified name +HRESULT ExpressionNode::FindTypeByName(ICorDebugAssembly* pAssembly, __in_z WCHAR* pTypeName, ICorDebugType** ppType) +{ + HRESULT Status = S_OK; + ToRelease<ICorDebugModuleEnum> pModuleEnum; + IfFailRet(pAssembly->EnumerateModules(&pModuleEnum)); + DWORD count; + IfFailRet(pModuleEnum->GetCount(&count)); + for(DWORD i = 0; i < count; i++) + { + ToRelease<ICorDebugModule> pModule; + DWORD countFetched = 0; + IfFailRet(pModuleEnum->Next(1, &pModule, &countFetched)); + Status = FindTypeByName(pModule, pTypeName, ppType); + if(SUCCEEDED(Status)) + break; + } + + return Status; +} + +// Searches a given module for any ICorDebugType that matches the given fully qualified type name +HRESULT ExpressionNode::FindTypeByName(ICorDebugModule* pModule, __in_z WCHAR* pTypeName, ICorDebugType** ppType) +{ + HRESULT Status = S_OK; + ToRelease<IUnknown> pMDUnknown; + ToRelease<IMetaDataImport> pMD; + IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown)); + IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD)); + + // If the name contains a generic argument list, extract the type name from + // before the list + WCHAR rootName[mdNameLen]; + WCHAR* pRootName = NULL; + int typeNameLen = (int) _wcslen(pTypeName); + int genericParamListStart = (int) _wcscspn(pTypeName, L"<"); + if(genericParamListStart != typeNameLen) + { + if(pTypeName[typeNameLen-1] != L'>' || genericParamListStart > mdNameLen) + { + return E_FAIL; // mal-formed type name + } + else + { + wcsncpy_s(rootName, mdNameLen, pTypeName, genericParamListStart); + pRootName = rootName; + } + } + else + { + pRootName = pTypeName; + } + + // Convert from name to token to ICorDebugClass + mdTypeDef typeDef; + IfFailRet(pMD->FindTypeDefByName(pRootName, NULL, &typeDef)); + DWORD flags; + ULONG nameLen; + mdToken tkExtends; + IfFailRet(pMD->GetTypeDefProps(typeDef, NULL, 0, &nameLen, &flags, &tkExtends)); + BOOL isValueType; + IfFailRet(IsTokenValueTypeOrEnum(tkExtends, pMD, &isValueType)); + CorElementType et = isValueType ? ELEMENT_TYPE_VALUETYPE : ELEMENT_TYPE_CLASS; + ToRelease<ICorDebugClass> pClass; + IfFailRet(pModule->GetClassFromToken(typeDef, &pClass)); + ToRelease<ICorDebugClass2> pClass2; + IfFailRet(pClass->QueryInterface(__uuidof(ICorDebugClass2), (void**)&pClass2)); + + // Convert from class to type - if generic then recursively resolve the generic + // parameter list + ArrayHolder<ToRelease<ICorDebugType>> typeParams = NULL; + int countTypeParams = 0; + if(genericParamListStart != typeNameLen) + { + ToRelease<ICorDebugAssembly> pAssembly; + IfFailRet(pModule->GetAssembly(&pAssembly)); + ToRelease<ICorDebugAppDomain> pDomain; + IfFailRet(pAssembly->GetAppDomain(&pDomain)); + + countTypeParams = 1; + for(int i = genericParamListStart+1; i < typeNameLen; i++) + { + if(pTypeName[i] == L',') countTypeParams++; + } + typeParams = new ToRelease<ICorDebugType>[countTypeParams]; + + WCHAR* pCurName = pTypeName + genericParamListStart+1; + for(int i = 0; i < countTypeParams; i++) + { + WCHAR typeParamName[mdNameLen]; + WCHAR* pNextComma = _wcschr(pCurName, L','); + int len = (pNextComma != NULL) ? (int)(pNextComma - pCurName) : (int)_wcslen(pCurName)-1; + if(len > mdNameLen) + return E_FAIL; + wcsncpy_s(typeParamName, mdNameLen, pCurName, len); + FindTypeByName(pDomain, typeParamName, &(typeParams[i])); + pCurName = pNextComma+1; + } + } + IfFailRet(pClass2->GetParameterizedType(et, countTypeParams, &(typeParams[0]), ppType)); + + return Status; +} + +// Checks whether the given token is or refers to type System.ValueType or System.Enum +HRESULT ExpressionNode::IsTokenValueTypeOrEnum(mdToken token, IMetaDataImport* pMetadata, BOOL* pResult) +{ + // This isn't a 100% correct check because we aren't verifying the module portion of the + // type identity. Arbitrary assemblies could define a type named System.ValueType or System.Enum. + // If that happens this code will get the answer wrong... we just assume that happens so rarely + // that it isn't worth doing all the overhead of assembly resolution to deal with + + HRESULT Status = S_OK; + CorTokenType type = (CorTokenType)(token & 0xFF000000); + + // only need enough space to hold either System.ValueType or System.Enum + //System.ValueType -> 16 characters + //System.Enum -> 11 characters + WCHAR nameBuffer[17]; + nameBuffer[0] = L'\0'; + + if(type == mdtTypeRef) + { + ULONG chTypeDef; + pMetadata->GetTypeRefProps(token, NULL, NULL, 0, &chTypeDef); + if(chTypeDef > _countof(nameBuffer)) + { + *pResult = FALSE; + return Status; + } + IfFailRet(pMetadata->GetTypeRefProps(token, NULL, nameBuffer, _countof(nameBuffer), &chTypeDef)); + } + else if(type == mdtTypeDef) + { + ULONG chTypeDef; + pMetadata->GetTypeDefProps(token, NULL, 0, &chTypeDef, NULL, NULL); + if(chTypeDef > _countof(nameBuffer)) + { + *pResult = FALSE; + return Status; + } + IfFailRet(pMetadata->GetTypeDefProps(token, nameBuffer, _countof(nameBuffer), &chTypeDef, NULL, NULL)); + } + + if(_wcscmp(nameBuffer, L"System.ValueType") == 0 || + _wcscmp(nameBuffer, L"System.Enum") == 0) + { + *pResult = TRUE; + } + else + { + *pResult = FALSE; + } + return Status; +} diff --git a/src/ToolBox/SOS/Strike/ExpressionNode.h b/src/ToolBox/SOS/Strike/ExpressionNode.h new file mode 100644 index 0000000000..507a8a53d3 --- /dev/null +++ b/src/ToolBox/SOS/Strike/ExpressionNode.h @@ -0,0 +1,307 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + + +#ifndef _EXPRESSION_NODE_ +#define _EXPRESSION_NODE_ + +#ifdef FEATURE_PAL +#error This file isn't designed to build in PAL +#endif + +#include "strike.h" +#include "sos.h" +#include "util.h" + +#define MAX_EXPRESSION 500 +#define MAX_ERROR 500 + + +// Represents one node in a tree of expressions and sub-expressions +// These nodes are used in the !watch expandable expression tree +// Each node consists of a string based C#-like expression and its +// evaluation within the current context of the debuggee. +// +// These nodes are also intended for eventual use in ClrStack -i expression tree +// but ClrStack -i hasn't yet been refactored to use them +// +// Each node can evaluate to: +// nothing - if an error occurs during expression parsing or the expression +// names don't match to anything in the debuggee +// a debuggee value - these are values that are backed in memory of the debuggee +// (ICorDebugValue objects) or build time constants which are +// stored in the assembly metadata. +// a debuggee type - instead of refering to a particular instance of a type (the +// value case above), nodes can directly refer to a type definition +// represented by an ICorDebugType object +class ExpressionNode +{ +public: + + typedef VOID (*ExpressionNodeVisitorCallback)(ExpressionNode* pExpressionNode, int depth, VOID* pUserData); + + // Returns the complete expression being evaluated to get the value for this node + // The returned pointer is a string interior to this object - once you release + // all references to this object the string is invalid. + WCHAR* GetAbsoluteExpression(); + + // Returns the sub expression that logically indicates how the parent expression + // was built upon to reach this node. This relative value has no purpose other + // than an identifier and to convey UI meaning to the user. At present typical values + // are the name of type, a local, a parameter, a field, an array index, or '<basetype>' + // for a baseclass casting operation + // The returned pointer is a string interior to this object - once you release + // all references to this object the string is invalid. + WCHAR* GetRelativeExpression(); + + // Returns a text representation of the type of value that this node refers to + // It is possible this node doesn't evaluate to anything and therefore has no + // type + // The returned pointer is a string interior to this object - once you release + // all references to this object the string is invalid. + WCHAR* GetTypeName(); + + // Returns a text representation of the value for this node. It is possible that + // this node doesn't evaluate to anything and therefore has no value text. + // The returned pointer is a string interior to this object - once you release + // all references to this object the string is invalid. + WCHAR* GetTextValue(); + + // If there is any error during the evaluation of this node's expression, it is + // returned here. + // The returned pointer is a string interior to this object - once you release + // all references to this object the string is invalid. + WCHAR* GetErrorMessage(); + + // Factory function for creating the expression node at the root of a tree + static HRESULT CreateExpressionNode(__in_z WCHAR* pExpression, ExpressionNode** ppExpressionNode); + + // Performs recursive expansion within the tree for nodes that are along the path to varToExpand. + // Expansion involves calulating a set of child expressions from the current expression via + // field dereferencing, array index dereferencing, or casting to a base type. + // For example if a tree was rooted with expression 'foo.bar' and varToExpand is '(Baz)foo.bar[9]' + // then 'foo.bar', 'foo.bar[9]', and '(Baz)foo.bar[9]' nodes would all be expanded. + HRESULT Expand(__in_z WCHAR* varToExpand); + + // Standard depth first search tree traversal pattern with a callback + VOID DFSVisit(ExpressionNodeVisitorCallback pFunc, VOID* pUserData, int depth=0); + +private: + // for nodes that evaluate to a type, this is that type + // for nodes that evaluate to a debuggee value, this is the type of that + // value or one of its base types. It represents the type the value should + // displayed and expanded as. + ToRelease<ICorDebugType> pTypeCast; + + // for nodes that evaluate to a memory backed debuggee value, this is that value + ToRelease<ICorDebugValue> pValue; + + // if this node gets expanded and it has thread-static or context-static sub-fields, + // this frame disambiguates which thread and context to use. + ToRelease<ICorDebugILFrame> pILFrame; + + // TODO: exactly which metadata is this supposed to be? try to get rid of this + ToRelease<IMetaDataImport> pMD; + + // PERF: this could be a lot more memory efficient + WCHAR pTextValue[MAX_EXPRESSION]; + WCHAR pErrorMessage[MAX_ERROR]; + WCHAR pAbsoluteExpression[MAX_EXPRESSION]; + WCHAR pRelativeExpression[MAX_EXPRESSION]; + WCHAR pTypeName[MAX_EXPRESSION]; + + // if this value represents a build time constant debuggee value, this is a pointer + // to the value data stored in metadata and its size + UVCP_CONSTANT pDefaultValue; + ULONG cchDefaultValue; + + // Pointer in a linked list of sibling nodes that all share the same parent + ExpressionNode* pNextSibling; + // Pointer to the first child node of this node, other children can be found + // by following the child's sibling list. + ExpressionNode* pChild; + + typedef VOID (*VariableEnumCallback)(ICorDebugValue* pValue, WCHAR* pName, WCHAR* pErrorMessage, VOID* pUserData); + typedef VOID (*FrameEnumCallback)(ICorDebugFrame* pFrame, VOID* pUserData); + + // Indicates how a child node was derived from its parent + enum ChildKind + { + ChildKind_Field, + ChildKind_Index, + ChildKind_BaseClass + }; + + // Creates a new expression with a given debuggee value and frame + ExpressionNode(__in_z WCHAR* pExpression, ICorDebugValue* pValue, ICorDebugILFrame* pFrame); + + // Creates a new expression that has an error and no value + ExpressionNode(__in_z WCHAR* pExpression, __in_z WCHAR* pErrorMessage); + + // Creates a new child expression + ExpressionNode(__in_z WCHAR* pParentExpression, ChildKind ck, __in_z WCHAR* pRelativeExpression, ICorDebugValue* pValue, ICorDebugType* pType, ICorDebugILFrame* pFrame, UVCP_CONSTANT pDefaultValue = NULL, ULONG cchDefaultValue = 0); + + // Common member initialization for the constructors + VOID Init(ICorDebugValue* pValue, ICorDebugType* pTypeCast, ICorDebugILFrame* pFrame); + + // Retreves the correct IMetaDataImport for the type represented in this node and stores it + // in pMD. + HRESULT PopulateMetaDataImport(); + + // Determines the string representation of pType and stores it in typeName + static HRESULT CalculateTypeName(ICorDebugType * pType, __inout_ecount(typeNameLen) WCHAR* typeName, DWORD typeNameLen); + + + // Appends angle brackets and the generic argument list to a type name + static HRESULT AddGenericArgs(ICorDebugType * pType, __inout_ecount(typeNameLen) WCHAR* typeName, DWORD typeNameLen); + + // Determines the text name for the type of this node and caches it + HRESULT PopulateType(); + + // Node expansion helpers + + // Inserts a new child at the end of the linked list of children + // PERF: This has O(N) insert time but these lists should never be large + VOID AddChild(ExpressionNode* pNewChild); + + // Helper that determines if the current node is on the path of nodes represented by + // expression varToExpand + BOOL ShouldExpandVariable(__in_z WCHAR* varToExpand); + + // Expands this array node by creating child nodes with expressions refering to individual array elements + HRESULT ExpandSzArray(ICorDebugValue* pInnerValue, __in_z WCHAR* varToExpand); + + // Expands this struct/class node by creating child nodes with expressions refering to individual field values + // and one node for the basetype value + HRESULT ExpandFields(ICorDebugValue* pInnerValue, __in_z WCHAR* varToExpand); + + // Value Population functions + + //Helper for unwrapping values + static HRESULT DereferenceAndUnboxValue(ICorDebugValue * pInputValue, ICorDebugValue** ppOutputValue, BOOL * pIsNull = NULL); + + // Returns TRUE if the value derives from System.Enum + static BOOL IsEnum(ICorDebugValue * pInputValue); + + // Calculates the value text for nodes that have enum values + HRESULT PopulateEnumValue(ICorDebugValue* pEnumValue, BYTE* enumValue); + + // Helper that fetches the text value of a string ICorDebugValue + HRESULT GetDebuggeeStringValue(ICorDebugValue* pInputValue, __inout_ecount(cchBuffer) WCHAR* wszBuffer, DWORD cchBuffer); + + // Helper that fetches the text value of a string build-time literal + HRESULT GetConstantStringValue(__inout_ecount(cchBuffer) WCHAR* wszBuffer, DWORD cchBuffer); + + // Helper that caches the textual value for nodes that evaluate to array objects + HRESULT PopulateSzArrayValue(ICorDebugValue* pInputValue); + + // Helper that caches the textual value for nodes of any type + HRESULT PopulateTextValueHelper(); + + // Caches the textual value of this node + HRESULT PopulateTextValue(); + + + // Expression parsing and search + + // In/Out parameters for the EvaluateExpressionFrameScanCallback + typedef struct _EvaluateExpressionFrameScanData + { + WCHAR* pIdentifier; + ToRelease<ICorDebugValue> pFoundValue; + ToRelease<ICorDebugILFrame> pFoundFrame; + ToRelease<ICorDebugILFrame> pFirstFrame; + WCHAR* pErrorMessage; + DWORD cchErrorMessage; + } EvaluateExpressionFrameScanData; + + //Callback that searches a frame to determine if it contains a local variable or parameter of a given name + static VOID EvaluateExpressionFrameScanCallback(ICorDebugFrame* pFrame, VOID* pUserData); + + //Callback checks to see if a given local/parameter has name pName + static VOID EvaluateExpressionVariableScanCallback(ICorDebugValue* pValue, __in_z WCHAR* pName, __out_z WCHAR* pErrorMessage, VOID* pUserData); + + //Factory method that recursively parses pExpression and create an ExpressionNode + // pExpression - the entire expression being parsed + // pExpressionRemainder - the portion of the expression that remains to be parsed in this + // recursive invocation + // charactersParsed - the number of characters that have been parsed from pExpression + // so far (todo: this is basically the difference between remainder and + // full expression, do we need it?) + // pParsedValue - A debuggee value that should be used as the context for interpreting + // pExpressionRemainder + // pParsedType - A debuggee type that should be used as the context for interpreting + // pExpressionRemainder. + // pParsedDefaultValue - A fixed value from metadata that should be used as context for + // interpretting pExpressionRemainder + // cchParsedDefaultValue- Size of pParsedDefaultValue + // pFrame - A debuggee IL frame that disambiguates the thread and context needed + // to evaluate a thread-static or context-static value + // ppExpressionNode - OUT - the resulting expression node + // + // + static HRESULT CreateExpressionNodeHelper(__in_z WCHAR* pExpression, + __in_z WCHAR* pExpressionParseRemainder, + DWORD charactersParsed, + ICorDebugValue* pParsedValue, + ICorDebugType* pParsedType, + UVCP_CONSTANT pParsedDefaultValue, + ULONG cchParsedDefaultValue, + ICorDebugILFrame* pFrame, + ExpressionNode** ppExpressionNode); + + // Splits apart a C#-like expression and determines the first identifier in the string and updates expression to point + // at the remaining unparsed portion + static HRESULT ParseNextIdentifier(__in_z WCHAR** expression, + __inout_ecount(cchIdentifierName) WCHAR* identifierName, + DWORD cchIdentifierName, + __inout_ecount(cchErrorMessage) WCHAR* errorMessage, + DWORD cchErrorMessage, + DWORD* charactersParsed, + BOOL* isArrayIndex); + + + // Iterate through all parameters in the ILFrame calling the callback function for each of them + static HRESULT EnumerateParameters(IMetaDataImport * pMD, + mdMethodDef methodDef, + ICorDebugILFrame * pILFrame, + VariableEnumCallback pCallback, + VOID* pUserData); + + // Enumerate all locals in the given ILFrame, calling the callback method for each of them + static HRESULT EnumerateLocals(IMetaDataImport * pMD, + mdMethodDef methodDef, + ICorDebugILFrame * pILFrame, + VariableEnumCallback pCallback, + VOID* pUserData); + + // Iterates over all frames on the current thread's stack, calling the callback function for each of them + static HRESULT EnumerateFrames(FrameEnumCallback pCallback, VOID* pUserData); + + // Determines the corresponding ICorDebugType for a given primitive type + static HRESULT FindTypeFromElementType(CorElementType et, ICorDebugType** ppType); + + // Gets the appropriate element type encoding for well-known fully qualified type names + // This doesn't work for arbitrary types, just types that have CorElementType short forms. + static HRESULT GetCanonicalElementTypeForTypeName(__in_z WCHAR* pTypeName, CorElementType *et); + + // Searches the debuggee for any ICorDebugType that matches the given fully qualified name + // This will search across all AppDomains and Assemblies + static HRESULT FindTypeByName(__in_z WCHAR* pTypeName, ICorDebugType** ppType); + + // Searches the debuggee for any ICorDebugType that matches the given fully qualified name + // This will search across all Assemblies in the given AppDomain + static HRESULT FindTypeByName(ICorDebugAppDomain* pAppDomain, __in_z WCHAR* pTypeName, ICorDebugType** ppType); + + // Searches the assembly for any ICorDebugType that matches the given fully qualified name + static HRESULT FindTypeByName(ICorDebugAssembly* pAssembly, __in_z WCHAR* pTypeName, ICorDebugType** ppType); + + // Searches a given module for any ICorDebugType that matches the given fully qualified type name + static HRESULT FindTypeByName(ICorDebugModule* pModule, __in_z WCHAR* pTypeName, ICorDebugType** ppType); + + // Checks whether the given token is or refers to type System.ValueType or System.Enum + static HRESULT IsTokenValueTypeOrEnum(mdToken token, IMetaDataImport* pMetadata, BOOL* pResult); +}; + +#endif diff --git a/src/ToolBox/SOS/Strike/Native.rc b/src/ToolBox/SOS/Strike/Native.rc new file mode 100644 index 0000000000..179ddfd24a --- /dev/null +++ b/src/ToolBox/SOS/Strike/Native.rc @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#define FX_VER_FILEDESCRIPTION_STR "Microsoft NTSD extension for .NET Runtime\0" + +#include <fxver.h> +#include <fxver.rc> + +DOCUMENTATION TEXT DISCARDABLE "sosdocs.txt" diff --git a/src/ToolBox/SOS/Strike/SOS.nativeproj b/src/ToolBox/SOS/Strike/SOS.nativeproj new file mode 100644 index 0000000000..4c0fc7616f --- /dev/null +++ b/src/ToolBox/SOS/Strike/SOS.nativeproj @@ -0,0 +1,7 @@ +<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\xplat\SetHostLocal.props"/> + <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\src\toolbox\sos\strike\sos.targets" /> + <PropertyGroup> + <BuildCoreBinaries>true</BuildCoreBinaries> + </PropertyGroup> +</Project> diff --git a/src/ToolBox/SOS/Strike/SOS.sln b/src/ToolBox/SOS/Strike/SOS.sln new file mode 100644 index 0000000000..08f4a64836 --- /dev/null +++ b/src/ToolBox/SOS/Strike/SOS.sln @@ -0,0 +1,76 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SOS", "SOS.vcxproj", "{3941DEDB-8183-4F82-9193-5EC0D5B6D4A6}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mscordacwks", "..\..\..\mscordacwks.vcproj", "{C5716445-C233-4491-85A4-31B75731DD95}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mscordbi", "..\..\..\mscordbi.vcproj", "{95A6AE03-EC45-4450-93DB-9B21890F79E7}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + amd64chk|Win32 = amd64chk|Win32 + amd64dbg|Win32 = amd64dbg|Win32 + amd64ret|Win32 = amd64ret|Win32 + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + x86chk|Win32 = x86chk|Win32 + x86dbg|Win32 = x86dbg|Win32 + x86ret|Win32 = x86ret|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3941DEDB-8183-4F82-9193-5EC0D5B6D4A6}.amd64chk|Win32.ActiveCfg = amd64chk|Win32 + {3941DEDB-8183-4F82-9193-5EC0D5B6D4A6}.amd64chk|Win32.Build.0 = amd64chk|Win32 + {3941DEDB-8183-4F82-9193-5EC0D5B6D4A6}.amd64dbg|Win32.ActiveCfg = amd64dbg|Win32 + {3941DEDB-8183-4F82-9193-5EC0D5B6D4A6}.amd64dbg|Win32.Build.0 = amd64dbg|Win32 + {3941DEDB-8183-4F82-9193-5EC0D5B6D4A6}.amd64ret|Win32.ActiveCfg = amd64ret|Win32 + {3941DEDB-8183-4F82-9193-5EC0D5B6D4A6}.amd64ret|Win32.Build.0 = amd64ret|Win32 + {3941DEDB-8183-4F82-9193-5EC0D5B6D4A6}.Debug|Win32.ActiveCfg = x86chk|Win32 + {3941DEDB-8183-4F82-9193-5EC0D5B6D4A6}.Debug|Win32.Build.0 = x86chk|Win32 + {3941DEDB-8183-4F82-9193-5EC0D5B6D4A6}.Release|Win32.ActiveCfg = x86chk|Win32 + {3941DEDB-8183-4F82-9193-5EC0D5B6D4A6}.Release|Win32.Build.0 = x86chk|Win32 + {3941DEDB-8183-4F82-9193-5EC0D5B6D4A6}.x86chk|Win32.ActiveCfg = x86chk|Win32 + {3941DEDB-8183-4F82-9193-5EC0D5B6D4A6}.x86chk|Win32.Build.0 = x86chk|Win32 + {3941DEDB-8183-4F82-9193-5EC0D5B6D4A6}.x86dbg|Win32.ActiveCfg = x86dbg|Win32 + {3941DEDB-8183-4F82-9193-5EC0D5B6D4A6}.x86dbg|Win32.Build.0 = x86dbg|Win32 + {3941DEDB-8183-4F82-9193-5EC0D5B6D4A6}.x86ret|Win32.ActiveCfg = x86ret|Win32 + {3941DEDB-8183-4F82-9193-5EC0D5B6D4A6}.x86ret|Win32.Build.0 = x86ret|Win32 + {C5716445-C233-4491-85A4-31B75731DD95}.amd64chk|Win32.ActiveCfg = Debug|Win32 + {C5716445-C233-4491-85A4-31B75731DD95}.amd64chk|Win32.Build.0 = Debug|Win32 + {C5716445-C233-4491-85A4-31B75731DD95}.amd64dbg|Win32.ActiveCfg = Debug|Win32 + {C5716445-C233-4491-85A4-31B75731DD95}.amd64dbg|Win32.Build.0 = Debug|Win32 + {C5716445-C233-4491-85A4-31B75731DD95}.amd64ret|Win32.ActiveCfg = Debug|Win32 + {C5716445-C233-4491-85A4-31B75731DD95}.amd64ret|Win32.Build.0 = Debug|Win32 + {C5716445-C233-4491-85A4-31B75731DD95}.Debug|Win32.ActiveCfg = Debug|Win32 + {C5716445-C233-4491-85A4-31B75731DD95}.Debug|Win32.Build.0 = Debug|Win32 + {C5716445-C233-4491-85A4-31B75731DD95}.Release|Win32.ActiveCfg = Debug|Win32 + {C5716445-C233-4491-85A4-31B75731DD95}.Release|Win32.Build.0 = Debug|Win32 + {C5716445-C233-4491-85A4-31B75731DD95}.x86chk|Win32.ActiveCfg = x86chk|Win32 + {C5716445-C233-4491-85A4-31B75731DD95}.x86chk|Win32.Build.0 = x86chk|Win32 + {C5716445-C233-4491-85A4-31B75731DD95}.x86dbg|Win32.ActiveCfg = Debug|Win32 + {C5716445-C233-4491-85A4-31B75731DD95}.x86dbg|Win32.Build.0 = Debug|Win32 + {C5716445-C233-4491-85A4-31B75731DD95}.x86ret|Win32.ActiveCfg = Debug|Win32 + {C5716445-C233-4491-85A4-31B75731DD95}.x86ret|Win32.Build.0 = Debug|Win32 + {95A6AE03-EC45-4450-93DB-9B21890F79E7}.amd64chk|Win32.ActiveCfg = amd64chk|Win32 + {95A6AE03-EC45-4450-93DB-9B21890F79E7}.amd64chk|Win32.Build.0 = amd64chk|Win32 + {95A6AE03-EC45-4450-93DB-9B21890F79E7}.amd64dbg|Win32.ActiveCfg = amd64dbg|Win32 + {95A6AE03-EC45-4450-93DB-9B21890F79E7}.amd64dbg|Win32.Build.0 = amd64dbg|Win32 + {95A6AE03-EC45-4450-93DB-9B21890F79E7}.amd64ret|Win32.ActiveCfg = amd64ret|Win32 + {95A6AE03-EC45-4450-93DB-9B21890F79E7}.amd64ret|Win32.Build.0 = amd64ret|Win32 + {95A6AE03-EC45-4450-93DB-9B21890F79E7}.Debug|Win32.ActiveCfg = x86chk|Win32 + {95A6AE03-EC45-4450-93DB-9B21890F79E7}.Debug|Win32.Build.0 = x86chk|Win32 + {95A6AE03-EC45-4450-93DB-9B21890F79E7}.Release|Win32.ActiveCfg = amd64ret|Win32 + {95A6AE03-EC45-4450-93DB-9B21890F79E7}.Release|Win32.Build.0 = amd64ret|Win32 + {95A6AE03-EC45-4450-93DB-9B21890F79E7}.x86chk|Win32.ActiveCfg = x86chk|Win32 + {95A6AE03-EC45-4450-93DB-9B21890F79E7}.x86chk|Win32.Build.0 = x86chk|Win32 + {95A6AE03-EC45-4450-93DB-9B21890F79E7}.x86dbg|Win32.ActiveCfg = x86dbg|Win32 + {95A6AE03-EC45-4450-93DB-9B21890F79E7}.x86dbg|Win32.Build.0 = x86dbg|Win32 + {95A6AE03-EC45-4450-93DB-9B21890F79E7}.x86ret|Win32.ActiveCfg = x86ret|Win32 + {95A6AE03-EC45-4450-93DB-9B21890F79E7}.x86ret|Win32.Build.0 = x86ret|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/ToolBox/SOS/Strike/SOS.vcproj b/src/ToolBox/SOS/Strike/SOS.vcproj new file mode 100644 index 0000000000..aff5e7cc60 --- /dev/null +++ b/src/ToolBox/SOS/Strike/SOS.vcproj @@ -0,0 +1,303 @@ +<?xml version="1.0" encoding="UTF-8"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="9.00" + Name="SOS" + ProjectGUID="{3941DEDB-8183-4F82-9193-5EC0D5B6D4A6}" + Keyword="MakeFileProj" + TargetFrameworkVersion="131072" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="x86chk|Win32" + OutputDirectory="$(ConfigurationName)" + IntermediateDirectory="$(ConfigurationName)" + ConfigurationType="0" + > + <Tool + Name="VCNMakeTool" + BuildCommandLine="$(ProjectDir)..\..\..\..\bin\runjs buildSOS /quickBuild=$(ProjectPath)/buildArch:x86/buildType:chk" + ReBuildCommandLine="$(ProjectDir)..\..\..\..\bin\runjs buildSOS /buildArgs:-c/buildArch:x86/buildType:chk" + CleanCommandLine="" + Output="$(ProjectDir)\obj2c\i386\SOS.dll" + PreprocessorDefinitions="_X86_=1;i386=1" + IncludeSearchPath="inc\" + ForcedIncludes="" + AssemblySearchPath="" + ForcedUsingAssemblies="" + CompileAsManaged="" + /> + </Configuration> + <Configuration + Name="x86dbg|Win32" + OutputDirectory="$(ConfigurationName)" + IntermediateDirectory="$(ConfigurationName)" + ConfigurationType="0" + > + <Tool + Name="VCNMakeTool" + BuildCommandLine="$(ProjectDir)..\..\..\..\bin\runjs buildSOS /quickBuild=$(ProjectPath)/buildArch:x86/buildType:dbg" + ReBuildCommandLine="$(ProjectDir)..\..\..\..\bin\runjs buildSOS /buildArgs:-c/buildArch:x86/buildType:dbg" + CleanCommandLine="" + Output="$(ProjectDir)\obj2d\i386\SOS.dll" + PreprocessorDefinitions="_X86_=1;i386=1" + IncludeSearchPath="inc\" + ForcedIncludes="" + AssemblySearchPath="" + ForcedUsingAssemblies="" + CompileAsManaged="" + /> + </Configuration> + <Configuration + Name="x86ret|Win32" + OutputDirectory="$(ConfigurationName)" + IntermediateDirectory="$(ConfigurationName)" + ConfigurationType="0" + > + <Tool + Name="VCNMakeTool" + BuildCommandLine="$(ProjectDir)..\..\..\..\bin\runjs buildSOS /quickBuild=$(ProjectPath)/buildArch:x86/buildType:ret" + ReBuildCommandLine="$(ProjectDir)..\..\..\..\bin\runjs buildSOS /buildArgs:-c/buildArch:x86/buildType:ret" + CleanCommandLine="" + Output="$(ProjectDir)\obj2r\i386\SOS.dll" + PreprocessorDefinitions="_X86_=1;i386=1" + IncludeSearchPath="inc\" + ForcedIncludes="" + AssemblySearchPath="" + ForcedUsingAssemblies="" + CompileAsManaged="" + /> + </Configuration> + <Configuration + Name="amd64chk|Win32" + OutputDirectory="$(ConfigurationName)" + IntermediateDirectory="$(ConfigurationName)" + ConfigurationType="0" + > + <Tool + Name="VCNMakeTool" + BuildCommandLine="$(ProjectDir)..\..\..\..\bin\runjs buildSOS /quickBuild=$(ProjectPath)/buildArch:amd64/buildType:chk" + ReBuildCommandLine="$(ProjectDir)..\..\..\..\bin\runjs buildSOS /buildArgs:-c/buildArch:amd64/buildType:chk" + CleanCommandLine="" + Output="$(ProjectDir)\obj2c\amd64\SOS.dll" + PreprocessorDefinitions="_AMD64_=1;_WIN64=1;_DEBUG=1" + IncludeSearchPath="inc\" + ForcedIncludes="" + AssemblySearchPath="" + ForcedUsingAssemblies="" + CompileAsManaged="" + /> + </Configuration> + <Configuration + Name="amd64dbg|Win32" + OutputDirectory="$(ConfigurationName)" + IntermediateDirectory="$(ConfigurationName)" + ConfigurationType="0" + > + <Tool + Name="VCNMakeTool" + BuildCommandLine="$(ProjectDir)..\..\..\..\bin\runjs buildSOS /quickBuild=$(ProjectPath)/buildArch:amd64/buildType:dbg" + ReBuildCommandLine="$(ProjectDir)..\..\..\..\bin\runjs buildSOS /buildArgs:-c/buildArch:amd64/buildType:dbg" + CleanCommandLine="" + Output="$(ProjectDir)\obj2d\amd64\SOS.dll" + PreprocessorDefinitions="_AMD64_=1;_WIN64=1" + IncludeSearchPath="inc\" + ForcedIncludes="" + AssemblySearchPath="" + ForcedUsingAssemblies="" + CompileAsManaged="" + /> + </Configuration> + <Configuration + Name="amd64ret|Win32" + OutputDirectory="$(ConfigurationName)" + IntermediateDirectory="$(ConfigurationName)" + ConfigurationType="0" + > + <Tool + Name="VCNMakeTool" + BuildCommandLine="$(ProjectDir)..\..\..\..\bin\runjs buildSOS /quickBuild=$(ProjectPath)/buildArch:amd64/buildType:ret" + ReBuildCommandLine="$(ProjectDir)..\..\..\..\bin\runjs buildSOS /buildArgs:-c/buildArch:amd64/buildType:ret" + CleanCommandLine="" + Output="$(ProjectDir)\obj2r\amd64\SOS.dll" + PreprocessorDefinitions="_AMD64_=1;_WIN64=1" + IncludeSearchPath="inc\" + ForcedIncludes="" + AssemblySearchPath="" + ForcedUsingAssemblies="" + CompileAsManaged="" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl;inc;xsd" + UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}" + > + <File + RelativePath=".\data.h" + > + </File> + <File + RelativePath=".\inc\dbgeng.h" + > + </File> + <File + RelativePath=".\inc\dbghelp.h" + > + </File> + <File + RelativePath=".\disasm.h" + > + </File> + <File + RelativePath=".\exts.h" + > + </File> + <File + RelativePath=".\ntinfo.h" + > + </File> + <File + RelativePath=".\sos_md.h" + > + </File> + <File + RelativePath=".\sos_stacktrace.h" + > + </File> + <File + RelativePath=".\strike.h" + > + </File> + <File + RelativePath=".\symbol.h" + > + </File> + <File + RelativePath=".\util.h" + > + </File> + <File + RelativePath=".\UtilCode.h" + > + </File> + <File + RelativePath=".\inc\wdbgexts.h" + > + </File> + </Filter> + <Filter + Name="Resource Files" + Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx" + UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}" + > + <File + RelativePath=".\Native.rc" + > + </File> + </Filter> + <Filter + Name="Source Files" + Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx" + UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}" + > + <File + RelativePath=".\disasm.cpp" + > + </File> + <File + RelativePath=".\disasmIA64.cpp" + > + </File> + <File + RelativePath=".\disasmX86.cpp" + > + </File> + <File + RelativePath=".\dllsext.cpp" + > + </File> + <File + RelativePath=".\eeheap.cpp" + > + </File> + <File + RelativePath=".\exts.cpp" + > + </File> + <File + RelativePath=".\gchist.cpp" + > + </File> + <File + RelativePath=".\gcroot.cpp" + > + </File> + <File + RelativePath=".\metadata.cpp" + > + </File> + <File + RelativePath=".\sildasm.cpp" + > + </File> + <File + RelativePath=".\sos.cpp" + > + </File> + <File + RelativePath=".\sos.def" + > + </File> + <File + RelativePath=".\sos.h" + > + </File> + <File + RelativePath=".\stressLogDump.cpp" + > + </File> + <File + RelativePath=".\strike.cpp" + > + </File> + <File + RelativePath=".\util.cpp" + > + </File> + <File + RelativePath=".\utilIA64.cpp" + > + </File> + <File + RelativePath=".\utilX86.cpp" + > + </File> + <File + RelativePath=".\vm.cpp" + > + </File> + </Filter> + <File + RelativePath=".\sosdocs.txt" + > + </File> + <File + RelativePath=".\sources" + > + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/src/ToolBox/SOS/Strike/UtilCode.h b/src/ToolBox/SOS/Strike/UtilCode.h new file mode 100644 index 0000000000..a002edc89e --- /dev/null +++ b/src/ToolBox/SOS/Strike/UtilCode.h @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ==++== +// + +// +// ==--== +// An empty file so that gcdump.cpp does not include the one from other +// places. diff --git a/src/ToolBox/SOS/Strike/WatchCmd.cpp b/src/ToolBox/SOS/Strike/WatchCmd.cpp new file mode 100644 index 0000000000..443f1dd6ef --- /dev/null +++ b/src/ToolBox/SOS/Strike/WatchCmd.cpp @@ -0,0 +1,331 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#include "WatchCmd.h" + +#ifndef IfFailRet +#define IfFailRet(EXPR) do { Status = (EXPR); if(FAILED(Status)) { return (Status); } } while (0) +#endif + +_PersistList::~_PersistList() +{ + PersistWatchExpression* pCur = pHeadExpr; + while(pCur != NULL) + { + PersistWatchExpression* toDelete = pCur; + pCur = pCur->pNext; + delete toDelete; + } +} + +WatchCmd::WatchCmd() : +pExpressionListHead(NULL) +{ } +WatchCmd::~WatchCmd() +{ + Clear(); + PersistList* pCur = pPersistListHead; + while(pCur != NULL) + { + PersistList* toDelete = pCur; + pCur = pCur->pNext; + delete toDelete; + } +} + +// Deletes all current watch expressions from the watch list +// (does not delete persisted watch lists though) +HRESULT WatchCmd::Clear() +{ + WatchExpression* pCurrent = pExpressionListHead; + while(pCurrent != NULL) + { + WatchExpression* toDelete = pCurrent; + pCurrent = pCurrent->pNext; + delete toDelete; + } + pExpressionListHead = NULL; + return S_OK; +} + +// Adds a new expression to the active watch list +HRESULT WatchCmd::Add(__in_z WCHAR* pExpression) +{ + WatchExpression* pExpr = new WatchExpression; + if(pExpr == NULL) + return E_OUTOFMEMORY; + wcsncpy_s(pExpr->pExpression, MAX_EXPRESSION, pExpression, _TRUNCATE); + pExpr->pNext = NULL; + + WatchExpression** ppCurrent = &pExpressionListHead; + while(*ppCurrent != NULL) + ppCurrent = &((*ppCurrent)->pNext); + *ppCurrent = pExpr; + return S_OK; +} + +// removes an expression at the given index in the active watch list +HRESULT WatchCmd::Remove(int index) +{ + HRESULT Status = S_FALSE; + WatchExpression** ppCurrent = &pExpressionListHead; + for(int i=1; *ppCurrent != NULL; i++) + { + if(i == index) + { + WatchExpression* toDelete = *ppCurrent; + *ppCurrent = (*ppCurrent)->pNext; + delete toDelete; + Status = S_OK; + break; + } + ppCurrent = &((*ppCurrent)->pNext); + + } + return Status; +} + +// Evaluates and prints a tree version of the active watch list +// The tree will be expanded along the nodes in expansionPath +// Optionally the list is filtered to only show differences from pFilterName (the name of a persisted watch list) +HRESULT WatchCmd::Print(int expansionIndex, __in_z WCHAR* expansionPath, __in_z WCHAR* pFilterName) +{ + HRESULT Status = S_OK; + INIT_API_EE(); + INIT_API_DAC(); + EnableDMLHolder dmlHolder(TRUE); + IfFailRet(InitCorDebugInterface()); + + PersistList* pFilterList = NULL; + if(pFilterName != NULL) + { + pFilterList = pPersistListHead; + while(pFilterList != NULL) + { + if(_wcscmp(pFilterList->pName, pFilterName)==0) + break; + pFilterList = pFilterList->pNext; + } + } + + PersistWatchExpression* pHeadFilterExpr = (pFilterList != NULL) ? pFilterList->pHeadExpr : NULL; + + WatchExpression* pExpression = pExpressionListHead; + int index = 1; + while(pExpression != NULL) + { + ExpressionNode* pResult = NULL; + if(FAILED(Status = ExpressionNode::CreateExpressionNode(pExpression->pExpression, &pResult))) + { + ExtOut(" %d) Error: HRESULT 0x%x while evaluating expression \'%S\'", index, Status, pExpression->pExpression); + } + else + { + //check for matching absolute expression + PersistWatchExpression* pCurFilterExpr = pHeadFilterExpr; + while(pCurFilterExpr != NULL) + { + if(_wcscmp(pCurFilterExpr->pExpression, pResult->GetAbsoluteExpression())==0) + break; + pCurFilterExpr = pCurFilterExpr->pNext; + } + + // check for matching persist evaluation on the matching expression + BOOL print = TRUE; + if(pCurFilterExpr != NULL) + { + WCHAR pCurPersistResult[MAX_EXPRESSION]; + FormatPersistResult(pCurPersistResult, MAX_EXPRESSION, pResult); + if(_wcscmp(pCurPersistResult, pCurFilterExpr->pPersistResult)==0) + { + print = FALSE; + } + } + + //expand and print + if(print) + { + if(index == expansionIndex) + pResult->Expand(expansionPath); + PrintCallbackData data; + data.index = index; + WCHAR pCommand[MAX_EXPRESSION]; + swprintf_s(pCommand, MAX_EXPRESSION, L"!watch -expand %d", index); + data.pCommand = pCommand; + pResult->DFSVisit(EvalPrintCallback, (VOID*)&data); + } + delete pResult; + } + pExpression = pExpression->pNext; + index++; + } + return Status; +} + +// Deletes an persisted watch list by name +HRESULT WatchCmd::RemoveList(__in_z WCHAR* pListName) +{ + PersistList** ppList = &pPersistListHead; + while(*ppList != NULL) + { + if(_wcscmp((*ppList)->pName, pListName) == 0) + { + PersistList* toDelete = *ppList; + *ppList = (*ppList)->pNext; + delete toDelete; + return S_OK; + } + ppList = &((*ppList)->pNext); + } + return S_FALSE; +} + +// Renames a previously saved persisted watch list +HRESULT WatchCmd::RenameList(__in_z WCHAR* pOldName, __in_z WCHAR* pNewName) +{ + if(_wcscmp(pOldName, pNewName)==0) + return S_OK; + PersistList** ppList = &pPersistListHead; + while(*ppList != NULL) + { + if(_wcscmp((*ppList)->pName, pOldName) == 0) + { + PersistList* pListToChangeName = *ppList; + RemoveList(pNewName); + wcsncpy_s(pListToChangeName->pName, MAX_EXPRESSION, pNewName, _TRUNCATE); + return S_OK; + } + ppList = &((*ppList)->pNext); + } + return S_FALSE; +} + +// Saves the active watch list together with the current evaluations as +// a new persisted watch list +HRESULT WatchCmd::SaveList(__in_z WCHAR* pSaveName) +{ + HRESULT Status = S_OK; + INIT_API_EE(); + INIT_API_DAC(); + IfFailRet(InitCorDebugInterface()); + + RemoveList(pSaveName); + PersistList* pList = new PersistList(); + wcsncpy_s(pList->pName, MAX_EXPRESSION, pSaveName, _TRUNCATE); + pList->pHeadExpr = NULL; + PersistCallbackData data; + data.ppNext = &(pList->pHeadExpr); + WatchExpression* pExpression = pExpressionListHead; + while(pExpression != NULL) + { + ExpressionNode* pResult = NULL; + if(SUCCEEDED(Status = ExpressionNode::CreateExpressionNode(pExpression->pExpression, &pResult))) + { + pResult->DFSVisit(PersistCallback, (VOID*)&data); + delete pResult; + } + pExpression = pExpression->pNext; + } + + pList->pNext = pPersistListHead; + pPersistListHead = pList; + return Status; +} + +// Saves the current watch list to file as a sequence of commands that will +// recreate the list +HRESULT WatchCmd::SaveListToFile(FILE* pFile) +{ + WatchExpression* pExpression = pExpressionListHead; + while(pExpression != NULL) + { + fprintf_s(pFile, "!watch -a %S\n", pExpression->pExpression); + pExpression = pExpression->pNext; + } + return S_OK; +} + +// Escapes characters that would be interpretted as DML markup, namely angle brackets +// that often appear in generic type names +VOID WatchCmd::DmlEscape(__in_ecount(cchInput) WCHAR* pInput, int cchInput, __in_ecount(cchOutput) WCHAR* pEscapedOutput, int cchOutput) +{ + pEscapedOutput[0] = L'\0'; + for(int i = 0; i < cchInput; i++) + { + if(pInput[i] == L'<') + { + if(0 != wcscat_s(pEscapedOutput, cchOutput, L"<")) return; + pEscapedOutput += 4; + cchOutput -= 4; + } + else if(pInput[i] == L'>') + { + if(0 != wcscat_s(pEscapedOutput, cchOutput, L">")) return; + pEscapedOutput += 4; + cchOutput -= 4; + } + else if(cchOutput > 1) + { + pEscapedOutput[0] = pInput[i]; + pEscapedOutput[1] = '\0'; + pEscapedOutput++; + cchOutput--; + } + if(pInput[i] == L'\0' || cchOutput == 1) break; + } +} + +// A DFS traversal callback for the expression node tree that prints it +VOID WatchCmd::EvalPrintCallback(ExpressionNode* pExpressionNode, int depth, VOID* pUserData) +{ + PrintCallbackData* pData = (PrintCallbackData*)pUserData; + for(int i = 0; i < depth; i++) ExtOut(" "); + if(depth == 0) + ExtOut(" %d) ", pData->index); + else + ExtOut(" |- "); + if(pExpressionNode->GetErrorMessage()[0] != 0) + { + ExtOut("%S (%S)\n", pExpressionNode->GetRelativeExpression(), pExpressionNode->GetErrorMessage()); + } + else + { + // names can have '<' and '>' in them, need to escape + WCHAR pEscapedTypeName[MAX_EXPRESSION]; + DmlEscape(pExpressionNode->GetTypeName(), (int)_wcslen(pExpressionNode->GetTypeName()), pEscapedTypeName, MAX_EXPRESSION); + WCHAR pRelativeExpression[MAX_EXPRESSION]; + DmlEscape(pExpressionNode->GetRelativeExpression(), (int)_wcslen(pExpressionNode->GetRelativeExpression()), pRelativeExpression, MAX_EXPRESSION); + DMLOut("%S <exec cmd=\"%S (%S)%S\">%S</exec> %S\n", pEscapedTypeName, pData->pCommand, pEscapedTypeName, pExpressionNode->GetAbsoluteExpression(), pRelativeExpression, pExpressionNode->GetTextValue()); + } +} + +// A DFS traversal callback for the expression node tree that saves all the values into a new +// persisted watch list +VOID WatchCmd::PersistCallback(ExpressionNode* pExpressionNode, int depth, VOID* pUserData) +{ + PersistCallbackData* pData = (PersistCallbackData*)pUserData; + if(depth != 0) + return; + + PersistWatchExpression* pPersistExpr = new PersistWatchExpression(); + wcsncpy_s(pPersistExpr->pExpression, MAX_EXPRESSION, pExpressionNode->GetAbsoluteExpression(), _TRUNCATE); + FormatPersistResult(pPersistExpr->pPersistResult, MAX_EXPRESSION, pExpressionNode); + pPersistExpr->pNext = NULL; + *(pData->ppNext) = pPersistExpr; + pData->ppNext = &(pPersistExpr->pNext); +} + +// Determines how the value of an expression node is saved as a persisted result. This effectively determines +// the definition of equality when determining if an expression has changed value +VOID WatchCmd::FormatPersistResult(__inout_ecount(cchPersistResult) WCHAR* pPersistResult, DWORD cchPersistResult, ExpressionNode* pExpressionNode) +{ + if(pExpressionNode->GetErrorMessage()[0] != 0) + { + _snwprintf_s(pPersistResult, MAX_EXPRESSION, _TRUNCATE, L"%s (%s)\n", pExpressionNode->GetRelativeExpression(), pExpressionNode->GetErrorMessage()); + } + else + { + _snwprintf_s(pPersistResult, MAX_EXPRESSION, _TRUNCATE, L"%s %s %s\n", pExpressionNode->GetTypeName(), pExpressionNode->GetRelativeExpression(), pExpressionNode->GetTextValue()); + } +} diff --git a/src/ToolBox/SOS/Strike/WatchCmd.h b/src/ToolBox/SOS/Strike/WatchCmd.h new file mode 100644 index 0000000000..a34e391b79 --- /dev/null +++ b/src/ToolBox/SOS/Strike/WatchCmd.h @@ -0,0 +1,110 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#ifndef _WATCH_CMD_ +#define _WATCH_CMD_ + +#ifdef FEATURE_PAL +#error This file not designed for use with FEATURE_PAL +#endif + +#include "ExpressionNode.h" +#include "windows.h" + +// A linked list node for watch expressions +typedef struct _WatchExpression +{ + WCHAR pExpression[MAX_EXPRESSION]; + _WatchExpression* pNext; + +} WatchExpression; + +// A linked list node that stores both the watch expression and a persisted result +// of the evaluation at some point in the past +typedef struct _PersistWatchExpression +{ + WCHAR pExpression[MAX_EXPRESSION]; + WCHAR pPersistResult[MAX_EXPRESSION]; + _PersistWatchExpression* pNext; + +} PersistWatchExpression; + +// A named list of persisted watch expressions, each of which has an expression and +// a saved value +typedef struct _PersistList +{ + ~_PersistList(); + WCHAR pName[MAX_EXPRESSION]; + PersistWatchExpression* pHeadExpr; + _PersistList* pNext; +} PersistList; + +// An API for the functionality in the !watch command +class WatchCmd +{ +public: + WatchCmd(); + ~WatchCmd(); + + // Deletes all current watch expressions from the watch list + // (does not delete persisted watch lists though) + HRESULT Clear(); + + // Adds a new expression to the active watch list + HRESULT Add(__in_z WCHAR* pExpression); + + // removes an expression at the given index in the active watch list + HRESULT Remove(int index); + + // Evaluates and prints a tree version of the active watch list + // The tree will be expanded along the nodes in expansionPath + // Optionally the list is filtered to only show differences from pFilterName (the name of a persisted watch list) + HRESULT Print(int expansionIndex, __in_z WCHAR* expansionPath, __in_z WCHAR* pFilterName); + + // Deletes an persisted watch list by name + HRESULT RemoveList(__in_z WCHAR* pListName); + + // Renames a previously saved persisted watch list + HRESULT RenameList(__in_z WCHAR* pOldName, __in_z WCHAR* pNewName); + + // Saves the active watch list together with the current evaluations as + // a new persisted watch list + HRESULT SaveList(__in_z WCHAR* pSaveName); + + // Saves the current watch list to file as a sequence of commands that will + // recreate the list + HRESULT SaveListToFile(FILE* pFile); + +private: + WatchExpression* pExpressionListHead; + PersistList* pPersistListHead; + + // Escapes characters that would be interpretted as DML markup, namely angle brackets + // that often appear in generic type names + static VOID DmlEscape(__in_z WCHAR* pInput, int cchInput, __inout_ecount(cchOutput) WCHAR* pEscapedOutput, int cchOutput); + + typedef struct _PrintCallbackData + { + int index; + WCHAR* pCommand; + } PrintCallbackData; + + // A DFS traversal callback for the expression node tree that prints it + static VOID EvalPrintCallback(ExpressionNode* pExpressionNode, int depth, VOID* pUserData); + + typedef struct _PersistCallbackData + { + PersistWatchExpression** ppNext; + } PersistCallbackData; + + // A DFS traversal callback for the expression node tree that saves all the values into a new + // persisted watch list + static VOID PersistCallback(ExpressionNode* pExpressionNode, int depth, VOID* pUserData); + + // Determines how the value of an expression node is saved as a persisted result. This effectively determines + // the definition of equality when determining if an expression has changed value + static VOID FormatPersistResult(__inout_ecount(cchPersistResult) WCHAR* pPersistResult, DWORD cchPersistResult, ExpressionNode* pExpressionNode); +}; + +#endif diff --git a/src/ToolBox/SOS/Strike/apollososdocs.txt b/src/ToolBox/SOS/Strike/apollososdocs.txt new file mode 100644 index 0000000000..71fefcf4d7 --- /dev/null +++ b/src/ToolBox/SOS/Strike/apollososdocs.txt @@ -0,0 +1,2727 @@ +------------------------------------------------------------------------------- +NOTE: THIS FILE CONTAINS SOS DOCUMENTATION. THE FORMAT OF THE FILE IS: + +<optional comments> +COMMAND: <cmd name, all lower case> +<descriptive text of the command> +\\ <these are two backslashes, immediately followed by a newline> + +<repeat the sequence above> + +The first command is "contents" which is the general help screen. The rest +correspond to SOS command names. This file is embedded as a resource in the SOS +binary. Be sure to list any new commands here. +------------------------------------------------------------------------------- + + + +COMMAND: contents. +SOS is a debugger extension DLL designed to aid in the debugging of managed +programs. Functions are listed by category, then roughly in order of +importance. Shortcut names for popular functions are listed in parenthesis. +Type "!help <functionname>" for detailed info on that function. + +Object Inspection Examining code and stacks +----------------------------- ----------------------------- +DumpObj (do) Threads +DumpArray (da) ThreadState +DumpStackObjects (dso) IP2MD +DumpHeap U +DumpVC DumpStack +GCRoot EEStack +ObjSize CLRStack +FinalizeQueue GCInfo +PrintException (pe) EHInfo +TraverseHeap BPMD +Watch COMState + StopOnCatch + SuppressJitOptimization + +Examining CLR data structures Diagnostic Utilities +----------------------------- ----------------------------- +DumpDomain VerifyHeap +EEHeap VerifyObj +Name2EE FindRoots +SyncBlk HeapStat +DumpMT GCWhere +DumpClass ListNearObj (lno) +DumpMD GCHandles +Token2EE GCHandleLeaks +EEVersion FinalizeQueue (fq) +DumpModule FindAppDomain +ThreadPool SaveModule +DumpAssembly ProcInfo +DumpSigElem StopOnException (soe) +DumpRuntimeTypes DumpLog +DumpSig VMMap +RCWCleanupList VMStat +DumpIL MinidumpMode +DumpRCW AnalyzeOOM (ao) +DumpCCW + +Examining the GC history Other +----------------------------- ----------------------------- +HistInit FAQ +HistRoot SaveState +HistObj +HistObjFind +HistClear +\\ + +COMMAND: faq. +>> Where can I get the right version of SOS for my build? + +If you are running version 1.1 or 2.0 of the CLR, SOS.DLL is installed in the +same directory as the main CLR dll (CLR.DLL). Newer versions of the +Windows Debugger provide a command to make it easy to load the right copy of +SOS.DLL: + + ".loadby sos clr" + +That will load the SOS extension DLL from the same place that CLR.DLL is +loaded in the process. You shouldn't attempt to use a version of SOS.DLL that +doesn't match the version of CLR.DLL. You can find the version of +CLR.DLL by running + + "lmvm clr" + +in the debugger. Note that if you are running CoreCLR (e.g. Silverlight) +then you should replace "clr" with "coreclr". + +If you are using a dump file created on another machine, it is a little bit +more complex. You need to make sure the mscordacwks.dll file that came with +that install is on your symbol path, and you need to load the corresponding +version of sos.dll (typing .load <full path to sos.dll> rather than using the +.loadby shortcut). Within the Microsoft corpnet, we keep tagged versions +of mscordacwks.dll, with names like mscordacwks_<architecture>_<version>.dll +that the Windows Debugger can load. If you have the correct symbol path to the +binaries for that version of the Runtime, the Windows Debugger will load the +correct mscordacwks.dll file. + +>> I have a chicken and egg problem. I want to use SOS commands, but the CLR + isn't loaded yet. What can I do? + +In the debugger at startup you can type: + + "sxe clrn" + +Let the program run, and it will stop with the notice + + "CLR notification: module 'mscorlib' loaded" + +At this time you can use SOS commands. To turn off spurious notifications, +type: + + "sxd clrn" + +>> I got the following error message. Now what? + + 0:000> .loadby sos clr + 0:000> !DumpStackObjects + Failed to find runtime DLL (clr.dll), 0x80004005 + Extension commands need clr.dll in order to have something to do. + 0:000> + +This means that the CLR is not loaded yet, or has been unloaded. You need to +wait until your managed program is running in order to use these commands. If +you have just started the program a good way to do this is to type + + bp clr!EEStartup "g @$ra" + +in the debugger, and let it run. After the function EEStartup is finished, +there will be a minimal managed environment for executing SOS commands. + +>> I have a partial memory minidump, and !DumpObj doesn't work. Why? + +In order to run SOS commands, many CLR data structures need to be traversed. +When creating a minidump without full memory, special functions are called at +dump creation time to bring those structures into the minidump, and allow a +minimum set of SOS debugging commands to work. At this time, those commands +that can provide full or partial output are: + +CLRStack +Threads +Help +PrintException +EEVersion + +For a minidump created with this minimal set of functionality in mind, you +will get an error message when running any other commands. A full memory dump +(obtained with ".dump /ma <filename>" in the Windows Debugger) is often the +best way to debug a managed program at this level. + +>> What other tools can I use to find my bug? + +Turn on Managed Debugging Assistants. These enable additional runtime diagnostics, +particularly in the area of PInvoke/Interop. Adam Nathan has written some great +information about that: + +http://blogs.msdn.com/adam_nathan/ + +>> Does SOS support DML? + +Yes. SOS respects the .prefer_dml option in the debugger. If this setting is +turned on, then SOS will output DML by default. Alternatively, you may leave +it off and add /D to the beginning of a command to get DML based output for it. +Not all SOS commands support DML output. + +\\ + +COMMAND: stoponexception. +!StopOnException [-derived] + [-create | -create2] + <Exception> + [<Pseudo-register number>] + +!StopOnException helps when you want the Windows Debugger to stop on a +particular managed exception, say a System.OutOfMemoryException, but continue +running if other exceptions are thrown. The command can be used in two ways: + +1) When you just want to stop on one particular CLR exception + + At the debugger prompt, anytime after loading SOS, type: + + !StopOnException -create System.OutOfMemoryException 1 + + The pseudo-register number (1) indicates that SOS can use register $t1 for + maintaining the breakpoint. The -create parameter allows SOS to go ahead + and set up the breakpoint as a first-chance exception. -create2 would set + it up as a 2nd-chance exception. + +2) When you need more complex logic for stopping on a CLR exception + + !StopOnException can be used purely as a predicate in a larger expression. + If you type: + + !StopOnException System.OutOfMemoryException 3 + + then register $t3 will be set to 1 if the last thrown exception on the + current thread is a System.OutOfMemoryException. Otherwise, $t3 will be set + to 0. Using the Windows Debugger scripting language, you could chain + such calls together to stop on various exception types. You'll have to + manually create such predicates, for example: + + sxe -c "!soe System.OutOfMemoryException 3; + !soe -derived System.IOException 4; + .if(@$t3==1 || @$t4==1) { .echo 'stop' } .else {g}" + +The -derived option will cause StopOnException to set the pseudo-register to +1 even if the thrown exception type doesn't exactly match the exception type +given, but merely derives from it. So, "-derived System.Exception" would catch +every exception in the System.Exception heirarchy. + +The pseudo-register number is optional. If you don't pass a number, SOS will +use pseudo-register $t1. + +Note that !PrintException with no parameters will print out the last thrown +exception on the current thread (if any). You can use !soe as a shortcut for +!StopOnException. +\\ + +COMMAND: minidumpmode. +!MinidumpMode <0 or 1> + +Minidumps created with ".dump /m" or ".dump" have a very small set of +CLR-specific data, just enough to run a subset of SOS commands correctly. You +are able to run other SOS commands, but they may fail with unexpected errors +because required areas of memory are not mapped in or only partially mapped +in. At this time, SOS cannot reliably detect if a dump file is of this type +(for one thing, custom dump commands can map in additional memory, but there +is no facility to read meta-information about this memory). You can turn this +option on to protect against running unsafe commands against small minidumps. + +By default, MinidumpMode is 0, so there is no restriction on commands that will +run against a minidump. +\\ + +COMMAND: dumpobj. +!DumpObj [-nofields] <object address> + +This command allows you to examine the fields of an object, as well as learn +important properties of the object such as the EEClass, the MethodTable, and +the size. + +You might find an object pointer by running !DumpStackObjects and choosing +from the resultant list. Here is a simple object: + + 0:000> !DumpObj a79d40 + Name: Customer + MethodTable: 009038ec + EEClass: 03ee1b84 + Size: 20(0x14) bytes + (C:\pub\unittest.exe) + Fields: + MT Field Offset Type VT Attr Value Name + 009038ec 4000008 4 Customer 0 instance 00a79ce4 name + 009038ec 4000009 8 Bank 0 instance 00a79d2c bank + +Note that fields of type Customer and Bank are themselves objects, and you can +run !DumpObj on them too. You could look at the field directly in memory using +the offset given. "dd a79d40+8 l1" would allow you to look at the bank field +directly. Be careful about using this to set memory breakpoints, since objects +can move around in the garbage collected heap. + +What else can you do with an object? You might run !GCRoot, to determine what +roots are keeping it alive. Or you can find all objects of that type with +"!DumpHeap -type Customer". + +The column VT contains the value 1 if the field is a valuetype structure, and +0 if the field contains a pointer to another object. For valuetypes, you can +take the MethodTable pointer in the MT column, and the Value and pass them to +the command !DumpVC. + +The abbreviation !do can be used for brevity. + +The arguments in detail: +-nofields: do not print fields of the object, useful for objects like + String +\\ + +COMMAND: dumparray. +!DumpArray + [-start <startIndex>] + [-length <length>] + [-details] + [-nofields] + <array object address> + +This command allows you to examine elements of an array object. +The arguments in detail: + -start <startIndex>: optional, only supported for single dimension array. + Specify from which index the command shows the elements. + -length <length>: optional, only supported for single dimension array. + Specify how many elements to show. + -details: optional. Ask the command to print out details + of the element using !DumpObj and !DumpVC format. + -nofields: optional, only takes effect when -details is used. Do + not print fields of the elements. Useful for arrays of + objects like String + + Example output: + + 0:000> !dumparray -start 2 -length 3 -details 00ad28d0 + Name: Value[] + MethodTable: 03e41044 + EEClass: 03e40fc0 + Size: 132(0x84) bytes + Array: Rank 1, Number of elements 10, Type VALUETYPE + Element Type: Value + [2] 00ad28f0 + Name: Value + MethodTable 03e40f4c + EEClass: 03ef1698 + Size: 20(0x14) bytes + (C:\bugs\225271\arraytest.exe) + Fields: + MT Field Offset Type Attr Value Name + 5b9a628c 4000001 0 System.Int32 instance 2 x + 5b9a628c 4000002 4 System.Int32 instance 4 y + 5b9a628c 4000003 8 System.Int32 instance 6 z + [3] 00ad28fc + Name: Value + MethodTable 03e40f4c + EEClass: 03ef1698 + Size: 20(0x14) bytes + (C:\bugs\225271\arraytest.exe) + Fields: + MT Field Offset Type Attr Value Name + 5b9a628c 4000001 0 System.Int32 instance 3 x + 5b9a628c 4000002 4 System.Int32 instance 6 y + 5b9a628c 4000003 8 System.Int32 instance 9 z + [4] 00ad2908 + Name: Value + MethodTable 03e40f4c + EEClass: 03ef1698 + Size: 20(0x14) bytes + (C:\bugs\225271\arraytest.exe) + Fields: + MT Field Offset Type Attr Value Name + 5b9a628c 4000001 0 System.Int32 instance 4 x + 5b9a628c 4000002 4 System.Int32 instance 8 y + 5b9a628c 4000003 8 System.Int32 instance 12 z + + +\\ + +COMMAND: dumpstackobjects. +!DumpStackObjects [-verify] [top stack [bottom stack]] + +This command will display any managed objects it finds within the bounds of +the current stack. Combined with the stack tracing commands like K and +!CLRStack, it is a good aid to determining the values of locals and +parameters. + +If you use the -verify option, each non-static CLASS field of an object +candidate is validated. This helps to eliminate false positives. It is not +on by default because very often in a debugging scenario, you are +interested in objects with invalid fields. + +The abbreviation !dso can be used for brevity. +\\ + +COMMAND: dumpheap. +!DumpHeap [-stat] + [-strings] + [-short] + [-min <size>] + [-max <size>] + [-thinlock] + [-startAtLowerBound] + [-mt <MethodTable address>] + [-type <partial type name>] + [start [end]] + +!DumpHeap is a powerful command that traverses the garbage collected heap, +collection statistics about objects. With it's various options, it can look for +particular types, restrict to a range, or look for ThinLocks (see !SyncBlk +documentation). Finally, it will provide a warning if it detects excessive +fragmentation in the GC heap. + +When called without options, the output is first a list of objects in the heap, +followed by a report listing all the types found, their size and number: + + 0:000> !dumpheap + Address MT Size + 00a71000 0015cde8 12 Free + 00a7100c 0015cde8 12 Free + 00a71018 0015cde8 12 Free + 00a71024 5ba58328 68 + 00a71068 5ba58380 68 + 00a710ac 5ba58430 68 + 00a710f0 5ba5dba4 68 + ... + total 619 objects + Statistics: + MT Count TotalSize Class Name + 5ba7607c 1 12 System.Security.Permissions.HostProtectionResource + 5ba75d54 1 12 System.Security.Permissions.SecurityPermissionFlag + 5ba61f18 1 12 System.Collections.CaseInsensitiveComparer + ... + 0015cde8 6 10260 Free + 5ba57bf8 318 18136 System.String + ... + +"Free" objects are simply regions of space the garbage collector can use later. +If 30% or more of the heap contains "Free" objects, the process may suffer from +heap fragmentation. This is usually caused by pinning objects for a long time +combined with a high rate of allocation. Here is example output where !DumpHeap +provides a warning about fragmentation: + + <After the Statistics section> + Fragmented blocks larger than 1MB: + Addr Size Followed by + 00a780c0 1.5MB 00bec800 System.Byte[] + 00da4e38 1.2MB 00ed2c00 System.Byte[] + 00f16df0 1.2MB 01044338 System.Byte[] + +The arguments in detail: + +-stat Restrict the output to the statistical type summary +-strings Restrict the output to a statistical string value summary +-short Limits output to just the address of each object. This allows you + to easily pipe output from the command to another debugger + command for automation. +-min Ignore objects less than the size given in bytes +-max Ignore objects larger than the size given in bytes +-thinlock Report on any ThinLocks (an efficient locking scheme, see !SyncBlk + documentation for more info) +-startAtLowerBound + Force heap walk to begin at lower bound of a supplied address range. + (During plan phase, the heap is often not walkable because objects + are being moved. In this case, DumpHeap may report spurious errors, + in particular bad objects. It may be possible to traverse more of + the heap after the reported bad object. Even if you specify an + address range, !DumpHeap will start its walk from the beginning of + the heap by default. If it finds a bad object before the specified + range, it will stop before displaying the part of the heap in which + you are interested. This switch will force !DumpHeap to begin its + walk at the specified lower bound. You must supply the address of a + good object as the lower bound for this to work. Display memory at + the address of the bad object to manually find the next method + table (use !dumpmt to verify). If the GC is currently in a call to + memcopy, You may also be able to find the next object's address by + adding the size to the start address given as parameters.) +-mt List only those objects with the MethodTable given +-type List only those objects whose type name is a substring match of the + string provided. +start Begin listing from this address +end Stop listing at this address + +A special note about -type: Often, you'd like to find not only Strings, but +System.Object arrays that are constrained to contain Strings. ("new +String[100]" actually creates a System.Object array, but it can only hold +System.String object pointers). You can use -type in a special way to find +these arrays. Just pass "-type System.String[]" and those Object arrays will +be returned. More generally, "-type <Substring of interesting type>[]". + +The start/end parameters can be obtained from the output of !EEHeap -gc. For +example, if you only want to list objects in the large heap segment: + + 0:000> !eeheap -gc + Number of GC Heaps: 1 + generation 0 starts at 0x00c32754 + generation 1 starts at 0x00c32748 + generation 2 starts at 0x00a71000 + segment begin allocated size + 00a70000 00a71000 010443a8 005d33a8(6108072) + Large object heap starts at 0x01a71000 + segment begin allocated size + 01a70000 01a71000 01a75000 0x00004000(16384) + Total Size 0x5d73a8(6124456) + ------------------------------ + GC Heap Size 0x5d73a8(6124456) + + 0:000> !dumpheap 1a71000 1a75000 + Address MT Size + 01a71000 5ba88bd8 2064 + 01a71810 0019fe48 2032 Free + 01a72000 5ba88bd8 4096 + 01a73000 0019fe48 4096 Free + 01a74000 5ba88bd8 4096 + total 5 objects + Statistics: + MT Count TotalSize Class Name + 0019fe48 2 6128 Free + 5ba88bd8 3 10256 System.Object[] + Total 5 objects + +Finally, if GC heap corruption is present, you may see an error like this: + + 0:000> !dumpheap -stat + object 00a73d24: does not have valid MT + curr_object : 00a73d24 + Last good object: 00a73d14 + ---------------- + +That indicates a serious problem. See the help for !VerifyHeap for more +information on diagnosing the cause. +\\ + +COMMAND: dumpvc. +!DumpVC <MethodTable address> <Address> + +!DumpVC allows you to examine the fields of a value class. In C#, this is a +struct, and lives on the stack or within an Object on the GC heap. You need +to know the MethodTable address to tell SOS how to interpret the fields, as +a value class is not a first-class object with it's own MethodTable as the +first field. For example: + + 0:000> !DumpObj a79d98 + Name: Mainy + MethodTable: 009032d8 + EEClass: 03ee1424 + Size: 28(0x1c) bytes + (C:\pub\unittest.exe) + Fields: + MT Field Offset Type Attr Value Name + 0090320c 4000010 4 VALUETYPE instance 00a79d9c m_valuetype + 009032d8 400000f 4 CLASS static 00a79d54 m_sExcep + +m_valuetype is a value type. The value in the MT column (0090320c) is the +MethodTable for it, and the Value column provides the start address: + + 0:000> !DumpVC 0090320c 00a79d9c + Name: Funny + MethodTable 0090320c + EEClass: 03ee14b8 + Size: 28(0x1c) bytes + (C:\pub\unittest.exe) + Fields: + MT Field Offset Type Attr Value Name + 0090320c 4000001 0 CLASS instance 00a743d8 signature + 0090320c 4000002 8 System.Int32 instance 2345 m1 + 0090320c 4000003 10 System.Boolean instance 1 b1 + 0090320c 4000004 c System.Int32 instance 1234 m2 + 0090320c 4000005 4 CLASS instance 00a79d98 backpointer + +!DumpVC is quite a specialized function. Some managed programs make heavy use +of value classes, while others do not. +\\ + +COMMAND: gcroot. +!GCRoot [-nostacks] <Object address> + +!GCRoot looks for references (or roots) to an object. These can exist in four +places: + + 1. On the stack + 2. Within a GC Handle + 3. In an object ready for finalization + 4. As a member of an object found in 1, 2 or 3 above. + +First, all stacks will be searched for roots, then handle tables, and finally +the freachable queue of the finalizer. Some caution about the stack roots: +!GCRoot doesn't attempt to determine if a stack root it encountered is valid +or is old (discarded) data. You would have to use !CLRStack and !U to +disassemble the frame that the local or argument value belongs to in order to +determine if it is still in use. + +Because people often want to restrict the search to gc handles and freachable +objects, there is a -nostacks option. +\\ + +COMMAND: objsize. +!ObjSize [<Object address>] | [-aggregate] [-stat] + +With no parameters, !ObjSize lists the size of all objects found on managed +threads. It also enumerates all GCHandles in the process, and totals the size +of any objects pointed to by those handles. In calculating object size, +!ObjSize includes the size of all child objects in addition to the parent. + +For example, !DumpObj lists a size of 20 bytes for this Customer object: + + 0:000> !do a79d40 + Name: Customer + MethodTable: 009038ec + EEClass: 03ee1b84 + Size: 20(0x14) bytes + (C:\pub\unittest.exe) + Fields: + MT Field Offset Type Attr Value Name + 009038ec 4000008 4 CLASS instance 00a79ce4 name + 009038ec 4000009 8 CLASS instance 00a79d2c bank + 009038ec 400000a c System.Boolean instance 1 valid + +but !ObjSize lists 152 bytes: + + 0:000> !ObjSize a79d40 + sizeof(00a79d40) = 152 ( 0x98) bytes (Customer) + +This is because a Customer points to a Bank, has a name, and the Bank points to +an Address string. You can use !ObjSize to identify any particularly large +objects, such as a managed cache in a web server. + +While running ObjSize with no arguments may point to specific roots that hold +onto large amounts of memory it does not provide information regarding the +amount of managed memory that is still alive. This is due to the fact that a +number of roots can share a common subgraph, and that part will be reported in +the size of all the roots that reference the subgraph. The -aggregate argument +is meant to answer this question: + +The -aggregate option can be used in conjunction with the -stat argument to get +a detailed view of what are the types that are still rooted. Using !dumpheap +-stat and !objsize -aggregate -stat one can determine what are the the objects +that are not rooted any more and diagnose various memory issues. + +Sample output when using the -aggregate and -stat: + + 0:003> !ObjSize -aggregate -stat + Scan Thread 0 OSTHread f70 + Scan Thread 2 OSTHread ef8 + Statistics: + MT Count TotalSize Class Name + 01e63768 1 12 Test+d + 01e63660 1 16 Test+c + 01e63548 1 16 Test+b + 01e632f8 1 16 Test+a + ... + 5b6c6d40 9 504 System.Collections.Hashtable + 5b6ebe28 3 552 System.Byte[] + 5b6ec0bc 9 1296 System.Collections.Hashtable+bucket[] + 5b6ec200 9 1436 System.Char[] + 5b6c447c 77 2468 System.String + 5b6ebd64 8 9020 System.Object[] + Total 203 objects + Total Memory Size 18332 (0x479c) bytes + +\\ + +COMMAND: finalizequeue. +!FinalizeQueue [-detail] | [-allReady] [-short] + +This command lists the objects registered for finalization. Here is output from +a simple program: + + 0:000> !finalizequeue + SyncBlocks to be cleaned up: 0 + MTA Interfaces to be released: 0 + STA Interfaces to be released: 1 + generation 0 has 4 finalizable objects (0015bc90->0015bca0) + generation 1 has 0 finalizable objects (0015bc90->0015bc90) + generation 2 has 0 finalizable objects (0015bc90->0015bc90) + Ready for finalization 0 objects (0015bca0->0015bca0) + Statistics: + MT Count TotalSize Class Name + 5ba6cf78 1 24 Microsoft.Win32.SafeHandles.SafeFileHandle + 5ba5db04 1 68 System.Threading.Thread + 5ba73e28 2 112 System.IO.StreamWriter + Total 4 objects + +The GC heap is divided into generations, and objects are listed accordingly. We +see that only generation 0 (the youngest generation) has any objects registered +for finalization. The notation "(0015bc90->0015bca0)" means that if you look at +memory in that range, you'll see the object pointers that are registered: + +0:000> dd 15bc90 15bca0-4 +0015bc90 00a743f4 00a79f00 00a7b3d8 00a7b47c + +You could run !DumpObj on any of those pointers to learn more. In this example, +there are no objects ready for finalization, presumably because they still have +roots (You can use !GCRoot to find out). The statistics section provides a +higher-level summary of the objects registered for finalization. Note that +objects ready for finalization are also included in the statistics (if any). + +Specifying -short will inhibit any display related to SyncBlocks or RCWs. + +The arguments in detail: + +-allReady Specifying this argument will allow for the display of all objects + that are ready for finalization, whether they are already marked by + the GC as such, or whether the next GC will. The objects that are + not in the "Ready for finalization" list are finalizable objects that + are no longer rooted. This option can be very expensive, as it + verifies whether all the objects in the finalizable queues are still + rooted or not. +-short Limits the output to just the address of each object. If used in + conjunction with -allReady it enumerates all objects that have a + finalizer that are no longer rooted. If used independently it lists + all objects in the finalizable and "ready for finalization" queues. +-detail Will display extra information on any SyncBlocks that need to be + cleaned up, and on any RuntimeCallableWrappers (RCWs) that await + cleanup. Both of these data structures are cached and cleaned up by + the finalizer thread when it gets a chance to run. +\\ + +COMMAND: printexception. +!PrintException [-nested] [-lines] [<Exception object address>] + +This will format fields of any object derived from System.Exception. One of the +more useful aspects is that it will format the _stackTrace field, which is a +binary array. If _stackTraceString field is not filled in, that can be helpful +for debugging. You can of course use !DumpObj on the same exception object to +explore more fields. + +If called with no parameters, PrintException will look for the last outstanding +exception on the current thread and print it. This will be the same exception +that shows up in a run of !Threads. + +!PrintException will notify you if there are any nested exceptions on the +current managed thread. (A nested exception occurs when you throw another +exception within a catch handler already being called for another exception). +If there are nested exceptions, you can re-run !PrintException with the +"-nested" option to get full details on the nested exception objects. The +!Threads command will also tell you which threads have nested exceptions. + +!PrintException can display source information if available, by specifying the +-lines command line argument. + +The abbreviation !pe can be used for brevity. +\\ + +COMMAND: traverseheap. +!TraverseHeap [-xml] [-verify] <filename> + +!TraverseHeap writes out a file in a format understood by the CLR Profiler. +You can download the CLR Profiler from this link: + +http://www.microsoft.com/downloads/details.aspx?FamilyId=86CE6052-D7F4-4AEB- +9B7A-94635BEEBDDA&displaylang=en + +It creates a graphical display of the GC heap to help you analyze the state of +your application. + +If you pass the -verify option it will do more sanity checking of the heap +as it dumps it. Use this option if heap corruption is suspected. + +If you pass the "-xml" flag, the file is instead written out in an easy to +understand xml format: + + <gcheap> + <types> + <type id="1" name="System.String"> + ... + </types> + <roots> + <root kind="handle" address="0x00a73ff0"/> + <root kind="stack" address="0x0069f0e0"/> + ... + </roots> + <objects> + <object address="0x00b73030" typeid="1" size="300"/> + <object address="0x00b75054" typeid="5" size="20"> + <member address="0x00b75088" /> + ... + </object> + ... + </objects> + </gcheap> + +You can break into your process, load SOS, take a snapshot of your heap with +this function, then continue. +\\ +COMMAND: threadstate. +!ThreadState value + +The !Threads command outputs, among other things, the state of the thread. +This is a bit field which corresponds to various states the thread is in. +To check the state of the thread, simply pass that bit field from the +output of !Threads into !ThreadState. + +Example: + 0:003> !Threads + ThreadCount: 2 + UnstartedThread: 0 + BackgroundThread: 1 + PendingThread: 0 + DeadThread: 0 + Hosted Runtime: no + PreEmptive GC Alloc Lock + ID OSID ThreadOBJ State GC Context Domain Count APT Exception + 0 1 250 0019b068 a020 Disabled 02349668:02349fe8 0015def0 0 MTA + 2 2 944 001a6020 b220 Enabled 00000000:00000000 0015def0 0 MTA (Finalizer) + 0:003> !ThreadState b220 + Legal to Join + Background + CLR Owns + CoInitialized + In Multi Threaded Apartment + +Possible thread states: + Thread Abort Requested + GC Suspend Pending + User Suspend Pending + Debug Suspend Pending + GC On Transitions + Legal to Join + Yield Requested + Hijacked by the GC + Blocking GC for Stack Overflow + Background + Unstarted + Dead + CLR Owns + CoInitialized + In Single Threaded Apartment + In Multi Threaded Apartment + Reported Dead + Fully initialized + Task Reset + Sync Suspended + Debug Will Sync + Stack Crawl Needed + Suspend Unstarted + Aborted + Thread Pool Worker Thread + Interruptible + Interrupted + Completion Port Thread + Abort Initiated + Finalized + Failed to Start + Detached +\\ +COMMAND: threads. +!Threads [-live] [-special] + +!Threads lists all the mananaged threads in the process. + +-live: optional. Only print threads associated with a live thread. +-special: optional. With this switch, the command will display all the special + threads created by CLR. Those threads might not be managed threads + so they might not be shown in the first part of the command's + output. Example of special threads include: GC threads (in + concurrent GC and server GC), Debugger helper threads, Finalizer + threads, AppDomain Unload threads, and Threadpool timer threads. + +Each thread has many attributes, many of which can be ignored. The important +ones are discussed below: + +There are three ID columns: + +1) The debugger shorthand ID (When the runtime is hosted this column might + display the special string "<<<<" when this internal thread object is not + associated with any physical thread - this may happen when the host reuses + the runtime internal thread object) +2) The CLR Thread ID +3) The OS thread ID. + +If PreEmptiveGC is enabled for a thread, then a garbage collection +can occur while that thread is running. For example, if you break in while +a managed thread is making a PInvoke call to a Win32 function, that thread +will be in PreEmptive GC mode. + +The Domain column indicates what AppDomain the thread is currently executing +in. You can pass this value to !DumpDomain to find out more. + +The APT column gives the COM apartment mode. + +Exception will list the last thrown exception (if any) for the thread. More +details can be obtained by passing the pointer value to !PrintException. If +you get the notation "(nested exceptions)", you can get details on those +exceptions by switching to the thread in question, and running +"!PrintException -nested". +\\ + +COMMAND: clrstack. +!CLRStack [-a] [-l] [-p] [-n] +!CLRStack [-a] [-l] [-p] [-i] [variable name] [frame] + +CLRStack attempts to provide a true stack trace for managed code only. It is +handy for clean, simple traces when debugging straightforward managed +programs. The -p parameter will show arguments to the managed function. The +-l parameter can be used to show information on local variables in a frame. +SOS can't retrieve local names at this time, so the output for locals is in +the format <local address> = <value>. The -a (all) parameter is a short-cut +for -l and -p combined. + +If the debugger has the option SYMOPT_LOAD_LINES specified (either by the +.lines or .symopt commands), SOS will look up the symbols for every managed +frame and if successful will display the corresponding source file name and +line number. The -n (No line numbers) parameter can be specified to disable +this behavior. + +When you see methods with the name "[Frame:...", that indicates a transition +between managed and unmanaged code. You could run !IP2MD on the return +addresses in the call stack to get more information on each managed method. + +On x64 platforms, Transition Frames are not displayed at this time. To avoid +heavy optimization of parameters and locals one can request the JIT compiler +to not optimize functions in the managed app by creating a file myapp.ini +(if your program is myapp.exe) in the same directory. Put the following lines +in myapp.ini and re-run: + +[.NET Framework Debugging Control] +GenerateTrackingInfo=1 +AllowOptimize=0 + +The -i option is a new EXPERIMENTAL addition to CLRStack and will use the ICorDebug +interfaces to display the managed stack and variables. With this option you can also +view and expand arrays and fields for managed variables. If a stack frame number is +specified in the command line, CLRStack will show you the parameters and/or locals +only for that frame (provided you specify -l or -p or -a of course). If a variable +name and a stack frame number are specified in the command line, CLRStack will show +you the parameters and/or locals for that frame, and will also show you the fields +for that variable name you specified. Here are some examples: + !CLRStack -i -a : This will show you all parameters and locals for all frames + !CLRStack -i -a 3 : This will show you all parameters and locals, for frame 3 + !CLRStack -i var1 0 : This will show you the fields of 'var1' for frame 0 + !CLRStack -i var1.abc 2 : This will show you the fields of 'var1', and expand + 'var1.abc' to show you the fields of the 'abc' field, + for frame 2. + !CLRStack -i var1.[basetype] 0 : This will show you the fields of 'var1', and + expand the base type of 'var1' to show you its + fields. + !CLRStack -i var1.[6] 0 : If 'var1' is an array, this will show you the element + at index 6 in the array, along with its fields +The -i options uses DML output for a better debugging experience, so typically you +should only need to execute "!CLRStack -i", and from there, click on the DML +hyperlinks to inspect the different managed stack frames and managed variables. +\\ + +COMMAND: ip2md. +!IP2MD <Code address> + +Given an address in managed JITTED code, IP2MD attempts to find the MethodDesc +associated with it. For example, this output from K: + + 0:000> K + ChildEBP RetAddr + 00a79c78 03ef02ab image00400000!Mainy.Top()+0xb + 00a79c78 03ef01a6 image00400000!Mainy.Level(Int32)+0xb + 00a79c78 5d3725a1 image00400000!Mainy.Main()+0xee + 0012ea04 5d512f59 clr!CallDescrWorkerInternal+0x30 + 0012ee34 5d7946aa clr!CallDescrWorker+0x109 + + 0:000> !IP2MD 03ef01a6 + MethodDesc: 00902f40 + Method Name: Mainy.Main() + Class: 03ee1424 + MethodTable: 009032d8 + mdToken: 0600000d + Module: 001caa38 + IsJitted: yes + CodeAddr: 03ef00b8 + Transparency: Critical + Source file: c:\Code\prj.mini\exc.cs @ 39 + +We have taken a return address into Mainy.Main, and discovered information +about that method. You could run !U, !DumpMT, !DumpClass, !DumpMD, or +!DumpModule on the fields listed to learn more. + +The "Source line" output will only be present if the debugger can find the +symbols for the managed module containing the given <code address>, and if the +debugger is configured to load line number information. +\\ + +COMMAND: u. +!U [-gcinfo] [-ehinfo] [-n] <MethodDesc address> | <Code address> + +Presents an annotated disassembly of a managed method when given a MethodDesc +pointer for the method, or a code address within the method body. Unlike the +debugger "U" function, the entire method from start to finish is printed, +with annotations that convert metadata tokens to names. + + <example output> + ... + 03ef015d b901000000 mov ecx,0x1 + 03ef0162 ff156477a25b call dword ptr [mscorlib_dll+0x3c7764 (5ba27764)] (System.Console.InitializeStdOutError(Boolean), mdToken: 06000713) + 03ef0168 a17c20a701 mov eax,[01a7207c] (Object: SyncTextWriter) + 03ef016d 89442414 mov [esp+0x14],eax + +If you pass the -gcinfo flag, you'll get inline display of the GCInfo for +the method. You can also obtain this information with the !GCInfo command. + +If you pass the -ehinfo flag, you'll get inline display of exception info +for the method. (Beginning and end of try/finally/catch handlers, etc.). +You can also obtain this information with the !EHInfo command. + +If the debugger has the option SYMOPT_LOAD_LINES specified (either by the +.lines or .symopt commands), and if symbols are available for the managed +module containing the method being examined, the output of the command will +include the source file name and line number corresponding to the +disassembly. The -n (No line numbers) flag can be specified to disable this +behavior. + + <example output> + ... + c:\Code\prj.mini\exc.cs @ 38: + 001b00b0 8b0d3020ab03 mov ecx,dword ptr ds:[3AB2030h] ("Break in debugger. When done type <Enter> to continue: ") + 001b00b6 e8d5355951 call mscorlib_ni+0x8b3690 (51743690) (System.Console.Write(System.String), mdToken: 0600091b) + 001b00bb 90 nop + + c:\Code\prj.mini\exc.cs @ 39: + 001b00bc e863cdc651 call mscorlib_ni+0xf8ce24 (51e1ce24) (System.Console.ReadLine(), mdToken: 060008f6) + >>> 001b00c1 90 nop + ... +\\ + +COMMAND: dumpstack. +!DumpStack [-EE] [-n] [top stack [bottom stack]] + +[x86 and x64 documentation] + +This command provides a verbose stack trace obtained by "scraping." Therefore +the output is very noisy and potentially confusing. The command is good for +viewing the complete call stack when "kb" gets confused. For best results, +make sure you have valid symbols. + +-EE will only show managed functions. + +If the debugger has the option SYMOPT_LOAD_LINES specified (either by the +.lines or .symopt commands), SOS will look up the symbols for every managed +frame and if successful will display the corresponding source file name and +line number. The -n (No line numbers) parameter can be specified to disable +this behavior. + +You can also pass a stack range to limit the output. Use the debugger +extension !teb to get the top and bottom stack values. + +\\ + +COMMAND: eestack. +!EEStack [-short] [-EE] + +This command runs !DumpStack on all threads in the process. The -EE option is +passed directly to !DumpStack. The -short option tries to narrow down the +output to "interesting" threads only, which is defined by + +1) The thread has taken a lock. +2) The thread has been "hijacked" in order to allow a garbage collection. +3) The thread is currently in managed code. + +See the documentation for !DumpStack for more info. +\\ + +COMMAND: ehinfo. +!EHInfo (<MethodDesc address> | <Code address>) + +!EHInfo shows the exception handling blocks in a jitted method. For each +handler, it shows the type, including code addresses and offsets for the clause +block and the handler block. For a TYPED handler, this would be the "try" and +"catch" blocks respectively. + +Sample output: + + 0:000> !ehinfo 33bbd3a + MethodDesc: 03310f68 + Method Name: MainClass.Main() + Class: 03571358 + MethodTable: 0331121c + mdToken: 0600000b + Module: 001e2fd8 + IsJitted: yes + CodeAddr: 033bbca0 + Transparency: Critical + + EHHandler 0: TYPED catch(System.IO.FileNotFoundException) + Clause: [033bbd2b, 033bbd3c] [8b, 9c] + Handler: [033bbd3c, 033bbd50] [9c, b0] + + EHHandler 1: FINALLY + Clause: [033bbd83, 033bbda3] [e3, 103] + Handler: [033bbda3, 033bbdc5] [103, 125] + + EHHandler 2: TYPED catch(System.Exception) + Clause: [033bbd7a, 033bbdc5] [da, 125] + Handler: [033bbdc5, 033bbdd6] [125, 136] + +\\ + +COMMAND: gcinfo. +!GCInfo (<MethodDesc address> | <Code address>) + +!GCInfo is especially useful for CLR Devs who are trying to determine if there +is a bug in the JIT Compiler. It parses the GCEncoding for a method, which is a +compressed stream of data indicating when registers or stack locations contain +managed objects. It is important to keep track of this information, because if +a garbage collection occurs, the collector needs to know where roots are so it +can update them with new object pointer values. + +Here is sample output where you can see the change in register state. Normally +you would print this output out and read it alongside a disassembly of the +method. For example, the notation "reg EDI becoming live" at offset 0x11 of the +method might correspond to a "mov edi,ecx" statement. + + 0:000> !gcinfo 5b68dbb8 (5b68dbb8 is the start of a JITTED method) + entry point 5b68dbb8 + preJIT generated code + GC info 5b9f2f09 + Method info block: + method size = 0036 + prolog size = 19 + epilog size = 8 + epilog count = 1 + epilog end = yes + saved reg. mask = 000B + ebp frame = yes + fully interruptible=yes + double align = no + security check = no + exception handlers = no + local alloc = no + edit & continue = no + varargs = no + argument count = 4 + stack frame size = 1 + untracked count = 5 + var ptr tab count = 0 + epilog at 002E + 36 D4 8C C7 AA | + 93 F3 40 05 | + + Pointer table: + 14 | [EBP+14H] an untracked local + 10 | [EBP+10H] an untracked local + 0C | [EBP+0CH] an untracked local + 08 | [EBP+08H] an untracked local + 44 | [EBP-04H] an untracked local + F1 79 | 0011 reg EDI becoming live + 72 | 0013 reg ESI becoming live + 83 | 0016 push ptr 0 + 8B | 0019 push ptr 1 + 93 | 001C push ptr 2 + 9B | 001F push ptr 3 + 56 | 0025 reg EDX becoming live + 4A | 0027 reg ECX becoming live + 0E | 002D reg ECX becoming dead + 10 | 002D reg EDX becoming dead + E0 | 002D pop 4 ptrs + F0 31 | 0036 reg ESI becoming dead + 38 | 0036 reg EDI becoming dead + FF | + +This function is important for CLR Devs, but very difficult for anyone else to +make sense of it. You would usually come to use it if you suspect a gc heap +corruption bug caused by invalid GCEncoding for a particular method. +\\ + +COMMAND: comstate. +!COMState + +!COMState lists the com apartment model for each thread, as well as a Context +pointer if provided. +\\ + +COMMAND: bpmd. +!BPMD [-nofuturemodule] <module name> <method name> [<il offset>] +!BPMD <source file name>:<line number> +!BPMD -md <MethodDesc> +!BPMD -list +!BPMD -clear <pending breakpoint number> +!BPMD -clearall + +!BPMD provides managed breakpoint support. If it can resolve the method name +to a loaded, jitted or ngen'd function it will create a breakpoint with "bp". +If not then either the module that contains the method hasn't been loaded yet +or the module is loaded, but the function is not jitted yet. In these cases, +!bpmd asks the Windows Debugger to receive CLR Notifications, and waits to +receive news of module loads and JITs, at which time it will try to resolve +the function to a breakpoint. -nofuturemodule can be used to suppress +creating a breakpoint against a module that has not yet been loaded. + +Management of the list of pending breakpoints can be done via !BPMD -list, +!BPMD -clear, and !BPMD -clearall commands. !BPMD -list generates a list of +all of the pending breakpoints. If the pending breakpoint has a non-zero +module id, then that pending breakpoint is specific to function in that +particular loaded module. If the pending breakpoint has a zero module id, then +the breakpoint applies to modules that have not yet been loaded. Use +!BPMD -clear or !BPMD -clearall to remove pending breakpoints from the list. + +This brings up a good question: "I want to set a breakpoint on the main +method of my application. How can I do this?" + + 1) If you know the full path to SOS, use this command and skip to step 6 + .load <the full path to sos.dll> + + 2) If you don't know the full path to sos, its usually next to clr.dll + You can wait for clr to load and then find it. + Start the debugger and type: + sxe -c "" clrn + 3) g + 4) You'll get the following notification from the debugger: + "CLR notification: module 'mscorlib' loaded" + 5) Now you can load SOS. Type + .loadby sos clr + + 6) Add the breakpoint with command such as: + !bpmd myapp.exe MyApp.Main + 7) g + 8) You will stop at the start of MyApp.Main. If you type "bl" you will + see the breakpoint listed. + +You can specify breakpoints by file and line number if: + a) You have some version of .Net Framework installed on your machine. Any OS from + Vista onwards should have .Net Framework installed by default. + b) You have PDBs for the managed modules that need breakpoints, and your symbol + path points to those PDBs. +This is often easier than module and method name syntax. For example: + !bpmd Demo.cs:15 + + +To correctly specify explicitly implemented methods make sure to retrieve the +method name from the metadata, or from the output of the "!dumpmt -md" command. +For example: + + public interface I1 + { + void M1(); + } + public class ExplicitItfImpl : I1 + { + ... + void I1.M1() // this method's name is 'I1.M1' + { ... } + } + + !bpmd myapp.exe ExplicitItfImpl.I1.M1 + + +!BPMD works equally well with generic types. Adding a breakpoint on a generic +type sets breakpoints on all already JIT-ted generic methods and sets a pending +breakpoint for any instantiation that will be JIT-ted in the future. + +Example for generics: + Given the following two classes: + + class G3<T1, T2, T3> + { + ... + public void F(T1 p1, T2 p2, T3 p3) + { ... } + } + + public class G1<T> { + // static method + static public void G<W>(W w) + { ... } + } + + One would issue the following commands to set breapoints on G3.F() and + G1.G(): + + !bpmd myapp.exe G3`3.F + !bpmd myapp.exe G1`1.G + +And for explicitly implemented methods on generic interfaces: + public interface IT1<T> + { + void M1(T t); + } + + public class ExplicitItfImpl<U> : IT1<U> + { + ... + void IT1<U>.M1(U u) // this method's name is 'IT1<U>.M1' + { ... } + } + + !bpmd bpmd.exe ExplicitItfImpl`1.IT1<U>.M1 + +Additional examples: + If IT1 and ExplicitItfImpl are types declared inside another class, + Outer, the bpmd command would become: + + !bpmd bpmd.exe Outer+ExplicitItfImpl`1.Outer.IT1<U>.M1 + + (note that the fully qualified type name for ExplicitItfImpl became + Outer+ExplicitItfImpl, using the '+' separator, while the method name + is Outer.IT1<U>.M1, using a '.' as the separator) + + Furthermore, if the Outer class resides in a namespace, NS, the bpmd + command to use becomes: + + !bpmd bpmd.exe NS.Outer+ExplicitItfImpl`1.NS.Outer.IT1<U>.M1 + +!BPMD does not accept offsets nor parameters in the method name. You can add +an IL offset as an optional parameter seperate from the name. If there are overloaded +methods, !bpmd will set a breakpoint for all of them. + +In the case of hosted environments such as SQL, the module name may be +complex, like 'price, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. +For this case, just be sure to surround the module name with single quotes, +like: + +!bpmd 'price, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' Price.M2 + +\\ + +COMMAND: dumpdomain. +!DumpDomain [<Domain address>] + +When called with no parameters, !DumpDomain will list all the AppDomains in the +process. It enumerates each Assembly loaded into those AppDomains as well. +In addition to your application domain, and any domains it might create, there +are two special domains: the Shared Domain and the System Domain. + +Any Assembly pointer in the output can be passed to !DumpAssembly. Any Module +pointer in the output can be passed to !DumpModule. Any AppDomain pointer can +be passed to !DumpDomain to limit output only to that AppDomain. Other +functions provide an AppDomain pointer as well, such as !Threads where it lists +the current AppDomain for each thread. +\\ + +COMMAND: eeheap. +!EEHeap [-gc] [-loader] + +!EEHeap enumerates process memory consumed by internal CLR data structures. You +can limit the output by passing "-gc" or "-loader". All information will be +displayed otherwise. + +The information for the Garbage Collector lists the ranges of each Segment in +the managed heap. This can be useful if you believe you have an object pointer. +If the pointer falls within a segment range given by "!EEHeap -gc", then you do +have an object pointer, and can attempt to run "!DumpObj" on it. + +Here is output for a simple program: + + 0:000> !eeheap -gc + Number of GC Heaps: 1 + generation 0 starts at 0x00a71018 + generation 1 starts at 0x00a7100c + generation 2 starts at 0x00a71000 + segment begin allocated size + 00a70000 00a71000 00a7e01c 0000d01c(53276) + Large object heap starts at 0x01a71000 + segment begin allocated size + 01a70000 01a71000 01a76000 0x00005000(20480) + Total Size 0x1201c(73756) + ------------------------------ + GC Heap Size 0x1201c(73756) + +So the total size of the GC Heap is only 72K. On a large web server, with +multiple processors, you can expect to see a GC Heap of 400MB or more. The +Garbage Collector attempts to collect and reclaim memory only when required to +by memory pressure for better performance. You can also see the notion of +"generations," wherein the youngest objects live in generation 0, and +long-lived objects eventually get "promoted" to generation 2. + +The loader output lists various private heaps associated with AppDomains. It +also lists heaps associated with the JIT compiler, and heaps associated with +Modules. For example: + + 0:000> !EEHeap -loader + Loader Heap: + -------------------------------------- + System Domain: 5e0662a0 + LowFrequencyHeap:008f0000(00002000:00001000) Size: 0x00001000 bytes. + HighFrequencyHeap:008f2000(00008000:00001000) Size: 0x00001000 bytes. + StubHeap:008fa000(00002000:00001000) Size: 0x00001000 bytes. + Total size: 0x3000(12288)bytes + -------------------------------------- + Shared Domain: 5e066970 + LowFrequencyHeap:00920000(00002000:00001000) 03e30000(00010000:00003000) Size: 0x00004000 bytes. + Wasted: 0x00001000 bytes. + HighFrequencyHeap:00922000(00008000:00001000) Size: 0x00001000 bytes. + StubHeap:0092a000(00002000:00001000) Size: 0x00001000 bytes. + Total size: 0x6000(24576)bytes + -------------------------------------- + Domain 1: 14f000 + LowFrequencyHeap:00900000(00002000:00001000) 03ee0000(00010000:00003000) Size: 0x00004000 bytes. + Wasted: 0x00001000 bytes. + HighFrequencyHeap:00902000(00008000:00003000) Size: 0x00003000 bytes. + StubHeap:0090a000(00002000:00001000) Size: 0x00001000 bytes. + Total size: 0x8000(32768)bytes + -------------------------------------- + Jit code heap: + Normal JIT:03ef0000(00010000:00002000) Size: 0x00002000 bytes. + Total size: 0x2000(8192)bytes + -------------------------------------- + Module Thunk heaps: + Module 5ba22410: Size: 0x00000000 bytes. + Module 001c1320: Size: 0x00000000 bytes. + Module 001c03f0: Size: 0x00000000 bytes. + Module 001caa38: Size: 0x00000000 bytes. + Total size: 0x0(0)bytes + -------------------------------------- + Module Lookup Table heaps: + Module 5ba22410:Size: 0x00000000 bytes. + Module 001c1320:Size: 0x00000000 bytes. + Module 001c03f0:Size: 0x00000000 bytes. + Module 001caa38:03ec0000(00010000:00002000) Size: 0x00002000 bytes. + Total size: 0x2000(8192)bytes + -------------------------------------- + Total LoaderHeap size: 0x15000(86016)bytes + ======================================= + +By using !EEHeap to keep track of the growth of these private heaps, we are +able to rule out or include them as a source of a memory leak. +\\ + +COMMAND: name2ee. +!Name2EE <module name> <type or method name> +!Name2EE <module name>!<type or method name> + +This function allows you to turn a class name into a MethodTable and EEClass. +It turns a method name into a MethodDesc. Here is an example for a method: + + 0:000> !name2ee unittest.exe MainClass.Main + Module: 001caa38 + Token: 0x0600000d + MethodDesc: 00902f40 + Name: MainClass.Main() + JITTED Code Address: 03ef00b8 + +and for a class: + + 0:000> !name2ee unittest!MainClass + Module: 001caa38 + Token: 0x02000005 + MethodTable: 009032d8 + EEClass: 03ee1424 + Name: MainClass + +The module you are "browsing" with Name2EE needs to be loaded in the process. +To get a type name exactly right, first browse the module with ILDASM. You +can also pass * as the <module name> to search all loaded managed modules. +<module name> can also be the debugger's name for a module, such as +mscorlib or image00400000. + +The Windows Debugger syntax of <module>!<type> is also supported. You can +use an asterisk on the left of the !, but the type on the right side needs +to be fully qualified. + +If you are looking for a way to display a static field of a class (and you +don't have an instance of the class, so !dumpobj won't help you), note that +once you have the EEClass, you can run !DumpClass, which will display the +value of all static fields. + +There is yet one more way to specify a module name. In the case of modules +loaded from an assembly store (such as a SQL db) rather than disk, the +module name will look like this: + +price, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + +For this kind of module, simply use price as the module name: + + 0:044> !name2ee price Price + Module: 10f028b0 (price, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null) + Token: 0x02000002 + MethodTable: 11a47ae0 + EEClass: 11a538c8 + Name: Price + +Where are we getting these module names from? Run !DumpDomain to see a list of +all loaded modules in all domains. And remember that you can browse all the +types in a module with !DumpModule -mt <module pointer>. +\\ + +COMMAND: syncblk. +!SyncBlk [-all | <syncblk number>] + +A SyncBlock is a holder for extra information that doesn't need to be created +for every object. It can hold COM Interop data, HashCodes, and locking +information for thread-safe operations. + +When called without arguments, !SyncBlk will print the list of SyncBlocks +corresponding to objects that are owned by a thread. For example, a + + lock(MyObject) + { + .... + } + +statement will set MyObject to be owned by the current thread. A SyncBlock will +be created for MyObject, and the thread ownership information stored there +(this is an oversimplification, see NOTE below). If another thread tries to +execute the same code, they won't be able to enter the block until the first +thread exits. + +This makes !SyncBlk useful for detecting managed deadlocks. Consider that the +following code is executed by Threads A & B: + + Resource r1 = new Resource(); + Resource r2 = new Resource(); + + ... + + lock(r1) lock(r2) + { { + lock(r2) lock(r1) + { { + ... ... + } } + } } + +This is a deadlock situation, as Thread A could take r1, and Thread B r2, +leaving both threads with no option but to wait forever in the second lock +statement. !SyncBlk will detect this with the following output: + + 0:003> !syncblk + Index SyncBlock MonitorHeld Recursion Owning Thread Info SyncBlock Owner + 238 001e40ec 3 1 001e4e60 e04 3 00a7a194 Resource + 239 001e4124 3 1 001e5980 ab8 4 00a7a1a4 Resource + +It means that Thread e04 owns object 00a7a194, and Thread ab8 owns object +00a7a1a4. Combine that information with the call stacks of the deadlock: + +(threads 3 and 4 have similar output) + 0:003> k + ChildEBP RetAddr + 0404ea04 77f5c524 SharedUserData!SystemCallStub+0x4 + 0404ea08 77e75ee0 ntdll!NtWaitForMultipleObjects+0xc + 0404eaa4 5d9de9d6 KERNEL32!WaitForMultipleObjectsEx+0x12c + 0404eb38 5d9def80 clr!Thread::DoAppropriateAptStateWait+0x156 + 0404ecc4 5d9dd8bb clr!Thread::DoAppropriateWaitWorker+0x360 + 0404ed20 5da628dd clr!Thread::DoAppropriateWait+0xbb + 0404ede4 5da4e2e2 clr!CLREvent::Wait+0x29d + 0404ee70 5da4dd41 clr!AwareLock::EnterEpilog+0x132 + 0404ef34 5da4efa3 clr!AwareLock::Enter+0x2c1 + 0404f09c 5d767880 clr!AwareLock::Contention+0x483 + 0404f1c4 03f00229 clr!JITutil_MonContention+0x2c0 + 0404f1f4 5b6ef077 image00400000!Worker.Work()+0x79 + ... + +By looking at the code corresponding to Worker.Work()+0x79 (run "!u 03f00229"), +you can see that thread 3 is attempting to acquire the Resource 00a7a1a4, which +is owned by thread 4. + +NOTE: +It is not always the case that a SyncBlock will be created for every object +that is locked by a thread. In version 2.0 of the CLR and above, a mechanism +called a ThinLock will be used if there is not already a SyncBlock for the +object in question. ThinLocks will not be reported by the !SyncBlk command. +You can use "!DumpHeap -thinlock" to list objects locked in this way. +\\ + +COMMAND: dumpmt. +!DumpMT [-MD] <MethodTable address> + +Examine a MethodTable. Each managed object has a MethodTable pointer at the +start. If you pass the "-MD" flag, you'll also see a list of all the methods +defined on the object. +\\ + +COMMAND: dumpclass. +!DumpClass <EEClass address> + +The EEClass is a data structure associated with an object type. !DumpClass +will show attributes, as well as list the fields of the type. The output is +similar to !DumpObj. Although static field values will be displayed, +non-static values won't because you need an instance of an object for that. + +You can get an EEClass to look at from !DumpMT, !DumpObj, !Name2EE, and +!Token2EE among others. +\\ + +COMMAND: dumpmd. +!DumpMD <MethodDesc address> + +This command lists information about a MethodDesc. You can use !IP2MD to turn +a code address in a managed function into a MethodDesc: + + 0:000> !dumpmd 902f40 + Method Name: Mainy.Main() + Class: 03ee1424 + MethodTable: 009032d8 + mdToken: 0600000d + Module: 001caa78 + IsJitted: yes + CodeAddr: 03ef00b8 + +If IsJitted is "yes," you can run !U on the CodeAddr pointer to see a +disassembly of the JITTED code. You can also call !DumpClass, !DumpMT, +!DumpModule on the Class, MethodTable and Module fields above. +\\ + +COMMAND: token2ee. +!Token2EE <module name> <token> + +This function allows you to turn a metadata token into a MethodTable or +MethodDesc. Here is an example showing class tokens being resolved: + + 0:000> !token2ee unittest.exe 02000003 + Module: 001caa38 + Token: 0x02000003 + MethodTable: 0090375c + EEClass: 03ee1ae0 + Name: Bank + 0:000> !token2ee image00400000 02000004 + Module: 001caa38 + Token: 0x02000004 + MethodTable: 009038ec + EEClass: 03ee1b84 + Name: Customer + +The module you are "browsing" with Token2EE needs to be loaded in the process. +This function doesn't see much use, especially since a tool like ILDASM can +show the mapping between metadata tokens and types/methods in a friendlier way. +But it could be handy sometimes. + +You can pass "*" for <module name> to find what that token maps to in every +loaded managed module. <module name> can also be the debugger's name for a +module, such as mscorlib or image00400000. +\\ + +COMMAND: eeversion. +!EEVersion + +This prints the Common Language Runtime version. It also tells you if the code +is running in "Workstation" or "Server" mode, a distinction which affects the +garbage collector. The most apparent difference in the debugger is that in +"Server" mode there is one dedicated garbage collector thread per CPU. + +A handy supplement to this function is to also run "lm v m clr". That +will provide more details about the CLR, including where clr.dll is +loaded from. +\\ + +COMMAND: dumpmodule. +!DumpModule [-mt] <Module address> + +You can get a Module address from !DumpDomain, !DumpAssembly and other +functions. Here is sample output: + + 0:000> !DumpModule 1caa50 + Name: C:\pub\unittest.exe + Attributes: PEFile + Assembly: 001ca248 + LoaderHeap: 001cab3c + TypeDefToMethodTableMap: 03ec0010 + TypeRefToMethodTableMap: 03ec0024 + MethodDefToDescMap: 03ec0064 + FieldDefToDescMap: 03ec00a4 + MemberRefToDescMap: 03ec00e8 + FileReferencesMap: 03ec0128 + AssemblyReferencesMap: 03ec012c + MetaData start address: 00402230 (1888 bytes) + +The Maps listed map metadata tokens to CLR data structures. Without going into +too much detail, you can examine memory at those addresses to find the +appropriate structures. For example, the TypeDefToMethodTableMap above can be +examined: + + 0:000> dd 3ec0010 + 03ec0010 00000000 00000000 0090320c 0090375c + 03ec0020 009038ec ... + +This means TypeDef token 2 maps to a MethodTable with the value 0090320c. You +can run !DumpMT to verify that. The MethodDefToDescMap takes a MethodDef token +and maps it to a MethodDesc, which can be passed to !DumpMD. + +There is a new option "-mt", which will display the types defined in a module, +and the types referenced by the module. For example: + + 0:000> !dumpmodule -mt 1aa580 + Name: C:\pub\unittest.exe + ...<etc>... + MetaData start address: 0040220c (1696 bytes) + + Types defined in this module + + MT TypeDef Name + -------------------------------------------------------------------------- + 030d115c 0x02000002 Funny + 030d1228 0x02000003 Mainy + + Types referenced in this module + + MT TypeRef Name + -------------------------------------------------------------------------- + 030b6420 0x01000001 System.ValueType + 030b5cb0 0x01000002 System.Object + 030fceb4 0x01000003 System.Exception + 0334e374 0x0100000c System.Console + 03167a50 0x0100000e System.Runtime.InteropServices.GCHandle + 0336a048 0x0100000f System.GC + +\\ + +COMMAND: threadpool. +!ThreadPool + +This command lists basic information about the ThreadPool, including the number +of work requests in the queue, number of completion port threads, and number of +timers. +\\ + +COMMAND: dumpassembly. +!DumpAssembly <Assembly address> + +Example output: + + 0:000> !dumpassembly 1ca248 + Parent Domain: 0014f000 + Name: C:\pub\unittest.exe + ClassLoader: 001ca060 + Module Name + 001caa50 C:\pub\unittest.exe + +An assembly can consist of multiple modules, and those will be listed. You can +get an Assembly address from the output of !DumpDomain. +\\ + +COMMAND: dumpruntimetypes. +!DumpRuntimeTypes + +!DumpRuntimeTypes finds all System.RuntimeType objects in the gc heap and +prints the type name and MethodTable they refer too. Sample output: + + Address Domain MT Type Name + ------------------------------------------------------------------------------ + a515f4 14a740 5baf8d28 System.TypedReference + a51608 14a740 5bb05764 System.Globalization.BaseInfoTable + a51958 14a740 5bb05b24 System.Globalization.CultureInfo + a51a44 14a740 5bb06298 System.Globalization.GlobalizationAssembly + a51de0 14a740 5bb069c8 System.Globalization.TextInfo + a56b98 14a740 5bb12d28 System.Security.Permissions.HostProtectionResource + a56bbc 14a740 5baf7248 System.Int32 + a56bd0 14a740 5baf3fdc System.String + a56cfc 14a740 5baf36a4 System.ValueType + ... + +This command will print a "?" in the domain column if the type is loaded into multiple +AppDomains. For example: + + 0:000> !DumpRuntimeTypes + Address Domain MT Type Name + ------------------------------------------------------------------------------ + 28435a0 ? 3f6a8c System.TypedReference + 28435b4 ? 214d6c System.ValueType + 28435c8 ? 216314 System.Enum + 28435dc ? 2147cc System.Object + 284365c ? 3cd57c System.IntPtr + 2843670 ? 3feaac System.Byte + 2843684 ? 23a544c System.IEquatable`1[[System.IntPtr, mscorlib]] + 2843784 ? 3c999c System.Int32 + 2843798 ? 3caa04 System.IEquatable`1[[System.Int32, mscorlib]] + +\\ + +COMMAND: dumpsig. +!DumpSig <sigaddr> <moduleaddr> + +This command dumps the signature of a method or field given by <sigaddr>. This is +useful when you are debugging parts of the runtime which returns a raw PCCOR_SIGNATURE +structure and need to know what its contents are. + +Sample output for a method: + 0:000> !dumpsig 0x000007fe`ec20879d 0x000007fe`eabd1000 + [DEFAULT] [hasThis] Void (Boolean,String,String) + +The first section of the output is the calling convention. This includes, but is not +limited to, "[DEFAULT]", "[C]", "[STDCALL]", "[THISCALL]", and so on. The second +portion of the output is either "[hasThis]" or "[explicit]" for whether the method +is an instance method or a static method respectively. The third portion of the +output is the return value (in this case a "void"). Finally, the method's arguments +are printed as the final portion of the output. + +Sample output for a field: + 0:000> !dumpsig 0x000007fe`eb7fd8cd 0x000007fe`eabd1000 + [FIELD] ValueClass System.RuntimeTypeHandle + +!DumpSig will also work with generics. Here is the output for the following +function: + public A Test(IEnumerable<B> n) + + 0:000> !dumpsig 00000000`00bc2437 000007ff00043178 + [DEFAULT] [hasThis] __Canon (Class System.Collections.Generic.IEnumerable`1<__Canon>) + +\\ + +COMMAND: dumpsigelem. +!DumpSigElem <sigaddr> <moduleaddr> + +This command dumps a single element of a signature object. For most circumstances, +you should use !DumpSig to look at individual signature objects, but if you find a +signature that has been corrupted in some manner you can use !DumpSigElem to read out +the valid portions of it. + +If we look at a valid signature object for a method we see the following: + 0:000> !dumpsig 0x000007fe`ec20879d 0x000007fe`eabd1000 + [DEFAULT] [hasThis] Void (Boolean,String,String) + +We can look at the individual elements of this object by adding the offsets into the +object which correspond to the return value and parameters: + 0:000> !dumpsigelem 0x000007fe`ec20879d+2 0x000007fe`eabd1000 + Void + 0:000> !dumpsigelem 0x000007fe`ec20879d+3 0x000007fe`eabd1000 + Boolean + 0:000> !dumpsigelem 0x000007fe`ec20879d+4 0x000007fe`eabd1000 + String + 0:000> !dumpsigelem 0x000007fe`ec20879d+5 0x000007fe`eabd1000 + String + +We can do something similar for fields. Here is the full signature of a field: + 0:000> !dumpsig 0x000007fe`eb7fd8cd 0x000007fe`eabd1000 + [FIELD] ValueClass System.RuntimeTypeHandle + +Using !DumpSigElem we can find the type of the field by adding the offset of it (1) to +the address of the signature: + 0:000> !dumpsigelem 0x000007fe`eb7fd8cd+1 0x000007fe`eabd1000 + ValueClass System.RuntimeTypeHandle + +!DumpSigElem will also work with generics. Let a function be defined as follows: + public A Test(IEnumerable<B> n) + +The elements of this signature can be obtained by adding offsets into the signature +when calling !DumpSigElem: + + 0:000> !dumpsigelem 00000000`00bc2437+2 000007ff00043178 + __Canon + 0:000> !dumpsigelem 00000000`00bc2437+4 000007ff00043178 + Class System.Collections.Generic.IEnumerable`1<__Canon> + +The actual offsets that you should add are determined by the contents of the +signature itself. By trial and error you should be able to find various elements +of the signature. + +\\ + +COMMAND: rcwcleanuplist. +!RCWCleanupList [address] + +A RuntimeCallableWrapper is an internal CLR structure used to host COM objects +which are exposed to managed code. This is exposed to managed code through the +System.__ComObject class, and when objects of this type are collected, and a +reference to the underlying COM object is no longer needed, the corresponding +RCW is cleaned up. If you are trying to debug an issue related to one of these +RCWs, then you can use the !RCWCleanupList function to display which COM objects +will be released the next time a cleanup occurs. + +If given an address, this function will display the RCWCleanupList at that address. +If no address is specified, it displays the default cleanup list, printing the +wrapper, the context, and the thread of the object. + +Example: + 0:002> !rcwcleanuplist 001c04d0 + RuntimeCallableWrappers (RCW) to be cleaned: + RCW CONTEXT THREAD Apartment + 1d54e0 192008 181180 STA + 1d4140 192178 0 MTA + 1dff50 192178 0 MTA + MTA Interfaces to be released: 2 + STA Interfaces to be released: 1 + +Note that CLR keeps track of which RCWs are bound to which managed objects through +the SyncBlock of the object. As such, you can see more information about RCW +objects through the !SyncBlk command. You can find more information about RCW +cleanup through the !FinalizeQueue command. + +\\ + +COMMAND: dumpil. +!DumpIL <Managed DynamicMethod object> | + <DynamicMethodDesc pointer> | + <MethodDesc pointer> | + /i <IL pointer> + +!DumpIL prints the IL code associated with a managed method. We added this +function specifically to debug DynamicMethod code which was constructed on +the fly. Happily it works for non-dynamic code as well. + +You can use it in four ways: + + 1) If you have a System.Reflection.Emit.DynamicMethod object, just pass + the pointer as the first argument. + 2) If you have a DynamicMethodDesc pointer you can use that to print the + IL associated with the dynamic method. + 3) If you have an ordinary MethodDesc, you can see the IL for that as well, + just pass it as the first argument. + 4) If you have a pointer directly to the IL, specify /i followed by the + the IL address. This is useful for writers of profilers that instrument + IL. + + +Note that dynamic IL is constructed a bit differently. Rather than referring +to metadata tokens, the IL points to objects in a managed object array. Here +is a simple example of the output for a dynamic method: + + 0:000> !dumpil b741dc + This is dynamic IL. Exception info is not reported at this time. + If a token is unresolved, run "!do <addr>" on the addr given + in parenthesis. You can also look at the token table yourself, by + running "!DumpArray 00b77388". + + IL_0000: ldstr 70000002 "Inside invoked method " + IL_0005: call 6000003 System.Console.WriteLine(System.String) + IL_000a: ldc.i4.1 + IL_000b: newarr 2000004 "System.Int32" + IL_0010: stloc.0 + IL_0011: ldloc.0 + IL_0012: ret + +\\ + +COMMAND: verifyheap. +!VerifyHeap + +!VerifyHeap is a diagnostic tool that checks the garbage collected heap for +signs of corruption. It walks objects one by one in a pattern like this: + + o = firstobject; + while(o != endobject) + { + o.ValidateAllFields(); + o = (Object *) o + o.Size(); + } + +If an error is found, !VerifyHeap will report it. I'll take a perfectly good +object and corrupt it: + + 0:000> !DumpObj a79d40 + Name: Customer + MethodTable: 009038ec + EEClass: 03ee1b84 + Size: 20(0x14) bytes + (C:\pub\unittest.exe) + Fields: + MT Field Offset Type Attr Value Name + 009038ec 4000008 4 CLASS instance 00a79ce4 name + 009038ec 4000009 8 CLASS instance 00a79d2c bank + 009038ec 400000a c System.Boolean instance 1 valid + + 0:000> ed a79d40+4 01 (change the name field to the bogus pointer value 1) + 0:000> !VerifyHeap + object 01ee60dc: bad member 00000003 at 01EE6168 + Last good object: 01EE60C4. + +If this gc heap corruption exists, there is a serious bug in your own code or +in the CLR. In user code, an error in constructing PInvoke calls can cause +this problem, and running with Managed Debugging Assistants is advised. If that +possibility is eliminated, consider contacting Microsoft Product Support for +help. + +\\ + +COMMAND: verifyobj. +!VerifyObj <object address> + +!VerifyObj is a diagnostic tool that checks the object that is passed as an +argument for signs of corruption. + + 0:002> !verifyobj 028000ec + object 0x28000ec does not have valid method table + + 0:002> !verifyobj 0680017c + object 0x680017c: bad member 00000001 at 06800184 + +\\ + +COMMAND: findroots. +!FindRoots -gen <N> | -gen any | <object address> + +The "-gen" form causes the debugger to break in the debuggee on the next +collection of the specified generation. The effect is reset as soon as the +break occurs, in other words, if you need to break on the next collection you +would need to reissue the command. + +The last form of this command is meant to be used after the break caused by the +other forms has occurred. Now the debuggee is in the right state for +!FindRoots to be able to identify roots for objects from the current condemned +generations. + +!FindRoots is a diagnostic command that is meant to answer the following +question: + +"I see that GCs are happening, however my objects have still not been +collected. Why? Who is holding onto them?" + +The process of answering the question would go something like this: + +1. Find out the generation of the object of interest using the !GCWhere +command, say it is gen 1: + !GCWhere <object address> + +2. Instruct the runtime to stop the next time it collects that generation using +the !FindRoots command: + !FindRoots -gen 1 + g + +3. When the next GC starts, and has proceeded past the mark phase a CLR +notification will cause a break in the debugger: + (fd0.ec4): CLR notification exception - code e0444143 (first chance) + CLR notification: GC - end of mark phase. + Condemned generation: 1. + +4. Now we can use the !FindRoots <object address> to find out the cross +generational references to the object of interest. In other words, even if the +object is not referenced by any "proper" root it may still be referenced by an +older object (from an older generation), from a generation that has not yet been +scheduled for collection. At this point !FindRoots will search those older +generations too, and report those roots. + 0:002> !findroots 06808094 + older generations::Root: 068012f8(AAA.Test+a)-> + 06808094(AAA.Test+b) + + +\\ + +COMMAND: heapstat. +!HeapStat [-inclUnrooted | -iu] + +This command shows the generation sizes for each heap and the total, how much free +space there is in each generation on each heap. If the -inclUnrooted option is +specified the report will include information about the managed objects from the +GC heap that are not rooted anymore. + +Sample output: + + 0:002> !heapstat + Heap Gen0 Gen1 Gen2 LOH + Heap0 177904 12 306956 8784 + Heap1 159652 12 12 16 + Total 337556 24 306968 8800 + + Free space: Percentage + Heap0 28 12 12 64 SOH: 0% LOH: 0% + Heap1 104 12 12 16 SOH: 0% LOH:100% + Total 132 24 24 80 + + 0:002> !heapstat -inclUnrooted + Heap Gen0 Gen1 Gen2 LOH + Heap0 177904 12 306956 8784 + Heap1 159652 12 12 16 + Total 337556 24 306968 8800 + + Free space: Percentage + Heap0 28 12 12 64 SOH: 0% LOH: 0% + Heap1 104 12 12 16 SOH: 0% LOH:100% + Total 132 24 24 80 + + Unrooted objects: Percentage + Heap0 152212 0 306196 0 SOH: 94% LOH: 0% + Heap1 155704 0 0 0 SOH: 97% LOH: 0% + Total 307916 0 306196 0 + +The percentage column contains a breakout of free or unrooted bytes to total bytes. + +\\ + +COMMAND: analyzeoom. +!AnalyzeOOM + +!AnalyzeOOM displays the info of the last OOM occurred on an allocation request to +the GC heap (in Server GC it displays OOM, if any, on each GC heap). + +To see the managed exception(s) use the !Threads command which will show you +managed exception(s), if any, on each managed thread. If you do see an +OutOfMemoryException exception you can use the !PrintException command on it. +To get the full callstack use the "kb" command in the debugger for that thread. +For example, to display thread 3's stack use ~3kb. + +OOM exceptions could be because of the following reasons: + +1) allocation request to GC heap + in which case you will see JIT_New* on the call stack because managed code called new. +2) other runtime allocation failure + for example, failure to expand the finalize queue when GC.ReRegisterForFinalize is + called. +3) some other code you use throws a managed OOM exception + for example, some .NET framework code converts a native OOM exception to managed + and throws it. + +The !AnalyzeOOM command aims to help you with investigating 1) which is the most +difficult because it requires some internal info from GC. The only exception is +we don't support allocating objects larger than 2GB on CLR v2.0 or prior. And this +command will not display any managed OOM because we will throw OOM right away +instead of even trying to allocate it on the GC heap. + +There are 2 legitimate scenarios where GC would return OOM to allocation requests - +one is if the process is running out of VM space to reserve a segment; the other +is if the system is running out physical memory (+ page file if you have one) so +GC can not commit memory it needs. You can look at these scenarios by using performance +counters or debugger commands. For example for the former scenario the "!address +-summary" debugger command will show you the largest free region in the VM. For +the latter scenario you can look at the "Memory\% Committed Bytes In Use" see +if you are running low on commit space. One important thing to keep in mind is +when you do this kind of memory analysis it could an aftereffect and doesn't +completely agree with what this command tells you, in which case the command should +be respected because it truly reflects what happened during GC. + +The other cases should be fairly obvious from the callstack. + +Sample output: + +0:011> !ao +---------Heap 2 --------- +Managed OOM occurred after GC #28 (Requested to allocate 1234 bytes) +Reason: Didn't have enough memory to commit +Detail: SOH: Didn't have enough memory to grow the internal GC datastructures (800000 bytes) - + on GC entry available commit space was 500 MB +---------Heap 4 --------- +Managed OOM occurred after GC #12 (Requested to allocate 100000 bytes) +Reason: Didn't have enough memory to allocate an LOH segment +Detail: LOH: Failed to reserve memory (16777216 bytes) + +\\ + +COMMAND: gcwhere. +!GCWhere <object address> + +!GCWhere displays the location in the GC heap of the argument passed in. + + 0:002> !GCWhere 02800038 + Address Gen Heap segment begin allocated size + 02800038 2 0 02800000 02800038 0282b740 12 + +When the argument lies in the managed heap, but is not a valid *object* address +the "size" is displayed as 0: + + 0:002> !GCWhere 0280003c + Address Gen Heap segment begin allocated size + 0280003c 2 0 02800000 02800038 0282b740 0 + +\\ + +COMMAND: listnearobj. +!ListNearObj <object address> + +!ListNearObj is a diagnostic tool that displays the object preceeding and +succeeding the address passed in: + +The command looks for the address in the GC heap that looks like a valid +beginning of a managed object (based on a valid method table) and the object +following the argument address. + + 0:002> !ListNearObj 028000ec + Before: 0x28000a4 72 (0x48 ) System.StackOverflowException + After: 0x2800134 72 (0x48 ) System.Threading.ThreadAbortException + Heap local consistency confirmed. + + 0:002> !ListNearObj 028000f0 + Before: 0x28000ec 72 (0x48 ) System.ExecutionEngineException + After: 0x2800134 72 (0x48 ) System.Threading.ThreadAbortException + Heap local consistency confirmed. + +The command considers the heap as "locally consistent" if: + prev_obj_addr + prev_obj_size = arg_addr && arg_obj + arg_size = next_obj_addr +OR + prev_obj_addr + prev_obj_size = next_obj_addr + +When the condition is not satisfied: + + 0:002> !lno 028000ec + Before: 0x28000a4 72 (0x48 ) System.StackOverflowException + After: 0x2800134 72 (0x48 ) System.Threading.ThreadAbortException + Heap local consistency not confirmed. + +\\ + +COMMAND: dumplog. +!DumpLog [-addr <addressOfStressLog>] [<Filename>] + +To aid in diagnosing hard-to-reproduce stress failures, the CLR team added an +in-memory log capability. The idea was to avoid using locks or I/O which could +disturb a fragile repro environment. The !DumpLog function allows you to write +that log out to a file. If no Filename is specified, the file "Stresslog.txt" +in the current directory is created. + +The optional argument addr allows one to specify a stress log other then the +default one. + + 0:000> !DumpLog + Attempting to dump Stress log to file 'StressLog.txt' + ................. + SUCCESS: Stress log dumped + +To turn on the stress log, set the following registry keys under +HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework: + + +(DWORD) StressLog = 1 +(DWORD) LogFacility = 0xffffffbf (this is a bit mask, almost all logging is on. + This is also the default value if the key + isn't specified) +(DWORD) StressLogSize = 65536 (this is the default value if the key isn't + specified) +(DWORD) LogLevel = 6 (this is the default value if the key isn't + specified. The higher the number the more + detailed logs are generated. The maximum + value is decimal 10) + +StressLogSize is the size in bytes of the in-memory log allocated for each +thread in the process. In the case above, each thread gets a 64K log. You +could increase this to get more logging, but more memory will be required for +this log in the process. For example, 20 threads with 524288 bytes per thread +has a memory demand of 10 Megabytes. The stress log is circular so new entries +will replace older ones on threads which have reached their buffer limit. + +The log facilities are defined as follows: + GC 0x00000001 + GCINFO 0x00000002 + STUBS 0x00000004 + JIT 0x00000008 + LOADER 0x00000010 + METADATA 0x00000020 + SYNC 0x00000040 + EEMEM 0x00000080 + GCALLOC 0x00000100 + CORDB 0x00000200 + CLASSLOADER 0x00000400 + CORPROF 0x00000800 + REMOTING 0x00001000 + DBGALLOC 0x00002000 + EH 0x00004000 + ENC 0x00008000 + ASSERT 0x00010000 + VERIFIER 0x00020000 + THREADPOOL 0x00040000 + GCROOTS 0x00080000 + INTEROP 0x00100000 + MARSHALER 0x00200000 + IJW 0x00400000 + ZAP 0x00800000 + STARTUP 0x01000000 + APPDOMAIN 0x02000000 + CODESHARING 0x04000000 + STORE 0x08000000 + SECURITY 0x10000000 + LOCKS 0x20000000 + BCL 0x40000000 + +Here is some sample output: + + 3560 9.981137099 : `SYNC` RareEnablePremptiveGC: entering. + Thread state = a030 + + 3560 9.981135033 : `GC`GCALLOC`GCROOTS` ========== ENDGC 4194 (gen = 2, + collect_classes = 0) ==========={ + + 3560 9.981125826 : `GC` Segment mem 00C61000 alloc + = 00D071F0 used 00D09254 committed 00D17000 + + 3560 9.981125726 : `GC` Generation 0 [00CED07C, 00000000 + ] cur = 00000000 + + 3560 9.981125529 : `GC` Generation 1 [00CED070, 00000000 + ] cur = 00000000 + + 3560 9.981125103 : `GC` Generation 2 [00C61000, 00000000 + ] cur = 00000000 + + 3560 9.981124963 : `GC` GC Heap 00000000 + + 3560 9.980618994 : `GC`GCROOTS` GcScanHandles (Promotion Phase = 0) + +The first column is the OS thread ID for the thread appending to the log, +the second column is the timestamp, the third is the facility category for the +log entry, and the fourth contains the log message. The facility field is +expressed as `facility1`facility2`facility3`. This facilitates the creation of +filters for displaying only specific message categories. To make sense of this +log, you would probably want the Shared Source CLI to find out exactly where +the log comes from. +\\ + +COMMAND: findappdomain. +!FindAppDomain <Object address> + +!FindAppDomain will attempt to resolve the AppDomain of an object. For example, +using an Object Pointer from the output of !DumpStackObjects: + + 0:000> !findappdomain 00a79d98 + AppDomain: 0014f000 + Name: unittest.exe + ID: 1 + +You can find out more about the AppDomain with the !DumpDomain command. Not +every object has enough clues about it's origin to determine the AppDomain. +Objects with Finalizers are the easiest case, as the CLR needs to be able to +call those when an AppDomain shuts down. +\\ + +COMMAND: savemodule. +!SaveModule <Base address> <Filename> + +This command allows you to take a image loaded in memory and write it to a +file. This is especially useful if you are debugging a full memory dump, and +don't have the original DLLs or EXEs. This is most often used to save a managed +binary to a file, so you can disassemble the code and browse types with ILDASM. + +The base address of an image can be found with the "LM" debugger command: + + 0:000> lm + start end module name + 00400000 00408000 image00400000 (deferred) + 10200000 102ac000 MSVCR80D (deferred) + 5a000000 5a0b1000 mscoree (deferred) + 5a140000 5a29e000 clrjit (deferred) + 5b660000 5c440000 mscorlib_dll (deferred) + 5d1d0000 5e13c000 clr (deferred) + ... + +If I wanted to save a copy of clr.dll, I could run: + + 0:000> !SaveModule 5d1d0000 c:\pub\out.tmp + 4 sections in file + section 0 - VA=1000, VASize=e82da9, FileAddr=400, FileSize=e82e00 + section 1 - VA=e84000, VASize=24d24, FileAddr=e83200, FileSize=ec00 + section 2 - VA=ea9000, VASize=5a8, FileAddr=e91e00, FileSize=600 + section 3 - VA=eaa000, VASize=c183c, FileAddr=e92400, FileSize=c1a00 + +The diagnostic output indicates that the operation was successful. If +c:\pub\out.tmp already exists, it will be overwritten. +\\ + +COMMAND: gchandles. +!GCHandles [-type handletype] [-stat] [-perdomain] + +!GCHandles provides statistics about GCHandles in the process. + +Paremeters: + stat - Only display the statistics and not the list of handles and + what they point to. + perdomain - Break down the statistics by the app domain in which + the handles reside. + type - A type of handle to filter it by. The handle types are: + Pinned + RefCounted + WeakShort + WeakLong + Strong + Variable + AsyncPinned + SizedRef + +Sometimes the source of a memory leak is a GCHandle leak. For example, code +might keep a 50 Megabyte array alive because a strong GCHandle points to it, +and the handle was discarded without freeing it. + +The most common handles are "Strong Handles," which keep the object they point +to alive until the handle is explicitly freed. "Pinned Handles" are used to +prevent the garbage collector from moving an object during collection. These +should be used sparingly, and for short periods of time. If you don't follow +that precept, the gc heap can become very fragmented. + +Here is sample output from a very simple program. Note that the "RefCount" +field only applies to RefCount Handles, and this field will contain the +reference count: + + 0:000> !GCHandles + Handle Type Object Size RefCount Type + 001611c0 Strong 01d00b58 84 System.IndexOutOfRangeException + 001611c4 Strong 01d00b58 84 System.IndexOutOfRangeException + 001611c8 Strong 01d1b48c 40 System.Diagnostics.LogSwitch + 001611d0 Strong 01cfd2c0 36 System.Security.PermissionSet + 001611d4 Strong 01cf7484 56 System.Object[] + 001611d8 Strong 01cf1238 32 System.SharedStatics + 001611dc Strong 01cf11c8 84 System.Threading.ThreadAbortException + 001611e0 Strong 01cf1174 84 System.Threading.ThreadAbortException + 001611e4 Strong 01cf1120 84 System.ExecutionEngineException + 001611e8 Strong 01cf10cc 84 System.StackOverflowException + 001611ec Strong 01cf1078 84 System.OutOfMemoryException + 001611f0 Strong 01cf1024 84 System.Exception + 001611f8 Strong 01cf2068 48 System.Threading.Thread + 001611fc Strong 01cf1328 112 System.AppDomain + 001613ec Pinned 02cf3268 8176 System.Object[] + 001613f0 Pinned 02cf2258 4096 System.Object[] + 001613f4 Pinned 02cf2038 528 System.Object[] + 001613f8 Pinned 01cf121c 12 System.Object + 001613fc Pinned 02cf1010 4116 System.Object[] + + Statistics: + MT Count TotalSize Class Name + 563266dc 1 12 System.Object + 56329708 1 32 System.SharedStatics + 5632bc38 1 36 System.Security.PermissionSet + 5635f934 1 40 System.Diagnostics.LogSwitch + 5632759c 1 48 System.Threading.Thread + 5632735c 1 84 System.ExecutionEngineException + 56327304 1 84 System.StackOverflowException + 563272ac 1 84 System.OutOfMemoryException + 563270c4 1 84 System.Exception + 56328914 1 112 System.AppDomain + 56335f78 2 168 System.IndexOutOfRangeException + 563273b4 2 168 System.Threading.ThreadAbortException + 563208d0 5 16972 System.Object[] + Total 19 objects + + Handles: + Strong Handles: 14 + Pinned Handles: 5 +\\ + +COMMAND: gchandleleaks. +!GCHandleLeaks + +This command is an aid in tracking down GCHandle leaks. It searches all of +memory for any references to the Strong and Pinned GCHandles in the process, +and reports what it found. If a handle is found, you'll see the address of the +reference. This might be a stack address or a field within an object, for +example. If a handle is not found in memory, you'll get notification of that +too. + +The command has diagnostic output which doesn't need to be repeated here. One +thing to keep in mind is that anytime you search all of memory for a value, you +can get false positives because even though the value was found, it might be +garbage in that no code knows about the address. You can also get false +negatives because a user is free to pass that GCHandle to unmanaged code that +might store the handle in a strange way (shifting bits, for example). + +For example, a GCHandle valuetype is stored on the stack with the low bit set +if it points to a Pinned handle. So !GCHandleLeaks ignores the low bit in it's +searches. + +That said, if a serious leak is going on, you'll get a ever-growing set of +handle addresses that couldn't be found. +\\ + +COMMAND: vmmap. +!VMMap + +!VMMap traverses the virtual address space and lists the type of protection +applied to each region. Sample output: + + 0:000> !VMMap + Start Stop Length AllocProtect Protect State Type + 00000000-0000ffff 00010000 NA Free + 00010000-00011fff 00002000 RdWr RdWr Commit Private + 00012000-0001ffff 0000e000 NA Free + 00020000-00020fff 00001000 RdWr RdWr Commit Private + 00021000-0002ffff 0000f000 NA Free + 00030000-00030fff 00001000 RdWr Reserve Private + ... +\\ + +COMMAND: vmstat. +!VMStat + +Provides a summary view of the virtual address space, ordered by each type of +protection applied to that memory (free, reserved, committed, private, mapped, +image). The TOTAL column is (AVERAGE * BLK COUNT). Sample output below: + + 0:000> !VMStat + ~~~~ ~~~~~~~ ~~~~~~~ ~~~~~~~ ~~~~~~~~~ ~~~~~ + TYPE MINIMUM MAXIMUM AVERAGE BLK COUNT TOTAL + Free: + Small 4,096 65,536 48,393 27 1,306,611 + Medium 139,264 528,384 337,920 4 1,351,680 + Large 6,303,744 974,778,368 169,089,706 12 2,029,076,472 + Summary 4,096 974,778,368 47,249,646 43 2,031,734,778 + + Reserve: + Small 4,096 65,536 43,957 41 1,802,237 + Medium 249,856 1,019,904 521,557 6 3,129,342 + Large 2,461,696 16,703,488 11,956,224 3 35,868,672 + Summary 4,096 16,703,488 816,005 50 40,800,250 + +\\ + +COMMAND: procinfo. +!ProcInfo [-env] [-time] [-mem] + +!ProcInfo lists the environment variables for the process, kernel CPU time, as +well as memory usage statistics. +\\ + +COMMAND: histinit. +!HistInit + +Before running any of the Hist - family commands you need to initialize the SOS +structures from the stress log saved in the debuggee. This is achieved by the +HistInit command. + +Sample output: + + 0:001> !HistInit + Attempting to read Stress log + STRESS LOG: + facilitiesToLog = 0xffffffff + levelToLog = 6 + MaxLogSizePerThread = 0x10000 (65536) + MaxTotalLogSize = 0x1000000 (16777216) + CurrentTotalLogChunk = 9 + ThreadsWithLogs = 3 + Clock frequency = 3.392 GHz + Start time 15:26:31 + Last message time 15:26:56 + Total elapsed time 25.077 sec + ..................................... + ---------------------------- 2407 total entries ----------------------------- + + + SUCCESS: GCHist structures initialized + +\\ + +COMMAND: histobjfind. +!HistObjFind <obj_address> + +To examine log entries related to an object whose present address is known one +would use this command. The output of this command contains all entries that +reference the object: + + 0:003> !HistObjFind 028970d4 + GCCount Object Message + --------------------------------------------------------- + 2296 028970d4 Promotion for root 01e411b8 (MT = 5b6c5cd8) + 2296 028970d4 Relocation NEWVALUE for root 00223fc4 + 2296 028970d4 Relocation NEWVALUE for root 01e411b8 + ... + 2295 028970d4 Promotion for root 01e411b8 (MT = 5b6c5cd8) + 2295 028970d4 Relocation NEWVALUE for root 00223fc4 + 2295 028970d4 Relocation NEWVALUE for root 01e411b8 + ... + +\\ + +COMMAND: histroot. +!HistRoot <root> + +The root value obtained from !HistObjFind can be used to track the movement of +an object through the GCs. + +HistRoot provides information related to both promotions and relocations of the +root specified as the argument. + + 0:003> !HistRoot 01e411b8 + GCCount Value MT Promoted? Notes + --------------------------------------------------------- + 2296 028970d4 5b6c5cd8 yes + 2295 028970d4 5b6c5cd8 yes + 2294 028970d4 5b6c5cd8 yes + 2293 028970d4 5b6c5cd8 yes + 2292 028970d4 5b6c5cd8 yes + 2291 028970d4 5b6c5cd8 yes + 2290 028970d4 5b6c5cd8 yes + 2289 028970d4 5b6c5cd8 yes + 2288 028970d4 5b6c5cd8 yes + 2287 028970d4 5b6c5cd8 yes + 2286 028970d4 5b6c5cd8 yes + 2285 028970d4 5b6c5cd8 yes + 322 028970e8 5b6c5cd8 yes Duplicate promote/relocs + ... + +\\ + +COMMAND: histobj. +!HistObj <obj_address> + +This command examines all stress log relocation records and displays the chain +of GC relocations that may have led to the address passed in as an argument. +Conceptually the output is: + + GenN obj_address root1, root2, root3, + GenN-1 prev_obj_addr root1, root2, + GenN-2 prev_prev_oa root1, root4, + ... + +Sample output: + 0:003> !HistObj 028970d4 + GCCount Object Roots + --------------------------------------------------------- + 2296 028970d4 00223fc4, 01e411b8, + 2295 028970d4 00223fc4, 01e411b8, + 2294 028970d4 00223fc4, 01e411b8, + 2293 028970d4 00223fc4, 01e411b8, + 2292 028970d4 00223fc4, 01e411b8, + 2291 028970d4 00223fc4, 01e411b8, + 2290 028970d4 00223fc4, 01e411b8, + 2289 028970d4 00223fc4, 01e411b8, + 2288 028970d4 00223fc4, 01e411b8, + 2287 028970d4 00223fc4, 01e411b8, + 2286 028970d4 00223fc4, 01e411b8, + 2285 028970d4 00223fc4, 01e411b8, + 322 028970d4 01e411b8, + 0 028970d4 + +\\ + +COMMAND: histclear. +!HistClear + +This command releases any resources used by the Hist-family of commands. +Generally there's no need to call this explicitly, as each HistInit will first +cleanup the previous resources. + + 0:003> !HistClear + Completed successfully. + +\\ + +COMMAND: dumprcw. +!DumpRCW <RCW address> + +This command lists information about a Runtime Callable Wrapper. You can use +!DumpObj to obtain the RCW address corresponding to a managed object. + +The output contains all COM interface pointers that the RCW holds on to, which +is useful for investigating lifetime issues of interop-heavy applications. +\\ + +COMMAND: dumpccw. +!DumpCCW <CCW address or COM IP> + +This command lists information about a COM Callable Wrapper. You can use +!DumpObj to obtain the CCW address corresponding to a managed object or pass +a COM interface pointer to which the object has been marshaled. + +The output contains the COM reference count of the CCW, which is useful for +investigating lifetime issues of interop-heavy applications. +\\ + +COMMAND: suppressjitoptimization. +!SuppressJitOptimization [on|off] + +!SuppressJitOptimization helps when you want the CLR to generate more debuggable +jitted code. This will turn off inlining, enregistering of local variables, +optimizing across sequence point boundaries, and precise variable lifetimes. +The resulting code should step more cleanly and make local variables more +accesible to inspection. The cost is that code quality is lower so your +application may execute a little slower. + +Once you execute !SuppressJitOptimization on, all managed modules that load +from that point onwards will not be optimized. The setting is only checked once +per module, at the time that module loads. Changing the setting later will only +affect modules that are loaded later. The recommendation is to set this once at +app startup and let the same setting apply for all managed modules. +\\ + +COMMAND: stoponcatch. +!StopOnCatch + +This commands sets a one-time breakpoint on the first managed catch clause +entered by managed code. This will cause the debugger to stop on the first line +of the catch handler. This is useful step from from the point of an exception +throw to the catch handler. +\\ + + +COMMAND: savestate. +!SaveState <file_path> + +!SaveState serializes SOS managed breakpoints and watch values into a script file +that can be executed on a future windbg session to restore them. Use the windbg +command <$ <file_path> to execute the saved script. +\\ + +COMMAND: watch. +!Watch +!Watch -add <expression> +!Watch -remove <index> +!Watch -save <watch_list_name> +!Watch -rename <old_watch_list_name> <new_watch_list_name> +!Watch [-filter <watch_list_name>] [-expand <index> [type_cast]<expression>] + +!Watch displays the value of managed expressions, and manages the list of +expressions. A quick example... + +DEBUGGEE CODE: + static int Main(string[] args) + { + int p14 = -123; + MyStruct ms = new MyStruct(); + ms.a = p14; + ms.b = true; + MyStruct.c = "Foo"; + ... + +COMMAND WINDOW: +0:000> !Watch -add ms +0:000> !Watch -add p14 +0:000> !Watch + 1) MyStruct ms @ 0x0093D1CC + 2) int p14 = -123 + +ms in the command window will be a DML link, if you click on it then you see: + +COMMAND WINDOW: +0:000> !watch -expand 1 (MyStruct)ms + 1) MyStruct ms @ 0x0093D1CC + |- int a = -123 + |- bool b = true + |- string c = null + 2) int p14 = -123 + + + +Watch is capable of parsing a variety of different expressions. Expressions +can start with the name of a local variable or parameter in any stack frame. +To access fields of classes and structures simply add a '.' and then the field +name. To access array elements use standard [<index>] notation. In the above +code example these would be valid expressions: +args[19] +p14 +ms.a + +Additionally 'this' is available in the leafmost frame to access fields. +You can not use unqualified field names. For example if there is a class: +class C +{ + int m_foo; + public void Hello() { Console.WriteLine(m_foo); } +} + +this.m_foo is a legal expression when executing inside C.Hello(), +m_foo would not work. + +Expressions also allow fully qualified type names and dereferencing static +fields from those types using the same '.' syntax. For example: +System.Int32 +System.Type.Missing + +Generic types can be used, though don't forget to use the CLI type +name which adds `<number_of_generic_params> to the type name you would see +in VB or C#. For example: +System.Collections.Generic.Dictionary`2<System.String,System.Type> + +Dereferencing fields or array indices can recurse as you might expect. A +made up example: +foo.bar.baz[19][12].m_field[4] + +Property values and method invocations can not be used in expressions as +the !Watch command will never execute any managed code to evaluate the +expressions. + +To remove entries in the list use !Watch -remove <index> where index is +the number printed next to the expression in the viewing list. In our +initial example !Watch -remove 2 would remove the 'p14' expression from +the list. + +Saving/Renaming/Filtering +Sometimes it can be helpful to see only those values which have changed +since some point in the past. As an example assume that MyStruct.c changes +values during the go: + +0:000> !Watch -save myOldValues +0:000> g +... +0:000> !Watch -filter myOldValues + 3) string MyStruct.c = "Foo" + +When saving a list of expressions, the string that would be displayed in a +!Watch command is also saved. It is this string that is compared to determine +if the value has changed. Some changes might not alter the console string +representation. In the first example changing the value of ms.a would not +cause the 'ms' expression to be different. + +Renaming can be useful in scripts. It allows the name of a saved watch list to +change after saving it. For example: +0:000> !Watch -rename myCurrentValues myOldValues +0:000> !Watch -save myCurrentValues +0:000> !Watch -filter myOldValues + 3) string MyStruct.c = "Foo" + +Placing that pattern inside a script shows the changed watch values +since the last time the script was run. +\\ + diff --git a/src/ToolBox/SOS/Strike/data.h b/src/ToolBox/SOS/Strike/data.h new file mode 100644 index 0000000000..9989038653 --- /dev/null +++ b/src/ToolBox/SOS/Strike/data.h @@ -0,0 +1,51 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ==++== +// + +// +// ==--== +#ifndef __data_h__ +#define __data_h__ + +#include "cor.h" +#include "corhdr.h" +#include "cor.h" +#include "dacprivate.h" + +BOOL FileExist (const char *filename); +BOOL FileExist (const WCHAR *filename); + +// We use global variables +// because move returns void if it fails +//typedef DWORD DWORD_PTR; +//typedef ULONG ULONG_PTR; + +// Max length in WCHAR for a buffer to store metadata name +const int mdNameLen = 2048; +extern WCHAR g_mdName[mdNameLen]; + +const int nMDIMPORT = 128; +struct MDIMPORT +{ + enum MDType {InMemory, InFile, Dynamic}; + WCHAR *name; + size_t base; // base of the PE module + size_t mdBase; // base of the metadata + char *metaData; + ULONG metaDataSize; + MDType type; + IMetaDataImport *pImport; + + MDIMPORT *left; + MDIMPORT *right; +}; + +class Module; + +extern "C" BOOL ControlC; +extern IMetaDataDispenserEx *pDisp; + +#endif // __data_h__ diff --git a/src/ToolBox/SOS/Strike/datatarget.cpp b/src/ToolBox/SOS/Strike/datatarget.cpp new file mode 100644 index 0000000000..8f24b7d841 --- /dev/null +++ b/src/ToolBox/SOS/Strike/datatarget.cpp @@ -0,0 +1,215 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#include "sos.h" +#include "datatarget.h" +#include "corhdr.h" +#include "cor.h" +#include "dacprivate.h" +#include "sospriv.h" +#include "corerror.h" + +#define IMAGE_FILE_MACHINE_AMD64 0x8664 // AMD64 (K8) + +DataTarget::DataTarget(void) : + m_ref(0) +{ +} + +STDMETHODIMP +DataTarget::QueryInterface( + THIS_ + ___in REFIID InterfaceId, + ___out PVOID* Interface + ) +{ + if (InterfaceId == IID_IUnknown || + InterfaceId == IID_ICLRDataTarget) + { + *Interface = (ICLRDataTarget*)this; + AddRef(); + return S_OK; + } + else if (InterfaceId == IID_ICorDebugDataTarget4) + { + *Interface = (ICorDebugDataTarget4*)this; + AddRef(); + return S_OK; + } + else + { + *Interface = NULL; + return E_NOINTERFACE; + } +} + +STDMETHODIMP_(ULONG) +DataTarget::AddRef( + THIS + ) +{ + LONG ref = InterlockedIncrement(&m_ref); + return ref; +} + +STDMETHODIMP_(ULONG) +DataTarget::Release( + THIS + ) +{ + LONG ref = InterlockedDecrement(&m_ref); + if (ref == 0) + { + delete this; + } + return ref; +} + +HRESULT STDMETHODCALLTYPE +DataTarget::GetMachineType( + /* [out] */ ULONG32 *machine) +{ + if (g_ExtControl == NULL) + { + return E_UNEXPECTED; + } + return g_ExtControl->GetExecutingProcessorType(machine); +} + +HRESULT STDMETHODCALLTYPE +DataTarget::GetPointerSize( + /* [out] */ ULONG32 *size) +{ +#if defined(SOS_TARGET_AMD64) || defined(SOS_TARGET_ARM64) + *size = 8; +#elif defined(SOS_TARGET_ARM) + *size = 4; +#elif + #error Unsupported architecture +#endif + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE +DataTarget::GetImageBase( + /* [string][in] */ LPCWSTR name, + /* [out] */ CLRDATA_ADDRESS *base) +{ + if (g_ExtSymbols == NULL) + { + return E_UNEXPECTED; + } + CHAR lpstr[MAX_LONGPATH]; + int name_length = WideCharToMultiByte(CP_ACP, 0, name, -1, lpstr, MAX_LONGPATH, NULL, NULL); + if (name_length == 0) + { + return E_FAIL; + } + return g_ExtSymbols->GetModuleByModuleName(lpstr, 0, NULL, base); +} + +HRESULT STDMETHODCALLTYPE +DataTarget::ReadVirtual( + /* [in] */ CLRDATA_ADDRESS address, + /* [length_is][size_is][out] */ PBYTE buffer, + /* [in] */ ULONG32 request, + /* [optional][out] */ ULONG32 *done) +{ + if (g_ExtData == NULL) + { + return E_UNEXPECTED; + } + return g_ExtData->ReadVirtual(address, (PVOID)buffer, request, done); +} + +HRESULT STDMETHODCALLTYPE +DataTarget::WriteVirtual( + /* [in] */ CLRDATA_ADDRESS address, + /* [size_is][in] */ PBYTE buffer, + /* [in] */ ULONG32 request, + /* [optional][out] */ ULONG32 *done) +{ + if (g_ExtData == NULL) + { + return E_UNEXPECTED; + } + return g_ExtData->WriteVirtual(address, (PVOID)buffer, request, done); +} + +HRESULT STDMETHODCALLTYPE +DataTarget::GetTLSValue( + /* [in] */ ULONG32 threadID, + /* [in] */ ULONG32 index, + /* [out] */ CLRDATA_ADDRESS* value) +{ + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE +DataTarget::SetTLSValue( + /* [in] */ ULONG32 threadID, + /* [in] */ ULONG32 index, + /* [in] */ CLRDATA_ADDRESS value) +{ + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE +DataTarget::GetCurrentThreadID( + /* [out] */ ULONG32* threadID) +{ + if (g_ExtSystem == NULL) + { + return E_UNEXPECTED; + } + return g_ExtSystem->GetCurrentThreadSystemId(threadID); +} + +HRESULT STDMETHODCALLTYPE +DataTarget::GetThreadContext( + /* [in] */ ULONG32 threadID, + /* [in] */ ULONG32 contextFlags, + /* [in] */ ULONG32 contextSize, + /* [out, size_is(contextSize)] */ PBYTE context) +{ + if (g_ExtSystem == NULL) + { + return E_UNEXPECTED; + } + return g_ExtSystem->GetThreadContextById(threadID, contextFlags, contextSize, context); +} + +HRESULT STDMETHODCALLTYPE +DataTarget::SetThreadContext( + /* [in] */ ULONG32 threadID, + /* [in] */ ULONG32 contextSize, + /* [out, size_is(contextSize)] */ PBYTE context) +{ + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE +DataTarget::Request( + /* [in] */ ULONG32 reqCode, + /* [in] */ ULONG32 inBufferSize, + /* [size_is][in] */ BYTE *inBuffer, + /* [in] */ ULONG32 outBufferSize, + /* [size_is][out] */ BYTE *outBuffer) +{ + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE +DataTarget::VirtualUnwind( + /* [in] */ DWORD threadId, + /* [in] */ ULONG32 contextSize, + /* [in, out, size_is(contextSize)] */ PBYTE context) +{ + if (g_ExtServices == NULL) + { + return E_UNEXPECTED; + } + return g_ExtServices->VirtualUnwind(threadId, contextSize, context); +} diff --git a/src/ToolBox/SOS/Strike/datatarget.h b/src/ToolBox/SOS/Strike/datatarget.h new file mode 100644 index 0000000000..0293bc668b --- /dev/null +++ b/src/ToolBox/SOS/Strike/datatarget.h @@ -0,0 +1,90 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +class DataTarget : public ICLRDataTarget, ICorDebugDataTarget4 +{ +private: + LONG m_ref; // Reference count. + +public: + DataTarget(void); + virtual ~DataTarget() {} + + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + ___in REFIID InterfaceId, + ___out PVOID* Interface + ); + STDMETHOD_(ULONG, AddRef)( + THIS + ); + STDMETHOD_(ULONG, Release)( + THIS + ); + + // + // ICLRDataTarget. + // + + virtual HRESULT STDMETHODCALLTYPE GetMachineType( + /* [out] */ ULONG32 *machine); + + virtual HRESULT STDMETHODCALLTYPE GetPointerSize( + /* [out] */ ULONG32 *size); + + virtual HRESULT STDMETHODCALLTYPE GetImageBase( + /* [string][in] */ LPCWSTR name, + /* [out] */ CLRDATA_ADDRESS *base); + + virtual HRESULT STDMETHODCALLTYPE ReadVirtual( + /* [in] */ CLRDATA_ADDRESS address, + /* [length_is][size_is][out] */ PBYTE buffer, + /* [in] */ ULONG32 request, + /* [optional][out] */ ULONG32 *done); + + virtual HRESULT STDMETHODCALLTYPE WriteVirtual( + /* [in] */ CLRDATA_ADDRESS address, + /* [size_is][in] */ PBYTE buffer, + /* [in] */ ULONG32 request, + /* [optional][out] */ ULONG32 *done); + + virtual HRESULT STDMETHODCALLTYPE GetTLSValue( + /* [in] */ ULONG32 threadID, + /* [in] */ ULONG32 index, + /* [out] */ CLRDATA_ADDRESS* value); + + virtual HRESULT STDMETHODCALLTYPE SetTLSValue( + /* [in] */ ULONG32 threadID, + /* [in] */ ULONG32 index, + /* [in] */ CLRDATA_ADDRESS value); + + virtual HRESULT STDMETHODCALLTYPE GetCurrentThreadID( + /* [out] */ ULONG32* threadID); + + virtual HRESULT STDMETHODCALLTYPE GetThreadContext( + /* [in] */ ULONG32 threadID, + /* [in] */ ULONG32 contextFlags, + /* [in] */ ULONG32 contextSize, + /* [out, size_is(contextSize)] */ PBYTE context); + + virtual HRESULT STDMETHODCALLTYPE SetThreadContext( + /* [in] */ ULONG32 threadID, + /* [in] */ ULONG32 contextSize, + /* [in, size_is(contextSize)] */ PBYTE context); + + virtual HRESULT STDMETHODCALLTYPE Request( + /* [in] */ ULONG32 reqCode, + /* [in] */ ULONG32 inBufferSize, + /* [size_is][in] */ BYTE *inBuffer, + /* [in] */ ULONG32 outBufferSize, + /* [size_is][out] */ BYTE *outBuffer); + + // ICorDebugDataTarget4 + + virtual HRESULT STDMETHODCALLTYPE VirtualUnwind( + /* [in] */ DWORD threadId, + /* [in] */ ULONG32 contextSize, + /* [in, out, size_is(contextSize)] */ PBYTE context); +};
\ No newline at end of file diff --git a/src/ToolBox/SOS/Strike/dirs.proj b/src/ToolBox/SOS/Strike/dirs.proj new file mode 100644 index 0000000000..1e391307aa --- /dev/null +++ b/src/ToolBox/SOS/Strike/dirs.proj @@ -0,0 +1,20 @@ +<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\clr.props" /> + + <PropertyGroup> + <BuildInPhase1>true</BuildInPhase1> + <BuildInPhaseDefault>false</BuildInPhaseDefault> + <BuildCoreBinaries>true</BuildCoreBinaries> + <BuildSysBinaries>true</BuildSysBinaries> + </PropertyGroup> + + <ItemGroup Condition="'$(BuildExePhase)' == '1'"> + <ProjectFile Condition="'$(BuildForCoreSystem)'!='true'" Include="sos.nativeproj" /> + <ProjectFile Condition="'$(BuildArchitecture)' == 'arm'" Include="sosx86\sosx86.nativeproj" /> + <ProjectFile Condition="'$(BuildArchitecture)' == 'i386' and '$(BuildForCoreSystem)' == 'true'" Include="sosx86\sosx86.nativeproj" /> + <ProjectFile Condition="'$(BuildArchitecture)' == 'amd64' and '$(BuildForCoreSystem)' == 'true'" Include="sosx64\sosx64.nativeproj" /> + <ProjectFile Condition="'$(BuildArchitecture)' == 'arm64' and '$(BuildForCoreSystem)' == 'true'" Include="sosx64\sosx64.nativeproj" /> + </ItemGroup> + + <Import Project="$(_NTDRIVE)$(_NTROOT)\tools\Microsoft.DevDiv.Traversal.targets" /> +</Project> diff --git a/src/ToolBox/SOS/Strike/disasm.cpp b/src/ToolBox/SOS/Strike/disasm.cpp new file mode 100644 index 0000000000..e141f8038f --- /dev/null +++ b/src/ToolBox/SOS/Strike/disasm.cpp @@ -0,0 +1,1142 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ==++== +// + +// +// ==--== + +#include "strike.h" +#include "gcinfo.h" +#include "util.h" +#include <dbghelp.h> +#include <limits.h> + +#include "sos_md.h" + +#ifdef SOS_TARGET_X86 +namespace X86GCDump +{ +#include "gcdump.h" +#undef assert +#define assert(a) +#define CONTRACTL +#define DAC_ARG(x) +#define CONTRACTL_END +#define LIMITED_METHOD_CONTRACT +#define NOTHROW +#define GC_NOTRIGGER +#define SUPPORTS_DAC +#define LIMITED_METHOD_DAC_CONTRACT +#include "gcdecoder.cpp" +#undef CONTRACTL +#undef CONTRACTL_END +#undef LIMITED_METHOD_CONTRACT +#undef NOTHROW +#undef GC_NOTRIGGER +#undef _ASSERTE +#define _ASSERTE(a) do {} while (0) + +#include "gcdump.cpp" +#include "i386/gcdumpx86.cpp" +} +#endif // SOS_TARGET_X86 + +#ifdef SOS_TARGET_AMD64 +#include "gcdump.h" +#define DAC_ARG(x) +#define SUPPORTS_DAC +#define LIMITED_METHOD_DAC_CONTRACT +#undef LIMITED_METHOD_CONTRACT +#undef PREGDISPLAY + #ifdef LOG + #undef LOG + #endif + #define LOG(x) ((void)0) + #ifdef LOG_PIPTR + #undef LOG_PIPTR + #endif + #define LOG_PIPTR(pObjRef, gcFlags, hCallBack) ((void)0) +#include "gcdumpnonx86.cpp" +#endif // SOS_TARGET_AMD64 + +#include "disasm.h" + +#ifndef ERANGE +#define ERANGE 34 +#endif + +PVOID +GenOpenMapping( + PCSTR FilePath, + PULONG Size + ) +{ +#ifndef FEATURE_PAL + HANDLE hFile; + HANDLE hMappedFile; + PVOID MappedFile; + + hFile = CreateFileA( + FilePath, + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + 0, + NULL + ); +#if 0 + if ( hFile == NULL || hFile == INVALID_HANDLE_VALUE ) { + + if (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) { + + // We're on an OS that doesn't support Unicode + // file operations. Convert to ANSI and see if + // that helps. + + CHAR FilePathA [ MAX_LONGPATH + 10 ]; + + if (WideCharToMultiByte (CP_ACP, + 0, + FilePath, + -1, + FilePathA, + sizeof (FilePathA), + 0, + 0 + ) > 0) { + + hFile = CreateFileA(FilePathA, + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + 0, + NULL + ); + } + } + + if ( hFile == NULL || hFile == INVALID_HANDLE_VALUE ) { + return NULL; + } + } +#endif + + *Size = GetFileSize(hFile, NULL); + if (*Size == ULONG_MAX) { + CloseHandle( hFile ); + return NULL; + } + + hMappedFile = CreateFileMapping ( + hFile, + NULL, + PAGE_READONLY, + 0, + 0, + NULL + ); + + if ( !hMappedFile ) { + CloseHandle ( hFile ); + return NULL; + } + + MappedFile = MapViewOfFile ( + hMappedFile, + FILE_MAP_READ, + 0, + 0, + 0 + ); + + CloseHandle (hMappedFile); + CloseHandle (hFile); + + return MappedFile; +#else // FEATURE_PAL + return NULL; +#endif // FEATURE_PAL +} + +char* PrintOneLine (__in_z char *begin, __in_z char *limit) +{ + if (begin == NULL || begin >= limit) { + return NULL; + } + char line[128]; + size_t length; + char *end; + while (1) { + if (IsInterrupt()) + return NULL; + length = strlen (begin); + end = strstr (begin, "\r\xa"); + if (end == NULL) { + ExtOut ("%s", begin); + end = begin+length+1; + if (end >= limit) { + return NULL; + } + } + else { + end += 2; + length = end-begin; + while (length) { + if (IsInterrupt()) + return NULL; + size_t n = length; + if (n > 127) { + n = 127; + } + strncpy_s (line,_countof(line), begin, n); + line[n] = '\0'; + ExtOut ("%s", line); + begin += n; + length -= n; + } + return end; + } + } +} + +void UnassemblyUnmanaged(DWORD_PTR IP, BOOL bSuppressLines) +{ + char filename[MAX_PATH_FNAME+1]; + char line[256]; + int lcount = 10; + + ULONG linenum = 0; + ULONG64 Displacement = 0; + BOOL fLineAvailable = FALSE; + ULONG64 vIP = 0; + + if (!bSuppressLines) + { + ReloadSymbolWithLineInfo(); + fLineAvailable = SUCCEEDED (g_ExtSymbols->GetLineByOffset(TO_CDADDR(IP), + &linenum, + filename, + MAX_PATH_FNAME+1, + NULL, + &Displacement)); + } + ULONG FileLines = 0; + ArrayHolder<ULONG64> Buffer = NULL; + + if (fLineAvailable) + { + g_ExtSymbols->GetSourceFileLineOffsets(filename, NULL, 0, &FileLines); + if (FileLines == 0xFFFFFFFF || FileLines == 0) + fLineAvailable = FALSE; + } + + if (fLineAvailable) + { + Buffer = new ULONG64[FileLines]; + if (Buffer == NULL) + fLineAvailable = FALSE; + } + + if (!fLineAvailable) + { + vIP = TO_CDADDR(IP); + // There is no line info. Just disasm the code. + while (lcount-- > 0) + { + if (IsInterrupt()) + return; + g_ExtControl->Disassemble (vIP, 0, line, 256, NULL, &vIP); + ExtOut (line); + } + return; + } + + g_ExtSymbols->GetSourceFileLineOffsets(filename, Buffer, FileLines, NULL); + + int beginLine = 0; + int endLine = 0; + int lastLine; + linenum --; + for (lastLine = linenum; lastLine >= 0; lastLine --) { + if (IsInterrupt()) + return; + if (Buffer[lastLine] != DEBUG_INVALID_OFFSET) { + g_ExtSymbols->GetNameByOffset(Buffer[lastLine], NULL, 0, NULL, &Displacement); + if (Displacement == 0) { + beginLine = lastLine; + break; + } + } + } + if (lastLine < 0) { + int n = lcount / 2; + lastLine = linenum-1; + beginLine = lastLine; + while (lastLine >= 0) { + if (IsInterrupt()) + return; + if (Buffer[lastLine] != DEBUG_INVALID_OFFSET) { + beginLine = lastLine; + n --; + if (n == 0) { + break; + } + } + lastLine --; + } + } + while (beginLine > 0 && Buffer[beginLine-1] == DEBUG_INVALID_OFFSET) { + if (IsInterrupt()) + return; + beginLine --; + } + int endOfFunc = 0; + for (lastLine = linenum+1; (ULONG)lastLine < FileLines; lastLine ++) { + if (IsInterrupt()) + return; + if (Buffer[lastLine] != DEBUG_INVALID_OFFSET) { + g_ExtSymbols->GetNameByOffset(Buffer[lastLine], NULL, 0, NULL, &Displacement); + if (Displacement == 0) { + endLine = lastLine; + break; + } + endOfFunc = lastLine; + } + } + if ((ULONG)lastLine == FileLines) { + int n = lcount / 2; + lastLine = linenum+1; + endLine = lastLine; + while ((ULONG)lastLine < FileLines) { + if (IsInterrupt()) + return; + if (Buffer[lastLine] != DEBUG_INVALID_OFFSET) { + endLine = lastLine; + n --; + if (n == 0) { + break; + } + } + lastLine ++; + } + } + + PVOID MappedBase = NULL; + ULONG MappedSize = 0; + + class ToUnmap + { + PVOID *m_Base; + public: + ToUnmap (PVOID *base) + :m_Base(base) + {} + ~ToUnmap () + { + if (*m_Base) { + UnmapViewOfFile (*m_Base); + *m_Base = NULL; + } + } + }; + ToUnmap toUnmap(&MappedBase); + +#define MAX_SOURCE_PATH 1024 + char Found[MAX_SOURCE_PATH]; + char *pFile; + if (g_ExtSymbols->FindSourceFile(0, + filename, + DEBUG_FIND_SOURCE_BEST_MATCH | DEBUG_FIND_SOURCE_FULL_PATH, + NULL, + Found, + sizeof(Found), + NULL) != S_OK) + { + pFile = filename; + } + else + { + MappedBase = GenOpenMapping(Found, &MappedSize); + pFile = Found; + } + + lastLine = beginLine; + char *pFileCh = (char*)MappedBase; + if (MappedBase) { + ExtOut ("%s\n", pFile); + int n = beginLine; + while (n > 0) { + while (!(pFileCh[0] == '\r' && pFileCh[1] == 0xa)) { + if (IsInterrupt()) + return; + pFileCh ++; + } + pFileCh += 2; + n --; + } + } + + char filename1[MAX_PATH_FNAME+1]; + for (lastLine = beginLine; lastLine < endLine; lastLine ++) { + if (IsInterrupt()) + return; + if (MappedBase) { + ExtOut("%4d ", lastLine+1); + pFileCh = PrintOneLine(pFileCh, (char*)MappedBase+MappedSize); + } + if (Buffer[lastLine] != DEBUG_INVALID_OFFSET) { + if (MappedBase == 0) { + ExtOut (">>> %s:%d\n", pFile, lastLine+1); + } + vIP = Buffer[lastLine]; + ULONG64 vNextLineIP; + int i; + for (i = lastLine + 1; (ULONG)i < FileLines && Buffer[i] == DEBUG_INVALID_OFFSET; i ++) { + if (IsInterrupt()) + return; + } + if ((ULONG)i == FileLines) { + vNextLineIP = 0; + } + else + vNextLineIP = Buffer[i]; + while (1) { + if (IsInterrupt()) + return; + g_ExtControl->Disassemble(vIP, 0, line, 256, NULL, &vIP); + ExtOut (line); + if (vIP > vNextLineIP || vNextLineIP - vIP > 40) { + if (FAILED (g_ExtSymbols->GetLineByOffset(vIP, &linenum, + filename1, + MAX_PATH_FNAME+1, + NULL, + &Displacement))) { + if (lastLine != endOfFunc) { + break; + } + if (strstr (line, "ret") || strstr (line, "jmp")) { + break; + } + } + + if (linenum != (ULONG)lastLine+1 || strcmp (filename, filename1)) { + break; + } + } + else if (vIP == vNextLineIP) { + break; + } + } + } + } +} + +void DisasmAndClean (DWORD_PTR &IP, __out_ecount_opt(length) char *line, ULONG length) +{ + ULONG64 vIP = TO_CDADDR(IP); + g_ExtControl->Disassemble (vIP, 0, line, length, NULL, &vIP); + IP = (DWORD_PTR)vIP; + // remove the ending '\n' + char *ptr = strrchr (line, '\n'); + if (ptr != NULL) + ptr[0] = '\0'; +} + +// If byref, move to pass the byref prefix +BOOL IsByRef (__deref_inout_z char *& ptr) +{ + BOOL bByRef = FALSE; + const char* qindirCh = "qword ptr ["; + const char* dindirCh = "dword ptr ["; + const char* qindirDsCh = "qword ptr ds:["; + const char* dindirDsCh = "dword ptr ds:["; + if (ptr[0] == '[') + { + bByRef = TRUE; + ptr ++; + } + else if (!IsDbgTargetArm() && !strncmp (ptr, IsDbgTargetWin64() ? qindirCh : dindirCh, 11)) + { + bByRef = TRUE; + ptr += 11; + } + // The new disassembly engine for windbg formats indirect calls + // slightly differently: + else if (!IsDbgTargetArm() && !strncmp (ptr, IsDbgTargetWin64() ? qindirDsCh : dindirDsCh, 14)) + { + bByRef = TRUE; + ptr += 14; + } + return bByRef; +} + +BOOL IsTermSep (char ch) +{ + return (ch == '\0' || isspace (ch) || ch == ',' || ch == '\n'); +} + +// Find next term. A term is seperated by space or , +void NextTerm (__deref_inout_z char *& ptr) +{ + // If we have a byref, skip to ']' + if (IsByRef (ptr)) + { + while (ptr[0] != ']' && ptr[0] != '\0') + { + if (IsInterrupt()) + return; + ptr ++; + } + if (ptr[0] == ']') + ptr ++; + } + + while (!IsTermSep (ptr[0])) + { + if (IsInterrupt()) + return; + ptr ++; + } + + while (IsTermSep(ptr[0]) && (*ptr != '\0')) + { + if (IsInterrupt()) + return; + ptr ++; + } +} + + +// Parses something like 6e24d310, 0x6e24d310, or 6e24d310h. +// On 64-bit, also parses things like 000006fb`f9b70f50 and +// 000006fbf9b70f50 (as well as their 0x-prefix, -h suffix variations). +INT_PTR ParseHexNumber (__in_z char *ptr, ___out char **endptr) +{ + char *endptr1; + INT_PTR value1 = strtoul(ptr, &endptr1, 16); + +#ifdef _TARGET_WIN64_ + if ('`' == endptr1[0] && isxdigit(endptr1[1])) + { + char *endptr2; + INT_PTR value2 = strtoul(endptr1+1, &endptr2, 16); + + value1 = (value1 << 32) | value2; + endptr1 = endptr2; + } + // if the hex number was specified as 000006fbf9b70f50, an overflow occurred + else if (ULONG_MAX == value1 && errno == ERANGE) + { + if (!strncmp(ptr, "0x", 2)) + ptr += 2; + + char savedigit = ptr[8]; + ptr[8] = '\0'; + + value1 = strtoul(ptr, &endptr1, 16); + + ptr[8] = savedigit; + + char *endptr2; + INT_PTR value2 = strtoul(ptr+8, &endptr2, 16); + + size_t ndigits2 = endptr2 - (ptr+8); + + value1 = (value1 << (ndigits2*4)) | value2; + endptr1 = endptr2; + } +#endif // _TARGET_WIN64_ + + // account for the possible 'h' suffix + if ((*endptr1 == 'h') || (*endptr1 == 'H')) + { + ++endptr1; + } + + *endptr = endptr1; + return value1; +} + + +// only handle pure value, or memory address +INT_PTR GetValueFromExpr(__in_z char *ptr, INT_PTR &value) +{ + BOOL bNegative = FALSE; + value = 0; + char *myPtr = ptr; + BOOL bByRef = IsByRef (myPtr); + + // ARM disassembly contains '#' prefixes for hex constants + if (*myPtr == '#') + ++myPtr; + + if (myPtr[0] == '-') + { + myPtr ++; + bNegative = TRUE; + } + if (!strncmp (myPtr, "0x", 2) || isxdigit (myPtr[0])) + { + char *endptr; + value = ParseHexNumber(myPtr, &endptr); + if ((!bByRef && IsTermSep(endptr[0])) || (bByRef && endptr[0] == ']')) + { + if (bNegative) + value = -value; + ptr = endptr; + if (bByRef) + { + ptr += 1; + SafeReadMemory (TO_TADDR(value), &value, 4, NULL); + } + return ptr - myPtr; + } + } + + // handle mscorlib+0xed310 (6e24d310) + if (!bByRef) + { + ptr = myPtr; + // handle 'offset ' before the expression: + if (strncmp(ptr, "offset ", 7) == 0) + { + ptr += 7; + } + while (ptr[0] != ' ' && ptr[0] != '+' && ptr[0] != '\0') + { + if (IsInterrupt()) + return 0; + ptr ++; + } + if (ptr[0] == '+') + { + NextTerm (ptr); + if (ptr[0] == '(') + { + ptr ++; + char *endptr; + value = ParseHexNumber(ptr, &endptr); + if (endptr[0] == ')') + { + ptr ++; + return ptr - myPtr; + } + } + } + } + if (bByRef) + { + // handle dword [mscorlib+0x2bd788 (02ead788)] + ptr = myPtr; + // handle 'offset ' before the expression: + if (strncmp(ptr, "offset ", 7) == 0) + { + ptr += 7; + } + while (ptr[0] != '(' && ptr[0] != '\0') + { + if (IsInterrupt()) + return 0; + ptr ++; + } + if (ptr[0] == '(') + { + ptr ++; + char *endptr; + value = ParseHexNumber(ptr, &endptr); + if (endptr[0] == ')' && endptr[1] == ']') + { + ptr = endptr + 2; + SafeReadMemory (TO_TADDR(value), &value, 4, NULL); + return ptr - myPtr; + } + } + } + +#ifdef _TARGET_WIN64_ + // handle CLRStub@7fffc8601cc (000007fffc8601cc) + if (!bByRef && !strncmp(myPtr, "CLRStub[", 8)) + { + ptr = myPtr; + while (ptr[0] != '(' && ptr[0] != '\0') + { + if (IsInterrupt()) + return 0; + ptr ++; + } + if (ptr[0] == '(') + { + ptr ++; + char *endptr; + value = ParseHexNumber(ptr, &endptr); + if (endptr[0] == ')') + { + ptr ++; + return ptr - myPtr; + } + } + } +#endif // _TARGET_WIN64_ + + return 0; +} + + +const char * HelperFuncName (size_t IP) +{ + static char s_szHelperName[100]; + if (S_OK == g_sos->GetJitHelperFunctionName(IP, sizeof(s_szHelperName), &s_szHelperName[0], NULL)) + return &s_szHelperName[0]; + else + return NULL; +} + + +// Returns: +// NULL if the EHInfo passed in does not refer to a Typed clause +// "..." if pEHInfo->isCatchAllHandler is TRUE +// "TypeName" if pEHInfo is a DACEHInfo*. +// Note: +// The return is a pointer to a global buffer, therefore this value must +// be consumed as soon as possible after a call to this function. +LPCWSTR EHTypedClauseTypeName(___in const DACEHInfo* pEHInfo) +{ + _ASSERTE(pEHInfo != NULL); + if ((pEHInfo->clauseType == EHTyped) && pEHInfo->isCatchAllHandler) + { + return W("..."); + } + + // is there a method table or a token to look at? + if (pEHInfo->clauseType == EHTyped) + { + TADDR mt; + if (pEHInfo->moduleAddr == 0) + { + mt = TO_TADDR(pEHInfo->mtCatch); + NameForMT_s(mt, g_mdName, mdNameLen); + } else { + PrettyPrintClassFromToken(TO_TADDR(pEHInfo->moduleAddr), pEHInfo->tokCatch, g_mdName, mdNameLen, FormatCSharp); + } + return g_mdName; + } + + return NULL; +} + +BOOL IsClonedFinally(DACEHInfo *pEHInfo) +{ + // This maybe should be determined in the VM and passed in the DACEHInfo struct. +#if defined(_TARGET_AMD64_) || defined(_TARGET_ARM64_) + return ((pEHInfo->tryStartOffset == pEHInfo->tryEndOffset) && + (pEHInfo->tryStartOffset == pEHInfo->handlerStartOffset) && + (pEHInfo->clauseType == EHFinally) && + pEHInfo->isDuplicateClause); +#else + return FALSE; +#endif +} + + +void SOSEHInfo::FormatForDisassembly(CLRDATA_ADDRESS offSet) +{ + LPCWSTR typeName = NULL; + // the order of printing and iterating will matter on the boundaries + + // Print END tags in forward order (most nested to least nested). However, cloned + // finally clauses are always at the end, but they should be considered most nested, + // so have a separate loop to output them first. + for (UINT i=0; i < EHCount; i++) + { + DACEHInfo *pCur = &m_pInfos[i]; + + if (IsClonedFinally(pCur) && + (offSet == pCur->handlerEndOffset)) + { + ExtOut ("EHHandler %d: CLONED FINALLY END\n", i); + } + } + + for (UINT i=0; i < EHCount; i++) + { + DACEHInfo *pCur = &m_pInfos[i]; + + if (pCur->isDuplicateClause) + { + // Don't print anything for duplicate clauses + continue; + } + + if (offSet == pCur->tryEndOffset) + { + ExtOut ("EHHandler %d: %s CLAUSE END\n", i, EHTypeName(pCur->clauseType)); + } + + if (offSet == pCur->handlerEndOffset) + { + ExtOut ("EHHandler %d: %s HANDLER END\n", i, EHTypeName(pCur->clauseType)); + } + } + + // Print BEGIN tags in reverse order (least nested to most nested). + for (UINT i=EHCount-1; i != (UINT)-1; --i) + { + DACEHInfo *pCur = &m_pInfos[i]; + + // Must do this before the isDuplicatedClause check, since these are marked as duplicated clauses. + if (IsClonedFinally(pCur) && + (offSet == pCur->handlerStartOffset)) + { + ExtOut ("EHHandler %d: CLONED FINALLY BEGIN\n", i); + } + + if (pCur->isDuplicateClause) + { + // Don't print anything for duplicate clauses + continue; + } + + if (offSet == pCur->tryStartOffset) + { + ExtOut ("EHHandler %d: %s CLAUSE BEGIN", i, EHTypeName(pCur->clauseType)); + typeName = EHTypedClauseTypeName(pCur); + if (typeName != NULL) + { + ExtOut(" catch(%S) ", typeName); + } + ExtOut ("\n"); + } + + if (offSet == pCur->handlerStartOffset) + { + ExtOut ("EHHandler %d: %s HANDLER BEGIN", i, EHTypeName(pCur->clauseType)); + typeName = EHTypedClauseTypeName(pCur); + if (typeName != NULL) + { + ExtOut(" catch(%S) ", typeName); + } + ExtOut ("\n"); + } + + if ((pCur->clauseType == EHFilter) && + (offSet == pCur->filterOffset)) + { + ExtOut ("EHHandler %d: %s FILTER BEGIN\n",i, EHTypeName(pCur->clauseType)); + } + } +} + + +// +// Implementation shared by X86, ARM, and X64 +// Any cross platform code should resolve through g_targetMachine or should +// use the IS_DBG_TARGET_XYZ macro. +// + +void PrintNativeStack(DWORD_PTR ip, BOOL bSuppressLines) +{ + char filename[MAX_PATH_FNAME + 1]; + char symbol[1024]; + ULONG64 displacement; + + HRESULT hr = g_ExtSymbols->GetNameByOffset(TO_CDADDR(ip), symbol, _countof(symbol), NULL, &displacement); + if (SUCCEEDED(hr) && symbol[0] != '\0') + { + ExtOut("%s", symbol); + + if (displacement) + { + ExtOut(" + %#x", displacement); + } + + if (!bSuppressLines) + { + ULONG line; + hr = g_ExtSymbols->GetLineByOffset(TO_CDADDR(ip), &line, filename, _countof(filename), NULL, NULL); + if (SUCCEEDED(hr)) + { + ExtOut(" [%s:%d]", filename, line); + } + } + } + else + { + DMLOut(DMLIP(ip)); + } +} + +// Return TRUE if we have printed something. +BOOL PrintCallInfo(DWORD_PTR vEBP, DWORD_PTR IP, DumpStackFlag& DSFlag, BOOL bSymbolOnly) +{ + char Symbol[1024]; + char filename[MAX_PATH_FNAME+1]; + ULONG64 Displacement; + BOOL bOutput = FALSE; + + // degrade gracefully for debuggees that don't have a runtime loaded, or a DAC available + DWORD_PTR methodDesc = 0; + if (!g_bDacBroken) + { + methodDesc = FunctionType (IP); + } + + if (methodDesc > 1) + { + bOutput = TRUE; + if (!bSymbolOnly) + DMLOut("%p %s ", SOS_PTR(vEBP), DMLIP(IP)); + DMLOut("(MethodDesc %s ", DMLMethodDesc(methodDesc)); + + // TODO: Microsoft, more checks to make sure method is not eeimpl, etc. Add this field to MethodDesc + + DacpCodeHeaderData codeHeaderData; + if (codeHeaderData.Request(g_sos, TO_CDADDR(IP)) == S_OK) + { + DWORD_PTR IPBegin = (DWORD_PTR) codeHeaderData.MethodStart; + methodDesc = (DWORD_PTR) codeHeaderData.MethodDescPtr; + Displacement = IP - IPBegin; + if (IP >= IPBegin && Displacement <= codeHeaderData.MethodSize) + ExtOut ("+ %#x ", Displacement); + } + if (NameForMD_s(methodDesc, g_mdName, mdNameLen)) + { + ExtOut("%S)", g_mdName); + } + else + { + ExtOut("%s)", DMLIP(IP)); + } + } + else + { + if (!DSFlag.fEEonly) + { + bOutput = TRUE; + const char *name; + if (!bSymbolOnly) + DMLOut("%p %s ", SOS_PTR(vEBP), DMLIP(IP)); + + // if AMD64 ever becomes a cross platform target this must be resolved through + // virtual dispatch rather than conditional compilation +#ifdef _TARGET_AMD64_ + // degrade gracefully for debuggees that don't have a runtime loaded, or a DAC available + eTargetType ett = ettUnk; + if (!g_bDacBroken) + { + DWORD_PTR finalMDorIP = 0; + ett = GetFinalTarget(IP, &finalMDorIP); + if (ett == ettNative || ett==ettJitHelp) + { + methodDesc = 0; + IP = finalMDorIP; + } + else + { + methodDesc = finalMDorIP; + } + } +#endif // _TARGET_AMD64_ + if (methodDesc == 0) + { + PrintNativeStack(IP, DSFlag.fSuppressSrcInfo); + } + else if (g_bDacBroken) + { + // degrade gracefully for debuggees that don't have a runtime loaded, or a DAC available + DMLOut(DMLIP(IP)); + } + else if (IsMethodDesc (IP)) + { + NameForMD_s(IP, g_mdName, mdNameLen); + ExtOut(" (stub for %S)", g_mdName); + } + else if (IsMethodDesc(IP+5)) { + NameForMD_s((DWORD_PTR)(IP+5), g_mdName, mdNameLen); + DMLOut("%s (MethodDesc %s %S)", DMLIP(IP), DMLMethodDesc(IP+5), g_mdName); + } + else if ((name = HelperFuncName(IP)) != NULL) { + ExtOut(" (JitHelp: %s)", name); + } +#ifdef _TARGET_AMD64_ + else if (ett == ettMD || ett == ettStub) + { + NameForMD_s(methodDesc, g_mdName,mdNameLen); + DMLOut("%s (stub for %S)", DMLIP(IP), g_mdName); + // fallthrough to return + } +#endif // _TARGET_AMD64_ + else + { + DMLOut(DMLIP(IP)); + } + } + } + return bOutput; +} + +void DumpStackWorker (DumpStackFlag &DSFlag) +{ + DWORD_PTR eip; + ULONG64 Offset; + g_ExtRegisters->GetInstructionOffset(&Offset); + eip = (DWORD_PTR)Offset; + + ExtOut("Current frame: "); + PrintCallInfo (0, eip, DSFlag, TRUE); + ExtOut ("\n"); + + // make certain dword/qword aligned + DWORD_PTR ptr = DSFlag.top & (~ALIGNCONST); + + ExtOut (g_targetMachine->GetDumpStackHeading()); + while (ptr < DSFlag.end) + { + if (IsInterrupt()) + return; + DWORD_PTR retAddr; + DWORD_PTR whereCalled; + move_xp(retAddr, ptr); + g_targetMachine->IsReturnAddress(retAddr, &whereCalled); + if (whereCalled) + { + BOOL bOutput = PrintCallInfo(ptr-sizeof(TADDR), retAddr, DSFlag, FALSE); + if (!DSFlag.fEEonly) + { + if (whereCalled != 0xFFFFFFFF) + { + ExtOut (", calling "); + PrintCallInfo (0, whereCalled, DSFlag, TRUE); + } + } + if (bOutput) + ExtOut ("\n"); + + DWORD_PTR cxrAddr; + CROSS_PLATFORM_CONTEXT cxr; + DWORD_PTR exrAddr; + EXCEPTION_RECORD exr; + + if (g_targetMachine->GetExceptionContext(ptr,retAddr,&cxrAddr,&cxr,&exrAddr,&exr)) + { + TADDR sp = g_targetMachine->GetSP(cxr); + TADDR ip = g_targetMachine->GetIP(cxr); + bOutput = PrintCallInfo(sp, ip, DSFlag, FALSE); + if (bOutput) + { + ExtOut(" ====> Exception "); + if (exrAddr) + ExtOut("Code %x ", exr.ExceptionCode); + ExtOut ("cxr@%p", SOS_PTR(cxrAddr)); + if (exrAddr) + ExtOut(" exr@%p", SOS_PTR(exrAddr)); + ExtOut("\n"); + } + } + } + ptr += sizeof (DWORD_PTR); + } +} + +#ifdef SOS_TARGET_X86 +/// +/// X86Machine implementation +/// +LPCSTR X86Machine::s_DumpStackHeading = "ChildEBP RetAddr Caller, Callee\n"; +LPCSTR X86Machine::s_DSOHeading = "ESP/REG Object Name\n"; +LPCSTR X86Machine::s_GCRegs[7] = {"eax", "ebx", "ecx", "edx", "esi", "edi", "ebp"}; +LPCSTR X86Machine::s_SPName = "ESP"; + +void PrintNothing (const char *fmt, ...) +{ + // Do nothing. +} + +/// +/// Dump X86 GCInfo header and table +/// +void X86Machine::DumpGCInfo(GCInfoToken gcInfoToken, unsigned methodSize, printfFtn gcPrintf, bool encBytes, bool bPrintHeader) const +{ + X86GCDump::InfoHdr header; + X86GCDump::GCDump gcDump(gcInfoToken.Version, encBytes, 5, true); + BYTE* pTable = dac_cast<PTR_BYTE>(gcInfoToken.Info); + if (bPrintHeader) + { + gcDump.gcPrintf = gcPrintf; + gcPrintf("Method info block:\n"); + } + else + { + gcDump.gcPrintf = PrintNothing; + } + pTable += gcDump.DumpInfoHdr(pTable, &header, &methodSize, 0); + if (bPrintHeader) + { + gcPrintf("\n"); + gcPrintf("Pointer table:\n"); + } + gcDump.gcPrintf = gcPrintf; + gcDump.DumpGCTable(pTable, header, methodSize, 0); +} +#endif // SOS_TARGET_X86 + +#ifdef SOS_TARGET_ARM +/// +/// ARMMachine implementation +/// +LPCSTR ARMMachine::s_DumpStackHeading = "ChildFP RetAddr Caller, Callee\n"; +LPCSTR ARMMachine::s_DSOHeading = "SP/REG Object Name\n"; +LPCSTR ARMMachine::s_GCRegs[14] = {"r0", "r1", "r2", "r3", "r4", "r5", "r6", + "r7", "r8", "r9", "r10", "r11", "r12", "lr"}; +LPCSTR ARMMachine::s_SPName = "sp"; + +#endif // SOS_TARGET_ARM + +#ifdef SOS_TARGET_AMD64 +/// +/// AMD64Machine implementation +/// +LPCSTR AMD64Machine::s_DumpStackHeading = "Child-SP RetAddr Caller, Callee\n"; +LPCSTR AMD64Machine::s_DSOHeading = "RSP/REG Object Name\n"; +LPCSTR AMD64Machine::s_GCRegs[15] = {"rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp", + "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15"}; +LPCSTR AMD64Machine::s_SPName = "RSP"; + +/// +/// Dump AMD64 GCInfo table +/// +void AMD64Machine::DumpGCInfo(GCInfoToken gcInfoToken, unsigned methodSize, printfFtn gcPrintf, bool encBytes, bool bPrintHeader) const +{ + if (bPrintHeader) + { + ExtOut("Pointer table:\n"); + } + + GCDump gcDump(gcInfoToken.Version, encBytes, 5, true); + gcDump.gcPrintf = gcPrintf; + + gcDump.DumpGCTable(dac_cast<PTR_BYTE>(gcInfoToken.Info), methodSize, 0); +} + +#endif // SOS_TARGET_AMD64 + +#ifdef SOS_TARGET_ARM64 +/// +/// ARM64Machine implementation +/// +LPCSTR ARM64Machine::s_DumpStackHeading = "ChildFP RetAddr Caller, Callee\n"; +LPCSTR ARM64Machine::s_DSOHeading = "SP/REG Object Name\n"; +// excluding x18, fp & lr as these will not contain object references +LPCSTR ARM64Machine::s_GCRegs[28] = {"x0", "x1", "x2", "x3", "x4", "x5", "x6", + "x7", "x8", "x9", "x10", "x11", "x12", "x13", + "x14", "x15", "x16", "x17", "x19", "x20","x21", + "x22", "x23", "x24", "x25", "x26", "x27", "x28"}; +LPCSTR ARM64Machine::s_SPName = "sp"; + +#endif // SOS_TARGET_ARM64 + + diff --git a/src/ToolBox/SOS/Strike/disasm.h b/src/ToolBox/SOS/Strike/disasm.h new file mode 100644 index 0000000000..59fc168a6e --- /dev/null +++ b/src/ToolBox/SOS/Strike/disasm.h @@ -0,0 +1,453 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ==++== +// + +// +// ==--== +#ifndef __disasm_h__ +#define __disasm_h__ + +#include "sos_stacktrace.h" + +struct InfoHdr; +class GCDump; + + +struct DumpStackFlag +{ + BOOL fEEonly; + BOOL fSuppressSrcInfo; + DWORD_PTR top; + DWORD_PTR end; +}; + +struct GCEncodingInfo +{ + LPVOID pvMainFiber; + LPVOID pvGCTableFiber; + + BYTE *table; + unsigned int methodSize; + + char buf[1000]; + int cch; + + SIZE_T ofs; + + // When decoding a cold region, set this to the size of the hot region to keep offset + // calculations working. + SIZE_T hotSizeToAdd; + bool fDoneDecoding; +}; + +// Returns: +// NULL if the EHInfo passed in does not refer to a Typed clause +// "..." if pEHInfo->isCatchAllHandler is TRUE +// "TypeName" if pEHInfo is a DACEHInfo* that references type "TypeName". +// Note: +// The return is a pointer to a global buffer, therefore this value must +// be consumed as soon as possible after a call to this function. +LPCWSTR EHTypedClauseTypeName(const DACEHInfo* pEHInfo); + +struct SOSEHInfo +{ +#ifndef FEATURE_CORECLR + __field_ecount(EHCount) +#endif + DACEHInfo *m_pInfos; + UINT EHCount; + CLRDATA_ADDRESS methodStart; + + SOSEHInfo() { ZeroMemory(this, sizeof(SOSEHInfo)); } + ~SOSEHInfo() { if (m_pInfos) { delete [] m_pInfos; } } + + void FormatForDisassembly(CLRDATA_ADDRESS offSet); +}; + +BOOL IsClonedFinally(DACEHInfo *pEHInfo); + +void DumpStackWorker (DumpStackFlag &DSFlag); + +void UnassemblyUnmanaged (DWORD_PTR IP, BOOL bSuppressLines); + +HRESULT CheckEEDll (); + +BOOL GetCalleeSite (DWORD_PTR IP, DWORD_PTR &IPCallee); + +void DisasmAndClean (DWORD_PTR &IP, __out_ecount_opt(length) char *line, ULONG length); + +INT_PTR GetValueFromExpr(___in __in_z char *ptr, INT_PTR &value); + +void NextTerm (__deref_inout_z char *& ptr); + +BOOL IsByRef (__deref_inout_z char *& ptr); + +BOOL IsTermSep (char ch); + +const char * HelperFuncName (size_t IP); + +enum eTargetType { ettUnk = 0, ettNative = 1, ettJitHelp = 2, ettStub = 3, ettMD = 4 }; + +// GetFinalTarget is based on HandleCall, but avoids printing anything to the output. +// This is currently only called on x64 +eTargetType GetFinalTarget(DWORD_PTR callee, DWORD_PTR* finalMDorIP); + +#ifdef _MSC_VER +// SOS is essentially single-threaded. ignore "construction of local static object is not thread-safe" +#pragma warning(push) +#pragma warning(disable:4640) +#endif // _MSC_VER + +//----------------------------------------------------------------------------------------- +// +// Implementations for the supported target platforms +// +//----------------------------------------------------------------------------------------- + +#ifndef THUMB_CODE +#define THUMB_CODE 1 +#endif +#define STACKWALK_CONTROLPC_ADJUST_OFFSET 2 + +#ifdef SOS_TARGET_X86 + +/// X86 Machine specific code +class X86Machine : public IMachine +{ +public: + typedef X86_CONTEXT TGT_CTXT; + + static IMachine* GetInstance() + { static X86Machine s_X86MachineInstance; return &s_X86MachineInstance; } + + ULONG GetPlatform() const { return IMAGE_FILE_MACHINE_I386; } + ULONG GetContextSize() const { return sizeof(X86_CONTEXT); } + virtual void Unassembly( + TADDR IPBegin, + TADDR IPEnd, + TADDR IPAskedFor, + TADDR GCStressCodeCopy, + GCEncodingInfo * pGCEncodingInfo, + SOSEHInfo *pEHInfo, + BOOL bSuppressLines, + BOOL bDisplayOffsets) const; + virtual void IsReturnAddress( + TADDR retAddr, + TADDR* whereCalled) const; + virtual BOOL GetExceptionContext ( + TADDR stack, + TADDR PC, + TADDR *cxrAddr, + CROSS_PLATFORM_CONTEXT * cxr, + TADDR * exrAddr, + PEXCEPTION_RECORD exr) const; + + // retrieve stack pointer, frame pointer, and instruction pointer from the target context + virtual TADDR GetSP(const CROSS_PLATFORM_CONTEXT & ctx) const { return ctx.X86Context.Esp; } + virtual TADDR GetBP(const CROSS_PLATFORM_CONTEXT & ctx) const { return ctx.X86Context.Ebp; } + virtual TADDR GetIP(const CROSS_PLATFORM_CONTEXT & ctx) const { return ctx.X86Context.Eip; } + + virtual void FillSimpleContext(StackTrace_SimpleContext * dest, LPVOID srcCtx) const; + virtual void FillTargetContext(LPVOID destCtx, LPVOID srcCtx, int idx = 0) const; + + virtual LPCSTR GetDumpStackHeading() const { return s_DumpStackHeading; } + virtual LPCSTR GetDumpStackObjectsHeading() const { return s_DSOHeading; } + virtual LPCSTR GetSPName() const { return s_SPName; } + virtual void GetGCRegisters(LPCSTR** regNames, unsigned int* cntRegs) const + { _ASSERTE(cntRegs != NULL); *regNames = s_GCRegs; *cntRegs = _countof(s_GCRegs); } + + virtual void DumpGCInfo(GCInfoToken gcInfoToken, unsigned methodSize, printfFtn gcPrintf, bool encBytes, bool bPrintHeader) const; + +private: + X86Machine() {} + ~X86Machine() {} + X86Machine(const X86Machine& machine); // undefined + X86Machine & operator=(const X86Machine&); // undefined + +private: + static LPCSTR s_DumpStackHeading; + static LPCSTR s_DSOHeading; + static LPCSTR s_GCRegs[7]; + static LPCSTR s_SPName; +}; // class X86Machine + +#endif // SOS_TARGET_X86 + + +#ifdef SOS_TARGET_ARM + +/// ARM Machine specific code +class ARMMachine : public IMachine +{ +public: + typedef ARM_CONTEXT TGT_CTXT; + + static IMachine* GetInstance() + { return &s_ARMMachineInstance; } + + ULONG GetPlatform() const { return IMAGE_FILE_MACHINE_ARMNT; } + ULONG GetContextSize() const { return sizeof(ARM_CONTEXT); } + virtual void Unassembly( + TADDR IPBegin, + TADDR IPEnd, + TADDR IPAskedFor, + TADDR GCStressCodeCopy, + GCEncodingInfo *pGCEncodingInfo, + SOSEHInfo *pEHInfo, + BOOL bSuppressLines, + BOOL bDisplayOffsets) const; + virtual void IsReturnAddress( + TADDR retAddr, + TADDR* whereCalled) const; + virtual BOOL GetExceptionContext ( + TADDR stack, + TADDR PC, + TADDR *cxrAddr, + CROSS_PLATFORM_CONTEXT * cxr, + TADDR *exrAddr, + PEXCEPTION_RECORD exr) const; + + // retrieve stack pointer, frame pointer, and instruction pointer from the target context + virtual TADDR GetSP(const CROSS_PLATFORM_CONTEXT & ctx) const { return ctx.ArmContext.Sp; } + // @ARMTODO: frame pointer + virtual TADDR GetBP(const CROSS_PLATFORM_CONTEXT & ctx) const { return 0; } + virtual TADDR GetIP(const CROSS_PLATFORM_CONTEXT & ctx) const { return ctx.ArmContext.Pc; } + + virtual void FillSimpleContext(StackTrace_SimpleContext * dest, LPVOID srcCtx) const; + virtual void FillTargetContext(LPVOID destCtx, LPVOID srcCtx, int idx = 0) const; + + virtual LPCSTR GetDumpStackHeading() const { return s_DumpStackHeading; } + virtual LPCSTR GetDumpStackObjectsHeading() const { return s_DSOHeading; } + virtual LPCSTR GetSPName() const { return s_SPName; } + virtual void GetGCRegisters(LPCSTR** regNames, unsigned int* cntRegs) const + { _ASSERTE(cntRegs != NULL); *regNames = s_GCRegs; *cntRegs = _countof(s_GCRegs); } + + virtual void DumpGCInfo(GCInfoToken gcInfoToken, unsigned methodSize, printfFtn gcPrintf, bool encBytes, bool bPrintHeader) const; + +private: + ARMMachine() {} + ~ARMMachine() {} + ARMMachine(const ARMMachine& machine); // undefined + ARMMachine & operator=(const ARMMachine&); // undefined + +private: + static LPCSTR s_DumpStackHeading; + static LPCSTR s_DSOHeading; + static LPCSTR s_GCRegs[14]; + static LPCSTR s_SPName; + static ARMMachine s_ARMMachineInstance; +}; // class ARMMachine + +#endif // SOS_TARGET_ARM + +#ifdef SOS_TARGET_AMD64 + +/// AMD64 Machine specific code +class AMD64Machine : public IMachine +{ +public: + typedef AMD64_CONTEXT TGT_CTXT; + + static IMachine* GetInstance() + { static AMD64Machine s_AMD64MachineInstance; return &s_AMD64MachineInstance; } + + ULONG GetPlatform() const { return IMAGE_FILE_MACHINE_AMD64; } + ULONG GetContextSize() const { return sizeof(AMD64_CONTEXT); } + + virtual void Unassembly( + TADDR IPBegin, + TADDR IPEnd, + TADDR IPAskedFor, + TADDR GCStressCodeCopy, + GCEncodingInfo *pGCEncodingInfo, + SOSEHInfo *pEHInfo, + BOOL bSuppressLines, + BOOL bDisplayOffsets) const; + + virtual void IsReturnAddress( + TADDR retAddr, + TADDR* whereCalled) const; + + virtual BOOL GetExceptionContext ( + TADDR stack, + TADDR PC, + TADDR *cxrAddr, + CROSS_PLATFORM_CONTEXT * cxr, + TADDR *exrAddr, + PEXCEPTION_RECORD exr) const; + + // retrieve stack pointer, frame pointer, and instruction pointer from the target context + virtual TADDR GetSP(const CROSS_PLATFORM_CONTEXT & ctx) const { return ctx.Amd64Context.Rsp; } + virtual TADDR GetBP(const CROSS_PLATFORM_CONTEXT & ctx) const { return ctx.Amd64Context.Rbp; } + virtual TADDR GetIP(const CROSS_PLATFORM_CONTEXT & ctx) const { return ctx.Amd64Context.Rip; } + + virtual void FillSimpleContext(StackTrace_SimpleContext * dest, LPVOID srcCtx) const; + virtual void FillTargetContext(LPVOID destCtx, LPVOID srcCtx, int idx = 0) const; + + virtual LPCSTR GetDumpStackHeading() const { return s_DumpStackHeading; } + virtual LPCSTR GetDumpStackObjectsHeading() const { return s_DSOHeading; } + virtual LPCSTR GetSPName() const { return s_SPName; } + virtual void GetGCRegisters(LPCSTR** regNames, unsigned int* cntRegs) const + { _ASSERTE(cntRegs != NULL); *regNames = s_GCRegs; *cntRegs = _countof(s_GCRegs); } + + virtual void DumpGCInfo(GCInfoToken gcInfoToken, unsigned methodSize, printfFtn gcPrintf, bool encBytes, bool bPrintHeader) const; + +private: + AMD64Machine() {} + ~AMD64Machine() {} + AMD64Machine(const AMD64Machine& machine); // undefined + AMD64Machine & operator=(const AMD64Machine&); // undefined + +private: + static LPCSTR s_DumpStackHeading; + static LPCSTR s_DSOHeading; + static LPCSTR s_GCRegs[15]; + static LPCSTR s_SPName; +}; // class AMD64Machine + +#endif // SOS_TARGET_AMD64 + +#ifdef SOS_TARGET_ARM64 + +/// ARM64 Machine specific code +class ARM64Machine : public IMachine +{ +public: + typedef ARM64_CONTEXT TGT_CTXT; + + static IMachine* GetInstance() + { static ARM64Machine s_ARM64MachineInstance; return &s_ARM64MachineInstance; } + + ULONG GetPlatform() const { return IMAGE_FILE_MACHINE_ARM64; } + ULONG GetContextSize() const { return sizeof(ARM64_CONTEXT); } + virtual void Unassembly( + TADDR IPBegin, + TADDR IPEnd, + TADDR IPAskedFor, + TADDR GCStressCodeCopy, + GCEncodingInfo *pGCEncodingInfo, + SOSEHInfo *pEHInfo, + BOOL bSuppressLines, + BOOL bDisplayOffsets) const; + virtual void IsReturnAddress( + TADDR retAddr, + TADDR* whereCalled) const; + virtual BOOL GetExceptionContext ( + TADDR stack, + TADDR PC, + TADDR *cxrAddr, + CROSS_PLATFORM_CONTEXT * cxr, + TADDR *exrAddr, + PEXCEPTION_RECORD exr) const; + + // retrieve stack pointer, frame pointer, and instruction pointer from the target context + virtual TADDR GetSP(const CROSS_PLATFORM_CONTEXT & ctx) const { return ctx.Arm64Context.Sp; } + virtual TADDR GetBP(const CROSS_PLATFORM_CONTEXT & ctx) const { return ctx.Arm64Context.Fp; } + virtual TADDR GetIP(const CROSS_PLATFORM_CONTEXT & ctx) const { return ctx.Arm64Context.Pc; } + + virtual void FillSimpleContext(StackTrace_SimpleContext * dest, LPVOID srcCtx) const; + virtual void FillTargetContext(LPVOID destCtx, LPVOID srcCtx, int idx = 0) const; + + virtual LPCSTR GetDumpStackHeading() const { return s_DumpStackHeading; } + virtual LPCSTR GetDumpStackObjectsHeading() const { return s_DSOHeading; } + virtual LPCSTR GetSPName() const { return s_SPName; } + virtual void GetGCRegisters(LPCSTR** regNames, unsigned int* cntRegs) const + { _ASSERTE(cntRegs != NULL); *regNames = s_GCRegs; *cntRegs = _countof(s_GCRegs);} + + virtual void DumpGCInfo(GCInfoToken gcInfoToken, unsigned methodSize, printfFtn gcPrintf, bool encBytes, bool bPrintHeader) const; + +private: + ARM64Machine() {} + ~ARM64Machine() {} + ARM64Machine(const ARM64Machine& machine); // undefined + ARM64Machine & operator=(const ARM64Machine&); // undefined + + static LPCSTR s_DumpStackHeading; + static LPCSTR s_DSOHeading; + static LPCSTR s_GCRegs[28]; + static LPCSTR s_SPName; + +}; // class ARM64Machine + +#endif // SOS_TARGET_ARM64 +#ifdef _MSC_VER +#pragma warning(pop) +#endif // _MSC_VER + + +// +// Inline methods +// + + +#ifdef SOS_TARGET_X86 +inline void X86Machine::FillSimpleContext(StackTrace_SimpleContext * dest, LPVOID srcCtx) const +{ + TGT_CTXT& src = *(TGT_CTXT*) srcCtx; + dest->StackOffset = src.Esp; + dest->FrameOffset = src.Ebp; + dest->InstructionOffset = src.Eip; +} + +inline void X86Machine::FillTargetContext(LPVOID destCtx, LPVOID srcCtx, int idx /*= 0*/) const +{ + TGT_CTXT* dest = (TGT_CTXT*)destCtx + idx; + *dest = *(TGT_CTXT*)srcCtx; +} +#endif // SOS_TARGET_X86 + + +#ifdef SOS_TARGET_ARM +inline void ARMMachine::FillSimpleContext(StackTrace_SimpleContext * dest, LPVOID srcCtx) const +{ + TGT_CTXT& src = *(TGT_CTXT*) srcCtx; + dest->StackOffset = src.Sp; + // @ARMTODO: frame pointer - keep in sync with ARMMachine::GetBP + dest->FrameOffset = 0; + dest->InstructionOffset = src.Pc; +} + +inline void ARMMachine::FillTargetContext(LPVOID destCtx, LPVOID srcCtx, int idx /*= 0*/) const +{ + TGT_CTXT* dest = (TGT_CTXT*)destCtx + idx; + *dest = *(TGT_CTXT*)srcCtx; +} +#endif // SOS_TARGET_ARM + + +#ifdef SOS_TARGET_AMD64 +inline void AMD64Machine::FillSimpleContext(StackTrace_SimpleContext * dest, LPVOID srcCtx) const +{ + TGT_CTXT& src = *(TGT_CTXT*) srcCtx; + dest->StackOffset = src.Rsp; + dest->FrameOffset = src.Rbp; + dest->InstructionOffset = src.Rip; +} + +inline void AMD64Machine::FillTargetContext(LPVOID destCtx, LPVOID srcCtx, int idx /*= 0*/) const +{ + TGT_CTXT* dest = (TGT_CTXT*)destCtx + idx; + *dest = *(TGT_CTXT*)srcCtx; +} +#endif // SOS_TARGET_AMD64 + +#ifdef SOS_TARGET_ARM64 +inline void ARM64Machine::FillSimpleContext(StackTrace_SimpleContext * dest, LPVOID srcCtx) const +{ + TGT_CTXT& src = *(TGT_CTXT*) srcCtx; + dest->StackOffset = src.Sp; + dest->FrameOffset = src.Fp; + dest->InstructionOffset = src.Pc; +} + +inline void ARM64Machine::FillTargetContext(LPVOID destCtx, LPVOID srcCtx, int idx /*= 0*/) const +{ + TGT_CTXT* dest = (TGT_CTXT*)destCtx + idx; + *dest = *(TGT_CTXT*)srcCtx; +} +#endif // SOS_TARGET_ARM64 + +#endif // __disasm_h__ diff --git a/src/ToolBox/SOS/Strike/disasmARM.cpp b/src/ToolBox/SOS/Strike/disasmARM.cpp new file mode 100644 index 0000000000..82173558fd --- /dev/null +++ b/src/ToolBox/SOS/Strike/disasmARM.cpp @@ -0,0 +1,626 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ==++== +// + +// +// ==--== + +#ifndef _TARGET_ARM_ +#define _TARGET_ARM_ +#endif + + +#include "strike.h" +#include "util.h" +#include <dbghelp.h> + + +#include "disasm.h" + +#include "../../../inc/corhdr.h" +#include "../../../inc/cor.h" +#include "../../../inc/dacprivate.h" + +#ifndef FEATURE_PAL +namespace ARMGCDump +{ +#undef _TARGET_X86_ +#undef LIMITED_METHOD_CONTRACT +#define LIMITED_METHOD_DAC_CONTRACT +#define SUPPORTS_DAC +#define LF_GCROOTS +#define LL_INFO1000 +#define LOG(x) +#define LOG_PIPTR(pObjRef, gcFlags, hCallBack) +#define DAC_ARG(x) +#include "gcdumpnonx86.cpp" +} +#endif // !FEATURE_PAL + +#if defined(_TARGET_WIN64_) +#error This file does not support SOS targeting ARM from a 64-bit debugger +#endif + +#if !defined(SOS_TARGET_ARM) +#error This file should be used to support SOS targeting ARM debuggees +#endif + +#ifdef SOS_TARGET_ARM +ARMMachine ARMMachine::s_ARMMachineInstance; + +// Decodes the target label of the immediate form of bl and blx instructions. The PC given is that of the +// start of the instruction. +static TADDR DecodeCallTarget(TADDR PC, WORD rgInstr[2]) +{ + // Displacement is spread across several bitfields in the two words of the instruction. Using the same + // bitfield names as the ARM Architecture Reference Manual. + DWORD S = (rgInstr[0] & 0x0400) >> 10; + DWORD imm10 = rgInstr[0] & 0x03ff; + DWORD J1 = (rgInstr[1] & 0x2000) >> 13; + DWORD J2 = (rgInstr[1] & 0x0800) >> 11; + DWORD imm11 = rgInstr[1] & 0x07ff; + + // For reasons that escape me the I1 and I2 fields are computed by XOR'ing J1 and J2 with S. + DWORD I1 = (~J1 ^ S) & 0x1; + DWORD I2 = (~J2 ^ S) & 0x1; + + // The final displacement is put together as: SignExtend(S:I1:I2:imm10:imm11:0) + DWORD highByte = S ? 0xff000000 : 0x00000000; + DWORD disp = highByte | (I1 << 23) | (I2 << 22) | (imm10 << 12) | (imm11 << 1); + + // The displacement is relative to the PC but the PC for a given instruction reads as the PC for the + // beginning of the instruction plus 4. + return PC + 4 + disp; +} + +// Validate that a potential call target points to readable memory. If so, and the code appears to be one of +// our standard jump thunks we'll deference through that and return the real target. Returns 0 if any checks +// fail. +static TADDR GetRealCallTarget(TADDR PC) +{ + WORD instr[2]; + + // Read the minimum (a WORD) first in case we're calling to a single WORD method at the end of a page + // (e.g. BLX <reg>). + if (g_ExtData->ReadVirtual(TO_CDADDR(PC), &instr[0], sizeof(WORD), NULL) != S_OK) + return 0; + + // All the jump thunks we handle start with the literal form of LDR (i.e. LDR <reg>, [PC +/- <imm>]). It's + // always the two word form since we're either loading R12 or PC. We never use the decrement version of + // the instruction (U == 0). + // If it's not an instruction of that form we can return immediately. + if (instr[0] != 0xf8df) + return PC; + + // The first instruction is defintely a LDR of the form we expect so it's OK to read the second half of + // the encoding. + if (g_ExtData->ReadVirtual(TO_CDADDR(PC + 2), &instr[1], sizeof(WORD), NULL) != S_OK) + return 0; + + // Determine which register we're loading. There are three cases: + // 1) PC: we're jumping, perform final calculation of the jump target + // 2) R12: we're possibly setting up a special argument to the jump target. Ignore this instruction and + // check for a LDR PC in the next instruction + // 3) Any other register: we don't recognize this instruction sequence, just return the PC we have + WORD reg = (instr[1] & 0xf000) >> 12; + if (reg == 12) + { + // Possibly a LDR R12, [...]; LDR PC, [...] thunk. Overwrite the current instruction with the next and + // then fall through into the common LDR PC, [...] handling below. If we fail to read the next word + // we're not really looking at valid code. But we need to be more careful reading the second word of + // the potential instruction since there are valid sequences that would terminate with a single word + // at the end of page. + if (g_ExtData->ReadVirtual(TO_CDADDR(PC + 4), &instr[0], sizeof(WORD), NULL) != S_OK) + return 0; + + // Following instruction is not a LDR <literal>. Return this PC as the real target. + if (instr[0] != 0xf8df) + return PC; + + // Read second half of the LDR instruction. + if (g_ExtData->ReadVirtual(TO_CDADDR(PC + 6), &instr[1], sizeof(WORD), NULL) != S_OK) + return 0; + + // Determine the target register. If it's not the PC then return this PC as the real target. + reg = (instr[1] & 0xf000) >> 12; + if (reg != 12) + return PC; + + // Fall through to process this LDR PC, [...] instruction. Update the input PC because it figures into + // the calculation below. + PC += 4; + } + else if (reg == 15) + { + // First instruction was a LDR PC, [...] Just fall through to common handling below. + } + else + { + // Any other target register is unrecognized. Just return what we have as the final target. + return PC; + } + + // Decode the LDR PC, [PC + <imm>] to find the jump target. + // The displacement is in the low order 12 bits of the second instruction word. + DWORD disp = instr[1] & 0x0fff; + + // The PC used for the effective address calculation is the PC from the start of the instruction rounded + // down to 4-byte alignment then incremented by 4. + TADDR targetAddress = (PC & ~3) + 4 + disp; + + // Read the target address from this routine. + TADDR target; + if (g_ExtData->ReadVirtual(TO_CDADDR(targetAddress), &target, sizeof(target), NULL) != S_OK) + return 0; + + // Clear the low-bit in the target used to indicate a Thumb mode destination. If this is not set we can't + // be looking at one of our jump thunks (in fact ARM mode code is illegal under CoreARM so this would + // indicate an issue). + _ASSERTE((target & 1) == 1); + target &= ~1; + + // Recursively call ourselves on this target in case we have any double jump thunks. + return GetRealCallTarget(target); +} + +// Determine (heuristically, basically a best effort guess) whether an address on the stack represents a +// return address. This is achieved by looking at the memory prior to the potential return address and +// disassembling it to see whether it looks like a potential call. If possible the target of the callsite is +// also returned. +// +// Result is returned in whereCalled: +// 0 : retAddr doesn't look like a return address +// 0xffffffff : retAddr looks like a return address but we couldn't tell where the call site was targeted +// <other> : retAddr looks like a return address, *whereCalled set to target address +void ARMMachine::IsReturnAddress(TADDR retAddr, TADDR* whereCalled) const +{ + *whereCalled = 0; + + // If retAddr doesn't have the low-order bit set (indicating a return to Thumb code) then it can't be a + // legal return address. + if ((retAddr & 1) == 0) + return; + retAddr &= ~1; + + // Potential calling instructions may have been one or two WORDs in length. + WORD rgPrevious[2]; + move_xp(rgPrevious, retAddr - sizeof(rgPrevious)); + + // Check two-word variants first. + if (((rgPrevious[0] & 0xf800) == 0xf000) && + ((rgPrevious[1] & 0xd000) == 0xd000)) + { + // BL <label> + + // Decode and validate PC-relative call target. Dereference through any jump thunks and return the + // call target. + TADDR target = GetRealCallTarget(DecodeCallTarget(retAddr - 4, rgPrevious)); + if (target) + { + *whereCalled = target; + return; + } + } + else if (((rgPrevious[0] & 0xf800) == 0xf000) && + ((rgPrevious[1] & 0xd001) == 0xc000)) + { + // BLX <label> + + // Decode and validate PC-relative call target. Dereference through any jump thunks and return the + // call target. + TADDR target = GetRealCallTarget(DecodeCallTarget(retAddr - 4, rgPrevious)); + if (target) + { + *whereCalled = target; + return; + } + } + else if (((rgPrevious[0] & 0xfff0) == 0xf8d0) && + ((rgPrevious[1] & 0xf000) == 0xf000)) + { + // LDR PC, [<reg> + #<imm>] + *whereCalled = 0xffffffff; + return; + } + else if (((rgPrevious[0] & 0xff7f) == 0xf85f) && + ((rgPrevious[1] & 0xf000) == 0xf000)) + { + // LDR PC, [PC + #<imm>] + *whereCalled = 0xffffffff; + return; + } + else if (((rgPrevious[0] & 0xfff0) == 0xf850) && + ((rgPrevious[1] & 0xffc0) == 0xf000)) + { + // LDR PC, [<reg> + <reg>, LSL #<imm>] + *whereCalled = 0xffffffff; + return; + } + + // Fall through any failures to decode as a two-word instruction to the one word cases below... + + // BLX <register> + if ((rgPrevious[1] & 0xff87) == 0x4780) + { + *whereCalled = 0xffffffff; + return; + } +} + + +// Return 0 for non-managed call. Otherwise return MD address. +static TADDR MDForCall (TADDR callee) +{ + // call managed code? + JITTypes jitType; + TADDR methodDesc; + TADDR PC = callee; + TADDR gcinfoAddr; + + PC = GetRealCallTarget(callee); + if (!PC) + return 0; + + IP2MethodDesc (PC, methodDesc, jitType, gcinfoAddr); + return methodDesc; +} + +// Determine if a value is MT/MD/Obj +static void HandleValue(TADDR value) +{ +#ifndef FEATURE_PAL + // remove the thumb bit (if set) + value = value & ~1; +#else + // set the thumb bit (if not set) + value = value | 1; +#endif //!FEATURE_PAL + + // A MethodTable? + if (IsMethodTable(value)) + { + NameForMT_s (value, g_mdName,mdNameLen); + ExtOut (" (MT: %S)", g_mdName); + return; + } + + // A Managed Object? + TADDR dwMTAddr; + move_xp (dwMTAddr, value); + if (IsStringObject(value)) + { + ExtOut (" (\""); + StringObjectContent (value, TRUE); + ExtOut ("\")"); + return; + } + else if (IsMethodTable(dwMTAddr)) + { + NameForMT_s (dwMTAddr, g_mdName,mdNameLen); + ExtOut (" (Object: %S)", g_mdName); + return; + } + + // A MethodDesc? + if (IsMethodDesc(value)) + { + NameForMD_s (value, g_mdName,mdNameLen); + ExtOut (" (MD: %S)", g_mdName); + return; + } + + // A JitHelper? + const char* name = HelperFuncName(value); + if (name) { + ExtOut (" (JitHelp: %s)", name); + return; + } + + // A call to managed code? + TADDR methodDesc = MDForCall(value); + if (methodDesc) + { + NameForMD_s (methodDesc, g_mdName,mdNameLen); + ExtOut (" (code for MD: %S)", g_mdName); + return; + } + + // Random symbol. + char Symbol[1024]; + if (SUCCEEDED(g_ExtSymbols->GetNameByOffset(TO_CDADDR(value), Symbol, 1024, + NULL, NULL))) + { + if (Symbol[0] != '\0') + { + ExtOut (" (%s)", Symbol); + return; + } + } +} + +/**********************************************************************\ +* Routine Description: * +* * +* Unassembly a managed code. Translating managed object, * +* call. * +* * +\**********************************************************************/ +void ARMMachine::Unassembly ( + TADDR PCBegin, + TADDR PCEnd, + TADDR PCAskedFor, + TADDR GCStressCodeCopy, + GCEncodingInfo *pGCEncodingInfo, + SOSEHInfo *pEHInfo, + BOOL bSuppressLines, + BOOL bDisplayOffsets) const +{ + ULONG_PTR PC = PCBegin; + char line[1024]; + char *ptr; + char *valueptr; + bool fLastWasMovW = false; + INT_PTR lowbits = 0; + ULONG curLine = -1; + WCHAR filename[MAX_LONGPATH]; + ULONG linenum; + + while (PC < PCEnd) + { + if (IsInterrupt()) + return; + + // Print out line numbers if needed + if (!bSuppressLines + && SUCCEEDED(GetLineByOffset(TO_CDADDR(PC), &linenum, filename, MAX_LONGPATH))) + { + if (linenum != curLine) + { + curLine = linenum; + ExtOut("\n%S @ %d:\n", filename, linenum); + } + } + +#ifndef FEATURE_PAL + // + // Print out any GC information corresponding to the current instruction offset. + // + if (pGCEncodingInfo) + { + SIZE_T curOffset = (PC - PCBegin) + pGCEncodingInfo->hotSizeToAdd; + while ( !pGCEncodingInfo->fDoneDecoding + && pGCEncodingInfo->ofs <= curOffset) + { + ExtOut(pGCEncodingInfo->buf); + ExtOut("\n"); + SwitchToFiber(pGCEncodingInfo->pvGCTableFiber); + } + } +#endif //!FEATURE_PAL + // + // Print out any EH info corresponding to the current offset + // + if (pEHInfo) + { + pEHInfo->FormatForDisassembly(PC - PCBegin); + } + + if ((PC & ~1) == (PCAskedFor & ~1)) + { + ExtOut (">>> "); + } + + // + // Print offsets, in addition to actual address. + // + if (bDisplayOffsets) + { + ExtOut("%04x ", PC - PCBegin); + } + + ULONG_PTR prevPC = PC; + DisasmAndClean (PC, line, _countof(line)); + + // look at the disassembled bytes + ptr = line; + NextTerm (ptr); + + // + // If there is gcstress info for this method, and this is a 'hlt' + // instruction, then gcstress probably put the 'hlt' there. Look + // up the original instruction and print it instead. + // + + + if ( GCStressCodeCopy + && ( !strncmp (ptr, "de00 ", 5) + || !strncmp (ptr, "de01 ", 5) + || !strncmp (ptr, "de02 ", 5) + || !strncmp (ptr, "f7f0a001", 8) + || !strncmp (ptr, "f7f0a002", 8) + || !strncmp (ptr, "f7f0a003", 8) + )) + { + ULONG_PTR InstrAddr = prevPC; + + // + // Compute address into saved copy of the code, and + // disassemble the original instruction + // + + ULONG_PTR OrigInstrAddr = GCStressCodeCopy + (InstrAddr - PCBegin); + ULONG_PTR OrigPC = OrigInstrAddr; + + DisasmAndClean(OrigPC, line, _countof(line)); + + // + // Increment the real PC based on the size of the unmodifed + // instruction + // + + PC = InstrAddr + (OrigPC - OrigInstrAddr); + + // + // Print out real code address in place of the copy address + // + + ExtOut("%08x ", (ULONG)InstrAddr); + + ptr = line; + NextTerm (ptr); + + // + // Print out everything after the code address, and skip the + // instruction bytes + // + + ExtOut(ptr); + + // + // Add an indicator that this address has not executed yet + // + + ExtOut(" (gcstress)"); + } + else + { + ExtOut (line); + } + + // Now advance to the opcode + NextTerm (ptr); + + if (!strncmp (ptr, "movw ", 5) || !strncmp (ptr, "mov ", 4)) + { + // Possibly the loading the low-order 16-bits of a 32-bit constant. Cache the value in case the + // next instruction is a movt with the high-order bits. + if ((valueptr = strchr(ptr, '#')) != NULL) + { + GetValueFromExpr(valueptr, lowbits); + fLastWasMovW = true; + } + } + else + { + if (!strncmp (ptr, "movt ", 5) && fLastWasMovW) + { + // A movt following a movw (if we were being really careful we'd check that the destination + // register was the same in both cases). Assemble the two 16-bit immediate values from both + // instructions and see if the resultant constant is interesting. + if ((valueptr = strchr(ptr, '#')) != NULL) + { + INT_PTR highbits; + GetValueFromExpr(valueptr, highbits); + HandleValue((highbits << 16) | lowbits); + } + } + else if ((valueptr = strchr(ptr, '=')) != NULL) + { + // Some instruction fetched a PC-relative constant which the disassembler nicely decoded for + // us using the ARM convention =<constant>. Retrieve this value and see if it's interesting. + INT_PTR value; + GetValueFromExpr(valueptr, value); + HandleValue(value); + } + + fLastWasMovW = false; + } + + ExtOut ("\n"); + } +} + +#if 0 // @ARMTODO: Figure out how to extract this information under CoreARM +static void ExpFuncStateInit (TADDR *PCRetAddr) +{ + ULONG64 offset; + if (FAILED(g_ExtSymbols->GetOffsetByName("ntdll!KiUserExceptionDispatcher", &offset))) { + return; + } + char line[256]; + int i = 0; + while (i < 3) { + g_ExtControl->Disassemble (offset, 0, line, 256, NULL, &offset); + if (strstr (line, "call")) { + PCRetAddr[i++] = (TADDR)offset; + } + } +} +#endif // 0 + + +// @ARMTODO: Figure out how to extract this information under CoreARM +BOOL ARMMachine::GetExceptionContext (TADDR stack, TADDR PC, TADDR *cxrAddr, CROSS_PLATFORM_CONTEXT * cxr, + TADDR * exrAddr, PEXCEPTION_RECORD exr) const +{ + return FALSE; +#if 0 // @ARMTODO: Figure out how to extract this information under CoreARM + static TADDR PCRetAddr[3] = {0,0,0}; + + if (PCRetAddr[0] == 0) { + ExpFuncStateInit (PCRetAddr); + } + *cxrAddr = 0; + *exrAddr = 0; + if (PC == PCRetAddr[0]) { + *exrAddr = stack + sizeof(TADDR); + *cxrAddr = stack + 2*sizeof(TADDR); + } + else if (PC == PCRetAddr[1]) { + *cxrAddr = stack + sizeof(TADDR); + } + else if (PC == PCRetAddr[2]) { + *exrAddr = stack + sizeof(TADDR); + *cxrAddr = stack + 2*sizeof(TADDR); + } + else + return FALSE; + + if (FAILED (g_ExtData->ReadVirtual(TO_CDADDR(*cxrAddr), &stack, sizeof(stack), NULL))) + return FALSE; + *cxrAddr = stack; + + if (FAILED (g_ExtData->ReadVirtual(TO_CDADDR(stack), cxr, sizeof(DT_CONTEXT), NULL))) { + return FALSE; + } + + if (*exrAddr) { + if (FAILED (g_ExtData->ReadVirtual(TO_CDADDR(*exrAddr), &stack, sizeof(stack), NULL))) + { + *exrAddr = 0; + return TRUE; + } + *exrAddr = stack; + size_t erSize = offsetof (EXCEPTION_RECORD, ExceptionInformation); + if (FAILED (g_ExtData->ReadVirtual(TO_CDADDR(stack), exr, erSize, NULL))) { + *exrAddr = 0; + return TRUE; + } + } + return TRUE; +#endif // 0 +} + + +/// +/// Dump ARM GCInfo table +/// +void ARMMachine::DumpGCInfo(GCInfoToken gcInfoToken, unsigned methodSize, printfFtn gcPrintf, bool encBytes, bool bPrintHeader) const +{ +#ifndef FEATURE_PAL + if (bPrintHeader) + { + ExtOut("Pointer table:\n"); + } + + ARMGCDump::GCDump gcDump(gcInfoToken.Version, encBytes, 5, true); + gcDump.gcPrintf = gcPrintf; + + gcDump.DumpGCTable(dac_cast<PTR_BYTE>(gcInfoToken.Info), methodSize, 0); +#endif // !FEATURE_PAL +} + +#endif // SOS_TARGET_ARM diff --git a/src/ToolBox/SOS/Strike/disasmARM64.cpp b/src/ToolBox/SOS/Strike/disasmARM64.cpp new file mode 100644 index 0000000000..6a19fc9377 --- /dev/null +++ b/src/ToolBox/SOS/Strike/disasmARM64.cpp @@ -0,0 +1,392 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ==++== +// + +// +// ==--== + +#ifndef _TARGET_ARM64_ +#define _TARGET_ARM64_ +#endif + +#ifdef _TARGET_AMD64_ +#undef _TARGET_AMD64_ +#endif + +#include "strike.h" +#include "util.h" +#include <dbghelp.h> + + +#include "disasm.h" + +#include "../../../inc/corhdr.h" +#include "../../../inc/cor.h" +#include "../../../inc/dacprivate.h" + +namespace ARM64GCDump +{ +#undef _TARGET_X86_ +#undef LIMITED_METHOD_CONTRACT +#define LIMITED_METHOD_DAC_CONTRACT +#define SUPPORTS_DAC +#define LF_GCROOTS +#define LL_INFO1000 +#define LOG(x) +#define LOG_PIPTR(pObjRef, gcFlags, hCallBack) +#define DAC_ARG(x) +#include "gcdumpnonx86.cpp" +} + +#ifdef FEATURE_PAL +void SwitchToFiber(void*) +{ + // TODO: Fix for linux + assert(false); +} +#endif + +#if !defined(_TARGET_WIN64_) +#error This file only supports SOS targeting ARM64 from a 64-bit debugger +#endif + +#if !defined(SOS_TARGET_ARM64) +#error This file should be used to support SOS targeting ARM64 debuggees +#endif + + +void ARM64Machine::IsReturnAddress(TADDR retAddr, TADDR* whereCalled) const +{ + *whereCalled = 0; + + DWORD previousInstr; + move_xp(previousInstr, retAddr - sizeof(previousInstr)); + + // ARM64TODO: needs to be implemented for jump stubs for ngen case + + if ((previousInstr & 0xfffffc1f) == 0xd63f0000) + { + // BLR <reg> + *whereCalled = 0xffffffff; + } + else if ((previousInstr & 0xfc000000) == 0x94000000) + { + // BL <label> + DWORD imm26 = previousInstr & 0x03ffffff; + // offset = SignExtend(imm26:'00', 64); + INT64 offset = ((INT64)imm26 << 38) >> 36; + *whereCalled = retAddr - 4 + offset; + } +} + +// Determine if a value is MT/MD/Obj +static void HandleValue(TADDR value) +{ + // A MethodTable? + if (IsMethodTable(value)) + { + NameForMT_s (value, g_mdName,mdNameLen); + ExtOut (" (MT: %S)", g_mdName); + return; + } + + // A Managed Object? + TADDR dwMTAddr; + move_xp (dwMTAddr, value); + if (IsStringObject(value)) + { + ExtOut (" (\""); + StringObjectContent (value, TRUE); + ExtOut ("\")"); + return; + } + else if (IsMethodTable(dwMTAddr)) + { + NameForMT_s (dwMTAddr, g_mdName,mdNameLen); + ExtOut (" (Object: %S)", g_mdName); + return; + } + + // A MethodDesc? + if (IsMethodDesc(value)) + { + NameForMD_s (value, g_mdName,mdNameLen); + ExtOut (" (MD: %S)", g_mdName); + return; + } + + // A JitHelper? + const char* name = HelperFuncName(value); + if (name) { + ExtOut (" (JitHelp: %s)", name); + return; + } + + // A call to managed code? + // ARM64TODO: not (yet) implemented. perhaps we don't need it at all. + + // Random symbol. + char Symbol[1024]; + if (SUCCEEDED(g_ExtSymbols->GetNameByOffset(TO_CDADDR(value), Symbol, 1024, + NULL, NULL))) + { + if (Symbol[0] != '\0') + { + ExtOut (" (%s)", Symbol); + return; + } + } + +} + +/**********************************************************************\ +* Routine Description: * +* * +* Unassembly a managed code. Translating managed object, * +* call. * +* * +\**********************************************************************/ +void ARM64Machine::Unassembly ( + TADDR PCBegin, + TADDR PCEnd, + TADDR PCAskedFor, + TADDR GCStressCodeCopy, + GCEncodingInfo *pGCEncodingInfo, + SOSEHInfo *pEHInfo, + BOOL bSuppressLines, + BOOL bDisplayOffsets) const +{ + TADDR PC = PCBegin; + char line[1024]; + ULONG lineNum; + ULONG curLine = -1; + WCHAR fileName[MAX_LONGPATH]; + char *ptr; + INT_PTR accumulatedConstant = 0; + BOOL loBitsSet = FALSE; + BOOL hiBitsSet = FALSE; + char *szConstant = NULL; + + + while(PC < PCEnd) + { + ULONG_PTR currentPC = PC; + DisasmAndClean (PC, line, _countof(line)); + + // This is the closing of the previous run. + // Check the next instruction. if it's not a the last movk, handle the accumulated value + // else simply print a new line. + if (loBitsSet && hiBitsSet) + { + ptr = line; + // Advance to the instruction encoding + NextTerm(ptr); + // Advance to the opcode + NextTerm(ptr); + // if it's not movk, handle the accumulated value + // otherwise simply print the new line. The constant in this expression will be + // accumulated below. + if (strncmp(ptr, "movk ", 5)) + { + HandleValue(accumulatedConstant); + accumulatedConstant = 0; + } + ExtOut ("\n"); + } + else if (currentPC != PCBegin) + { + ExtOut ("\n"); + } + + // This is the new instruction + + if (IsInterrupt()) + return; + // + // Print out line numbers if needed + // + if (!bSuppressLines && + SUCCEEDED(GetLineByOffset(TO_CDADDR(currentPC), &lineNum, fileName, MAX_LONGPATH))) + { + if (lineNum != curLine) + { + curLine = lineNum; + ExtOut("\n%S @ %d:\n", fileName, lineNum); + } + } + + // + // Print out any GC information corresponding to the current instruction offset. + // + if (pGCEncodingInfo) + { + SIZE_T curOffset = (currentPC - PCBegin) + pGCEncodingInfo->hotSizeToAdd; + while ( !pGCEncodingInfo->fDoneDecoding + && pGCEncodingInfo->ofs <= curOffset) + { + ExtOut(pGCEncodingInfo->buf); + ExtOut("\n"); + SwitchToFiber(pGCEncodingInfo->pvGCTableFiber); + } + } + + // + // Print out any EH info corresponding to the current offset + // + if (pEHInfo) + { + pEHInfo->FormatForDisassembly(currentPC - PCBegin); + } + + if (currentPC == PCAskedFor) + { + ExtOut (">>> "); + } + + // + // Print offsets, in addition to actual address. + // + if (bDisplayOffsets) + { + ExtOut("%04x ", currentPC - PCBegin); + } + + // look at the disassembled bytes + ptr = line; + NextTerm (ptr); + + // + // If there is gcstress info for this method, and this is a 'hlt' + // instruction, then gcstress probably put the 'hlt' there. Look + // up the original instruction and print it instead. + // + + + if ( GCStressCodeCopy + && ( !strncmp (ptr, "badc0de0", 8) + || !strncmp (ptr, "badc0de1", 8) + || !strncmp (ptr, "badc0de2", 8) + )) + { + ULONG_PTR InstrAddr = currentPC; + + // + // Compute address into saved copy of the code, and + // disassemble the original instruction + // + + ULONG_PTR OrigInstrAddr = GCStressCodeCopy + (InstrAddr - PCBegin); + ULONG_PTR OrigPC = OrigInstrAddr; + + DisasmAndClean(OrigPC, line, _countof(line)); + + // + // Increment the real PC based on the size of the unmodifed + // instruction + // + + PC = InstrAddr + (OrigPC - OrigInstrAddr); + + // + // Print out real code address in place of the copy address + // + + ExtOut("%08x`%08x ", (ULONG)(InstrAddr >> 32), (ULONG)InstrAddr); + + ptr = line; + NextTerm (ptr); + + // + // Print out everything after the code address, and skip the + // instruction bytes + // + + ExtOut(ptr); + + // + // Add an indicator that this address has not executed yet + // + + ExtOut(" (gcstress)"); + } + else + { + ExtOut (line); + } + + // Now advance to the opcode + NextTerm (ptr); + + if (!strncmp(ptr, "mov ", 4)) + { + if ((szConstant = strchr(ptr, '#')) != NULL) + { + GetValueFromExpr(szConstant, accumulatedConstant); + loBitsSet = TRUE; + } + } + else if (!strncmp(ptr, "movk ", 5)) + { + char *szShiftAmount = NULL; + INT_PTR shiftAmount = 0; + INT_PTR constant = 0; + if (((szShiftAmount = strrchr(ptr, '#')) != NULL) && + ((szConstant = strchr(ptr, '#')) != NULL) && + (szShiftAmount != szConstant) && + (accumulatedConstant > 0)) // Misses when movk is succeeding mov reg, #0x0, which I don't think makes any sense + { + GetValueFromExpr(szShiftAmount, shiftAmount); + GetValueFromExpr(szConstant, constant); + accumulatedConstant += (constant<<shiftAmount); + hiBitsSet = TRUE; + } + } + else + { + accumulatedConstant = 0; + loBitsSet = hiBitsSet = FALSE; + if ((szConstant = strchr(ptr, '=')) != NULL) + { + // Some instruction fetched a PC-relative constant which the disassembler nicely decoded for + // us using the ARM convention =<constant>. Retrieve this value and see if it's interesting. + INT_PTR value; + GetValueFromExpr(szConstant, value); + HandleValue(value); + } + + + // ARM64TODO: we could possibly handle adr(p)/ldr pair too. + } + + } + ExtOut ("\n"); +} + + +// @ARMTODO: Figure out how to extract this information under CoreARM +BOOL ARM64Machine::GetExceptionContext (TADDR stack, TADDR PC, TADDR *cxrAddr, CROSS_PLATFORM_CONTEXT * cxr, + TADDR * exrAddr, PEXCEPTION_RECORD exr) const +{ + _ASSERTE("ARM64:NYI"); + return FALSE; +} + +/// +/// Dump ARM GCInfo table +/// +void ARM64Machine::DumpGCInfo(GCInfoToken gcInfoToken, unsigned methodSize, printfFtn gcPrintf, bool encBytes, bool bPrintHeader) const +{ + if (bPrintHeader) + { + ExtOut("Pointer table:\n"); + } + + ARM64GCDump::GCDump gcDump(gcInfoToken.Version, encBytes, 5, true); + gcDump.gcPrintf = gcPrintf; + + gcDump.DumpGCTable(dac_cast<PTR_BYTE>(gcInfoToken.Info), methodSize, 0); +} + diff --git a/src/ToolBox/SOS/Strike/disasmX86.cpp b/src/ToolBox/SOS/Strike/disasmX86.cpp new file mode 100644 index 0000000000..36a08d20a3 --- /dev/null +++ b/src/ToolBox/SOS/Strike/disasmX86.cpp @@ -0,0 +1,1707 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ==++== +// + +// +// ==--== +#include "strike.h" +#include "util.h" +#include "disasm.h" +#include <dbghelp.h> + +#include "../../../inc/corhdr.h" +#include "../../../inc/cor.h" +#include "../../../inc/dacprivate.h" + + +#if defined(SOS_TARGET_X86) && defined(SOS_TARGET_AMD64) +#error This file does not support SOS targeting both X86 and AMD64 debuggees +#endif + +#if !defined(SOS_TARGET_X86) && !defined(SOS_TARGET_AMD64) +#error This file should be used to support SOS targeting either X86 or AMD64 debuggees +#endif + + +// These must be in the same order as they are used in the instruction +// encodings/same as the CONTEXT field order. +enum RegIndex +{ + EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI, + +#ifdef _TARGET_AMD64_ + R8, R9, R10, R11, R12, R13, R14, R15, +#endif // _TARGET_AMD64_ + + EIP, NONE +}; + +const int NumReg = NONE; +struct Register +{ + TADDR value; + BOOL bValid; + TADDR stack; + BOOL bOnStack; +}; + +// Find the index for a register name +inline RegIndex FindReg (___in __in_z char *ptr, __out_opt int *plen = NULL, __out_opt int *psize = NULL) +{ + struct RegName + { + RegIndex index; + PCSTR pszName; + int cchName; + int size; + }; + + static RegName rgRegNames[] = { + +#define REG(index, reg, size) { index, #reg, sizeof(#reg)-1, size } +#define REG8(index, reg) REG(index, reg, 1) +#define REG16(index, reg) REG(index, reg, 2) +#define REG32(index, reg) REG(index, reg, 4) +#define REG64(index, reg) REG(index, reg, 8) + + REG8(EAX, al), + REG8(EAX, ah), + REG8(EBX, bl), + REG8(EBX, bh), + REG8(ECX, cl), + REG8(ECX, ch), + REG8(EDX, dl), + REG8(EDX, dh), + + REG16(EAX, ax), + REG16(EBX, bx), + REG16(ECX, cx), + REG16(EDX, dx), + REG16(ESI, si), + REG16(EDI, di), + REG16(EBP, bp), + REG16(ESP, sp), + + REG32(EAX, eax), + REG32(EBX, ebx), + REG32(ECX, ecx), + REG32(EDX, edx), + REG32(ESI, esi), + REG32(EDI, edi), + REG32(EBP, ebp), + REG32(ESP, esp), + +#ifdef _TARGET_AMD64_ + + REG8(R8, r8b), + REG8(R9, r9b), + REG8(R10, r10b), + REG8(R11, r11b), + REG8(R12, r12b), + REG8(R13, r13b), + REG8(R14, r14b), + REG8(R15, r15b), + + REG16(R8, r8w), + REG16(R9, r9w), + REG16(R10, r10w), + REG16(R11, r11w), + REG16(R12, r12w), + REG16(R13, r13w), + REG16(R14, r14w), + REG16(R15, r15w), + + REG32(R8, r8d), + REG32(R9, r9d), + REG32(R10, r10d), + REG32(R11, r11d), + REG32(R12, r12d), + REG32(R13, r13d), + REG32(R14, r14d), + REG32(R15, r15d), + + REG64(EAX, rax), + REG64(EBX, rbx), + REG64(ECX, rcx), + REG64(EDX, rdx), + REG64(ESI, rsi), + REG64(EDI, rdi), + REG64(EBP, rbp), + REG64(ESP, rsp), + REG64(R8, r8), + REG64(R9, r9), + REG64(R10, r10), + REG64(R11, r11), + REG64(R12, r12), + REG64(R13, r13), + REG64(R14, r14), + REG64(R15, r15), + +#endif // _TARGET_AMD64_ + +#undef REG +#undef REG8 +#undef REG16 +#undef REG32 +#undef REG64 + + }; + + for (size_t i = 0; i < sizeof(rgRegNames)/sizeof(rgRegNames[0]); i++) + { + if (!strncmp(ptr, rgRegNames[i].pszName, rgRegNames[i].cchName)) + { + if (psize) + *psize = rgRegNames[i].size; + + if (plen) + *plen = rgRegNames[i].cchName; + + return rgRegNames[i].index; + } + } + + return NONE; +} + +// Find the value of an expression. +inline BOOL FindSrc (__in_z char *ptr, ___in Register *reg, INT_PTR &value, BOOL &bDigit) +{ + if (GetValueFromExpr (ptr, value)) + { + bDigit = TRUE; + return TRUE; + } + + BOOL bValid = FALSE; + BOOL bByRef = IsByRef (ptr); + bDigit = FALSE; + + int regnamelen; + RegIndex index = FindReg (ptr, ®namelen); + if (index != NONE) + { + if (reg[index].bValid) + { + value = reg[index].value; + ptr += regnamelen; + // TODO: consider ecx+edi*4+0x4 + if ((IsTermSep (ptr[0]) && !bByRef) + || (ptr[0] == ']' && bByRef)) + { + bValid = TRUE; + if (bByRef) + SafeReadMemory (TO_TADDR(value), &value, sizeof(void*), NULL); + } + } + } + return bValid; +} + +enum ADDRESSMODE {REG, DATA, INDIRECT, NODATA, BAD}; + +struct RegState +{ + RegIndex reg; + BOOL bFullReg; + char scale; + int namelen; +}; + +struct InstData +{ + ADDRESSMODE mode; + RegState reg[2]; + INT_PTR value; +}; + +void FindMainReg (___in __in_z char *ptr, RegState ®) +{ + int size = 0; + + reg.reg = FindReg(ptr, ®.namelen, &size); + + reg.bFullReg = (reg.reg!=NONE && sizeof(void*)==size) ? TRUE : FALSE; +} + +static void DecodeAddressIndirect (___in __in_z char *term, InstData& arg) +{ + arg.mode = BAD; + arg.value = 0; + arg.reg[0].scale = 0; + arg.reg[1].scale = 0; + + if (!IsByRef (term)) + { + return; + } + + // first part must be a reg + arg.reg[0].scale = 1; + if (term[0] == '+') + term ++; + else if (term[0] == '-') + { + term ++; + arg.reg[0].scale = -1; + } + if (isdigit(term[0])) + { + arg.reg[0].scale *= term[0]-'0'; + term ++; + } + + FindMainReg (term, arg.reg[0]); + if (arg.reg[0].reg == NONE) + return; + term += arg.reg[0].namelen; + + if (term[0] == ']') + { + // It is [reg] + arg.mode = INDIRECT; + arg.value = 0; + return; + } + + char sign = (char)((term[0] == '+')?1:-1); + term ++; + FindMainReg (term, arg.reg[1]); + if (arg.reg[1].reg != NONE) + { + // It is either [reg+reg*c] or [reg+reg*c+c] + + term += arg.reg[1].namelen; + + if (term[0] == '*') + { + term ++; + arg.reg[1].scale = sign*(term[0]-'0'); + term ++; + } + else + arg.reg[1].scale = sign; + + if (term[0] == ']') + { + // It is [reg+reg*c] + arg.mode = INDIRECT; + arg.value = 0; + return; + } + sign = (char)((term[0] == '+')?1:-1); + term ++; + } + + char *endptr; + arg.value = strtoul(term, &endptr, 16); + if (endptr[0] == ']') + { + // It is [reg+reg*c+c] + arg.value *= sign; + arg.mode = INDIRECT; + } +} + +void DecodeAddressTerm (___in __in_z char *term, InstData& arg) +{ + arg.mode = BAD; + arg.reg[0].scale = 0; + arg.reg[1].scale = 0; + arg.value = 0; + INT_PTR value; + + if (GetValueFromExpr (term, value)) + { + arg.value = value; + arg.mode = DATA; + } + else + { + FindMainReg (term, arg.reg[0]); + if (arg.reg[0].reg != NONE) + { + arg.mode = REG; + } + else + { + DecodeAddressIndirect (term, arg); + } + } +} + +// Return 0 for non-managed call. Otherwise return MD address. +TADDR MDForCall (TADDR callee) +{ + // call managed code? + JITTypes jitType; + TADDR methodDesc; + TADDR IP = callee; + TADDR gcinfoAddr; + + if (!GetCalleeSite (callee, IP)) + return 0; + + IP2MethodDesc (IP, methodDesc, jitType, gcinfoAddr); + if (methodDesc) + { + return methodDesc; + } + + // jmp stub + char line[256]; + DisasmAndClean (IP, line, 256); + char *ptr = line; + NextTerm (ptr); + NextTerm (ptr); + if (!strncmp (ptr, "jmp ", 4)) + { + // jump thunk + NextTerm (ptr); + INT_PTR value; + methodDesc = 0; + if (GetValueFromExpr (ptr, value)) + { + IP2MethodDesc (value, methodDesc, jitType, gcinfoAddr); + } + return methodDesc; + } + return 0; +} + +// Handle a call instruction. +void HandleCall(TADDR callee, Register *reg) +{ + // call managed code? + TADDR methodDesc = MDForCall (callee); + if (methodDesc) + { + DacpMethodDescData MethodDescData; + if (MethodDescData.Request(g_sos, TO_CDADDR(methodDesc)) == S_OK) + { + NameForMD_s(methodDesc, g_mdName,mdNameLen); + ExtOut(" (%S, mdToken: %p)", g_mdName, SOS_PTR(MethodDescData.MDToken)); + return; + } + } + +#ifdef _TARGET_AMD64_ + // A jump thunk? + + CONTEXT ctx = {0}; + + ctx.ContextFlags = (CONTEXT_AMD64 | CONTEXT_CONTROL | CONTEXT_INTEGER); + + for (unsigned ireg = 0; ireg < 16; ireg++) + { + if (reg[ireg].bValid) + { + *(&ctx.Rax + ireg) = reg[ireg].value; + } + } + + ctx.Rip = callee; + + CLRDATA_ADDRESS ip = 0, md = 0; + if (S_OK == g_sos->GetJumpThunkTarget(&ctx, &ip, &md)) + { + if (md) + { + DacpMethodDescData MethodDescData; + if (MethodDescData.Request(g_sos, md) == S_OK) + { + NameForMD_s(md, g_mdName,mdNameLen); + ExtOut(" (%S, mdToken: %p)", g_mdName, SOS_PTR(MethodDescData.MDToken)); + return; + } + } + + if (ip != callee) + { + return HandleCall(ip, reg); + } + } +#endif // _TARGET_AMD64_ + + // A JitHelper? + const char* name = HelperFuncName(callee); + if (name) { + ExtOut (" (JitHelp: %s)", name); + return; + } + + // call unmanaged code? + char Symbol[1024]; + if (SUCCEEDED(g_ExtSymbols->GetNameByOffset(TO_CDADDR(callee), Symbol, 1024, + NULL, NULL))) + { + if (Symbol[0] != '\0') + { + ExtOut (" (%s)", Symbol); + return; + } + } +} + +// Determine if a value is MT/MD/Obj +void HandleValue(TADDR value) +{ + // A MethodTable? + if (IsMethodTable(value)) + { + NameForMT_s (value, g_mdName,mdNameLen); + ExtOut (" (MT: %S)", g_mdName); + return; + } + + // A Managed Object? + TADDR dwMTAddr; + move_xp (dwMTAddr, value); + if (IsStringObject(value)) + { + ExtOut (" (\""); + StringObjectContent (value, TRUE); + ExtOut ("\")"); + return; + } + else if (IsMethodTable(dwMTAddr)) + { + NameForMT_s (dwMTAddr, g_mdName,mdNameLen); + ExtOut (" (Object: %S)", g_mdName); + return; + } + + // A MethodDesc? + if (IsMethodDesc(value)) + { + NameForMD_s (value, g_mdName,mdNameLen); + ExtOut (" (MD: %S)", g_mdName); + return; + } + + // A JitHelper? + const char* name = HelperFuncName(value); + if (name) { + ExtOut (" (JitHelp: %s)", name); + return; + } +} + +/**********************************************************************\ +* Routine Description: * +* * +* Unassembly a managed code. Translating managed object, * +* call. * +* * +\**********************************************************************/ +void +#ifdef _TARGET_X86_ + X86Machine::Unassembly +#elif defined(_TARGET_AMD64_) + AMD64Machine::Unassembly +#endif + (TADDR IPBegin, + TADDR IPEnd, + TADDR IPAskedFor, + TADDR GCStressCodeCopy, + GCEncodingInfo *pGCEncodingInfo, + SOSEHInfo *pEHInfo, + BOOL bSuppressLines, + BOOL bDisplayOffsets) const +{ + ULONG_PTR IP = IPBegin; + char line[1024]; + Register reg [NumReg]; + ZeroMemory (reg, sizeof(reg)); + RegIndex dest; + INT_PTR value; + BOOL bDigit; + char *ptr; + + ULONG curLine = -1; + WCHAR filename[MAX_LONGPATH]; + ULONG linenum; + + while (IP < IPEnd) + { + if (IsInterrupt()) + return; + + // Print out line numbers if needed + if (!bSuppressLines + && SUCCEEDED(GetLineByOffset(TO_CDADDR(IP), &linenum, filename, MAX_LONGPATH))) + { + if (linenum != curLine) + { + curLine = linenum; + ExtOut("\n%S @ %d:\n", filename, linenum); + } + } + + // + // Print out any GC information corresponding to the current instruction offset. + // + +#ifndef FEATURE_PAL + if (pGCEncodingInfo) + { + SIZE_T curOffset = (IP - IPBegin) + pGCEncodingInfo->hotSizeToAdd; + while ( !pGCEncodingInfo->fDoneDecoding + && pGCEncodingInfo->ofs <= curOffset) + { + ExtOut(pGCEncodingInfo->buf); + ExtOut("\n"); + SwitchToFiber(pGCEncodingInfo->pvGCTableFiber); + } + } +#endif // FEATURE_PAL + + ULONG_PTR InstrAddr = IP; + + // + // Print out any EH info corresponding to the current offset + // + if (pEHInfo) + { + pEHInfo->FormatForDisassembly(IP - IPBegin); + } + + if (IP == IPAskedFor) + { + ExtOut (">>> "); + } + + // + // Print offsets, in addition to actual address. + // + if (bDisplayOffsets) + { + ExtOut("%04x ", IP - IPBegin); + } + + DisasmAndClean (IP, line, _countof(line)); + + // look at key word + ptr = line; + NextTerm (ptr); + NextTerm (ptr); + + // + // If there is gcstress info for this method, and this is a 'hlt' + // instruction, then gcstress probably put the 'hlt' there. Look + // up the original instruction and print it instead. + // + + SSIZE_T cbIPOffset = 0; + + if ( GCStressCodeCopy + && ( !strncmp (ptr, "hlt", 3) + || !strncmp (ptr, "cli", 3) + || !strncmp (ptr, "sti", 3))) + { + // + // Compute address into saved copy of the code, and + // disassemble the original instruction + // + + ULONG_PTR OrigInstrAddr = GCStressCodeCopy + (InstrAddr - IPBegin); + ULONG_PTR OrigIP = OrigInstrAddr; + + DisasmAndClean(OrigIP, line, _countof(line)); + + // + // Increment the real IP based on the size of the unmodifed + // instruction + // + + IP = InstrAddr + (OrigIP - OrigInstrAddr); + + cbIPOffset = IP - OrigIP; + + // + // Print out real code address in place of the copy address + // + +#ifdef _WIN64 + ExtOut("%08x`%08x ", (ULONG)(InstrAddr >> 32), (ULONG)InstrAddr); +#else + ExtOut("%08x ", (ULONG)InstrAddr); +#endif + + ptr = line; + NextTerm (ptr); + + // + // Print out everything after the code address, and skip the + // instruction bytes + // + + ExtOut(ptr); + + NextTerm (ptr); + + // + // Add an indicator that this address has not executed yet + // + + ExtOut(" (gcstress)"); + } + else + { + ExtOut (line); + } + + if (!strncmp (ptr, "mov ", 4)) + { + NextTerm (ptr); + + dest = FindReg(ptr); + if (dest != NONE) + { + NextTerm (ptr); + + if (FindSrc (ptr, reg, value, bDigit)) + { + reg[dest].bValid = TRUE; + reg[dest].value = value; + // Is it a managed obj + if (bDigit) + HandleValue (reg[dest].value); + } + else + { + reg[dest].bValid = FALSE; + } + } + } + else if (!strncmp (ptr, "call ", 5)) + { + NextTerm (ptr); + if (FindSrc (ptr, reg, value, bDigit)) + { + if (bDigit) + value += cbIPOffset; + + HandleCall (value, reg); + } + + // trash EAX, ECX, EDX + reg[EAX].bValid = FALSE; + reg[ECX].bValid = FALSE; + reg[EDX].bValid = FALSE; + +#ifdef _TARGET_AMD64_ + reg[R8].bValid = FALSE; + reg[R9].bValid = FALSE; + reg[R10].bValid = FALSE; + reg[R11].bValid = FALSE; +#endif // _TARGET_AMD64_ + } + else if (!strncmp (ptr, "lea ", 4)) + { + NextTerm (ptr); + dest = FindReg(ptr); + if (dest != NONE) + { + NextTerm (ptr); + if (FindSrc (ptr, reg, value, bDigit)) + { + reg[dest].bValid = TRUE; + reg[dest].value = value; + } + else + { + reg[dest].bValid = FALSE; + } + } + } + else if (!strncmp (ptr, "push ", 5)) + { + // do not do anything + NextTerm (ptr); + if (FindSrc (ptr, reg, value, bDigit)) + { + if (bDigit) + { + HandleValue (value); + } + } + } + else + { + // assume this instruction will trash dest reg + NextTerm (ptr); + dest = FindReg(ptr); + if (dest != NONE) + reg[dest].bValid = FALSE; + } + ExtOut ("\n"); + } + + // + // Print out any "end" EH info (where the end address is the byte immediately following the last instruction) + // + if (pEHInfo) + { + pEHInfo->FormatForDisassembly(IP - IPBegin); + } +} + +// Find the real callee site. Handle JMP instruction. +// Return TRUE if we get the address, FALSE if not. +BOOL GetCalleeSite (TADDR IP, TADDR &IPCallee) +{ + while (TRUE) { + unsigned char inst[2]; + if (g_ExtData->ReadVirtual(TO_CDADDR(IP), inst, sizeof(inst), NULL) != S_OK) + { + return FALSE; + } + if (inst[0] == 0xEB) { + IP += 2+(char)inst[1]; + } + else if (inst[0] == 0xE9) { + int displace; + if (g_ExtData->ReadVirtual(TO_CDADDR(IP+1), &displace, sizeof(displace), NULL) != S_OK) + { + return FALSE; + } + else + { + IP += 5+displace; + } + } + else if (inst[0] == 0xFF && (inst[1] & 070) == 040) { + if (inst[1] == 0x25) { + DWORD displace; + if (g_ExtData->ReadVirtual(TO_CDADDR(IP+2), &displace, sizeof(displace), NULL) != S_OK) + { + return FALSE; + } + if (g_ExtData->ReadVirtual(TO_CDADDR(displace), &displace, sizeof(displace), NULL) != S_OK) + { + return FALSE; + } + else + { + IP = displace; + } + } + else + // Target for jmp is determined from register values. + return FALSE; + } + else + { + IPCallee = IP; + return TRUE; + } + } +} + +// GetFinalTarget is based on HandleCall, but avoids printing anything to the output. +// This is currently only called on x64 +eTargetType GetFinalTarget(TADDR callee, TADDR* finalMDorIP) +{ + // call managed code? + TADDR methodDesc = MDForCall (callee); + if (methodDesc) + { + DacpMethodDescData MethodDescData; + if (MethodDescData.Request(g_sos, TO_CDADDR(methodDesc)) == S_OK) + { + *finalMDorIP = methodDesc; + return ettMD; + } + } + +#ifdef _TARGET_AMD64_ + // A jump thunk? + + CONTEXT ctx = {0}; + ctx.ContextFlags = (CONTEXT_AMD64 | CONTEXT_CONTROL | CONTEXT_INTEGER); + ctx.Rip = callee; + + CLRDATA_ADDRESS ip = 0, md = 0; + if (S_OK == g_sos->GetJumpThunkTarget(&ctx, &ip, &md)) + { + if (md) + { + DacpMethodDescData MethodDescData; + if (MethodDescData.Request(g_sos, md) == S_OK) + { + *finalMDorIP = md; + return ettStub; + } + } + + if (ip != callee) + { + return GetFinalTarget(ip, finalMDorIP); + } + } +#endif // _TARGET_AMD64_ + + // A JitHelper? + const char* name = HelperFuncName(callee); + if (name) { + *finalMDorIP = callee; + return ettJitHelp; + } + + // call unmanaged code? + *finalMDorIP = callee; + return ettNative; +} + +#ifndef FEATURE_PAL + +void ExpFuncStateInit (TADDR *IPRetAddr) +{ + ULONG64 offset; + if (FAILED(g_ExtSymbols->GetOffsetByName("ntdll!KiUserExceptionDispatcher", &offset))) { + return; + } + + // test if we have a minidump for which the image is not cached anymore. this avoids + // the having the while loop below spin forever (or a very long time)... + // (Watson backend hit this a few times, and they had to institute a timeout policy + // to work around this) + SIZE_T instrs; + if (FAILED(g_ExtData->ReadVirtual(offset, &instrs, sizeof(instrs), NULL)) || instrs == 0) { + return; + } + + char line[256]; + int i = 0; + int cnt = 0; +#ifdef SOS_TARGET_X86 + // On x86 and x64 the last 3 "call" instructions in ntdll!KiUserExceptionDispatcher + // are making calls to OS APIs that take as argument the context record (and some + // of them the exception record as well) + const int cCallInstrs = 3; +#elif defined(SOS_TARGET_AMD64) + // On x64 the first "call" instruction should be considered, as well + const int cCallInstrs = 4; +#endif + + while (i < cCallInstrs) { + g_ExtControl->Disassemble (offset, 0, line, 256, NULL, &offset); + if (strstr (line, "call")) { + IPRetAddr[i++] = (TADDR)offset; + } + // if we didn't find at least one "call" in the first 500 instructions give up... + if (++cnt >= 500 && IPRetAddr[0] == 0) + break; + } +} + +#endif // FEATURE_PAL + + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to fill in a cross platform context * +* struct by looking on the stack for return addresses into * +* KiUserExceptionDispatcher * +* * +\**********************************************************************/ +BOOL +#ifdef SOS_TARGET_X86 + X86Machine::GetExceptionContext +#elif defined(SOS_TARGET_AMD64) + AMD64Machine::GetExceptionContext +#endif + (TADDR stack, + TADDR IP, + TADDR * cxrAddr, + CROSS_PLATFORM_CONTEXT * pcxr, + TADDR * exrAddr, + PEXCEPTION_RECORD exr) const +{ +#ifndef FEATURE_PAL +#ifdef SOS_TARGET_X86 + X86_CONTEXT * cxr = &pcxr->X86Context; + size_t contextSize = offsetof(CONTEXT, ExtendedRegisters); +#elif defined(SOS_TARGET_AMD64) + AMD64_CONTEXT * cxr = &pcxr->Amd64Context; + size_t contextSize = offsetof(CONTEXT, FltSave); +#endif + + static TADDR IPRetAddr[4] = {0, 0, 0, 0}; + + if (IPRetAddr[0] == 0) { + ExpFuncStateInit (IPRetAddr); + } + *cxrAddr = 0; + *exrAddr = 0; + +#ifdef SOS_TARGET_X86 + + if (IP == IPRetAddr[0]) { + *exrAddr = stack + sizeof(TADDR); + *cxrAddr = stack + 2*sizeof(TADDR); + } + else if (IP == IPRetAddr[1]) { + *cxrAddr = stack + sizeof(TADDR); + } + else if (IP == IPRetAddr[2]) { + *exrAddr = stack + sizeof(TADDR); + *cxrAddr = stack + 2*sizeof(TADDR); + } + else + return FALSE; + + if (FAILED (g_ExtData->ReadVirtual(TO_CDADDR(*cxrAddr), &stack, sizeof(stack), NULL))) + return FALSE; + *cxrAddr = stack; + + //if ((pContext->ContextFlags & CONTEXT_EXTENDED_REGISTERS) == CONTEXT_EXTENDED_REGISTERS) + // contextSize += sizeof(pContext->ExtendedRegisters); + if (FAILED (g_ExtData->ReadVirtual(TO_CDADDR(stack), cxr, (ULONG)contextSize, NULL))) { + return FALSE; + } + + if (*exrAddr) { + if (FAILED (g_ExtData->ReadVirtual(TO_CDADDR(*exrAddr), &stack, sizeof(stack), NULL))) + { + *exrAddr = 0; + return TRUE; + } + *exrAddr = stack; + size_t erSize = offsetof (EXCEPTION_RECORD, ExceptionInformation); + if (FAILED (g_ExtData->ReadVirtual(TO_CDADDR(stack), exr, (ULONG)erSize, NULL))) { + *exrAddr = 0; + return TRUE; + } + } + +#elif defined(SOS_TARGET_AMD64) + + if (IP == IPRetAddr[0] || IP == IPRetAddr[1] || IP == IPRetAddr[3]) { + *exrAddr = stack + sizeof(TADDR) + 0x4F0; + *cxrAddr = stack + sizeof(TADDR); + } else if (IP == IPRetAddr[2]) { + *cxrAddr = stack + sizeof(TADDR); + } + else { + return FALSE; + } + + if (FAILED (g_ExtData->ReadVirtual(TO_CDADDR(*cxrAddr), cxr, (ULONG)contextSize, NULL))) { + return FALSE; + } + + if (*exrAddr) { + size_t erSize = offsetof (EXCEPTION_RECORD, ExceptionInformation); + if (FAILED (g_ExtData->ReadVirtual(TO_CDADDR(*exrAddr), exr, (ULONG)erSize, NULL))) { + *exrAddr = 0; + return TRUE; + } + } + +#endif + return TRUE; +#else + return FALSE; +#endif // FEATURE_PAL +} + + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to determine if a DWORD on the stack is * +* a return address. * +* It does this by checking several bytes before the DWORD to see if * +* there is a call instruction. * +* * +\**********************************************************************/ + +void +#ifdef _TARGET_X86_ + X86Machine::IsReturnAddress +#elif defined(_TARGET_AMD64_) + AMD64Machine::IsReturnAddress +#endif + (TADDR retAddr, TADDR* whereCalled) const +{ + *whereCalled = 0; + + unsigned char spotend[6]; + move_xp (spotend, retAddr-6); + unsigned char *spot = spotend+6; + TADDR addr; + + // Note this is possible to be spoofed, but pretty unlikely + // call XXXXXXXX + if (spot[-5] == 0xE8) { + DWORD offs = 0; + move_xp (offs, retAddr-4); + *whereCalled = retAddr + (ULONG64)(LONG)(offs); + //*whereCalled = *((int*) (retAddr-4)) + retAddr; + // on WOW64 the range valid for code is almost the whole 4GB adddress space + if (g_ExtData->ReadVirtual(TO_CDADDR(*whereCalled), &addr, sizeof(addr), NULL) == S_OK) + { + TADDR callee; + if (GetCalleeSite(*whereCalled, callee)) { + *whereCalled = callee; + } + return; + } + else + *whereCalled = 0; + } + + // call [XXXXXXXX] + if (spot[-6] == 0xFF && (spot[-5] == 025)) { + DWORD offs = 0; + move_xp (offs, retAddr-4); +#ifdef _TARGET_AMD64_ + // on x64 this 32-bit is an RIP offset + addr = retAddr + (ULONG64)(LONG)(offs); +#elif defined (_TARGET_X86_) + addr = offs; +#endif + if (g_ExtData->ReadVirtual(TO_CDADDR(addr), whereCalled, sizeof(*whereCalled), NULL) == S_OK) { + move_xp (*whereCalled, addr); + //*whereCalled = **((unsigned**) (retAddr-4)); + // on WOW64 the range valid for code is almost the whole 4GB adddress space + if (g_ExtData->ReadVirtual(TO_CDADDR(*whereCalled), &addr, sizeof(addr), NULL) == S_OK) + { + TADDR callee; + if (GetCalleeSite(*whereCalled,callee)) { + *whereCalled = callee; + } + return; + } + else + *whereCalled = 0; + } + else + *whereCalled = 0; + } + + // call [REG+XX] + if (spot[-3] == 0xFF && (spot[-2] & ~7) == 0120 && (spot[-2] & 7) != 4) + { + *whereCalled = 0xFFFFFFFF; + return; + } + if (spot[-4] == 0xFF && spot[-3] == 0124) + { + *whereCalled = 0xFFFFFFFF; + return; + } + + // call [REG+XXXX] + if (spot[-6] == 0xFF && (spot[-5] & ~7) == 0220 && (spot[-5] & 7) != 4) + { + *whereCalled = 0xFFFFFFFF; + return; + } + if (spot[-7] == 0xFF && spot[-6] == 0224) + { + *whereCalled = 0xFFFFFFFF; + return; + } + + // call [REG] + if (spot[-2] == 0xFF && (spot[-1] & ~7) == 0020 && (spot[-1] & 7) != 4 && (spot[-1] & 7) != 5) + { + *whereCalled = 0xFFFFFFFF; + return; + } + + // call REG + if (spot[-2] == 0xFF && (spot[-1] & ~7) == 0320 && (spot[-1] & 7) != 4) + { + *whereCalled = 0xFFFFFFFF; + return; + } + + // There are other cases, but I don't believe they are used. + return; +} + + +#ifdef _X86_ + +/// +/// This is dead code, not called from anywhere, not linked in the final product. +/// +static BOOL DecodeLine (___in __in_z char *line, ___in __in_z const char *const inst, InstData& arg1, InstData& arg2) +{ + char *ptr = line; + if (inst[0] == '*' || !strncmp (ptr, inst, strlen (inst))) + { + arg1.mode = BAD; + arg2.mode = BAD; + NextTerm (ptr); + if (*ptr == '\0') + { + arg1.mode = NODATA; + return TRUE; + } + + DecodeAddressTerm (ptr, arg1); + NextTerm (ptr); + if (*ptr == '\0') + { + return TRUE; + } + DecodeAddressTerm (ptr, arg2); + return TRUE; + } + else + return FALSE; +} + +void PrintReg (Register *reg) +{ + ExtOut ("[EBX=%08x ESI=%08x EDI=%08x EBP=%08x ESP=%08x]\n", + reg[EBX].value, reg[ESI].value, reg[EDI].value, reg[EBP].value, + reg[ESP].value); +} + + +struct CallInfo +{ + DWORD_PTR stackPos; + DWORD_PTR retAddr; + DWORD_PTR whereCalled; +}; + +// Search for a Return address on stack. +BOOL GetNextRetAddr (DWORD_PTR stackBegin, DWORD_PTR stackEnd, + CallInfo &callInfo) +{ + for (callInfo.stackPos = stackBegin; + callInfo.stackPos <= stackEnd; + callInfo.stackPos += 4) + { + if (!SafeReadMemory (callInfo.stackPos, &callInfo.retAddr, 4, NULL)) + continue; + + g_targetMachine->IsReturnAddress(callInfo.retAddr, &callInfo.whereCalled); + if (callInfo.whereCalled) + { + return TRUE; + } + } + + return FALSE; +} + +struct FrameInfo +{ + DWORD_PTR IPStart; + DWORD_PTR Prolog; + DWORD_PTR FrameBase; // The value of ESP at the entry. + DWORD_PTR StackEnd; + DWORD_PTR argCount; + BOOL bEBPFrame; +}; + +// if a EBP frame, return TRUE if EBP has been setup +void GetFrameBaseHelper (DWORD_PTR IPBegin, DWORD_PTR IPEnd, + INT_PTR &StackChange) +{ + char line[256]; + char *ptr; + InstData arg1; + InstData arg2; + DWORD_PTR IP = IPBegin; + StackChange = 0; + while (IP < IPEnd) + { + DisasmAndClean (IP, line, 256); + ptr = line; + NextTerm (ptr); + NextTerm (ptr); + if (DecodeLine (ptr, "push ", arg1, arg2)) + { + StackChange += 4; + } + else if (DecodeLine (ptr, "pop ", arg1, arg2)) + { + StackChange -= 4; + } + else if (DecodeLine (ptr, "sub ", arg1, arg2)) + { + if (arg1.mode == REG && arg1.reg[0].reg == ESP) + { + if (arg2.mode == DATA) + StackChange -= arg2.value; + } + } + else if (DecodeLine (ptr, "add ", arg1, arg2)) + { + if (arg1.mode == REG && arg1.reg[0].reg == ESP) + { + if (arg2.mode == DATA) + StackChange += arg2.value; + } + } + else if (!strncmp (ptr, "ret", 3)) { + return; + } + } +} + +enum IPSTATE {IPPROLOG1 /*Before EBP set*/, IPPROLOG2 /*After EBP set*/, IPCODE, IPEPILOG, IPEND}; + +IPSTATE GetIpState (DWORD_PTR IP, FrameInfo* pFrame) +{ + char line[256]; + char *ptr; + + if (IP >= pFrame->IPStart && IP < pFrame->IPStart + pFrame->Prolog) + { + if (pFrame->bEBPFrame) { + DWORD_PTR pIP = pFrame->IPStart; + while (pIP < IP) { + DisasmAndClean (IP,line, 256); + ptr = line; + NextTerm (ptr); + NextTerm (ptr); + if (!strncmp (ptr, "mov ", 4)) { + NextTerm (ptr); + if (!strncmp (ptr, "ebp", 3)) { + NextTerm (ptr); + if (!strncmp (ptr, "esp", 3)) { + return IPPROLOG2; + } + } + } + else if (!strncmp (ptr, "call ", 5)) { + NextTerm (ptr); + if (strstr (ptr, "__EH_prolog")) { + return IPPROLOG2; + } + } + } + pIP = IP; + while (pIP < pFrame->IPStart + pFrame->Prolog) { + DisasmAndClean (IP,line, 256); + ptr = line; + NextTerm (ptr); + NextTerm (ptr); + if (!strncmp (ptr, "mov ", 4)) { + NextTerm (ptr); + if (!strncmp (ptr, "ebp", 3)) { + NextTerm (ptr); + if (!strncmp (ptr, "esp", 3)) { + return IPPROLOG1; + } + } + } + else if (!strncmp (ptr, "call ", 5)) { + NextTerm (ptr); + if (strstr (ptr, "__EH_prolog")) { + return IPPROLOG1; + } + } + } + + ExtOut ("Fail to find where EBP is saved\n"); + return IPPROLOG2; + } + else + { + return IPPROLOG1; + } + } + + int nline = 0; + while (1) { + DisasmAndClean (IP,line, 256); + nline ++; + ptr = line; + NextTerm (ptr); + NextTerm (ptr); + if (!strncmp (ptr, "ret", 3)) { + return (nline==1)?IPEND:IPEPILOG; + } + else if (!strncmp (ptr, "leave", 5)) { + return IPEPILOG; + } + else if (!strncmp (ptr, "call", 4)) { + return IPCODE; + } + else if (ptr[0] == 'j') { + return IPCODE; + } + } +} + +// FrameBase is the ESP value at the entry of a function. +BOOL GetFrameBase (Register callee[], FrameInfo* pFrame) +{ + //char line[256]; + //char *ptr; + INT_PTR dwpushed = 0; + //DWORD_PTR IP; + + IPSTATE IpState = GetIpState (callee[EIP].value, pFrame); + + if (pFrame->bEBPFrame) + { + if (IpState == IPEND || IpState == IPPROLOG1) { + pFrame->FrameBase = callee[ESP].value; + } + else + { + pFrame->FrameBase = callee[EBP].value+4; + } + return TRUE; + } + else + { + if (IpState == IPEND) { + pFrame->FrameBase = callee[ESP].value; + return TRUE; + } + + DWORD_PTR IPBegin, IPEnd; + if (IpState == IPEPILOG) { + IPBegin = callee[EIP].value; + IPEnd = ~0ul; + } + else if (IpState == IPPROLOG1) { + IPBegin = pFrame->IPStart; + IPEnd = callee[EIP].value; + } + else + { + IPBegin = pFrame->IPStart; + IPEnd = IPBegin + pFrame->Prolog; + } + GetFrameBaseHelper (IPBegin, IPEnd, dwpushed); + + if (IpState == IPEPILOG) { + ExtOut ("stack %d\n", dwpushed); + pFrame->FrameBase = callee[ESP].value - dwpushed; + return TRUE; + } + + CallInfo callInfo; + if (GetNextRetAddr (callee[ESP].value + dwpushed, + pFrame->StackEnd, callInfo)) + { + pFrame->FrameBase = callInfo.stackPos; + return TRUE; + } + + return FALSE; + } +} + +// caller[ESP]: the ESP value when we return to caller. +void RestoreCallerRegister (Register callee[], Register caller[], + FrameInfo *pFrame) +{ + if (pFrame->bEBPFrame) + { + if (callee[ESP].value < pFrame->FrameBase) + { + SafeReadMemory (pFrame->FrameBase-4, &caller[EBP].value, 4, NULL); + } + else + caller[EBP].value = callee[EBP].value; + } + else + caller[EBP].value = callee[EBP].value; + + caller[EBP].bValid = TRUE; + caller[ESP].value = pFrame->FrameBase + 4 + pFrame->argCount; + callee[EBP].value = pFrame->FrameBase - sizeof(void*); + SafeReadMemory (pFrame->FrameBase, &caller[EIP].value, 4, NULL); +} + +BOOL GetFrameInfoHelper (Register callee[], Register caller[], + FrameInfo *pFrame) +{ + if (GetFrameBase (callee, pFrame)) + { + RestoreCallerRegister (callee, caller, pFrame); + return TRUE; + } + else + return FALSE; +} + +// Return TRUE if Frame Info is OK, otherwise FALSE. +BOOL GetUnmanagedFrameInfo (Register callee[], Register caller[], + DumpStackFlag &DSFlag, PFPO_DATA data) +{ + FrameInfo Frame; + ULONG64 base; + g_ExtSymbols->GetModuleByOffset (callee[EIP].value, 0, NULL, &base); + Frame.IPStart = data->ulOffStart + (ULONG_PTR)base; + Frame.Prolog = data->cbProlog; + // Why do we have to do this to make it work? + if (Frame.Prolog == 1) { + Frame.Prolog = 0; + } + Frame.bEBPFrame = (data->cbFrame == FRAME_NONFPO); + Frame.StackEnd = DSFlag.end; + Frame.argCount = data->cdwParams*4; + + return GetFrameInfoHelper (callee, caller, &Frame); +} + +// offsetEBP: offset of stack position where EBP is saved. +// If EBP is not saved, *offsetEBP = -1 (~0ul); +BOOL IPReachable (DWORD_PTR IPBegin, DWORD_PTR IP, DWORD *offsetEBP) +{ + *offsetEBP = ~0ul; + return FALSE; +} + +BOOL HandleEEStub (Register callee[], Register caller[], + DumpStackFlag &DSFlag) +{ + // EEStub can only be called by IP directory. Let's look for possible caller. + CallInfo callInfo; + DWORD_PTR stackPos = callee[ESP].value; + while (stackPos < DSFlag.end) { + if (GetNextRetAddr (stackPos, + DSFlag.end, callInfo)) + { + if (callInfo.whereCalled != ~0ul) { + DWORD offsetEBP; + if (IPReachable (callInfo.whereCalled, callee[EIP].value, &offsetEBP)) { + caller[EIP].value = callInfo.retAddr; + // TODO: We may have saved EBP. + if (offsetEBP == ~0ul) { + caller[EBP].value = callee[EBP].value; + } + else + { + TADDR offs = TO_TADDR(callInfo.stackPos)-sizeof(PVOID)-offsetEBP; + SafeReadMemory (offs, &caller[EBP].value, sizeof(PVOID), NULL); + } + caller[ESP].value = callInfo.stackPos+sizeof(PVOID); + return TRUE; + } + } + stackPos = callInfo.stackPos+sizeof(PVOID); + } + else + return FALSE; + } + + return FALSE; +} + + +BOOL HandleByEpilog (Register callee[], Register caller[], + DumpStackFlag &DSFlag) +{ + return FALSE; +} + +#ifndef FEATURE_PAL +void RestoreFrameUnmanaged (Register *reg, DWORD_PTR CurIP) +{ + char line[256]; + char *ptr; + DWORD_PTR IP = CurIP; + INT_PTR value; + BOOL bDigit; + BOOL bGoodESP = true; + RegIndex dest; + + ULONG64 base; + g_ExtSymbols->GetModuleByOffset (TO_CDADDR(CurIP), 0, NULL, &base); + ULONG64 handle; + g_ExtSystem->GetCurrentProcessHandle(&handle); + PFPO_DATA data = + (PFPO_DATA)SymFunctionTableAccess((HANDLE)handle, CurIP); + DWORD_PTR IPBegin = data->ulOffStart + (ULONG_PTR)base; + + if (CurIP - IPBegin <= data->cbProlog) + { + // We are inside a prolog. + // See where we save the callee saved register. + // Also how many DWORD's we pushd + IP = IPBegin; + reg[ESP].stack = 0; + reg[ESP].bOnStack = FALSE; + reg[EBP].stack = 0; + reg[EBP].bOnStack = FALSE; + reg[ESI].stack = 0; + reg[ESI].bOnStack = FALSE; + reg[EDI].stack = 0; + reg[EDI].bOnStack = FALSE; + reg[EBX].stack = 0; + reg[EBX].bOnStack = FALSE; + + while (IP < CurIP) + { + DisasmAndClean (IP, line, 256); + ptr = line; + NextTerm (ptr); + NextTerm (ptr); + if (!strncmp (ptr, "push ", 5)) + { + reg[ESP].stack += 4; + NextTerm (ptr); + dest = FindReg(ptr); + if (dest == EBP || dest == EBX || dest == ESI || dest == EDI) + { + reg[dest].bOnStack = TRUE; + reg[dest].stack = reg[ESP].stack; + } + } + else if (!strncmp (ptr, "sub ", 4)) + { + NextTerm (ptr); + dest = FindReg(ptr); + if (dest == ESP) + { + NextTerm (ptr); + char *endptr; + reg[ESP].stack += strtoul(ptr, &endptr, 16);; + } + } + } + + DWORD_PTR baseESP = reg[ESP].value + reg[ESP].stack; + if (reg[EBP].bOnStack) + { + move_xp (reg[EBP].value, baseESP-reg[EBP].stack); + } + if (reg[EBX].bOnStack) + { + move_xp (reg[EBX].value, baseESP-reg[EBX].stack); + } + if (reg[ESI].bOnStack) + { + move_xp (reg[ESI].value, baseESP-reg[ESI].stack); + } + if (reg[EDI].bOnStack) + { + move_xp (reg[EDI].value, baseESP-reg[EDI].stack); + } + move_xp (reg[EIP].value, baseESP); + reg[ESP].value = baseESP + 4; + return; + } + + if (data->cbFrame == FRAME_NONFPO) + { + // EBP Frame + } + + // Look for epilog + while (1) + { + DisasmAndClean (IP, line, 256); + ptr = line; + NextTerm (ptr); + NextTerm (ptr); + if (!strncmp (ptr, "mov ", 4)) + { + NextTerm (ptr); + dest = FindReg(ptr); + if (dest == ESP) + { + NextTerm (ptr); + if (FindReg(ptr) == EBP) + { + // We have a EBP frame + bGoodESP = true; + reg[ESP].value = reg[EBP].value; + } + } + } + else if (!strncmp (ptr, "ret", 3)) + { + NextTerm (ptr); + // check the value on stack is a return address. + DWORD_PTR retAddr; + DWORD_PTR whereCalled; + move_xp (retAddr, reg[ESP].value); + int ESPAdjustCount = 0; + while (1) + { + g_targetMachine->IsReturnAddress(retAddr, &whereCalled); + if (whereCalled) + break; + ESPAdjustCount ++; + reg[ESP].value += 4; + move_xp (retAddr, reg[ESP].value); + } + reg[EIP].value = retAddr; + if (ESPAdjustCount) + { + ESPAdjustCount *= 4; + } + if (reg[EBX].bOnStack) + { + reg[EBX].stack += ESPAdjustCount; + move_xp (reg[EBX].value, reg[EBX].stack); + } + if (reg[ESI].bOnStack) + { + reg[ESI].stack += ESPAdjustCount; + move_xp (reg[ESI].value, reg[EBX].stack); + } + if (reg[EDI].bOnStack) + { + reg[EDI].stack += ESPAdjustCount; + move_xp (reg[EDI].value, reg[EBX].stack); + } + + reg[ESP].value += 4; + if (ptr[0] != '\0') + { + FindSrc (ptr, reg, value, bDigit); + reg[ESP].value += value; + } + break; + } + else if (!strncmp (ptr, "pop ", 4)) + { + NextTerm (ptr); + dest = FindReg(ptr); + if (dest == EBP || dest == EBX || dest == ESI || dest == EDI) + { + reg[dest].stack = reg[ESP].value; + reg[dest].bOnStack = TRUE; + } + reg[ESP].value += 4; + } + else if (!strncmp (ptr, "add ", 4)) + { + NextTerm (ptr); + dest = FindReg(ptr); + if (dest == ESP) + { + NextTerm (ptr); + FindSrc (ptr, reg, value, bDigit); + reg[ESP].value += value; + } + } + else if (!strncmp (ptr, "call ", 5)) + { + // assume we do not have a good value on ESP. + // We could go into the call and find out number of pushed args. + bGoodESP = FALSE; + } + } + + // Look for prolog +} +#endif // !FEATURE_PAL + +#elif defined(_AMD64_) + + +#endif // !_X86_ diff --git a/src/ToolBox/SOS/Strike/dllsext.cpp b/src/ToolBox/SOS/Strike/dllsext.cpp new file mode 100644 index 0000000000..757a04c91f --- /dev/null +++ b/src/ToolBox/SOS/Strike/dllsext.cpp @@ -0,0 +1,278 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ==++== +// + +// +// ==--== +#include "strike.h" +#include "data.h" +#include "util.h" +#include "platformspecific.h" + +typedef struct _PRIVATE_LDR_DATA_TABLE_ENTRY { + LIST_ENTRY InLoadOrderLinks; + LIST_ENTRY InMemoryOrderLinks; + LIST_ENTRY InInitializationOrderLinks; + PVOID DllBase; + PVOID EntryPoint; + ULONG SizeOfImage; + UNICODE_STRING FullDllName; + UNICODE_STRING BaseDllName; + ULONG Flags; + USHORT LoadCount; + USHORT TlsIndex; + union _LDR_DATA_TABLE_ENTRY_UNION1 { //DevDiv LKG RC Changes: Added union name to avoid warning C4408 + LIST_ENTRY HashLinks; + struct _LDR_DATA_TABLE_ENTRY_STRUCT1 { //DevDiv LKG RC Changes: Added struct name to avoid warning C4201 + PVOID SectionPointer; + ULONG CheckSum; + }; + }; + union _LDR_DATA_TABLE_ENTRY_UNION2 { //DevDiv LKG RC Changes: Added union name to avoid warning C4408 + struct _LDR_DATA_TABLE_ENTRY_STRUCT2 { //DevDiv LKG RC Changes: Added struct name to avoid warning C4201 + ULONG TimeDateStamp; + }; + struct _LDR_DATA_TABLE_ENTRY_STRUCT3 { //DevDiv LKG RC Changes: Added struct name to avoid warning C4201 + PVOID LoadedImports; + }; + }; + struct _ACTIVATION_CONTEXT * EntryPointActivationContext; + + PVOID PatchInformation; + +} PRIVATE_LDR_DATA_TABLE_ENTRY, *PRIVATE_PLDR_DATA_TABLE_ENTRY; + + +#ifndef FEATURE_PAL +static void DllsNameFromPeb( + ULONG_PTR addrContaining, + __out_ecount (MAX_LONGPATH) WCHAR *dllName + ) +{ + ULONG64 ProcessPeb; + g_ExtSystem->GetCurrentProcessPeb (&ProcessPeb); + + ULONG64 pLdrEntry; + ULONG64 PebLdrAddress; + ULONG64 Next; + ULONG64 OrderModuleListStart; + + // + // Capture PebLdrData + // + + static ULONG Offset_Ldr = -1; + if (Offset_Ldr == -1) + { + ULONG TypeId; + ULONG64 NtDllBase; + if (SUCCEEDED(g_ExtSymbols->GetModuleByModuleName ("ntdll",0,NULL, + &NtDllBase)) + && SUCCEEDED(g_ExtSymbols->GetTypeId (NtDllBase, "PEB", &TypeId))) + { + if (FAILED (g_ExtSymbols->GetFieldOffset(NtDllBase, TypeId, + "Ldr", &Offset_Ldr))) + Offset_Ldr = -1; + } + } + // We can not get it from PDB. Use the fixed one. + if (Offset_Ldr == -1) + Offset_Ldr = offsetof (DT_PEB, Ldr); + + DT_PEB peb = {0}; + if (FAILED(g_ExtData->ReadVirtual(ProcessPeb+Offset_Ldr, &peb.Ldr, + sizeof(peb.Ldr), NULL))) + { + ExtOut ( " Unable to read PEB_LDR_DATA address at %p\n", SOS_PTR(ProcessPeb+Offset_Ldr)); + return; + } + + PebLdrAddress = (ULONG64)peb.Ldr; + + // + // Walk through the loaded module table and display all ldr data + // + + static ULONG Offset_ModuleList = -1; + if (Offset_ModuleList == -1) + { + ULONG TypeId; + ULONG64 NtDllBase; + if (SUCCEEDED(g_ExtSymbols->GetModuleByModuleName ("ntdll",0,NULL, + &NtDllBase)) + && SUCCEEDED(g_ExtSymbols->GetTypeId (NtDllBase, "PEB_LDR_DATA", + &TypeId))) + { + if (FAILED (g_ExtSymbols->GetFieldOffset(NtDllBase, TypeId, + "InMemoryOrderModuleList", + &Offset_ModuleList))) + Offset_ModuleList = -1; + } + } + // We can not get it from PDB. Use the fixed one. + if (Offset_ModuleList == -1) + Offset_ModuleList = offsetof (DT_PEB_LDR_DATA, InMemoryOrderModuleList); + + OrderModuleListStart = PebLdrAddress + Offset_ModuleList; + DT_PEB_LDR_DATA Ldr = {0}; + if (FAILED(g_ExtData->ReadVirtual(OrderModuleListStart, + &Ldr.InMemoryOrderModuleList, + sizeof(Ldr.InMemoryOrderModuleList), + NULL))) + { + ExtOut ( " Unable to read InMemoryOrderModuleList address at %p\n", SOS_PTR(OrderModuleListStart)); + return; + } + Next = (ULONG64)Ldr.InMemoryOrderModuleList.Flink; + + static ULONG Offset_OrderLinks = -1; + static ULONG Offset_FullDllName = -1; + static ULONG Offset_DllBase = -1; + static ULONG Offset_SizeOfImage = -1; + if (Offset_OrderLinks == -1) + { + ULONG TypeId; + ULONG64 NtDllBase; + if (SUCCEEDED(g_ExtSymbols->GetModuleByModuleName ("ntdll",0,NULL, + &NtDllBase)) + && SUCCEEDED(g_ExtSymbols->GetTypeId (NtDllBase, "LDR_DATA_TABLE_ENTRY", + &TypeId))) + { + if (FAILED (g_ExtSymbols->GetFieldOffset(NtDllBase, TypeId, + "InMemoryOrderLinks", + &Offset_OrderLinks))) + Offset_OrderLinks = -1; + if (FAILED (g_ExtSymbols->GetFieldOffset(NtDllBase, TypeId, + "FullDllName", + &Offset_FullDllName))) + Offset_FullDllName = -1; + if (FAILED (g_ExtSymbols->GetFieldOffset(NtDllBase, TypeId, + "DllBase", + &Offset_DllBase))) + Offset_DllBase = -1; + if (FAILED (g_ExtSymbols->GetFieldOffset(NtDllBase, TypeId, + "SizeOfImage", + &Offset_SizeOfImage))) + Offset_SizeOfImage = -1; + } + } + + // We can not get it from PDB. Use the fixed one. + if (Offset_OrderLinks == -1 || Offset_OrderLinks == 0) + { + Offset_OrderLinks = offsetof (PRIVATE_LDR_DATA_TABLE_ENTRY, + InMemoryOrderLinks); + Offset_FullDllName = offsetof (PRIVATE_LDR_DATA_TABLE_ENTRY, + FullDllName); + Offset_DllBase = offsetof (PRIVATE_LDR_DATA_TABLE_ENTRY, + DllBase); + Offset_SizeOfImage = offsetof (PRIVATE_LDR_DATA_TABLE_ENTRY, + SizeOfImage); + } + + _UNICODE_STRING FullDllName; + __try { + while (Next != OrderModuleListStart) { + if (IsInterrupt()) + return; + + pLdrEntry = Next - Offset_OrderLinks; + + // + // Capture LdrEntry + // + if (FAILED(g_ExtData->ReadVirtual(pLdrEntry + Offset_FullDllName, + &FullDllName, + sizeof(FullDllName), + NULL))) + { + ExtOut ( " Unable to read FullDllName address at %p\n", + pLdrEntry + Offset_FullDllName); + return; + } + ZeroMemory( dllName, MAX_LONGPATH * sizeof (WCHAR) ); + if (FAILED(g_ExtData->ReadVirtual((ULONG64)FullDllName.Buffer, + dllName, + MAX_LONGPATH < FullDllName.Length ? MAX_LONGPATH : FullDllName.Length, + NULL))) + { +#if 0 + ExtOut ( " Unable to read FullDllName.Buffer address at %p\n", + SOS_PTR(FullDllName.Buffer)); +#endif + ZeroMemory( dllName, MAX_LONGPATH * sizeof (WCHAR) ); + } + + // + // Dump the ldr entry data + // (dump all the entries if no containing address specified) + // + PRIVATE_LDR_DATA_TABLE_ENTRY LdrEntry = {0}; + if (SUCCEEDED(g_ExtData->ReadVirtual(pLdrEntry + Offset_DllBase, + &LdrEntry.DllBase, + sizeof(LdrEntry.DllBase), + NULL)) + && + SUCCEEDED(g_ExtData->ReadVirtual(pLdrEntry + Offset_SizeOfImage, + &LdrEntry.SizeOfImage, + sizeof(LdrEntry.SizeOfImage), + NULL)) + ) + { + if (((ULONG_PTR)LdrEntry.DllBase <= addrContaining) && + (addrContaining <= (ULONG_PTR)LdrEntry.DllBase + (ULONG_PTR)LdrEntry.SizeOfImage)) + break; + } + + ZeroMemory( dllName, MAX_LONGPATH * sizeof (WCHAR) ); + if (FAILED(g_ExtData->ReadVirtual(pLdrEntry + Offset_OrderLinks, + &LdrEntry.InMemoryOrderLinks, + sizeof(LdrEntry.InMemoryOrderLinks), + NULL))) + break; + + Next = (ULONG64)LdrEntry.InMemoryOrderLinks.Flink; + } + } __except (EXCEPTION_EXECUTE_HANDLER) + { + ExtOut ("exception during reading PEB\n"); + return; + } +} +#endif + +HRESULT +DllsName( + ULONG_PTR addrContaining, + __out_ecount (MAX_LONGPATH) WCHAR *dllName + ) +{ + dllName[0] = L'\0'; + + ULONG Index; + ULONG64 base; + HRESULT hr = g_ExtSymbols->GetModuleByOffset(addrContaining, 0, &Index, &base); + if (FAILED(hr)) + return hr; + + CHAR name[MAX_LONGPATH+1]; + ULONG length; + + hr = g_ExtSymbols->GetModuleNames(Index,base,name,MAX_LONGPATH,&length,NULL,0,NULL,NULL,0,NULL); + + if (SUCCEEDED(hr)) + { + MultiByteToWideChar (CP_ACP,0,name,-1,dllName,MAX_LONGPATH); + } + +#ifndef FEATURE_PAL + if (_wcsrchr (dllName, '\\') == NULL) { + DllsNameFromPeb (addrContaining,dllName); + } +#endif + + return hr; +} diff --git a/src/ToolBox/SOS/Strike/eeheap.cpp b/src/ToolBox/SOS/Strike/eeheap.cpp new file mode 100644 index 0000000000..ac41e2deb6 --- /dev/null +++ b/src/ToolBox/SOS/Strike/eeheap.cpp @@ -0,0 +1,1913 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ==++== +// + +// +// ==--== +#include <assert.h> +#include "sos.h" +#include "safemath.h" + + +// This is the increment for the segment lookup data +const int nSegLookupStgIncrement = 100; + +#define CCH_STRING_PREFIX_SUMMARY 64 + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to update GC heap statistics. * +* * +\**********************************************************************/ +void HeapStat::Add(DWORD_PTR aData, DWORD aSize) +{ + if (head == 0) + { + head = new Node(); + if (head == NULL) + { + ReportOOM(); + ControlC = TRUE; + return; + } + + if (bHasStrings) + { + size_t capacity_pNew = _wcslen((WCHAR*)aData) + 1; + WCHAR *pNew = new WCHAR[capacity_pNew]; + if (pNew == NULL) + { + ReportOOM(); + ControlC = TRUE; + return; + } + wcscpy_s(pNew, capacity_pNew, (WCHAR*)aData); + aData = (DWORD_PTR)pNew; + } + + head->data = aData; + } + Node *walk = head; + int cmp = 0; + + for (;;) + { + if (IsInterrupt()) + return; + + cmp = CompareData(aData, walk->data); + + if (cmp == 0) + break; + + if (cmp < 0) + { + if (walk->left == NULL) + break; + walk = walk->left; + } + else + { + if (walk->right == NULL) + break; + walk = walk->right; + } + } + + if (cmp == 0) + { + walk->count ++; + walk->totalSize += aSize; + } + else + { + Node *node = new Node(); + if (node == NULL) + { + ReportOOM(); + ControlC = TRUE; + return; + } + + if (bHasStrings) + { + size_t capacity_pNew = _wcslen((WCHAR*)aData) + 1; + WCHAR *pNew = new WCHAR[capacity_pNew]; + if (pNew == NULL) + { + ReportOOM(); + ControlC = TRUE; + return; + } + wcscpy_s(pNew, capacity_pNew, (WCHAR*)aData); + aData = (DWORD_PTR)pNew; + } + + node->data = aData; + node->totalSize = aSize; + node->count ++; + + if (cmp < 0) + { + walk->left = node; + } + else + { + walk->right = node; + } + } +} +/**********************************************************************\ +* Routine Description: * +* * +* This function compares two nodes in the tree. * +* * +\**********************************************************************/ +int HeapStat::CompareData(DWORD_PTR d1, DWORD_PTR d2) +{ + if (bHasStrings) + return _wcscmp((WCHAR*)d1, (WCHAR*)d2); + + if (d1 > d2) + return 1; + + if (d1 < d2) + return -1; + + return 0; +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to sort all entries in the heap stat. * +* * +\**********************************************************************/ +void HeapStat::Sort () +{ + Node *root = head; + head = NULL; + ReverseLeftMost (root); + + Node *sortRoot = NULL; + while (head) + { + Node *tmp = head; + head = head->left; + if (tmp->right) + ReverseLeftMost (tmp->right); + // add tmp + tmp->right = NULL; + tmp->left = NULL; + SortAdd (sortRoot, tmp); + } + head = sortRoot; + + Linearize(); + + //reverse the order + root = head; + head = NULL; + sortRoot = NULL; + while (root) + { + Node *tmp = root->right; + root->left = NULL; + root->right = NULL; + LinearAdd (sortRoot, root); + root = tmp; + } + head = sortRoot; +} + +void HeapStat::Linearize() +{ + // Change binary tree to a linear tree + Node *root = head; + head = NULL; + ReverseLeftMost (root); + Node *sortRoot = NULL; + while (head) + { + Node *tmp = head; + head = head->left; + if (tmp->right) + ReverseLeftMost (tmp->right); + // add tmp + tmp->right = NULL; + tmp->left = NULL; + LinearAdd (sortRoot, tmp); + } + head = sortRoot; + fLinear = TRUE; +} + +void HeapStat::ReverseLeftMost (Node *root) +{ + while (root) + { + Node *tmp = root->left; + root->left = head; + head = root; + root = tmp; + } +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to help to sort heap stat. * +* * +\**********************************************************************/ +void HeapStat::SortAdd (Node *&root, Node *entry) +{ + if (root == NULL) + { + root = entry; + } + else + { + Node *parent = root; + Node *ptr = root; + while (ptr) + { + parent = ptr; + if (ptr->totalSize < entry->totalSize) + ptr = ptr->right; + else + ptr = ptr->left; + } + if (parent->totalSize < entry->totalSize) + parent->right = entry; + else + parent->left = entry; + } +} + +void HeapStat::LinearAdd(Node *&root, Node *entry) +{ + if (root == NULL) + { + root = entry; + } + else + { + entry->right = root; + root = entry; + } +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to print GC heap statistics. * +* * +\**********************************************************************/ +void HeapStat::Print(const char* label /* = NULL */) +{ + if (label == NULL) + { + label = "Statistics:\n"; + } + ExtOut(label); + if (bHasStrings) + ExtOut("%8s %12s %s\n", "Count", "TotalSize", "String Value"); + else + ExtOut("%" POINTERSIZE "s %8s %12s %s\n","MT", "Count", "TotalSize", "Class Name"); + + Node *root = head; + int ncount = 0; + while (root) + { + if (IsInterrupt()) + return; + + ncount += root->count; + + if (bHasStrings) + { + ExtOut("%8d %12I64u \"%S\"\n", root->count, (unsigned __int64)root->totalSize, root->data); + } + else + { + DMLOut("%s %8d %12I64u ", DMLDumpHeapMT(root->data), root->count, (unsigned __int64)root->totalSize); + if (IsMTForFreeObj(root->data)) + { + ExtOut("%9s\n", "Free"); + } + else + { + wcscpy_s(g_mdName, mdNameLen, W("UNKNOWN")); + NameForMT_s((DWORD_PTR) root->data, g_mdName, mdNameLen); + ExtOut("%S\n", g_mdName); + } + } + root = root->right; + + } + ExtOut ("Total %d objects\n", ncount); +} + +void HeapStat::Delete() +{ + if (head == NULL) + return; + + // Ensure the data structure is already linearized. + if (!fLinear) + Linearize(); + + while (head) + { + // The list is linearized on such that the left node is always null. + Node *tmp = head; + head = head->right; + + if (bHasStrings) + delete[] ((WCHAR*)tmp->data); + delete tmp; + } + + // return to default state + bHasStrings = FALSE; + fLinear = FALSE; +} + +// ----------------------------------------------------------------------- +// +// MethodTableCache implementation +// +// Used during heap traversals for quick object size computation +// +MethodTableInfo* MethodTableCache::Lookup (DWORD_PTR aData) +{ + Node** addHere = &head; + if (head != 0) { + Node *walk = head; + int cmp = 0; + + for (;;) + { + cmp = CompareData(aData, walk->data); + + if (cmp == 0) + return &walk->info; + + if (cmp < 0) + { + if (walk->left == NULL) + { + addHere = &walk->left; + break; + } + walk = walk->left; + } + else + { + if (walk->right == NULL) + { + addHere = &walk->right; + break; + } + walk = walk->right; + } + } + } + Node* newNode = new Node(aData); + if (newNode == NULL) + { + ReportOOM(); + return NULL; + } + *addHere = newNode; + return &newNode->info; +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function compares two nodes in the tree. * +* * +\**********************************************************************/ +int MethodTableCache::CompareData(DWORD_PTR d1, DWORD_PTR d2) +{ + if (d1 > d2) + return 1; + + if (d1 < d2) + return -1; + + return 0; +} + +void MethodTableCache::ReverseLeftMost (Node *root) +{ + if (root) + { + if (root->left) ReverseLeftMost(root->left); + if (root->right) ReverseLeftMost(root->right); + delete root; + } +} + +void MethodTableCache::Clear() +{ + Node *root = head; + head = NULL; + ReverseLeftMost (root); +} + +MethodTableCache g_special_mtCache; + +size_t Align (size_t nbytes) +{ + return (nbytes + ALIGNCONST) & ~ALIGNCONST; +} + +size_t AlignLarge(size_t nbytes) +{ + return (nbytes + ALIGNCONSTLARGE) & ~ALIGNCONSTLARGE; +} + +/**********************************************************************\ +* Routine Description: * +* * +* Print the gc heap info. * +* * +\**********************************************************************/ +void GCPrintGenerationInfo(const DacpGcHeapDetails &heap) +{ + UINT n; + for (n = 0; n <= GetMaxGeneration(); n ++) + { + if (IsInterrupt()) + return; + ExtOut("generation %d starts at 0x%p\n", + n, SOS_PTR(heap.generation_table[n].allocation_start)); + } + + // We also need to look at the gen0 alloc context. + ExtOut("ephemeral segment allocation context: "); + if (heap.generation_table[0].allocContextPtr) + { + ExtOut("(0x%p, 0x%p)\n", + SOS_PTR(heap.generation_table[0].allocContextPtr), + SOS_PTR(heap.generation_table[0].allocContextLimit + Align(min_obj_size))); + } + else + { + ExtOut("none\n"); + } +} + + +void GCPrintSegmentInfo(const DacpGcHeapDetails &heap, DWORD_PTR &total_size) +{ + DWORD_PTR dwAddrSeg; + DacpHeapSegmentData segment; + + dwAddrSeg = (DWORD_PTR)heap.generation_table[GetMaxGeneration()].start_segment; + total_size = 0; + // the loop below will terminate, because we retrieved at most nMaxHeapSegmentCount segments + while (dwAddrSeg != (DWORD_PTR)heap.generation_table[0].start_segment) + { + if (IsInterrupt()) + return; + if (segment.Request(g_sos, dwAddrSeg, heap) != S_OK) + { + ExtOut("Error requesting heap segment %p\n", SOS_PTR(dwAddrSeg)); + return; + } + ExtOut("%p %p %p 0x%" POINTERSIZE_TYPE "x(%" POINTERSIZE_TYPE "d)\n", SOS_PTR(dwAddrSeg), + SOS_PTR(segment.mem), SOS_PTR(segment.allocated), + (ULONG_PTR)(segment.allocated - segment.mem), + (ULONG_PTR)(segment.allocated - segment.mem)); + total_size += (DWORD_PTR) (segment.allocated - segment.mem); + dwAddrSeg = (DWORD_PTR)segment.next; + } + + if (segment.Request(g_sos, dwAddrSeg, heap) != S_OK) + { + ExtOut("Error requesting heap segment %p\n", SOS_PTR(dwAddrSeg)); + return; + } + + DWORD_PTR end = (DWORD_PTR)heap.alloc_allocated; + ExtOut("%p %p %p 0x%" POINTERSIZE_TYPE "x(%" POINTERSIZE_TYPE "d)\n", SOS_PTR(dwAddrSeg), + SOS_PTR(segment.mem), SOS_PTR(end), + (ULONG_PTR)(end - (DWORD_PTR)segment.mem), + (ULONG_PTR)(end - (DWORD_PTR)segment.mem)); + + total_size += end - (DWORD_PTR)segment.mem; + +} + + +void GCPrintLargeHeapSegmentInfo(const DacpGcHeapDetails &heap, DWORD_PTR &total_size) +{ + DWORD_PTR dwAddrSeg; + DacpHeapSegmentData segment; + dwAddrSeg = (DWORD_PTR)heap.generation_table[GetMaxGeneration()+1].start_segment; + + // total_size = 0; + // the loop below will terminate, because we retrieved at most nMaxHeapSegmentCount segments + while (dwAddrSeg != NULL) + { + if (IsInterrupt()) + return; + if (segment.Request(g_sos, dwAddrSeg, heap) != S_OK) + { + ExtOut("Error requesting heap segment %p\n", SOS_PTR(dwAddrSeg)); + return; + } + ExtOut("%p %p %p 0x%" POINTERSIZE_TYPE "x(%" POINTERSIZE_TYPE "d)\n", SOS_PTR(dwAddrSeg), + SOS_PTR(segment.mem), SOS_PTR(segment.allocated), + (ULONG_PTR)(segment.allocated - segment.mem), + segment.allocated - segment.mem); + total_size += (DWORD_PTR) (segment.allocated - segment.mem); + dwAddrSeg = (DWORD_PTR)segment.next; + } +} + +void GCHeapInfo(const DacpGcHeapDetails &heap, DWORD_PTR &total_size) +{ + GCPrintGenerationInfo(heap); + ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %" POINTERSIZE "s %" POINTERSIZE "s\n", "segment", "begin", "allocated", "size"); + GCPrintSegmentInfo(heap, total_size); + ExtOut("Large object heap starts at 0x%p\n", + SOS_PTR(heap.generation_table[GetMaxGeneration()+1].allocation_start)); + ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %" POINTERSIZE "s %" POINTERSIZE "s\n", "segment", "begin", "allocated", "size"); + GCPrintLargeHeapSegmentInfo(heap,total_size); +} + +BOOL GCObjInGeneration(TADDR taddrObj, const DacpGcHeapDetails &heap, + const TADDR_SEGINFO& /*seg*/, int& gen, TADDR_RANGE& allocCtx) +{ + gen = -1; + for (UINT n = 0; n <= GetMaxGeneration(); n ++) + { + if (taddrObj >= TO_TADDR(heap.generation_table[n].allocation_start)) + { + gen = n; + break; + } + } + + // We also need to look at the gen0 alloc context. + if (heap.generation_table[0].allocContextPtr + && taddrObj >= TO_TADDR(heap.generation_table[0].allocContextPtr) + && taddrObj < TO_TADDR(heap.generation_table[0].allocContextLimit) + Align(min_obj_size)) + { + gen = 0; + allocCtx.start = (TADDR)heap.generation_table[0].allocContextPtr; + allocCtx.end = (TADDR)heap.generation_table[0].allocContextLimit; + } + else + { + allocCtx.start = allocCtx.end = 0; + } + return (gen != -1); +} + + +BOOL GCObjInSegment(TADDR taddrObj, const DacpGcHeapDetails &heap, + TADDR_SEGINFO& rngSeg, int& gen, TADDR_RANGE& allocCtx) +{ + TADDR taddrSeg; + DacpHeapSegmentData dacpSeg; + + taddrSeg = (TADDR)heap.generation_table[GetMaxGeneration()].start_segment; + // the loop below will terminate, because we retrieved at most nMaxHeapSegmentCount segments + while (taddrSeg != (TADDR)heap.generation_table[0].start_segment) + { + if (IsInterrupt()) + return FALSE; + if (dacpSeg.Request(g_sos, taddrSeg, heap) != S_OK) + { + ExtOut("Error requesting heap segment %p\n", SOS_PTR(taddrSeg)); + return FALSE; + } + if (taddrObj >= TO_TADDR(dacpSeg.mem) && taddrObj < TO_TADDR(dacpSeg.allocated)) + { + rngSeg.segAddr = (TADDR)dacpSeg.segmentAddr; + rngSeg.start = (TADDR)dacpSeg.mem; + rngSeg.end = (TADDR)dacpSeg.allocated; + gen = 2; + allocCtx.start = allocCtx.end = 0; + return TRUE; + } + taddrSeg = (TADDR)dacpSeg.next; + } + + // the ephemeral segment + if (dacpSeg.Request(g_sos, taddrSeg, heap) != S_OK) + { + ExtOut("Error requesting heap segment %p\n", SOS_PTR(taddrSeg)); + return FALSE; + } + + if (taddrObj >= TO_TADDR(dacpSeg.mem) && taddrObj < TO_TADDR(heap.alloc_allocated)) + { + if (GCObjInGeneration(taddrObj, heap, rngSeg, gen, allocCtx)) + { + rngSeg.segAddr = (TADDR)dacpSeg.segmentAddr; + rngSeg.start = (TADDR)dacpSeg.mem; + rngSeg.end = (TADDR)heap.alloc_allocated; + return TRUE; + } + } + + return FALSE; +} + +BOOL GCObjInLargeSegment(TADDR taddrObj, const DacpGcHeapDetails &heap, TADDR_SEGINFO& rngSeg) +{ + TADDR taddrSeg; + DacpHeapSegmentData dacpSeg; + taddrSeg = (TADDR)heap.generation_table[GetMaxGeneration()+1].start_segment; + + // the loop below will terminate, because we retrieved at most nMaxHeapSegmentCount segments + while (taddrSeg != NULL) + { + if (IsInterrupt()) + return FALSE; + if (dacpSeg.Request(g_sos, taddrSeg, heap) != S_OK) + { + ExtOut("Error requesting heap segment %p\n", SOS_PTR(taddrSeg)); + return FALSE; + } + if (taddrObj >= TO_TADDR(dacpSeg.mem) && taddrObj && taddrObj < TO_TADDR(dacpSeg.allocated)) + { + rngSeg.segAddr = (TADDR)dacpSeg.segmentAddr; + rngSeg.start = (TADDR)dacpSeg.mem; + rngSeg.end = (TADDR)dacpSeg.allocated; + return TRUE; + } + taddrSeg = (TADDR)dacpSeg.next; + } + return FALSE; +} + +BOOL GCObjInHeap(TADDR taddrObj, const DacpGcHeapDetails &heap, + TADDR_SEGINFO& rngSeg, int& gen, TADDR_RANGE& allocCtx, BOOL &bLarge) +{ + if (GCObjInSegment(taddrObj, heap, rngSeg, gen, allocCtx)) + { + bLarge = FALSE; + return TRUE; + } + if (GCObjInLargeSegment(taddrObj, heap, rngSeg)) + { + bLarge = TRUE; + gen = GetMaxGeneration()+1; + allocCtx.start = allocCtx.end = 0; + return TRUE; + } + return FALSE; +} + +#ifndef FEATURE_PAL +// this function updates genUsage to reflect statistics from the range defined by [start, end) +void GCGenUsageStats(TADDR start, TADDR end, const std::unordered_set<TADDR> &liveObjs, + const DacpGcHeapDetails &heap, BOOL bLarge, const AllocInfo *pAllocInfo, GenUsageStat *genUsage) +{ + // if this is an empty segment or generation return + if (start >= end) + { + return; + } + + // otherwise it should start with a valid object + _ASSERTE(sos::IsObject(start)); + + // update the "allocd" field + genUsage->allocd += end - start; + + size_t objSize = 0; + for (TADDR taddrObj = start; taddrObj < end; taddrObj += objSize) + { + TADDR taddrMT; + + move_xp(taddrMT, taddrObj); + taddrMT &= ~3; + + // skip allocation contexts + if (!bLarge) + { + // Is this the beginning of an allocation context? + int i; + for (i = 0; i < pAllocInfo->num; i ++) + { + if (taddrObj == (TADDR)pAllocInfo->array[i].alloc_ptr) + { + ExtDbgOut("Skipping allocation context: [%#p-%#p)\n", + SOS_PTR(pAllocInfo->array[i].alloc_ptr), SOS_PTR(pAllocInfo->array[i].alloc_limit)); + taddrObj = + (TADDR)pAllocInfo->array[i].alloc_limit + Align(min_obj_size); + break; + } + } + if (i < pAllocInfo->num) + { + // we already adjusted taddrObj, so reset objSize + objSize = 0; + continue; + } + + // We also need to look at the gen0 alloc context. + if (taddrObj == (DWORD_PTR) heap.generation_table[0].allocContextPtr) + { + taddrObj = (DWORD_PTR) heap.generation_table[0].allocContextLimit + Align(min_obj_size); + // we already adjusted taddrObj, so reset objSize + objSize = 0; + continue; + } + + // Are we at the end of gen 0? + if (taddrObj == end - Align(min_obj_size)) + { + objSize = 0; + break; + } + } + + BOOL bContainsPointers; + BOOL bMTOk = GetSizeEfficient(taddrObj, taddrMT, bLarge, objSize, bContainsPointers); + if (!bMTOk) + { + ExtErr("bad object: %#p - bad MT %#p\n", SOS_PTR(taddrObj), SOS_PTR(taddrMT)); + // set objSize to size_t to look for the next valid MT + objSize = sizeof(TADDR); + continue; + } + + // at this point we should have a valid objSize, and there whould be no + // integer overflow when moving on to next object in heap + _ASSERTE(objSize > 0 && taddrObj < taddrObj + objSize); + if (objSize == 0 || taddrObj > taddrObj + objSize) + { + break; + } + + if (IsMTForFreeObj(taddrMT)) + { + genUsage->freed += objSize; + } + else if (!(liveObjs.empty()) && liveObjs.find(taddrObj) == liveObjs.end()) + { + genUsage->unrooted += objSize; + } + } +} +#endif // !FEATURE_PAL + +BOOL GCHeapUsageStats(const DacpGcHeapDetails& heap, BOOL bIncUnreachable, HeapUsageStat *hpUsage) +{ + memset(hpUsage, 0, sizeof(*hpUsage)); + + AllocInfo allocInfo; + allocInfo.Init(); + + // 1. Start with small object segments + TADDR taddrSeg; + DacpHeapSegmentData dacpSeg; + + taddrSeg = (TADDR)heap.generation_table[GetMaxGeneration()].start_segment; + +#ifndef FEATURE_PAL + // this will create the bitmap of rooted objects only if bIncUnreachable is true + GCRootImpl gcroot; + std::unordered_set<TADDR> emptyLiveObjs; + const std::unordered_set<TADDR> &liveObjs = (bIncUnreachable ? gcroot.GetLiveObjects() : emptyLiveObjs); + + // 1a. enumerate all non-ephemeral segments + while (taddrSeg != (TADDR)heap.generation_table[0].start_segment) + { + if (IsInterrupt()) + return FALSE; + + if (dacpSeg.Request(g_sos, taddrSeg, heap) != S_OK) + { + ExtErr("Error requesting heap segment %p\n", SOS_PTR(taddrSeg)); + return FALSE; + } + GCGenUsageStats((TADDR)dacpSeg.mem, (TADDR)dacpSeg.allocated, liveObjs, heap, FALSE, &allocInfo, &hpUsage->genUsage[2]); + taddrSeg = (TADDR)dacpSeg.next; + } +#endif + + // 1b. now handle the ephemeral segment + if (dacpSeg.Request(g_sos, taddrSeg, heap) != S_OK) + { + ExtErr("Error requesting heap segment %p\n", SOS_PTR(taddrSeg)); + return FALSE; + } + + TADDR endGen = TO_TADDR(heap.alloc_allocated); + for (UINT n = 0; n <= GetMaxGeneration(); n ++) + { + TADDR startGen; + // gen 2 starts at the beginning of the segment + if (n == GetMaxGeneration()) + { + startGen = TO_TADDR(dacpSeg.mem); + } + else + { + startGen = TO_TADDR(heap.generation_table[n].allocation_start); + } + +#ifndef FEATURE_PAL + GCGenUsageStats(startGen, endGen, liveObjs, heap, FALSE, &allocInfo, &hpUsage->genUsage[n]); +#endif + endGen = startGen; + } + + // 2. Now process LOH + taddrSeg = (TADDR) heap.generation_table[GetMaxGeneration()+1].start_segment; + while (taddrSeg != NULL) + { + if (IsInterrupt()) + return FALSE; + + if (dacpSeg.Request(g_sos, taddrSeg, heap) != S_OK) + { + ExtErr("Error requesting heap segment %p\n", SOS_PTR(taddrSeg)); + return FALSE; + } + +#ifndef FEATURE_PAL + GCGenUsageStats((TADDR) dacpSeg.mem, (TADDR) dacpSeg.allocated, liveObjs, heap, TRUE, NULL, &hpUsage->genUsage[3]); +#endif + taddrSeg = (TADDR)dacpSeg.next; + } + + return TRUE; +} + +DWORD GetNumComponents(TADDR obj) +{ + // The number of components is always the second pointer in the object. + DWORD Value = NULL; + HRESULT hr = MOVE(Value, obj + sizeof(size_t)); + + // If we fail to read out the number of components, let's assume 0 so we don't try to + // read further data from the object. + if (FAILED(hr)) + return 0; + + // The component size on a String does not contain the trailing NULL character, + // so we must add that ourselves. + if(IsStringObject(obj)) + return Value+1; + + return Value; +} + +BOOL GetSizeEfficient(DWORD_PTR dwAddrCurrObj, + DWORD_PTR dwAddrMethTable, BOOL bLarge, size_t& s, BOOL& bContainsPointers) +{ + // Remove lower bits in case we are in mark phase + dwAddrMethTable = dwAddrMethTable & ~3; + MethodTableInfo* info = g_special_mtCache.Lookup(dwAddrMethTable); + if (!info->IsInitialized()) // An uninitialized entry + { + // this is the first time we see this method table, so we need to get the information + // from the target + DacpMethodTableData dmtd; + // see code:ClrDataAccess::RequestMethodTableData for details + if (dmtd.Request(g_sos,dwAddrMethTable) != S_OK) + return FALSE; + + info->BaseSize = dmtd.BaseSize; + info->ComponentSize = dmtd.ComponentSize; + info->bContainsPointers = dmtd.bContainsPointers; + } + + bContainsPointers = info->bContainsPointers; + s = info->BaseSize; + + if (info->ComponentSize) + { + // this is an array, so the size has to include the size of the components. We read the number + // of components from the target and multiply by the component size to get the size. + s += info->ComponentSize*GetNumComponents(dwAddrCurrObj); + } + + // On x64 we do an optimization to save 4 bytes in almost every string we create + // IMPORTANT: This cannot be done in ObjectSize, which is a wrapper to this function, + // because we must Align only after these changes are made +#ifdef _TARGET_WIN64_ + // Pad to min object size if necessary + if (s < min_obj_size) + s = min_obj_size; +#endif // _TARGET_WIN64_ + + s = (bLarge ? AlignLarge(s) : Align (s)); + return TRUE; +} + +// This function expects stat to be valid, and ready to get statistics. +void GatherOneHeapFinalization(DacpGcHeapDetails& heapDetails, HeapStat *stat, BOOL bAllReady, BOOL bShort) +{ + DWORD_PTR dwAddr=0; + UINT m; + + if (!bShort) + { + for (m = 0; m <= GetMaxGeneration(); m ++) + { + if (IsInterrupt()) + return; + + ExtOut("generation %d has %d finalizable objects ", m, + (SegQueueLimit(heapDetails,gen_segment(m)) - SegQueue(heapDetails,gen_segment(m))) / sizeof(size_t)); + + ExtOut ("(%p->%p)\n", + SOS_PTR(SegQueue(heapDetails,gen_segment(m))), + SOS_PTR(SegQueueLimit(heapDetails,gen_segment(m)))); + } + } +#ifndef FEATURE_PAL + if (bAllReady) + { + if (!bShort) + { + ExtOut ("Finalizable but not rooted: "); + } + + TADDR rngStart = (TADDR)SegQueue(heapDetails, gen_segment(GetMaxGeneration())); + TADDR rngEnd = (TADDR)SegQueueLimit(heapDetails, gen_segment(0)); + + PrintNotReachableInRange(rngStart, rngEnd, TRUE, bAllReady ? stat : NULL, bShort); + } +#endif + + if (!bShort) + { + ExtOut ("Ready for finalization %d objects ", + (SegQueueLimit(heapDetails,FinalizerListSeg)-SegQueue(heapDetails,CriticalFinalizerListSeg)) / sizeof(size_t)); + ExtOut ("(%p->%p)\n", + SOS_PTR(SegQueue(heapDetails,CriticalFinalizerListSeg)), + SOS_PTR(SegQueueLimit(heapDetails,FinalizerListSeg))); + } + + // if bAllReady we only count objects that are ready for finalization, + // otherwise we count all finalizable objects. + TADDR taddrLowerLimit = (bAllReady ? (TADDR)SegQueue(heapDetails, CriticalFinalizerListSeg) : + (DWORD_PTR)SegQueue(heapDetails, gen_segment(GetMaxGeneration()))); + for (dwAddr = taddrLowerLimit; + dwAddr < (DWORD_PTR)SegQueueLimit(heapDetails, FinalizerListSeg); + dwAddr += sizeof (dwAddr)) + { + if (IsInterrupt()) + { + return; + } + + DWORD_PTR objAddr = NULL, + MTAddr = NULL; + + if (SUCCEEDED(MOVE(objAddr, dwAddr)) && SUCCEEDED(GetMTOfObject(objAddr, &MTAddr)) && MTAddr) + { + if (bShort) + { + DMLOut("%s\n", DMLObject(objAddr)); + } + else + { + size_t s = ObjectSize(objAddr); + stat->Add(MTAddr, (DWORD)s); + } + } + } +} + +BOOL GCHeapTraverse(const DacpGcHeapDetails &heap, AllocInfo* pallocInfo, VISITGCHEAPFUNC pFunc, LPVOID token, BOOL verify) +{ + DWORD_PTR begin_youngest; + DWORD_PTR end_youngest; + begin_youngest = (DWORD_PTR)heap.generation_table[0].allocation_start; + DWORD_PTR dwAddr = (DWORD_PTR)heap.ephemeral_heap_segment; + DacpHeapSegmentData segment; + + end_youngest = (DWORD_PTR)heap.alloc_allocated; + + DWORD_PTR dwAddrSeg = (DWORD_PTR)heap.generation_table[GetMaxGeneration()].start_segment; + dwAddr = dwAddrSeg; + + if (segment.Request(g_sos, dwAddr, heap) != S_OK) + { + ExtOut("Error requesting heap segment %p\n", SOS_PTR(dwAddr)); + return FALSE; + } + + // DWORD_PTR dwAddrCurrObj = (DWORD_PTR)heap.generation_table[GetMaxGeneration()].allocation_start; + DWORD_PTR dwAddrCurrObj = (DWORD_PTR)segment.mem; + + size_t s, sPrev=0; + BOOL bPrevFree=FALSE; + DWORD_PTR dwAddrMethTable; + DWORD_PTR dwAddrPrevObj=0; + + while(1) + { + if (IsInterrupt()) + { + ExtOut("<heap walk interrupted>\n"); + return FALSE; + } + DWORD_PTR end_of_segment = (DWORD_PTR)segment.allocated; + if (dwAddrSeg == (DWORD_PTR)heap.ephemeral_heap_segment) + { + end_of_segment = end_youngest; + if (dwAddrCurrObj - SIZEOF_OBJHEADER == end_youngest - Align(min_obj_size)) + break; + } + if (dwAddrCurrObj >= (DWORD_PTR)end_of_segment) + { + if (dwAddrCurrObj > (DWORD_PTR)end_of_segment) + { + ExtOut ("curr_object: %p > heap_segment_allocated (seg: %p)\n", + SOS_PTR(dwAddrCurrObj), SOS_PTR(dwAddrSeg)); + if (dwAddrPrevObj) { + ExtOut ("Last good object: %p\n", SOS_PTR(dwAddrPrevObj)); + } + return FALSE; + } + dwAddrSeg = (DWORD_PTR)segment.next; + if (dwAddrSeg) + { + dwAddr = dwAddrSeg; + if (segment.Request(g_sos, dwAddr, heap) != S_OK) + { + ExtOut("Error requesting heap segment %p\n", SOS_PTR(dwAddr)); + return FALSE; + } + dwAddrCurrObj = (DWORD_PTR)segment.mem; + continue; + } + else + break; // Done Verifying Heap + } + + if (dwAddrSeg == (DWORD_PTR)heap.ephemeral_heap_segment + && dwAddrCurrObj >= end_youngest) + { + if (dwAddrCurrObj > end_youngest) + { + // prev_object length is too long + ExtOut("curr_object: %p > end_youngest: %p\n", + SOS_PTR(dwAddrCurrObj), SOS_PTR(end_youngest)); + if (dwAddrPrevObj) { + DMLOut("Last good object: %s\n", DMLObject(dwAddrPrevObj)); + } + return FALSE; + } + return FALSE; + } + + if (FAILED(GetMTOfObject(dwAddrCurrObj, &dwAddrMethTable))) + { + return FALSE; + } + + dwAddrMethTable = dwAddrMethTable & ~3; + if (dwAddrMethTable == 0) + { + // Is this the beginning of an allocation context? + int i; + for (i = 0; i < pallocInfo->num; i ++) + { + if (dwAddrCurrObj == (DWORD_PTR)pallocInfo->array[i].alloc_ptr) + { + dwAddrCurrObj = + (DWORD_PTR)pallocInfo->array[i].alloc_limit + Align(min_obj_size); + break; + } + } + if (i < pallocInfo->num) + continue; + + // We also need to look at the gen0 alloc context. + if (dwAddrCurrObj == (DWORD_PTR) heap.generation_table[0].allocContextPtr) + { + dwAddrCurrObj = (DWORD_PTR) heap.generation_table[0].allocContextLimit + Align(min_obj_size); + continue; + } + } + + BOOL bContainsPointers; + BOOL bMTOk = GetSizeEfficient(dwAddrCurrObj, dwAddrMethTable, FALSE, s, bContainsPointers); + if (verify && bMTOk) + bMTOk = VerifyObject (heap, dwAddrCurrObj, dwAddrMethTable, s, TRUE); + if (!bMTOk) + { + DMLOut("curr_object: %s\n", DMLListNearObj(dwAddrCurrObj)); + if (dwAddrPrevObj) + DMLOut("Last good object: %s\n", DMLObject(dwAddrPrevObj)); + + ExtOut ("----------------\n"); + return FALSE; + } + + pFunc (dwAddrCurrObj, s, dwAddrMethTable, token); + + // We believe we did this alignment in ObjectSize above. + assert((s & ALIGNCONST) == 0); + dwAddrPrevObj = dwAddrCurrObj; + sPrev = s; + bPrevFree = IsMTForFreeObj(dwAddrMethTable); + + dwAddrCurrObj += s; + } + + // Now for the large object generation: + dwAddrSeg = (DWORD_PTR)heap.generation_table[GetMaxGeneration()+1].start_segment; + dwAddr = dwAddrSeg; + + if (segment.Request(g_sos, dwAddr, heap) != S_OK) + { + ExtOut("Error requesting heap segment %p\n", SOS_PTR(dwAddr)); + return FALSE; + } + + // dwAddrCurrObj = (DWORD_PTR)heap.generation_table[GetMaxGeneration()+1].allocation_start; + dwAddrCurrObj = (DWORD_PTR)segment.mem; + + dwAddrPrevObj=0; + + while(1) + { + if (IsInterrupt()) + { + ExtOut("<heap traverse interrupted>\n"); + return FALSE; + } + + DWORD_PTR end_of_segment = (DWORD_PTR)segment.allocated; + + if (dwAddrCurrObj >= (DWORD_PTR)end_of_segment) + { + if (dwAddrCurrObj > (DWORD_PTR)end_of_segment) + { + ExtOut("curr_object: %p > heap_segment_allocated (seg: %p)\n", + SOS_PTR(dwAddrCurrObj), SOS_PTR(dwAddrSeg)); + if (dwAddrPrevObj) { + ExtOut("Last good object: %p\n", SOS_PTR(dwAddrPrevObj)); + } + return FALSE; + } + dwAddrSeg = (DWORD_PTR)segment.next; + if (dwAddrSeg) + { + dwAddr = dwAddrSeg; + if (segment.Request(g_sos, dwAddr, heap) != S_OK) + { + ExtOut("Error requesting heap segment %p\n", SOS_PTR(dwAddr)); + return FALSE; + } + dwAddrCurrObj = (DWORD_PTR)segment.mem; + continue; + } + else + break; // Done Verifying Heap + } + + if (FAILED(GetMTOfObject(dwAddrCurrObj, &dwAddrMethTable))) + { + return FALSE; + } + + dwAddrMethTable = dwAddrMethTable & ~3; + BOOL bContainsPointers; + BOOL bMTOk = GetSizeEfficient(dwAddrCurrObj, dwAddrMethTable, TRUE, s, bContainsPointers); + if (verify && bMTOk) + bMTOk = VerifyObject (heap, dwAddrCurrObj, dwAddrMethTable, s, TRUE); + if (!bMTOk) + { + DMLOut("curr_object: %s\n", DMLListNearObj(dwAddrCurrObj)); + + if (dwAddrPrevObj) + DMLOut("Last good object: %s\n", dwAddrPrevObj); + + ExtOut ("----------------\n"); + return FALSE; + } + + pFunc (dwAddrCurrObj, s, dwAddrMethTable, token); + + // We believe we did this alignment in ObjectSize above. + assert((s & ALIGNCONSTLARGE) == 0); + dwAddrPrevObj = dwAddrCurrObj; + dwAddrCurrObj += s; + } + + return TRUE; +} + +BOOL GCHeapsTraverse(VISITGCHEAPFUNC pFunc, LPVOID token, BOOL verify) +{ + // Obtain allocation context for each managed thread. + AllocInfo allocInfo; + allocInfo.Init(); + + if (!IsServerBuild()) + { + DacpGcHeapDetails heapDetails; + if (heapDetails.Request(g_sos) != S_OK) + { + ExtOut("Error requesting gc heap details\n"); + return FALSE; + } + + return GCHeapTraverse (heapDetails, &allocInfo, pFunc, token, verify); + } + else + { + DacpGcHeapData gcheap; + if (gcheap.Request(g_sos) != S_OK) + { + ExtOut("Error requesting GC Heap data\n"); + return FALSE; + } + + DWORD dwAllocSize; + DWORD dwNHeaps = gcheap.HeapCount; + if (!ClrSafeInt<DWORD>::multiply(sizeof(CLRDATA_ADDRESS), dwNHeaps, dwAllocSize)) + { + ExtOut("Failed to get GCHeaps: integer overflow error\n"); + return FALSE; + } + CLRDATA_ADDRESS *heapAddrs = (CLRDATA_ADDRESS*)alloca(dwAllocSize); + if (g_sos->GetGCHeapList(dwNHeaps, heapAddrs, NULL) != S_OK) + { + ExtOut("Failed to get GCHeaps\n"); + return FALSE; + } + + DWORD n; + for (n = 0; n < dwNHeaps; n ++) + { + DacpGcHeapDetails heapDetails; + if (heapDetails.Request(g_sos, heapAddrs[n]) != S_OK) + { + ExtOut("Error requesting details\n"); + return FALSE; + } + + if (!GCHeapTraverse (heapDetails, &allocInfo, pFunc, token, verify)) + { + ExtOut("Traversing a gc heap failed\n"); + return FALSE; + } + } + } + + return TRUE; +} + +GCHeapSnapshot::GCHeapSnapshot() +{ + m_isBuilt = FALSE; + m_heapDetails = NULL; +} + +/////////////////////////////////////////////////////////// +SegmentLookup::SegmentLookup() +{ + m_iSegmentsSize = m_iSegmentCount = 0; + + m_segments = new DacpHeapSegmentData[nSegLookupStgIncrement]; + if (m_segments == NULL) + { + ReportOOM(); + } + else + { + m_iSegmentsSize = nSegLookupStgIncrement; + } +} + +BOOL SegmentLookup::AddSegment(DacpHeapSegmentData *pData) +{ + // appends the address of a new (initialized) instance of DacpHeapSegmentData to the list of segments + // (m_segments) adding space for a segment when necessary. + // @todo Microsoft: The field name m_iSegmentSize is a little misleading. It's not the size in bytes, + // but the number of elements allocated for the array. It probably should have been named something like + // m_iMaxSegments instead. + if (m_iSegmentCount >= m_iSegmentsSize) + { + // expand buffer--allocate enough space to hold the elements we already have plus nSegLookupStgIncrement + // more elements + DacpHeapSegmentData *pNewBuffer = new DacpHeapSegmentData[m_iSegmentsSize+nSegLookupStgIncrement]; + if (pNewBuffer==NULL) + return FALSE; + + // copy the old elements into the new array + memcpy(pNewBuffer, m_segments, sizeof(DacpHeapSegmentData)*m_iSegmentsSize); + + // record the new number of elements available + m_iSegmentsSize+=nSegLookupStgIncrement; + + // delete the old array + delete [] m_segments; + + // set m_segments to point to the new array + m_segments = pNewBuffer; + } + + // add pData to the array + m_segments[m_iSegmentCount++] = *pData; + + return TRUE; +} + +SegmentLookup::~SegmentLookup() +{ + if (m_segments) + { + delete [] m_segments; + m_segments = NULL; + } +} + +void SegmentLookup::Clear() +{ + m_iSegmentCount = 0; +} + +CLRDATA_ADDRESS SegmentLookup::GetHeap(CLRDATA_ADDRESS object, BOOL& bFound) +{ + CLRDATA_ADDRESS ret = NULL; + bFound = FALSE; + + // Visit our segments + for (int i=0; i<m_iSegmentCount; i++) + { + if (TO_TADDR(m_segments[i].mem) <= TO_TADDR(object) && + TO_TADDR(m_segments[i].highAllocMark) > TO_TADDR(object)) + { + ret = m_segments[i].gc_heap; + bFound = TRUE; + break; + } + } + + return ret; +} + +/////////////////////////////////////////////////////////////////////////// + +BOOL GCHeapSnapshot::Build() +{ + Clear(); + + m_isBuilt = FALSE; + + ///- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + /// 1. Get some basic information such as the heap type (SVR or WKS), how many heaps there are, mode and max generation + /// (See code:ClrDataAccess::RequestGCHeapData) + ///- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if (m_gcheap.Request(g_sos) != S_OK) + { + ExtOut("Error requesting GC Heap data\n"); + return FALSE; + } + + ArrayHolder<CLRDATA_ADDRESS> heapAddrs = NULL; + + ///- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + /// 2. Get a list of the addresses of the heaps when we have multiple heaps in server mode + ///- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if (m_gcheap.bServerMode) + { + UINT AllocSize; + // allocate an array to hold the starting addresses of each heap when we're in server mode + if (!ClrSafeInt<UINT>::multiply(sizeof(CLRDATA_ADDRESS), m_gcheap.HeapCount, AllocSize) || + (heapAddrs = new CLRDATA_ADDRESS [m_gcheap.HeapCount]) == NULL) + { + ReportOOM(); + return FALSE; + } + + // and initialize it with their addresses (see code:ClrDataAccess::RequestGCHeapList + // for details) + if (g_sos->GetGCHeapList(m_gcheap.HeapCount, heapAddrs, NULL) != S_OK) + { + ExtOut("Failed to get GCHeaps\n"); + return FALSE; + } + } + + ///- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + /// 3. Get some necessary information about each heap, such as the card table location, the generation + /// table, the heap bounds, etc., and retrieve the heap segments + ///- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + // allocate an array to hold the information + m_heapDetails = new DacpGcHeapDetails[m_gcheap.HeapCount]; + + if (m_heapDetails == NULL) + { + ReportOOM(); + return FALSE; + } + + // get the heap information for each heap + // See code:ClrDataAccess::RequestGCHeapDetails for details + for (UINT n = 0; n < m_gcheap.HeapCount; n ++) + { + if (m_gcheap.bServerMode) + { + if (m_heapDetails[n].Request(g_sos, heapAddrs[n]) != S_OK) + { + ExtOut("Error requesting details\n"); + return FALSE; + } + } + else + { + if (m_heapDetails[n].Request(g_sos) != S_OK) + { + ExtOut("Error requesting details\n"); + return FALSE; + } + } + + // now get information about the heap segments for this heap + if (!AddSegments(m_heapDetails[n])) + { + ExtOut("Failed to retrieve segments for gc heap\n"); + return FALSE; + } + } + + m_isBuilt = TRUE; + return TRUE; +} + +BOOL GCHeapSnapshot::AddSegments(DacpGcHeapDetails& details) +{ + int n = 0; + DacpHeapSegmentData segment; + + // This array of two addresses gives us access to all the segments. The generation segments are linked + // to each other, starting with the maxGeneration segment. The second address gives us the large object heap. + CLRDATA_ADDRESS AddrSegs[] = + { + details.generation_table[GetMaxGeneration()].start_segment, + details.generation_table[GetMaxGeneration()+1].start_segment // large object heap + }; + + // this loop will get information for all the heap segments in this heap. The outer loop iterates once + // for the "normal" generation segments and once for the large object heap. The inner loop follows the chain + // of segments rooted at AddrSegs[i] + for (unsigned int i = 0; i < sizeof(AddrSegs)/sizeof(AddrSegs[0]); ++i) + { + CLRDATA_ADDRESS AddrSeg = AddrSegs[i]; + + while (AddrSeg != NULL) + { + if (IsInterrupt()) + { + return FALSE; + } + // Initialize segment by copying fields from the target's heap segment at AddrSeg. + // See code:ClrDataAccess::RequestGCHeapSegment for details. + if (segment.Request(g_sos, AddrSeg, details) != S_OK) + { + ExtOut("Error requesting heap segment %p\n", SOS_PTR(AddrSeg)); + return FALSE; + } + if (n++ > nMaxHeapSegmentCount) // that would be insane + { + ExtOut("More than %d heap segments, there must be an error\n", nMaxHeapSegmentCount); + return FALSE; + } + + // add the new segment to the array of segments. This will expand the array if necessary + if (!m_segments.AddSegment(&segment)) + { + ExtOut("strike: Failed to store segment\n"); + return FALSE; + } + // get the next segment in the chain + AddrSeg = segment.next; + } + } + + return TRUE; +} + +void GCHeapSnapshot::Clear() +{ + if (m_heapDetails != NULL) + { + delete [] m_heapDetails; + m_heapDetails = NULL; + } + + m_segments.Clear(); + + m_isBuilt = FALSE; +} + +GCHeapSnapshot g_snapshot; + +DacpGcHeapDetails *GCHeapSnapshot::GetHeap(CLRDATA_ADDRESS objectPointer) +{ + // We need bFound because heap will be NULL if we are Workstation Mode. + // We still need a way to know if the address was found in our segment + // list. + BOOL bFound = FALSE; + CLRDATA_ADDRESS heap = m_segments.GetHeap(objectPointer, bFound); + if (heap) + { + for (UINT i=0; i<m_gcheap.HeapCount; i++) + { + if (m_heapDetails[i].heapAddr == heap) + return m_heapDetails + i; + } + } + else if (!m_gcheap.bServerMode) + { + if (bFound) + { + return m_heapDetails; + } + } + + // Not found + return NULL; +} + +// TODO: Do we need to handle the LOH here? +int GCHeapSnapshot::GetGeneration(CLRDATA_ADDRESS objectPointer) +{ + DacpGcHeapDetails *pDetails = GetHeap(objectPointer); + if (pDetails == NULL) + { + ExtOut("Object %p has no generation\n", SOS_PTR(objectPointer)); + return 0; + } + + TADDR taObj = TO_TADDR(objectPointer); + // The DAC doesn't fill the generation table with true CLRDATA_ADDRESS values + // but rather with ULONG64 values (i.e. non-sign-extended 64-bit values) + // We use the TO_TADDR below to ensure we won't break if this will ever + // be fixed in the DAC. + if (taObj >= TO_TADDR(pDetails->generation_table[0].allocation_start) && + taObj <= TO_TADDR(pDetails->alloc_allocated)) + return 0; + + if (taObj >= TO_TADDR(pDetails->generation_table[1].allocation_start) && + taObj <= TO_TADDR(pDetails->generation_table[0].allocation_start)) + return 1; + + return 2; +} + + +DWORD_PTR g_trav_totalSize = 0; +DWORD_PTR g_trav_wastedSize = 0; + +void LoaderHeapTraverse(CLRDATA_ADDRESS blockData,size_t blockSize,BOOL blockIsCurrentBlock) +{ + DWORD_PTR dwAddr1; + DWORD_PTR curSize = 0; + char ch; + for (dwAddr1 = (DWORD_PTR)blockData; + dwAddr1 < (DWORD_PTR)blockData + blockSize; + dwAddr1 += OSPageSize()) + { + if (IsInterrupt()) + break; + if (SafeReadMemory(dwAddr1, &ch, sizeof(ch), NULL)) + { + curSize += OSPageSize(); + } + else + break; + } + + if (!blockIsCurrentBlock) + { + g_trav_wastedSize += blockSize - curSize; + } + + g_trav_totalSize += curSize; + ExtOut("%p(%x:%x) ", SOS_PTR(blockData), blockSize, curSize); +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function prints out the size for various heaps. * +* total - the total size of the heap * +* wasted - the amount of size wasted by the heap. * +* * +\**********************************************************************/ +void PrintHeapSize(DWORD_PTR total, DWORD_PTR wasted) +{ + ExtOut("Size: 0x%" POINTERSIZE_TYPE "x (%" POINTERSIZE_TYPE "lu) bytes", total, total); + if (wasted) + ExtOut(" total, 0x%" POINTERSIZE_TYPE "x (%" POINTERSIZE_TYPE "lu) bytes wasted", wasted, wasted); + ExtOut(".\n"); +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function prints out the size information for the JIT heap. * +* * +* Returns: The size of this heap. * +* * +\**********************************************************************/ +DWORD_PTR JitHeapInfo() +{ + // walk ExecutionManager__m_pJitList + unsigned int count = 0; + if (FAILED(g_sos->GetJitManagerList(0, NULL, &count))) + { + ExtOut("Unable to get JIT info\n"); + return 0; + } + + ArrayHolder<DacpJitManagerInfo> pArray = new DacpJitManagerInfo[count]; + if (pArray==NULL) + { + ReportOOM(); + return 0; + } + + if (g_sos->GetJitManagerList(count, pArray, NULL) != S_OK) + { + ExtOut("Unable to get array of JIT Managers\n"); + return 0; + } + + DWORD_PTR totalSize = 0; + DWORD_PTR wasted = 0; + + for (unsigned int n=0; n < count; n++) + { + if (IsInterrupt()) + break; + + if (IsMiIL(pArray[n].codeType)) // JIT + { + unsigned int heapCount = 0; + if (FAILED(g_sos->GetCodeHeapList(pArray[n].managerAddr, 0, NULL, &heapCount))) + { + ExtOut("Error getting EEJitManager code heaps\n"); + break; + } + + if (heapCount > 0) + { + ArrayHolder<DacpJitCodeHeapInfo> codeHeapInfo = new DacpJitCodeHeapInfo[heapCount]; + if (codeHeapInfo == NULL) + { + ReportOOM(); + break; + } + + if (g_sos->GetCodeHeapList(pArray[n].managerAddr, heapCount, codeHeapInfo, NULL) != S_OK) + { + ExtOut("Unable to get code heap info\n"); + break; + } + + for (unsigned int iHeaps = 0; iHeaps < heapCount; iHeaps++) + { + if (IsInterrupt()) + break; + + if (codeHeapInfo[iHeaps].codeHeapType == CODEHEAP_LOADER) + { + ExtOut("LoaderCodeHeap: "); + totalSize += LoaderHeapInfo(codeHeapInfo[iHeaps].LoaderHeap, &wasted); + } + else if (codeHeapInfo[iHeaps].codeHeapType == CODEHEAP_HOST) + { + ExtOut("HostCodeHeap: "); + ExtOut("%p ", SOS_PTR(codeHeapInfo[iHeaps].HostData.baseAddr)); + DWORD dwSize = (DWORD)(codeHeapInfo[iHeaps].HostData.currentAddr - codeHeapInfo[iHeaps].HostData.baseAddr); + PrintHeapSize(dwSize, 0); + totalSize += dwSize; + } + } + } + } + else if (!IsMiNative(pArray[n].codeType)) // ignore native heaps for now + { + ExtOut("Unknown Jit encountered, ignored\n"); + } + } + + ExtOut("Total size: "); + PrintHeapSize(totalSize, wasted); + + return totalSize; +} + + +/**********************************************************************\ +* Routine Description: * +* * +* This function prints out the loader heap info for a single AD. * +* pLoaderHeapAddr - pointer to the loader heap * +* wasted - a pointer to store the number of bytes wasted in this * +* VSDHeap (this pointer can be NULL) * +* * +* Returns: The size of this heap. * +* * +\**********************************************************************/ +DWORD_PTR LoaderHeapInfo(CLRDATA_ADDRESS pLoaderHeapAddr, DWORD_PTR *wasted) +{ + g_trav_totalSize = 0; + g_trav_wastedSize = 0; + + if (pLoaderHeapAddr) + g_sos->TraverseLoaderHeap(pLoaderHeapAddr, LoaderHeapTraverse); + + PrintHeapSize(g_trav_totalSize, g_trav_wastedSize); + + if (wasted) + *wasted += g_trav_wastedSize; + return g_trav_totalSize; +} + + +/**********************************************************************\ +* Routine Description: * +* * +* This function prints out the heap info for a single VSDHeap. * +* name - the name to print * +* type - the type of heap * +* appDomain - the app domain in which this resides * +* wasted - a pointer to store the number of bytes wasted in this * +* VSDHeap (this pointer can be NULL) * +* * +* Returns: The size of this heap. * +* * +\**********************************************************************/ +static DWORD_PTR PrintOneVSDHeap(const char *name, VCSHeapType type, CLRDATA_ADDRESS appDomain, DWORD_PTR *wasted) +{ + g_trav_totalSize = 0; g_trav_wastedSize = 0; + + ExtOut(name); + g_sos->TraverseVirtCallStubHeap(appDomain, type, LoaderHeapTraverse); + + PrintHeapSize(g_trav_totalSize, g_trav_wastedSize); + if (wasted) + *wasted += g_trav_wastedSize; + return g_trav_totalSize; +} + + +/**********************************************************************\ +* Routine Description: * +* * +* This function prints out the heap info for VSDHeaps. * +* appDomain - The AppDomain to print info for. * +* wasted - a pointer to store the number of bytes wasted in this * +* AppDomain (this pointer can be NULL) * +* * +* Returns: The size of this heap. * +* * +\**********************************************************************/ +DWORD_PTR VSDHeapInfo(CLRDATA_ADDRESS appDomain, DWORD_PTR *wasted) +{ + DWORD_PTR totalSize = 0; + + if (appDomain) + { + totalSize += PrintOneVSDHeap(" IndcellHeap: ", IndcellHeap, appDomain, wasted); + totalSize += PrintOneVSDHeap(" LookupHeap: ", LookupHeap, appDomain, wasted); + totalSize += PrintOneVSDHeap(" ResolveHeap: ", ResolveHeap, appDomain, wasted); + totalSize += PrintOneVSDHeap(" DispatchHeap: ", DispatchHeap, appDomain, wasted); + totalSize += PrintOneVSDHeap(" CacheEntryHeap: ", CacheEntryHeap, appDomain, wasted); + } + + return totalSize; +} + + +/**********************************************************************\ +* Routine Description: * +* * +* This function prints out the heap info for a domain * +* name - the name of the domain (to be printed) * +* adPtr - a pointer to the AppDomain to print info about * +* outSize - a pointer to an int to store the size at (this may be * +* NULL) * +* outWasted - a pointer to an int to store the number of bytes this * +* domain is wasting (this may be NULL) * +* * +* returns: SUCCESS if we successfully printed out the domain heap * +* info, FAILED otherwise; if FAILED, outSize and * +* outWasted are untouched. * +* * +\**********************************************************************/ +HRESULT PrintDomainHeapInfo(const char *name, CLRDATA_ADDRESS adPtr, DWORD_PTR *outSize, DWORD_PTR *outWasted) +{ + DacpAppDomainData appDomain; + HRESULT hr = appDomain.Request(g_sos, adPtr); + if (FAILED(hr)) + { + ExtOut("Unable to get information for %s.\n", name); + return hr; + } + + ExtOut("--------------------------------------\n"); + + const int column = 19; + ExtOut("%s:", name); + WhitespaceOut(column - (int)strlen(name) - 1); + DMLOut("%s\n", DMLDomain(adPtr)); + + DWORD_PTR domainHeapSize = 0; + DWORD_PTR wasted = 0; + + ExtOut("LowFrequencyHeap: "); + domainHeapSize += LoaderHeapInfo(appDomain.pLowFrequencyHeap, &wasted); + + ExtOut("HighFrequencyHeap: "); + domainHeapSize += LoaderHeapInfo(appDomain.pHighFrequencyHeap, &wasted); + + ExtOut("StubHeap: "); + domainHeapSize += LoaderHeapInfo(appDomain.pStubHeap, &wasted); + + ExtOut("Virtual Call Stub Heap:\n"); + domainHeapSize += VSDHeapInfo(appDomain.AppDomainPtr, &wasted); + + ExtOut("Total size: "); + PrintHeapSize(domainHeapSize, wasted); + + if (outSize) + *outSize += domainHeapSize; + if (outWasted) + *outWasted += wasted; + + return hr; +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function prints out the heap info for a list of modules. * +* moduleList - an array of modules * +* count - the number of modules in moduleList * +* type - the type of heap * +* outWasted - a pointer to store the number of bytes wasted in this * +* heap (this pointer can be NULL) * +* * +* Returns: The size of this heap. * +* * +\**********************************************************************/ +DWORD_PTR PrintModuleHeapInfo(__out_ecount(count) DWORD_PTR *moduleList, int count, ModuleHeapType type, DWORD_PTR *outWasted) +{ + DWORD_PTR toReturn = 0; + DWORD_PTR wasted = 0; + + if (IsMiniDumpFile()) + { + ExtOut("<no information>\n"); + } + else + { + DWORD_PTR thunkHeapSize = 0; + + for (int i = 0; i < count; i++) + { + CLRDATA_ADDRESS addr = moduleList[i]; + DacpModuleData dmd; + if (dmd.Request(g_sos, addr) != S_OK) + { + ExtOut("Unable to read module %p\n", SOS_PTR(addr)); + } + else + { + DMLOut("Module %s: ", DMLModule(addr)); + CLRDATA_ADDRESS heap = type == ModuleHeapType_ThunkHeap ? dmd.pThunkHeap : dmd.pLookupTableHeap; + thunkHeapSize += LoaderHeapInfo(heap, &wasted); + } + } + + ExtOut("Total size: " WIN86_8SPACES); + PrintHeapSize(thunkHeapSize, wasted); + + toReturn = thunkHeapSize; + } + + if (outWasted) + *outWasted += wasted; + + return toReturn; +} diff --git a/src/ToolBox/SOS/Strike/exts.cpp b/src/ToolBox/SOS/Strike/exts.cpp new file mode 100644 index 0000000000..0b1f976cc2 --- /dev/null +++ b/src/ToolBox/SOS/Strike/exts.cpp @@ -0,0 +1,435 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ==++== +// + +// +// ==--== +#include "exts.h" +#include "disasm.h" +#ifndef FEATURE_PAL +#include "EventCallbacks.h" + +#define VER_PRODUCTVERSION_W (0x0100) + +// +// globals +// +EXT_API_VERSION ApiVersion = { (VER_PRODUCTVERSION_W >> 8), (VER_PRODUCTVERSION_W & 0xff), EXT_API_VERSION_NUMBER64, 0 }; +WINDBG_EXTENSION_APIS ExtensionApis; + +ULONG PageSize; + +OnUnloadTask *OnUnloadTask::s_pUnloadTaskList = NULL; + +// +// Valid for the lifetime of the debug session. +// + +ULONG TargetMachine; +BOOL Connected; +ULONG g_TargetClass; +DWORD_PTR g_filterHint = 0; + +PDEBUG_CLIENT g_ExtClient; +PDEBUG_DATA_SPACES2 g_ExtData2; +PDEBUG_SYMBOLS2 g_ExtSymbols2; +PDEBUG_ADVANCED3 g_ExtAdvanced3; +PDEBUG_CLIENT g_pCallbacksClient; + +#else + +DebugClient* g_DebugClient; +ILLDBServices* g_ExtServices; + +#endif // FEATURE_PAL + +IMachine* g_targetMachine = NULL; +BOOL g_bDacBroken = FALSE; + +PDEBUG_CONTROL2 g_ExtControl; +PDEBUG_DATA_SPACES g_ExtData; +PDEBUG_REGISTERS g_ExtRegisters; +PDEBUG_SYMBOLS g_ExtSymbols; +PDEBUG_SYSTEM_OBJECTS g_ExtSystem; + +#define SOS_ExtQueryFailGo(var, riid) \ + var = NULL; \ + if ((Status = client->QueryInterface(__uuidof(riid), \ + (void **)&var)) != S_OK) \ + { \ + goto Fail; \ + } + +// Queries for all debugger interfaces. +#ifndef FEATURE_PAL +extern "C" HRESULT +ExtQuery(PDEBUG_CLIENT client) +{ + g_ExtClient = client; +#else +extern "C" HRESULT +ExtQuery(ILLDBServices* services) +{ + g_ExtServices = services; + DebugClient* client = new DebugClient(services); + g_DebugClient = client; +#endif + HRESULT Status; + SOS_ExtQueryFailGo(g_ExtControl, IDebugControl2); + SOS_ExtQueryFailGo(g_ExtData, IDebugDataSpaces); + SOS_ExtQueryFailGo(g_ExtRegisters, IDebugRegisters); + SOS_ExtQueryFailGo(g_ExtSymbols, IDebugSymbols); + SOS_ExtQueryFailGo(g_ExtSystem, IDebugSystemObjects); +#ifndef FEATURE_PAL + SOS_ExtQueryFailGo(g_ExtData2, IDebugDataSpaces2); + SOS_ExtQueryFailGo(g_ExtSymbols2, IDebugSymbols2); + SOS_ExtQueryFailGo(g_ExtAdvanced3, IDebugAdvanced3); +#endif // FEATURE_PAL + return S_OK; + + Fail: + if (Status == E_OUTOFMEMORY) + ReportOOM(); + + ExtRelease(); + return Status; +} + +extern "C" HRESULT +ArchQuery(void) +{ + ULONG targetArchitecture; + IMachine* targetMachine = NULL; + + g_ExtControl->GetExecutingProcessorType(&targetArchitecture); + +#ifdef SOS_TARGET_AMD64 + if(targetArchitecture == IMAGE_FILE_MACHINE_AMD64) + { + targetMachine = AMD64Machine::GetInstance(); + } +#endif // SOS_TARGET_AMD64 +#ifdef SOS_TARGET_X86 + if (targetArchitecture == IMAGE_FILE_MACHINE_I386) + { + targetMachine = X86Machine::GetInstance(); + } +#endif // SOS_TARGET_X86 +#ifdef SOS_TARGET_ARM + if (targetArchitecture == IMAGE_FILE_MACHINE_ARMNT) + { + targetMachine = ARMMachine::GetInstance(); + } +#endif // SOS_TARGET_ARM +#ifdef SOS_TARGET_ARM64 + if (targetArchitecture == IMAGE_FILE_MACHINE_ARM64) + { + targetMachine = ARM64Machine::GetInstance(); + } +#endif // SOS_TARGET_ARM64 + + if (targetMachine == NULL) + { + g_targetMachine = NULL; + ExtErr("SOS does not support the current target architecture.\n"); + return E_FAIL; + } + + g_targetMachine = targetMachine; + return S_OK; +} + +// Cleans up all debugger interfaces. +void +ExtRelease(void) +{ + EXT_RELEASE(g_ExtControl); + EXT_RELEASE(g_ExtData); + EXT_RELEASE(g_ExtRegisters); + EXT_RELEASE(g_ExtSymbols); + EXT_RELEASE(g_ExtSystem); +#ifndef FEATURE_PAL + EXT_RELEASE(g_ExtData2); + EXT_RELEASE(g_ExtSymbols2); + EXT_RELEASE(g_ExtAdvanced3); + g_ExtClient = NULL; +#else + EXT_RELEASE(g_DebugClient); + g_ExtServices = NULL; +#endif // FEATURE_PAL +} + +#ifndef FEATURE_PAL + +BOOL IsMiniDumpFileNODAC(); +extern HMODULE g_hInstance; + +// This function throws an exception that can be caught by the debugger, +// instead of allowing the default CRT behavior of invoking Watson to failfast. +void __cdecl _SOS_invalid_parameter( + const WCHAR * expression, + const WCHAR * function, + const WCHAR * file, + unsigned int line, + uintptr_t pReserved +) +{ + ExtErr("\nSOS failure!\n"); + throw "SOS failure"; +} + +// Unregisters our windbg event callbacks and releases the client, event callback objects +void CleanupEventCallbacks() +{ + if(g_pCallbacksClient != NULL) + { + g_pCallbacksClient->Release(); + g_pCallbacksClient = NULL; + } +} + +bool g_Initialized = false; + +extern "C" +HRESULT +CALLBACK +DebugExtensionInitialize(PULONG Version, PULONG Flags) +{ + IDebugClient *DebugClient; + PDEBUG_CONTROL DebugControl; + HRESULT Hr; + + *Version = DEBUG_EXTENSION_VERSION(1, 0); + *Flags = 0; + + if (g_Initialized) + { + return S_OK; + } + g_Initialized = true; + + if ((Hr = DebugCreate(__uuidof(IDebugClient), + (void **)&DebugClient)) != S_OK) + { + return Hr; + } + if ((Hr = DebugClient->QueryInterface(__uuidof(IDebugControl), + (void **)&DebugControl)) != S_OK) + { + return Hr; + } + + ExtensionApis.nSize = sizeof (ExtensionApis); + if ((Hr = DebugControl->GetWindbgExtensionApis64(&ExtensionApis)) != S_OK) + { + return Hr; + } + + // Fixes the "Unable to read dynamic function table entries" error messages by disabling the WinDbg security + // feature that prevents the loading of unknown out of proc tack walkers. + DebugControl->Execute(DEBUG_OUTCTL_IGNORE, ".settings set EngineInitialization.VerifyFunctionTableCallbacks=false", + DEBUG_EXECUTE_NOT_LOGGED | DEBUG_EXECUTE_NO_REPEAT); + + ExtQuery(DebugClient); + if (IsMiniDumpFileNODAC()) + { + ExtOut ( + "----------------------------------------------------------------------------\n" + "The user dump currently examined is a minidump. Consequently, only a subset\n" + "of sos.dll functionality will be available. If needed, attaching to the live\n" + "process or debugging a full dump will allow access to sos.dll's full feature\n" + "set.\n" + "To create a full user dump use the command: .dump /ma <filename>\n" + "----------------------------------------------------------------------------\n"); + } + ExtRelease(); + + OnUnloadTask::Register(CleanupEventCallbacks); + g_pCallbacksClient = DebugClient; + EventCallbacks* pCallbacksObj = new EventCallbacks(DebugClient); + IDebugEventCallbacks* pCallbacks = NULL; + pCallbacksObj->QueryInterface(__uuidof(IDebugEventCallbacks), (void**)&pCallbacks); + pCallbacksObj->Release(); + + if(FAILED(Hr = g_pCallbacksClient->SetEventCallbacks(pCallbacks))) + { + ExtOut ("SOS: Failed to register callback events\n"); + pCallbacks->Release(); + return Hr; + } + pCallbacks->Release(); + +#ifndef _ARM_ + // Make sure we do not tear down the debugger when a security function fails + // Since we link statically against CRT this will only affect the SOS module. + _set_invalid_parameter_handler(_SOS_invalid_parameter); +#endif + + DebugControl->Release(); + return S_OK; +} + +extern "C" +void +CALLBACK +DebugExtensionNotify(ULONG Notify, ULONG64 /*Argument*/) +{ + // + // The first time we actually connect to a target, get the page size + // + + if ((Notify == DEBUG_NOTIFY_SESSION_ACCESSIBLE) && (!Connected)) + { + IDebugClient *DebugClient; + PDEBUG_DATA_SPACES DebugDataSpaces; + PDEBUG_CONTROL DebugControl; + HRESULT Hr; + ULONG64 Page; + + if ((Hr = DebugCreate(__uuidof(IDebugClient), + (void **)&DebugClient)) == S_OK) + { + // + // Get the page size and PAE enable flag + // + + if ((Hr = DebugClient->QueryInterface(__uuidof(IDebugDataSpaces), + (void **)&DebugDataSpaces)) == S_OK) + { + if ((Hr = DebugDataSpaces->ReadDebuggerData( + DEBUG_DATA_MmPageSize, &Page, + sizeof(Page), NULL)) == S_OK) + { + PageSize = (ULONG)(ULONG_PTR)Page; + } + + DebugDataSpaces->Release(); + } + // + // Get the architecture type. + // + + if ((Hr = DebugClient->QueryInterface(__uuidof(IDebugControl), + (void **)&DebugControl)) == S_OK) + { + if ((Hr = DebugControl->GetActualProcessorType( + &TargetMachine)) == S_OK) + { + Connected = TRUE; + } + ULONG Qualifier; + if ((Hr = DebugControl->GetDebuggeeType(&g_TargetClass, &Qualifier)) == S_OK) + { + } + + DebugControl->Release(); + } + + DebugClient->Release(); + } + } + + + if (Notify == DEBUG_NOTIFY_SESSION_INACTIVE) + { + Connected = FALSE; + PageSize = 0; + TargetMachine = 0; + } + + return; +} + +extern "C" +void +CALLBACK +DebugExtensionUninitialize(void) +{ + // execute all registered cleanup tasks + OnUnloadTask::Run(); + return; +} + + +BOOL DllInit( + HANDLE /*hModule*/, + DWORD dwReason, + DWORD /*dwReserved*/ + ) +{ + switch (dwReason) { + case DLL_THREAD_ATTACH: + break; + + case DLL_THREAD_DETACH: + break; + + case DLL_PROCESS_DETACH: + break; + + case DLL_PROCESS_ATTACH: + break; + } + + return TRUE; +} + +BOOL WINAPI DllMain(HANDLE hInstance, DWORD dwReason, LPVOID lpReserved) +{ + if (dwReason == DLL_PROCESS_ATTACH) + { + g_hInstance = (HMODULE) hInstance; + } + return true; +} + +#else // FEATURE_PAL + +HRESULT +DebugClient::QueryInterface( + REFIID InterfaceId, + PVOID* Interface + ) +{ + if (InterfaceId == __uuidof(IUnknown) || + InterfaceId == __uuidof(IDebugControl2) || + InterfaceId == __uuidof(IDebugControl4) || + InterfaceId == __uuidof(IDebugDataSpaces) || + InterfaceId == __uuidof(IDebugSymbols) || + InterfaceId == __uuidof(IDebugSystemObjects) || + InterfaceId == __uuidof(IDebugRegisters)) + { + *Interface = this; + AddRef(); + return S_OK; + } + else + { + *Interface = NULL; + return E_NOINTERFACE; + } +} + +ULONG +DebugClient::AddRef() +{ + LONG ref = InterlockedIncrement(&m_ref); + return ref; +} + +ULONG +DebugClient::Release() +{ + LONG ref = InterlockedDecrement(&m_ref); + if (ref == 0) + { + m_lldbservices->Release(); + delete this; + } + return ref; +} + +#endif // FEATURE_PAL diff --git a/src/ToolBox/SOS/Strike/exts.h b/src/ToolBox/SOS/Strike/exts.h new file mode 100644 index 0000000000..36b5230c37 --- /dev/null +++ b/src/ToolBox/SOS/Strike/exts.h @@ -0,0 +1,513 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ==++== +// + +// +// ==--== +#ifndef __exts_h__ +#define __exts_h__ + +#define KDEXT_64BIT + +#include <windows.h> +#include <winternl.h> + +#if defined(_MSC_VER) +#pragma warning(disable:4245) // signed/unsigned mismatch +#pragma warning(disable:4100) // unreferenced formal parameter +#pragma warning(disable:4201) // nonstandard extension used : nameless struct/union +#pragma warning(disable:4127) // conditional expression is constant +#pragma warning(disable:4430) // missing type specifier: C++ doesn't support default-int +#endif +#include "strike.h" +#include <wdbgexts.h> +#include <dbgeng.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +// wdbgexts.h defines StackTrace which interferes with other parts of the +// system that use the StackTrace identifier +#ifdef StackTrace +#undef StackTrace +#endif + +#include "platformspecific.h" + +// We need to define the target address type. This has to be used in the +// functions that read directly from the debuggee address space, vs. using +// the DAC to read the DAC-ized data structures. +#include "daccess.h" + +#include "gcinfo.h" + +// Convert between CLRDATA_ADDRESS and TADDR. +#define TO_TADDR(cdaddr) ((TADDR)(cdaddr)) +#define TO_CDADDR(taddr) ((CLRDATA_ADDRESS)(LONG_PTR)(taddr)) + +// We also need a "correction" macro: there are a number of places in the DAC +// where instead of using the CLRDATA_ADDRESS sign-extension convention +// we 0-extend (most notably DacpGcHeapDetails) +#define NEED_DAC_CLRDATA_ADDRESS_CORRECTION 1 +#if NEED_DAC_CLRDATA_ADDRESS_CORRECTION == 1 + // the macro below "corrects" a CDADDR to always represent the + // sign-extended equivalent ULONG64 value of the original TADDR + #define UL64_TO_CDA(ul64) (TO_CDADDR(TO_TADDR(ul64))) +#else + #define UL64_TO_CDA(ul64) (ul64) +#endif // NEED_DAC_CLRDATA_ADDRESS_CORRECTION 1 + +// The macro below removes the sign extension, returning the +// equivalent ULONG64 value to the original TADDR. Useful when +// printing CDA values. +#define CDA_TO_UL64(cda) ((ULONG64)(TO_TADDR(cda))) + +typedef struct _TADDR_RANGE +{ + TADDR start; + TADDR end; +} TADDR_RANGE; + +typedef struct _TADDR_SEGINFO +{ + TADDR segAddr; + TADDR start; + TADDR end; +} TADDR_SEGINFO; + +#include "util.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Cleanup tasks to be executed when the extension is unloaded +class OnUnloadTask +{ +public: + FORCEINLINE static void Register(void (*fn)()) + { + // append a new unload task to the head of the list + OnUnloadTask *pNew = new OnUnloadTask(fn); + pNew->pNext = s_pUnloadTaskList; + s_pUnloadTaskList = pNew; + } + + static void Run() + { + // walk the list of UnloadTasks and execute each in turn + OnUnloadTask* pCur = s_pUnloadTaskList; + while (pCur != NULL) + { + OnUnloadTask* pNext = pCur->pNext; + pCur->OnUnloadFn(); + delete pCur; + pCur = pNext; + } + s_pUnloadTaskList = NULL; + } + +private: + OnUnloadTask(void(*fn)()) + : OnUnloadFn(fn) + , pNext(NULL) + { } + +private: + void (*OnUnloadFn)(); + OnUnloadTask* pNext; + + static OnUnloadTask *s_pUnloadTaskList; +}; + +#ifndef MINIDUMP + +#define EXIT_API ExtRelease + +// Safe release and NULL. +#define EXT_RELEASE(Unk) \ + ((Unk) != NULL ? ((Unk)->Release(), (Unk) = NULL) : NULL) + +extern PDEBUG_CONTROL2 g_ExtControl; +extern PDEBUG_DATA_SPACES g_ExtData; +extern PDEBUG_SYMBOLS g_ExtSymbols; +extern PDEBUG_SYSTEM_OBJECTS g_ExtSystem; +extern PDEBUG_REGISTERS g_ExtRegisters; + +#ifndef FEATURE_PAL + +// Global variables initialized by query. +extern PDEBUG_CLIENT g_ExtClient; +extern PDEBUG_DATA_SPACES2 g_ExtData2; +extern PDEBUG_SYMBOLS2 g_ExtSymbols2; +extern PDEBUG_ADVANCED3 g_ExtAdvanced3; + +#else // FEATURE_PAL + +extern ILLDBServices* g_ExtServices; + +#endif // FEATURE_PAL + +HRESULT +ExtQuery(PDEBUG_CLIENT client); + +HRESULT +ArchQuery(void); + +void +ExtRelease(void); + +#ifdef _DEBUG +extern DWORD_PTR g_filterHint; +#endif + +extern BOOL ControlC; + +inline BOOL IsInterrupt() +{ + if (!ControlC && g_ExtControl->GetInterrupt() == S_OK) + { + ExtOut("Command cancelled at the user's request.\n"); + ControlC = TRUE; + } + + return ControlC; +} + +// +// undef the wdbgexts +// +#undef DECLARE_API + +#define DECLARE_API(extension) \ +CPPMOD HRESULT CALLBACK extension(PDEBUG_CLIENT client, PCSTR args) + +class __ExtensionCleanUp +{ +public: + __ExtensionCleanUp(){} + ~__ExtensionCleanUp(){ExtRelease();} +}; + +inline void EENotLoadedMessage(HRESULT Status) +{ + ExtOut("Failed to find runtime DLL (%s), 0x%08x\n", MAKEDLLNAME_A("coreclr"), Status); + ExtOut("Extension commands need it in order to have something to do.\n"); +} + +inline void DACMessage(HRESULT Status) +{ + ExtOut("Failed to load data access DLL, 0x%08x\n", Status); +#ifndef FEATURE_PAL + ExtOut("Verify that 1) you have a recent build of the debugger (6.2.14 or newer)\n"); + ExtOut(" 2) the file mscordacwks.dll that matches your version of coreclr.dll is \n"); + ExtOut(" in the version directory or on the symbol path\n"); + ExtOut(" 3) or, if you are debugging a dump file, verify that the file \n"); + ExtOut(" mscordacwks_<arch>_<arch>_<version>.dll is on your symbol path.\n"); + ExtOut(" 4) you are debugging on supported cross platform architecture as \n"); + ExtOut(" the dump file. For example, an ARM dump file must be debugged\n"); + ExtOut(" on an X86 or an ARM machine; an AMD64 dump file must be\n"); + ExtOut(" debugged on an AMD64 machine.\n"); + ExtOut("\n"); + ExtOut("You can also run the debugger command .cordll to control the debugger's\n"); + ExtOut("load of mscordacwks.dll. .cordll -ve -u -l will do a verbose reload.\n"); + ExtOut("If that succeeds, the SOS command should work on retry.\n"); + ExtOut("\n"); + ExtOut("If you are debugging a minidump, you need to make sure that your executable\n"); + ExtOut("path is pointing to coreclr.dll as well.\n"); +#else // FEATURE_PAL + if (Status == CORDBG_E_MISSING_DEBUGGER_EXPORTS) + { + ExtOut("You can run the debugger command 'setclrpath' to control the load of %s.\n", MAKEDLLNAME_A("mscordaccore")); + ExtOut("If that succeeds, the SOS command should work on retry.\n"); + } + else + { + ExtOut("Can not load or initialize %s. The target runtime may not be initialized.\n", MAKEDLLNAME_A("mscordaccore")); + } +#endif // FEATURE_PAL +} + +HRESULT CheckEEDll(); + +#define INIT_API_NOEE() \ + HRESULT Status; \ + __ExtensionCleanUp __extensionCleanUp; \ + if ((Status = ExtQuery(client)) != S_OK) return Status; \ + if ((Status = ArchQuery()) != S_OK) return Status; \ + ControlC = FALSE; \ + g_bDacBroken = TRUE; \ + g_clrData = NULL; \ + g_sos = NULL; + +#define INIT_API_EE() \ + if ((Status = CheckEEDll()) != S_OK) \ + { \ + EENotLoadedMessage(Status); \ + return Status; \ + } + +#define INIT_API_NODAC() \ + INIT_API_NOEE() \ + INIT_API_EE() + +#define INIT_API_DAC() \ + if ((Status = LoadClrDebugDll()) != S_OK) \ + { \ + DACMessage(Status); \ + return Status; \ + } \ + g_bDacBroken = FALSE; \ + /* If LoadClrDebugDll() succeeded make sure we release g_clrData. */ \ + /* We may reconsider caching g_clrData in the future */ \ + ToRelease<IXCLRDataProcess> spIDP(g_clrData); \ + ToRelease<ISOSDacInterface> spISD(g_sos); \ + ResetGlobals(); + +#define INIT_API() \ + INIT_API_NODAC() \ + INIT_API_DAC() + +// Attempt to initialize DAC and SOS globals, but do not "return" on failure. +// Instead, mark the failure to initialize the DAC by setting g_bDacBroken to TRUE. +// This should be used from extension commands that should work OK even when no +// runtime is loaded in the debuggee, e.g. DumpLog, DumpStack. These extensions +// and functions they call should test g_bDacBroken before calling any DAC enabled +// feature. +#define INIT_API_NO_RET_ON_FAILURE() \ + INIT_API_NOEE() \ + if ((Status = CheckEEDll()) != S_OK) \ + { \ + ExtOut("Failed to find runtime DLL (%s), 0x%08x\n", MAKEDLLNAME_A("coreclr"), Status); \ + ExtOut("Some functionality may be impaired\n"); \ + } \ + else if ((Status = LoadClrDebugDll()) != S_OK) \ + { \ + ExtOut("Failed to load data access DLL (%s), 0x%08x\n", MAKEDLLNAME_A("mscordaccore"), Status); \ + ExtOut("Some functionality may be impaired\n"); \ + } \ + else \ + { \ + g_bDacBroken = FALSE; \ + ResetGlobals(); \ + } \ + /* If LoadClrDebugDll() succeeded make sure we release g_clrData. */ \ + /* We may reconsider caching g_clrData in the future */ \ + ToRelease<ISOSDacInterface> spISD(g_sos); \ + ToRelease<IXCLRDataProcess> spIDP(g_clrData); + +extern BOOL g_bDacBroken; + +#define PAGE_ALIGN64(Va) ((ULONG64)((Va) & ~((ULONG64) ((LONG64) (LONG) PageSize - 1)))) + +extern ULONG PageSize; + + +//----------------------------------------------------------------------------------------- +// +// Target platform abstraction +// +//----------------------------------------------------------------------------------------- + +// some needed forward declarations +struct StackTrace_SimpleContext; +struct GCEncodingInfo; +struct SOSEHInfo; +class GCDump; + +/// +/// IMachine interface +/// +/// Note: +/// The methods accepting target address args take them as size_t==DWORD_PTR==TADDR, +/// which means this can only provide cross platform support for same-word size +/// architectures (only ARM on x86 currently). Since this is not exposed outside SOS +/// and since the some-word-size limitation exists across EE/DAC/SOS this is not an +/// actual limitation. +/// + +class IMachine +{ +public: + // Returns the IMAGE_FILE_MACHINE_*** constant corresponding to the target machine + virtual ULONG GetPlatform() const = 0; + // Returns the size of the CONTEXT for the target machine + virtual ULONG GetContextSize() const = 0; + + // Disassembles a managed method specified by the IPBegin-IPEnd range + virtual void Unassembly( + TADDR IPBegin, + TADDR IPEnd, + TADDR IPAskedFor, + TADDR GCStressCodeCopy, + GCEncodingInfo *pGCEncodingInfo, + SOSEHInfo *pEHInfo, + BOOL bSuppressLines, + BOOL bDisplayOffsets) const = 0; + + // Validates whether retAddr represents a return address by unassembling backwards. + // If the instruction before retAddr represents a target-specific call instruction + // it attempts to identify the target of the call. If successful it sets *whereCalled + // to the call target, otherwise it sets it to 0xffffffff. + virtual void IsReturnAddress( + TADDR retAddr, + TADDR* whereCalled) const = 0; + + // If, while unwinding the stack, "PC" represents a known return address in + // KiUserExceptionDispatcher, "stack" is used to retrieve an exception context record + // in "cxr", and an exception record in "exr" + virtual BOOL GetExceptionContext ( + TADDR stack, + TADDR PC, + TADDR *cxrAddr, + CROSS_PLATFORM_CONTEXT * cxr, + TADDR *exrAddr, + PEXCEPTION_RECORD exr) const = 0; + + // Retrieves stack pointer, frame pointer, and instruction pointer from the target context + virtual TADDR GetSP(const CROSS_PLATFORM_CONTEXT & ctx) const = 0; + virtual TADDR GetBP(const CROSS_PLATFORM_CONTEXT & ctx) const = 0; + virtual TADDR GetIP(const CROSS_PLATFORM_CONTEXT & ctx) const = 0; + + // Fills dest's data fields from a target specific context + virtual void FillSimpleContext(StackTrace_SimpleContext * dest, LPVOID srcCtx) const = 0; + // Fills a target specific context, destCtx, from the idx-th location in a target specific + // array of contexts that start at srcCtx + virtual void FillTargetContext(LPVOID destCtx, LPVOID srcCtx, int idx = 0) const = 0; + + // Retrieve some target specific output strings + virtual LPCSTR GetDumpStackHeading() const = 0; + virtual LPCSTR GetDumpStackObjectsHeading() const = 0; + virtual LPCSTR GetSPName() const = 0; + // Retrieves the non-volatile registers reported to the GC + virtual void GetGCRegisters(LPCSTR** regNames, unsigned int* cntRegs) const = 0; + + typedef void (*printfFtn)(const char* fmt, ...); + // Dumps the GCInfo + virtual void DumpGCInfo(GCInfoToken gcInfoToken, unsigned methodSize, printfFtn gcPrintf, bool encBytes, bool bPrintHeader) const = 0; + +protected: + IMachine() {} + virtual ~IMachine() {} + +private: + IMachine(const IMachine& machine); // undefined + IMachine & operator=(const IMachine&); // undefined +}; // class IMachine + + +extern IMachine* g_targetMachine; + + +inline BOOL IsDbgTargetX86() { return g_targetMachine->GetPlatform() == IMAGE_FILE_MACHINE_I386; } +inline BOOL IsDbgTargetAmd64() { return g_targetMachine->GetPlatform() == IMAGE_FILE_MACHINE_AMD64; } +inline BOOL IsDbgTargetArm() { return g_targetMachine->GetPlatform() == IMAGE_FILE_MACHINE_ARMNT; } +inline BOOL IsDbgTargetWin64() { return IsDbgTargetAmd64(); } + +/* Returns the instruction pointer for the given CONTEXT. We need this and its family of + * functions because certain headers are inconsistantly included on the various platforms, + * meaning that we cannot use GetIP and GetSP as defined by CLR. + */ +inline CLRDATA_ADDRESS GetIP(const CROSS_PLATFORM_CONTEXT& context) +{ + return TO_CDADDR(g_targetMachine->GetIP(context)); +} + +/* Returns the stack pointer for the given CONTEXT. + */ +inline CLRDATA_ADDRESS GetSP(const CROSS_PLATFORM_CONTEXT& context) +{ + return TO_CDADDR(g_targetMachine->GetSP(context)); +} + +/* Returns the base/frame pointer for the given CONTEXT. + */ +inline CLRDATA_ADDRESS GetBP(const CROSS_PLATFORM_CONTEXT& context) +{ + return TO_CDADDR(g_targetMachine->GetBP(context)); +} + + +//----------------------------------------------------------------------------------------- +// +// api declaration macros & api access macros +// +//----------------------------------------------------------------------------------------- + +#ifndef FEATURE_PAL + +extern WINDBG_EXTENSION_APIS ExtensionApis; +#define GetExpression (ExtensionApis.lpGetExpressionRoutine) + +extern ULONG TargetMachine; +extern ULONG g_TargetClass; +extern ULONG g_VDbgEng; + +#else // FEATURE_PAL + +#define GetExpression(exp) g_ExtServices->GetExpression(exp) + +#endif // FEATURE_PAL + +#define CACHE_SIZE DT_OS_PAGE_SIZE + +struct ReadVirtualCache +{ + BYTE m_cache[CACHE_SIZE]; + TADDR m_startCache; + BOOL m_cacheValid; + ULONG m_cacheSize; + + ReadVirtualCache() { Clear(); } + HRESULT Read(TADDR Offset, PVOID Buffer, ULONG BufferSize, PULONG lpcbBytesRead); + void Clear() { m_cacheValid = FALSE; m_cacheSize = CACHE_SIZE; } +}; + +extern ReadVirtualCache *rvCache; + +#define MOVE(dst, src) rvCache->Read(TO_TADDR(src), &(dst), sizeof(dst), NULL) +#define MOVEBLOCK(dst, src, size) rvCache->Read(TO_TADDR(src), &(dst), size, NULL) + +#define moveN(dst, src) \ +{ \ + HRESULT ret = MOVE(dst, src); \ + if (FAILED(ret)) return ret; \ +} + +#define moveBlockN(dst, src, size) \ +{ \ + HRESULT ret = MOVEBLOCK(dst, src, size); \ + if (FAILED(ret)) return ret; \ +} + +// move cross-process: reads memory from the debuggee into +// debugger address space and returns in case of error +#define move_xp(dst, src) \ +{ \ + HRESULT ret = MOVE(dst, src); \ + if (FAILED(ret)) return; \ +} + +#define moveBlock(dst, src, size) \ +{ \ + HRESULT ret = MOVEBLOCK(dst, src, size); \ + if (FAILED(ret)) return; \ +} + +#ifdef __cplusplus +#define CPPMOD extern "C" +#else +#define CPPMOD +#endif + +#endif + +#ifdef __cplusplus +} +#endif + +#endif // __exts_h__ + diff --git a/src/ToolBox/SOS/Strike/gchist.cpp b/src/ToolBox/SOS/Strike/gchist.cpp new file mode 100644 index 0000000000..ee9d5b4033 --- /dev/null +++ b/src/ToolBox/SOS/Strike/gchist.cpp @@ -0,0 +1,636 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ==++== +// + +// +// ==--== +/**************************************************************************** +* STRIKE.C * +* Routines for the NTSD extension - STRIKE * +* * +* History: * +* 09/07/99 Microsoft Created * +* * +* * +\***************************************************************************/ +#include <windows.h> +#include <winternl.h> +#include <winver.h> +#include <wchar.h> + +#define NOEXTAPI +#define KDEXT_64BIT +#include <wdbgexts.h> +#undef DECLARE_API +#undef StackTrace + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <stddef.h> + +#include "strike.h" +// We need to define the target address type. This will be used in the +// functions that read directly from the debuggee address space, vs. using +// the DAC tgo read the DAC-ized data structures. +#include "daccess.h" +//#include "dbgeng.h" + + +#ifndef STRESS_LOG +#define STRESS_LOG +#endif // STRESS_LOG +#define STRESS_LOG_READONLY +#include "stresslog.h" +#include <dbghelp.h> + +#include "corhdr.h" +#include "dacprivate.h" + +#define CORHANDLE_MASK 0x1 +#define DEFINE_EXT_GLOBALS + +#include "util.h" + +#ifndef _ASSERTE +#ifdef _DEBUG +#define _ASSERTE(expr) \ + do { if (!(expr) ) { ExtOut(#expr); DebugBreak(); } } while (0) +#else // _DEBUG +#define _ASSERTE(expr) +#endif // _DEBUG else +#endif // !_ASSERTE + +#ifdef _MSC_VER +#pragma warning(disable:4244) // conversion from 'unsigned int' to 'unsigned short', possible loss of data +#pragma warning(disable:4189) // local variable is initialized but not referenced +#endif // _MSC_VER + +struct PlugRecord +{ + PlugRecord *next; + + size_t PlugStart; + size_t PlugEnd; + size_t Delta; + + PlugRecord() { ZeroMemory(this,sizeof(PlugRecord)); } +}; + +struct PromoteRecord +{ + PromoteRecord *next; + + size_t Root; + size_t Value; + size_t methodTable; + + PromoteRecord() { ZeroMemory(this,sizeof(PromoteRecord)); } +}; + +struct RelocRecord +{ + RelocRecord *next; + + size_t Root; + size_t PrevValue; + size_t NewValue; + size_t methodTable; + + RelocRecord() { ZeroMemory(this,sizeof(RelocRecord)); } +}; + +struct GCRecord +{ + ULONG64 GCCount; + + // BOOL IsComplete() { return bFinished && bHaveStart; } + + PlugRecord *PlugList; + RelocRecord *RelocList; + PromoteRecord *PromoteList; + + void AddPlug(PlugRecord& p) { + PlugRecord *pTmp = PlugList; + PlugList = new PlugRecord(p); + PlugList->next = pTmp; + } + + void AddReloc(RelocRecord& r) { + RelocRecord *pTmp = RelocList; + RelocList = new RelocRecord(r); + RelocList->next = pTmp; + } + + void AddPromote(PromoteRecord& r) { + PromoteRecord *pTmp = PromoteList; + PromoteList = new PromoteRecord(r); + PromoteList->next = pTmp; + } + + UINT PlugCount() { + UINT ret = 0; + PlugRecord *Iter = PlugList; + while (Iter) { + Iter = Iter->next; + ret++; + } + return ret; + } + + UINT RelocCount() { + UINT ret = 0; + RelocRecord *Iter = RelocList; + while (Iter) { + Iter = Iter->next; + ret++; + } + return ret; + } + + UINT PromoteCount() { + UINT ret = 0; + PromoteRecord *Iter = PromoteList; + while (Iter) { + Iter = Iter->next; + ret++; + } + return ret; + } + + void Clear() { + + PlugRecord *pTrav = PlugList; + while (pTrav) { + PlugRecord *pTmp = pTrav->next; + delete pTrav; + pTrav = pTmp; + } + + RelocRecord *pTravR = RelocList; + while (pTravR) { + RelocRecord *pTmp = pTravR->next; + delete pTravR; + pTravR = pTmp; + } + + PromoteRecord *pTravP = PromoteList; + while (pTravP) { + PromoteRecord *pTmp = pTravP->next; + delete pTravP; + pTravP = pTmp; + } + + ZeroMemory(this,sizeof(GCRecord)); + } + +}; + +#define MAX_GCRECORDS 500 +UINT g_recordCount = 0; +GCRecord g_records[MAX_GCRECORDS]; + +void GcHistClear() +{ + for (UINT i=0; i < g_recordCount; i++) + { + g_records[i].Clear(); + } + g_recordCount = 0; +} + +void GcHistAddLog(LPCSTR msg, StressMsg* stressMsg) +{ + if (g_recordCount >= MAX_GCRECORDS) + { + return; + } + + if (strcmp(msg, ThreadStressLog::gcPlugMoveMsg()) == 0) + { + PlugRecord pr; + // this is a plug message + _ASSERTE(stressMsg->numberOfArgs == 3); + pr.PlugStart = (size_t) stressMsg->args[0]; + pr.PlugEnd = (size_t) stressMsg->args[1]; + pr.Delta = (size_t) stressMsg->args[2]; + + g_records[g_recordCount].AddPlug(pr); + } + else if (strcmp(msg, ThreadStressLog::gcRootMsg()) == 0) + { + // this is a root message + _ASSERTE(stressMsg->numberOfArgs == 4); + RelocRecord rr; + rr.Root = (size_t) stressMsg->args[0]; + rr.PrevValue = (size_t) stressMsg->args[1]; + rr.NewValue = (size_t) stressMsg->args[2]; + rr.methodTable = (size_t) stressMsg->args[3]; + g_records[g_recordCount].AddReloc(rr); + } + else if (strcmp(msg, ThreadStressLog::gcRootPromoteMsg()) == 0) + { + // this is a promote message + _ASSERTE(stressMsg->numberOfArgs == 3); + PromoteRecord pr; + pr.Root = (size_t) stressMsg->args[0]; + pr.Value = (size_t) stressMsg->args[1]; + pr.methodTable = (size_t) stressMsg->args[2]; + g_records[g_recordCount].AddPromote(pr); + } + else if (strcmp(msg, ThreadStressLog::gcStartMsg()) == 0) + { + // Gc start! + _ASSERTE(stressMsg->numberOfArgs == 3); + ULONG64 gc_count = (ULONG64) stressMsg->args[0]; + g_records[g_recordCount].GCCount = gc_count; + g_recordCount++; + } + else if (strcmp(msg, ThreadStressLog::gcEndMsg()) == 0) + { + // Gc end! + // ULONG64 gc_count = (ULONG64) stressMsg->data; + // ExtOut ("ENDGC %d\n", gc_count); + } +} + +DECLARE_API(HistStats) +{ + INIT_API(); + + ExtOut ("%8s %8s %8s\n", + "GCCount", "Promotes", "Relocs"); + ExtOut ("-----------------------------------\n"); + + // Just traverse the data structure, printing basic stats + for (UINT i=0; i < g_recordCount; i++) + { + UINT PromoteCount = g_records[i].PromoteCount(); + UINT RelocCount = g_records[i].RelocCount(); + UINT GCCount = (UINT) g_records[i].GCCount; + + ExtOut ("%8d %8d %8d\n", + GCCount, + PromoteCount, + RelocCount); + } + + BOOL bErrorFound = FALSE; + + // Check for duplicate Reloc or Promote messages within one gc. + // Method is very inefficient, improve it later. + for (UINT i=0; i < g_recordCount; i++) + { + { // Promotes + PromoteRecord *Iter = g_records[i].PromoteList; + UINT GCCount = (UINT) g_records[i].GCCount; + while (Iter) + { + PromoteRecord *innerIter = Iter->next; + while (innerIter) + { + if (Iter->Root == innerIter->Root) + { + ExtOut ("Root %p promoted multiple times in gc %d\n", + SOS_PTR(Iter->Root), + GCCount); + bErrorFound = TRUE; + } + innerIter = innerIter->next; + } + + Iter = Iter->next; + } + } + + { // Relocates + RelocRecord *Iter = g_records[i].RelocList; + UINT GCCount = (UINT) g_records[i].GCCount; + while (Iter) + { + RelocRecord *innerIter = Iter->next; + while (innerIter) + { + if (Iter->Root == innerIter->Root) + { + ExtOut ("Root %p relocated multiple times in gc %d\n", + SOS_PTR(Iter->Root), + GCCount); + bErrorFound = TRUE; + } + innerIter = innerIter->next; + } + + Iter = Iter->next; + } + } + } + + if (!bErrorFound) + { + ExtOut ("No duplicate promote or relocate messages found in the log.\n"); + } + + return Status; +} + +DECLARE_API(HistRoot) +{ + INIT_API(); + size_t nArg; + + StringHolder rootstr; + CMDValue arg[] = + { + // vptr, type + {&rootstr.data, COSTRING}, + }; + + if (!GetCMDOption(args, NULL, 0, arg, _countof(arg), &nArg)) + return Status; + + if (nArg != 1) + { + ExtOut ("!Root <valid object pointer>\n"); + return Status; + } + + size_t Root = (size_t) GetExpression(rootstr.data); + + ExtOut ("%8s %" POINTERSIZE "s %" POINTERSIZE "s %9s %20s\n", + "GCCount", "Value", "MT", "Promoted?", "Notes"); + ExtOut ("---------------------------------------------------------\n"); + + bool bBoringPeople = false; + + // Just traverse the data structure, printing basic stats + for (UINT i=0; i < g_recordCount; i++) + { + UINT GCCount = (UINT) g_records[i].GCCount; + + // Find promotion records...there should only be one. + PromoteRecord *pPtr = g_records[i].PromoteList; + PromoteRecord *pPromoteRec = NULL; + bool bPromotedMoreThanOnce = false; + while(pPtr) + { + if (pPtr->Root == Root) + { + if (pPromoteRec) + { + bPromotedMoreThanOnce = true; + } + else + { + pPromoteRec = pPtr; + } + } + pPtr = pPtr->next; + } + + RelocRecord *pReloc = g_records[i].RelocList; + RelocRecord *pRelocRec = NULL; + bool bRelocatedMoreThanOnce = false; + while(pReloc) + { + if (pReloc->Root == Root) + { + if (pRelocRec) + { + bRelocatedMoreThanOnce = true; + } + else + { + pRelocRec = pReloc; + } + } + pReloc = pReloc->next; + } + + // Validate the records found for this root. + if (pRelocRec != NULL) + { + bBoringPeople = false; + + ExtOut ("%8d %p %p %9s ", GCCount, + SOS_PTR(pRelocRec->NewValue), + SOS_PTR(pRelocRec->methodTable), + pPromoteRec ? "yes" : "no"); + if (pPromoteRec != NULL) + { + // There should be similarities between the promote and reloc record + if (pPromoteRec->Value != pRelocRec->PrevValue || + pPromoteRec->methodTable != pRelocRec->methodTable) + { + ExtOut ("promote/reloc records in error "); + } + + if (bPromotedMoreThanOnce || bRelocatedMoreThanOnce) + { + ExtOut ("Duplicate promote/relocs"); + } + } + ExtOut ("\n"); + } + else if (pPromoteRec) + { + ExtOut ("Error: There is a promote record for root %p, but no relocation record\n", + (ULONG64) pPromoteRec->Root); + } + else + { + if (!bBoringPeople) + { + ExtOut ("...\n"); + bBoringPeople = true; + } + } + } + return Status; +} + +DECLARE_API(HistObjFind) +{ + INIT_API(); + size_t nArg; + + StringHolder objstr; + CMDValue arg[] = + { + // vptr, type + {&objstr.data, COSTRING}, + }; + + if (!GetCMDOption(args, NULL, 0, arg, _countof(arg), &nArg)) + return Status; + + if (nArg != 1) + { + ExtOut ("!ObjSearch <valid object pointer>\n"); + return Status; + } + + size_t object = (size_t) GetExpression(objstr.data); + + ExtOut ("%8s %" POINTERSIZE "s %40s\n", + "GCCount", "Object", "Message"); + ExtOut ("---------------------------------------------------------\n"); + + size_t curAddress = object; + bool bBoringPeople = false; + + // Just traverse the data structure, printing basic stats + for (UINT i=0; i < g_recordCount; i++) + { + if (curAddress == 0) + { + break; + } + + UINT GCCount = (UINT) g_records[i].GCCount; + + PromoteRecord *pPtr = g_records[i].PromoteList; + while(pPtr) + { + if (pPtr->Value == curAddress) + { + bBoringPeople = false; + ExtOut ("%8d %p ", GCCount, SOS_PTR(curAddress)); + ExtOut ("Promotion for root %p (MT = %p)\n", + SOS_PTR(pPtr->Root), + SOS_PTR(pPtr->methodTable)); + } + pPtr = pPtr->next; + } + + RelocRecord *pReloc = g_records[i].RelocList; + while(pReloc) + { + if (pReloc->NewValue == curAddress || + pReloc->PrevValue == curAddress) + { + bBoringPeople = false; + ExtOut ("%8d %p ", GCCount, SOS_PTR(curAddress)); + ExtOut ("Relocation %s for root %p\n", + (pReloc->NewValue == curAddress) ? "NEWVALUE" : "PREVVALUE", + SOS_PTR(pReloc->Root)); + } + pReloc = pReloc->next; + } + + if (!bBoringPeople) + { + ExtOut ("...\n"); + bBoringPeople = true; + } + + } + return Status; +} + +DECLARE_API(HistObj) +{ + INIT_API(); + size_t nArg; + + StringHolder objstr; + CMDValue arg[] = + { + // vptr, type + {&objstr.data, COSTRING}, + }; + + if (!GetCMDOption(args, NULL, 0, arg, _countof(arg), &nArg)) + return Status; + + if (nArg != 1) + { + ExtOut ("!object <valid object pointer>\n"); + return Status; + } + + size_t object = (size_t) GetExpression(objstr.data); + + ExtOut ("%8s %" POINTERSIZE "s %40s\n", + "GCCount", "Object", "Roots"); + ExtOut ("---------------------------------------------------------\n"); + + size_t curAddress = object; + + // Just traverse the data structure, printing basic stats + for (UINT i=0; i < g_recordCount; i++) + { + if (curAddress == 0) + { + break; + } + + UINT GCCount = (UINT) g_records[i].GCCount; + + ExtOut ("%8d %p ", GCCount, SOS_PTR(curAddress)); + + RelocRecord *pReloc = g_records[i].RelocList; + size_t candidateCurAddress = curAddress; + bool bFirstReloc = true; + while(pReloc) + { + if (pReloc->NewValue == curAddress) + { + ExtOut ("%p, ", SOS_PTR(pReloc->Root)); + if (bFirstReloc) + { + candidateCurAddress = pReloc->PrevValue; + bFirstReloc = false; + } + else if (candidateCurAddress != pReloc->PrevValue) + { + ExtOut ("differing reloc values for this object!\n"); + } + } + pReloc = pReloc->next; + } + + ExtOut ("\n"); + curAddress = candidateCurAddress; + } + return Status; +} + +DECLARE_API(HistInit) +{ + INIT_API(); + + GcHistClear(); + + CLRDATA_ADDRESS stressLogAddr = 0; + if (g_sos->GetStressLogAddress(&stressLogAddr) != S_OK) + { + ExtOut("Unable to find stress log via DAC\n"); + return E_FAIL; + } + + ExtOut ("Attempting to read Stress log\n"); + + Status = StressLog::Dump(stressLogAddr, NULL, g_ExtData); + if (Status == S_OK) + ExtOut("SUCCESS: GCHist structures initialized\n"); + else if (Status == S_FALSE) + ExtOut("No Stress log in the image, GCHist commands unavailable\n"); + else + ExtOut("FAILURE: Stress log unreadable\n"); + + return Status; +} + +DECLARE_API(HistClear) +{ + INIT_API(); + GcHistClear(); + ExtOut("Completed successfully.\n"); + return Status; +} + diff --git a/src/ToolBox/SOS/Strike/gcroot.cpp b/src/ToolBox/SOS/Strike/gcroot.cpp new file mode 100644 index 0000000000..f68b935e21 --- /dev/null +++ b/src/ToolBox/SOS/Strike/gcroot.cpp @@ -0,0 +1,2503 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ==++== +// + +// +// ==--== + +/* + * This file defines the support classes that allow us to operate on the object graph of the process that SOS + * is analyzing. + * + * The GCRoot algorithm is based on three simple principles: + * 1. Only consider an object once. When we inspect an object, read its references and don't ever touch + * it again. This ensures that our upper bound on the amount of time we spend walking the object + * graph very quickly reaches resolution. The objects we've already inspected (and thus won't inspect + * again) is tracked by the mConsidered variable. + * 2. Be extremely careful about reads from the target process. We use a linear cache for reading from + * object data. We also cache everything about the method tables we read out of, as well as caching + * the GCDesc which is required to walk the object's references. + * 3. Use O(1) data structures for anything perf-critical. Almost all of the data structures we use to + * keep track of data have very fast lookups. For example, to keep track of the objects we've considered + * we use a unordered_set. Similarly to keep track of MethodTable data we use a unordered_map to track the + * mt -> mtinfo mapping. + */ + +#include "sos.h" +#include "disasm.h" + +#ifdef _ASSERTE +#undef _ASSERTE +#endif + +#define _ASSERTE(a) {;} + +#include "gcdesc.h" + +#include "safemath.h" + + +#ifdef _ASSERTE +#undef _ASSERTE +#endif + +#ifndef _ASSERTE +#ifdef _DEBUG +#define _ASSERTE(expr) \ + do { if (!(expr) ) { ExtErr("_ASSERTE fired:\n\t%s\n", #expr); if (IsDebuggerPresent()) DebugBreak(); } } while (0) +#else +#define _ASSERTE(x) +#endif +#endif // ASSERTE + +inline size_t ALIGN_DOWN( size_t val, size_t alignment ) +{ + // alignment must be a power of 2 for this implementation to work (need modulo otherwise) + _ASSERTE( 0 == (alignment & (alignment - 1)) ); + size_t result = val & ~(alignment - 1); + return result; +} + +inline void* ALIGN_DOWN( void* val, size_t alignment ) +{ + return (void*) ALIGN_DOWN( (size_t)val, alignment ); +} + +LinearReadCache::LinearReadCache(ULONG pageSize) + : mCurrPageStart(0), mPageSize(pageSize), mCurrPageSize(0), mPage(0) +{ + mPage = new BYTE[pageSize]; + ClearStats(); +} + +LinearReadCache::~LinearReadCache() +{ + if (mPage) + delete [] mPage; +} + +bool LinearReadCache::MoveToPage(TADDR addr, unsigned int size) +{ + if (size > mPageSize) + size = mPageSize; + + mCurrPageStart = addr; + HRESULT hr = g_ExtData->ReadVirtual(mCurrPageStart, mPage, size, &mCurrPageSize); + + if (hr != S_OK) + { + mCurrPageStart = 0; + mCurrPageSize = 0; + return false; + } + +#ifdef _DEBUG + mMisses++; +#endif + return true; +} + + +static const char *NameForHandle(unsigned int type) +{ + switch (type) + { + case 0: + return "weak short"; + case 1: + return "weak long"; + case 2: + return "strong"; + case 3: + return "pinned"; + case 5: + return "ref counted"; + case 6: + return "dependent"; + case 7: + return "async pinned"; + case 8: + return "sized ref"; + } + + return "unknown"; +} + +/////////////////////////////////////////////////////////////////////////////// +// GCRoot functions to cleanup data. +/////////////////////////////////////////////////////////////////////////////// +void GCRootImpl::ClearSizeData() +{ + mConsidered.clear(); + mSizes.clear(); +} + +void GCRootImpl::ClearAll() +{ + ClearNodes(); + + { + std::unordered_map<TADDR, MTInfo*>::iterator itr; + for (itr = mMTs.begin(); itr != mMTs.end(); ++itr) + delete itr->second; + } + + { + std::unordered_map<TADDR, RootNode*>::iterator itr; + for (itr = mTargets.begin(); itr != mTargets.end(); ++itr) + delete itr->second; + } + + mMTs.clear(); + mTargets.clear(); + mConsidered.clear(); + mSizes.clear(); + mDependentHandleMap.clear(); + mCache.ClearStats(); + + mAll = false; + mSize = false; +} + +void GCRootImpl::ClearNodes() +{ + std::list<RootNode*>::iterator itr; + + for (itr = mCleanupList.begin(); itr != mCleanupList.end(); ++itr) + delete *itr; + + mCleanupList.clear(); + mRootNewList.clear(); +} + +GCRootImpl::RootNode *GCRootImpl::NewNode(TADDR obj, MTInfo *mtInfo, bool fromDependent) +{ + // We need to create/destroy a TON of nodes during execution of GCRoot functions. + // To avoid heap fragmentation (and since it's faster), we don't actually new/delete + // nodes unless we have to. Instead we keep a stl list with free nodes to use. + RootNode *toReturn = NULL; + + if (mRootNewList.size()) + { + toReturn = mRootNewList.back(); + mRootNewList.pop_back(); + } + else + { + toReturn = new RootNode(); + mCleanupList.push_back(toReturn); + } + + toReturn->Object = obj; + toReturn->MTInfo = mtInfo; + toReturn->FromDependentHandle = fromDependent; + return toReturn; +} + +void GCRootImpl::DeleteNode(RootNode *node) +{ + // Add node to the "new list". + node->Clear(); + mRootNewList.push_back(node); +} + +void GCRootImpl::GetDependentHandleMap(std::unordered_map<TADDR, std::list<TADDR>> &map) +{ + unsigned int type = HNDTYPE_DEPENDENT; + ToRelease<ISOSHandleEnum> handles; + + HRESULT hr = g_sos->GetHandleEnumForTypes(&type, 1, &handles); + + if (FAILED(hr)) + { + ExtOut("Failed to walk dependent handles. GCRoot may miss paths.\n"); + return; + } + + SOSHandleData data[4]; + unsigned int fetched = 0; + + do + { + hr = handles->Next(_countof(data), data, &fetched); + + if (FAILED(hr)) + { + ExtOut("Error walking dependent handles. GCRoot may miss paths.\n"); + return; + } + + for (unsigned int i = 0; i < fetched; ++i) + { + if (data[i].Secondary != 0) + { + TADDR obj = 0; + TADDR target = TO_TADDR(data[i].Secondary); + + MOVE(obj, TO_TADDR(data[i].Handle)); + + map[obj].push_back(target); + } + } + } while (fetched == _countof(data)); +} + +/////////////////////////////////////////////////////////////////////////////// +// Public functions. +/////////////////////////////////////////////////////////////////////////////// +int GCRootImpl::PrintRootsForObject(TADDR target, bool all, bool noStacks) +{ + ClearAll(); + GetDependentHandleMap(mDependentHandleMap); + + mAll = all; + + // Add "target" to the mTargets list. + TADDR mt = ReadPointerCached(target); + RootNode *node = NewNode(target, GetMTInfo(mt)); + mTargets[target] = node; + + // Look for roots on the HandleTable, FQ, and all threads. + int count = 0; + + if (!noStacks) + count += PrintRootsOnAllThreads(); + + count += PrintRootsOnHandleTable(); + count += PrintRootsOnFQ(); + + if(count == 0) + { + count += PrintRootsOnFQ(true); + if(count) + { + ExtOut("Warning: These roots are from finalizable objects that are not yet ready for finalization.\n"); + ExtOut("This is to handle the case where objects re-register themselves for finalization.\n"); + ExtOut("These roots may be false positives.\n"); + } + } + + mCache.PrintStats(__FUNCTION__); + return count; +} + + +bool GCRootImpl::PrintPathToObject(TADDR root, TADDR target) +{ + ClearAll(); + GetDependentHandleMap(mDependentHandleMap); + + // Add "target" to the mTargets list. + TADDR mt = ReadPointerCached(target); + RootNode *node = NewNode(target, GetMTInfo(mt)); + mTargets[target] = node; + + // Check to see if the root reaches the target. + RootNode *path = FindPathToTarget(root); + if (path) + { + ExtOut("%p %S\n", SOS_PTR(path->Object), path->GetTypeName()); + path = path->Next; + + while (path) + { + ExtOut(" -> %p %S%s\n",SOS_PTR(path->Object), path->GetTypeName(), path->FromDependentHandle ? " (dependent handle)" : ""); + path = path->Next; + } + + mCache.PrintStats(__FUNCTION__); + return true; + } + + mCache.PrintStats(__FUNCTION__); + return false; +} + +size_t GCRootImpl::ObjSize(TADDR root) +{ + // Calculates the size of the closure of objects kept alive by root. + ClearAll(); + GetDependentHandleMap(mDependentHandleMap); + + // mSize tells GCRootImpl to build the "mSizes" table with the total size + // each object roots. + mSize = true; + + // Note that we are calling the same method as we would to locate the rooting + // chain for an object, but we haven't added any items to mTargets. This means + // the algorithm will scan all objects and never terminate until it has walked + // all objects in the closure. + FindPathToTarget(root); + + mCache.PrintStats(__FUNCTION__); + return mSizes[root]; +} + +void GCRootImpl::ObjSize() +{ + ClearAll(); + GetDependentHandleMap(mDependentHandleMap); + mSize = true; + + // Walks all roots in the process, and prints out the object size for each one. + PrintRootsOnAllThreads(); + PrintRootsOnHandleTable(); + PrintRootsOnFQ(); + + mCache.PrintStats(__FUNCTION__); +} + + +const std::unordered_set<TADDR> &GCRootImpl::GetLiveObjects(bool excludeFQ) +{ + ClearAll(); + GetDependentHandleMap(mDependentHandleMap); + + // Walk each root in the process without setting a target. This has the effect of + // causing us to walk every object in the process, adding them to mConsidered as we + // go. + PrintRootsOnAllThreads(); + PrintRootsOnHandleTable(); + + if (!excludeFQ) + PrintRootsOnFQ(); + + mCache.PrintStats(__FUNCTION__); + return mConsidered; +} + +int GCRootImpl::FindRoots(int gen, TADDR target) +{ + ClearAll(); + GetDependentHandleMap(mDependentHandleMap); + + if (gen == -1 || ((UINT)gen) == GetMaxGeneration()) + { + // If this is a gen 2 !FindRoots, just do a regular !GCRoot + return PrintRootsForObject(target, false, false); + } + else + { + // Otherwise walk all roots for only the given generation. + int count = PrintRootsInOlderGen(); + count += PrintRootsOnHandleTable(gen); + count += PrintRootsOnFQ(); + return count; + } +} + + + +/////////////////////////////////////////////////////////////////////////////// +// GCRoot Methods for printing out results. +/////////////////////////////////////////////////////////////////////////////// +void GCRootImpl::ReportSizeInfo(const SOSHandleData &handle, TADDR obj) +{ + // Print size for a handle (!objsize) + TADDR mt = ReadPointer(obj); + MTInfo *mtInfo = GetMTInfo(mt); + + const WCHAR *type = mtInfo ? mtInfo->GetTypeName() : W("unknown type"); + + size_t size = mSizes[obj]; + ExtOut("Handle (%s): %p -> %p: %d (0x%x) bytes (%S)\n", NameForHandle(handle.Type), SOS_PTR(handle.Handle), + SOS_PTR(obj), size, size, type); +} + + +void GCRootImpl::ReportSizeInfo(DWORD thread, const SOSStackRefData &stackRef, TADDR obj) +{ + // Print size for a stack root (!objsize) + WString frame; + if (stackRef.SourceType == SOS_StackSourceIP) + frame = MethodNameFromIP(stackRef.Source); + else + frame = GetFrameFromAddress(TO_TADDR(stackRef.Source)); + + WString regOutput = BuildRegisterOutput(stackRef); + + TADDR mt = ReadPointer(obj); + MTInfo *mtInfo = GetMTInfo(mt); + const WCHAR *type = mtInfo ? mtInfo->GetTypeName() : W("unknown type"); + + size_t size = mSizes[obj]; + ExtOut("Thread %x (%S): %S: %d (0x%x) bytes (%S)\n", thread, frame.c_str(), regOutput.c_str(), size, size, type); +} + +void GCRootImpl::ReportOneHandlePath(const SOSHandleData &handle, RootNode *path, bool printHeader) +{ + if (printHeader) + ExtOut("HandleTable:\n"); + + ExtOut(" %p (%s handle)\n", SOS_PTR(handle.Handle), NameForHandle(handle.Type)); + while (path) + { + ExtOut(" -> %p %S%s\n", SOS_PTR(path->Object), path->GetTypeName(), path->FromDependentHandle ? " (dependent handle)" : ""); + path = path->Next; + } + + ExtOut("\n"); +} + +void GCRootImpl::ReportOnePath(DWORD thread, const SOSStackRefData &stackRef, RootNode *path, bool printThread, bool printFrame) +{ + if (printThread) + ExtOut("Thread %x:\n", thread); + + if (printFrame) + { + if (stackRef.SourceType == SOS_StackSourceIP) + { + WString methodName = MethodNameFromIP(stackRef.Source); + ExtOut(" %p %p %S\n", SOS_PTR(stackRef.StackPointer), SOS_PTR(stackRef.Source), methodName.c_str()); + } + else + { + WString frameName = GetFrameFromAddress(TO_TADDR(stackRef.Source)); + ExtOut(" %p %S\n", SOS_PTR(stackRef.Source), frameName.c_str()); + } + } + + WString regOutput = BuildRegisterOutput(stackRef, false); + ExtOut(" %S\n", regOutput.c_str()); + + while (path) + { + ExtOut(" -> %p %S%s\n", SOS_PTR(path->Object), path->GetTypeName(), path->FromDependentHandle ? " (dependent handle)" : ""); + path = path->Next; + } + + ExtOut("\n"); +} + +void GCRootImpl::ReportOneFQEntry(TADDR root, RootNode *path, bool printHeader) +{ + if (printHeader) + ExtOut("Finalizer Queue:\n"); + + ExtOut(" %p\n", SOS_PTR(root)); + while (path) + { + ExtOut(" -> %p %S%s\n", SOS_PTR(path->Object), path->GetTypeName(), path->FromDependentHandle ? " (dependent handle)" : ""); + path = path->Next; + } + + ExtOut("\n"); +} + +void GCRootImpl::ReportOlderGenEntry(TADDR root, RootNode *path, bool printHeader) +{ + if (printHeader) + ExtOut("Older Generation:\n"); + + ExtOut(" %p\n", SOS_PTR(root)); + while (path) + { + ExtOut(" -> %p %S%s\n", SOS_PTR(path->Object), path->GetTypeName(), path->FromDependentHandle ? " (dependent handle)" : ""); + path = path->Next; + } + + ExtOut("\n"); +} + +////////////////////////////////////////////////////// +int GCRootImpl::PrintRootsInOlderGen() +{ + // Use a different linear read cache here instead of polluting the object read cache. + LinearReadCache cache(512); + + if (!IsServerBuild()) + { + DacpGcHeapAnalyzeData analyzeData; + if (analyzeData.Request(g_sos) != S_OK) + { + ExtErr("Error requesting gc heap analyze data\n"); + return 0; + } + + if (!analyzeData.heap_analyze_success) + { + ExtOut("Failed to gather needed data, possibly due to memory contraints in the debuggee.\n"); + ExtOut("To try again re-issue the !FindRoots -gen <N> command.\n"); + return 0; + } + + ExtDbgOut("internal_root_array = %#p\n", SOS_PTR(analyzeData.internal_root_array)); + ExtDbgOut("internal_root_array_index = %#p\n", SOS_PTR(analyzeData.internal_root_array_index)); + + TADDR start = TO_TADDR(analyzeData.internal_root_array); + TADDR stop = TO_TADDR(analyzeData.internal_root_array + sizeof(TADDR) * (size_t)analyzeData.internal_root_array_index); + + return PrintRootsInRange(cache, start, stop, &GCRootImpl::ReportOlderGenEntry, true); + } + else + { + int total = 0; + DWORD dwAllocSize; + DWORD dwNHeaps = GetGcHeapCount(); + if (!ClrSafeInt<DWORD>::multiply(sizeof(CLRDATA_ADDRESS), dwNHeaps, dwAllocSize)) + { + ExtErr("Failed to get GCHeaps: integer overflow\n"); + return 0; + } + + CLRDATA_ADDRESS *heapAddrs = (CLRDATA_ADDRESS*)alloca(dwAllocSize); + if (g_sos->GetGCHeapList(dwNHeaps, heapAddrs, NULL) != S_OK) + { + ExtErr("Failed to get GCHeaps\n"); + return 0; + } + + for (UINT n = 0; n < dwNHeaps; n ++) + { + DacpGcHeapAnalyzeData analyzeData; + if (analyzeData.Request(g_sos, heapAddrs[n]) != S_OK) + { + ExtErr("Error requesting gc heap analyze data for heap %p\n", SOS_PTR(heapAddrs[n])); + continue; + } + + if (!analyzeData.heap_analyze_success) + { + ExtOut("Failed to gather needed data, possibly due to memory contraints in the debuggee.\n"); + ExtOut("To try again re-issue the !FindRoots -gen <N> command.\n"); + continue; + } + + ExtDbgOut("internal_root_array = %#p\n", SOS_PTR(analyzeData.internal_root_array)); + ExtDbgOut("internal_root_array_index = %#p\n", SOS_PTR(analyzeData.internal_root_array_index)); + + TADDR start = TO_TADDR(analyzeData.internal_root_array); + TADDR stop = TO_TADDR(analyzeData.internal_root_array + sizeof(TADDR) * (size_t)analyzeData.internal_root_array_index); + + total += PrintRootsInRange(cache, start, stop, &GCRootImpl::ReportOlderGenEntry, total == 0); + } + + return total; + } +} + + +int GCRootImpl::PrintRootsOnFQ(bool notReadyForFinalization) +{ + // Here are objects kept alive by objects in the finalizer queue. + DacpGcHeapDetails heapDetails; + + // Use a different linear read cache here instead of polluting the object read cache. + LinearReadCache cache(512); + + if (!IsServerBuild()) + { + if (heapDetails.Request(g_sos) != S_OK) + { + ExtErr("Error requesting heap data.\n"); + return 0; + } + + // If we include objects that are not ready for finalization, we may report + // false positives. False positives occur if the object is not ready for finalization + // and does not re-register itself for finalization inside the finalizer. + TADDR start = 0; + TADDR stop = 0; + if(notReadyForFinalization) + { + start = TO_TADDR(SegQueue(heapDetails, gen_segment(GetMaxGeneration()))); + stop = TO_TADDR(SegQueueLimit(heapDetails, CriticalFinalizerListSeg)); + } + else + { + start = TO_TADDR(SegQueue(heapDetails, CriticalFinalizerListSeg)); + stop = TO_TADDR(SegQueue(heapDetails, FinalizerListSeg)); + } + + return PrintRootsInRange(cache, start, stop, &GCRootImpl::ReportOneFQEntry, true); + } + else + { + DWORD dwAllocSize; + DWORD dwNHeaps = GetGcHeapCount(); + if (!ClrSafeInt<DWORD>::multiply(sizeof(CLRDATA_ADDRESS), dwNHeaps, dwAllocSize)) + { + ExtErr("Failed to get GCHeaps: integer overflow\n"); + return 0; + } + + CLRDATA_ADDRESS *heapAddrs = (CLRDATA_ADDRESS*)alloca(dwAllocSize); + if (g_sos->GetGCHeapList(dwNHeaps, heapAddrs, NULL) != S_OK) + { + ExtErr("Error requesting heap data.\n"); + return 0; + } + + int total = 0; + for (UINT n = 0; n < dwNHeaps; n ++) + { + if (heapDetails.Request(g_sos, heapAddrs[n]) != S_OK) + { + ExtErr("Error requesting heap data for heap %d.\n", n); + continue; + } + + // If we include objects that are not ready for finalization, we may report + // false positives. False positives occur if the object is not ready for finalization + // and does not re-register itself for finalization inside the finalizer. + TADDR start = 0; + TADDR stop = 0; + if(notReadyForFinalization) + { + start = TO_TADDR(SegQueue(heapDetails, gen_segment(GetMaxGeneration()))); + stop = TO_TADDR(SegQueueLimit(heapDetails, CriticalFinalizerListSeg)); + } + else + { + start = TO_TADDR(SegQueue(heapDetails, CriticalFinalizerListSeg)); + stop = TO_TADDR(SegQueueLimit(heapDetails, FinalizerListSeg)); + } + + total += PrintRootsInRange(cache, start, stop, &GCRootImpl::ReportOneFQEntry, total == 0); + } + + return total; + } +} + +int GCRootImpl::PrintRootsInRange(LinearReadCache &cache, TADDR start, TADDR stop, ReportCallback func, bool printHeader) +{ + int total = 0; + + // Walk the range [start, stop) and consider each pointer in the range as a root. + while (start < stop) + { + if (IsInterrupt()) + return total; + + // Use the cache parameter here instead of mCache. If you use mCache it will be reset + // when calling into FindPathToTarget. + TADDR root = 0; + bool res = cache.Read(start, &root, true); + + if (res && root) + { + RootNode *path = FindPathToTarget(root); + if (path) + { + func(root, path, printHeader); + total++; + printHeader = false; + } + } + + start += sizeof(TADDR); + } + + return total; +} + +int GCRootImpl::PrintRootsOnAllThreads() +{ + ArrayHolder<DWORD_PTR> threadList = NULL; + int numThreads = 0; + + // GetThreadList calls ReportOOM so we don't need to do that here. + HRESULT hr = GetThreadList(&threadList, &numThreads); + if (FAILED(hr) || !threadList) + return 0; + + // Walk each thread and process the roots on it. + int total = 0; + DacpThreadData vThread; + for (int i = 0; i < numThreads; i++) + { + if (IsInterrupt()) + return total; + + if (FAILED(vThread.Request(g_sos, threadList[i]))) + continue; + + if (vThread.osThreadId) + total += PrintRootsOnThread(vThread.osThreadId); + } + + return total; +} + +int GCRootImpl::PrintRootsOnThread(DWORD osThreadId) +{ + // Grab all object rootson the thread. + unsigned int refCount = 0; + ArrayHolder<SOSStackRefData> refs = NULL; + + int total = 0; + bool first = true; + if (FAILED(::GetGCRefs(osThreadId, &refs, &refCount, NULL, NULL))) + { + ExtOut("Failed to walk thread %x\n", osThreadId); + return total; + } + + // Walk each non-null root, find if it contains a path to the target, + // and if so print it out. + CLRDATA_ADDRESS prevSource = 0, prevSP = 0; + for (unsigned int i = 0; i < refCount; ++i) + { + if (IsInterrupt()) + return total; + + if (refs[i].Object) + { + if (mSize) + ClearSizeData(); + + RootNode *path = FindPathToTarget(TO_TADDR(refs[i].Object)); + if (path) + { + bool reportFrame = refs[i].Source != prevSource || refs[i].StackPointer != prevSP; + ReportOnePath(osThreadId, refs[i], path, first, reportFrame); + first = false; + total++; + } + + if (mSize) + ReportSizeInfo(osThreadId, refs[i], TO_TADDR(refs[i].Object)); + } + } + + return total; +} + +int GCRootImpl::PrintRootsOnHandleTable(int gen) +{ + // Get handle data. + ToRelease<ISOSHandleEnum> pEnum = NULL; + HRESULT hr = S_OK; + + if (gen == -1 || (ULONG)gen == GetMaxGeneration()) + hr = g_sos->GetHandleEnum(&pEnum); + else + hr = g_sos->GetHandleEnumForGC(gen, &pEnum); + + if (FAILED(hr)) + { + ExtOut("Failed to walk the HandleTable!\n"); + return 0; + } + + int total = 0; + unsigned int fetched = 0; + SOSHandleData handles[8]; + + bool printHeader = true; + do + { + // Fetch more handles + hr = pEnum->Next(_countof(handles), handles, &fetched); + if (FAILED(hr)) + { + ExtOut("Failed to request more handles."); + return total; + } + + // Find rooting info for each handle. + for (unsigned int i = 0; i < fetched; ++i) + { + if (IsInterrupt()) + return total; + + // Ignore handles which aren't actually roots. + if (!handles[i].StrongReference) + continue; + + // clear the size table + if (mAll) + ClearSizeData(); + + // Get the object the handle points to. + TADDR root = ReadPointer(TO_TADDR(handles[i].Handle)); + + // Only inspect handle if the object is non-null, and not one we've already walked. + if (root) + { + // Find all paths to the object and don't clean up the return value. + // It's tracked by mCleanupList. + RootNode *path = FindPathToTarget(root); + if (path) + { + ReportOneHandlePath(handles[i], path, printHeader); + printHeader = false; + total++; + } + + if (mSize) + ReportSizeInfo(handles[i], root); + } + } + } + while (_countof(handles) == fetched); + + return total; +} + +GCRootImpl::RootNode *GCRootImpl::FilterRoots(RootNode *&list) +{ + // Filter the list of GC refs: + // - Remove objects that we have already considered + // - Check to see if we've located the target object (or an object which points to the target). + RootNode *curr = list; + RootNode *keep = NULL; + + while (curr) + { + // We don't check for Control-C in this loop to avoid inconsistent data. + RootNode *curr_next = curr->Next; + + std::unordered_map<TADDR, RootNode *>::iterator targetItr = mTargets.find(curr->Object); + if (targetItr != mTargets.end()) + { + // We found the object we are looking for (or an object which points to it)! + // Return the target, propogate whether we got the target from a dependent handle. + targetItr->second->FromDependentHandle = curr->FromDependentHandle; + return targetItr->second; + } + else if (mConsidered.find(curr->Object) != mConsidered.end()) + { + curr->Remove(list); + + DeleteNode(curr); + } + + curr = curr_next; + } + + return NULL; +} + +/* This is the core of the GCRoot algorithm. It is: + * 1. Start with a list of "targets" (objects we are trying to find the roots for) and a root + * in the process. + * 2. Let the root be "curr". + * 3. Find all objects curr points to and place them in curr->GCRefs (a linked list). + * 4. Walk curr->GCRefs. If we find any of the "targets", return the current path. If not, + * filter out any objects we've already considered (the mConsidered set). + * 5. Look at curr->GCRefs: + * 5a. If curr->GCRefs is NULL then we have walked all references of this object. Pop "curr" + * from the current path (curr = curr->Prev). If curr is NULL, we walked all objects and + * didn't find a target, return NULL. If curr is non-null, goto 5. + * 5b. If curr->GCRefs is non-NULL, pop one entry and push it onto the path (that is: + * curr->Next = curr->GCRefs; curr = curr->Next; curr->GCRefs = curr->GCRefs->Next) + * 6. Goto 3. + */ +GCRootImpl::RootNode *GCRootImpl::FindPathToTarget(TADDR root) +{ + // Early out. We may have already looked at this object. + std::unordered_map<TADDR, RootNode *>::iterator targetItr = mTargets.find(root); + if (targetItr != mTargets.end()) + return targetItr->second; + else if (mConsidered.find(root) != mConsidered.end()) + return NULL; + + // Add obj as a considered node (since we are considering it now). + mConsidered.insert(root); + + // Create path. + RootNode *path = NewNode(root); + + RootNode *curr = path; + while (curr) + { + if (IsInterrupt()) + return NULL; + + // If this is a new reference we are walking, we haven't filled the list of objects + // this one points to. Update that first. + if (!curr->FilledRefs) + { + // Get the list of GC refs. + curr->GCRefs = GetGCRefs(path, curr); + + // Filter the refs to remove objects we've already inspected. + RootNode *foundTarget = FilterRoots(curr->GCRefs); + + // If we've found the target, great! Return the path to the target. + if (foundTarget) + { + // Link the current to the target. + curr->Next = foundTarget; + foundTarget->Prev = curr; + + // If the user requested all paths, set each node in the path to be a target. + // Normally, we don't consider a node we've already seen, which means if we don't + // get a *completely* unique path, it's not printed out. By adding each of the + // nodes in the paths we find as potential targets, it prints out *every* path + // to the target, including ones with duplicate nodes. This is much slower. + if (mAll) + { + RootNode *tmp = path; + + while (tmp) + { + if (mTargets.find(tmp->Object) != mTargets.end()) + break; + + mTargets[tmp->Object] = tmp; + tmp = tmp->Next; + } + } + + return path; + } + } + + // We have filled the references, now walk them depth-first. + if (curr->GCRefs) + { + RootNode *next = curr->GCRefs; + curr->GCRefs = next->Next; + + if (mConsidered.find(next->Object) != mConsidered.end()) + { + // Whoops. This object was considered deeper down the tree, so we + // don't need to do it again. Delete this node and continue looping. + DeleteNode(next); + } + else + { + // Clear associations. + if (curr->GCRefs) + curr->GCRefs->Prev = NULL; + + next->Next = NULL; + + // Link curr and next, make next the current node. + curr->Next = next; + next->Prev = curr; + curr = next; + + // Finally, insert the current object into the considered set. + mConsidered.insert(curr->Object); + // Now the next iteration will operate on "next". + } + } + else + { + // This object has no more GCRefs. We now need to "pop" it from the current path. + RootNode *tmp = curr; + curr = curr->Prev; + DeleteNode(tmp); + } + } + + return NULL; +} + + +GCRootImpl::RootNode *GCRootImpl::GetGCRefs(RootNode *path, RootNode *node) +{ + // If this node doesn't have the method table ready, fill it out + TADDR obj = node->Object; + if (!node->MTInfo) + { + TADDR mt = ReadPointerCached(obj); + MTInfo *mtInfo = GetMTInfo(mt); + node->MTInfo = mtInfo; + } + + node->FilledRefs = true; + + // MTInfo can be null if we encountered an error reading out of the target + // process, just early out here as if it has no references. + if (!node->MTInfo) + return NULL; + + // Only calculate the size if we need it. + size_t objSize = 0; + if (mSize || node->MTInfo->ContainsPointers) + { + objSize = GetSizeOfObject(obj, node->MTInfo); + + // Update object size list, if requested. + if (mSize) + { + mSizes[obj] = 0; + + while (path) + { + mSizes[path->Object] += objSize; + path = path->Next; + } + } + } + + // Early out: If the object doesn't contain any pointers, return. + if (!node->MTInfo->ContainsPointers) + return NULL; + + // Make sure we have the object's data in the cache. + mCache.EnsureRangeInCache(obj, (unsigned int)objSize); + + // Storage for the gc refs. + RootNode *refs = NewNode(); + RootNode *curr = refs; + + // Walk the GCDesc, fill "refs" with non-null references. + CGCDesc *gcdesc = node->MTInfo->GCDesc; + bool aovc = node->MTInfo->ArrayOfVC; + for (sos::RefIterator itr(obj, gcdesc, aovc, &mCache); itr; ++itr) + { + if (*itr) + { + curr->Next = NewNode(*itr); + curr->Next->Prev = curr; + curr = curr->Next; + } + } + + // Add edges from dependent handles. + std::unordered_map<TADDR, std::list<TADDR>>::iterator itr = mDependentHandleMap.find(obj); + if (itr != mDependentHandleMap.end()) + { + for (std::list<TADDR>::iterator litr = itr->second.begin(); litr != itr->second.end(); ++litr) + { + curr->Next = NewNode(*litr, NULL, true); + curr->Next->Prev = curr; + curr = curr->Next; + } + } + + // The gcrefs actually start on refs->Next. + curr = refs; + refs = refs->Next; + DeleteNode(curr); + + return refs; +} + +DWORD GCRootImpl::GetComponents(TADDR obj, TADDR mt) +{ + // Get the number of components in the object (for arrays and such). + DWORD Value = 0; + + // If we fail to read out the number of components, let's assume 0 so we don't try to + // read further data from the object. + if (!mCache.Read(obj + sizeof(TADDR), &Value, false)) + return 0; + + // The component size on a String does not contain the trailing NULL character, + // so we must add that ourselves. + if(TO_TADDR(g_special_usefulGlobals.StringMethodTable) == mt) + return Value+1; + + return Value; +} + +// Get the size of the object. +size_t GCRootImpl::GetSizeOfObject(TADDR obj, MTInfo *info) +{ + size_t res = info->BaseSize; + + if (info->ComponentSize) + { + // this is an array, so the size has to include the size of the components. We read the number + // of components from the target and multiply by the component size to get the size. + DWORD components = GetComponents(obj, info->MethodTable); + res += info->ComponentSize * components; + } + +#ifdef _TARGET_WIN64_ + // On x64 we do an optimization to save 4 bytes in almost every string we create, so + // pad to min object size if necessary. + if (res < min_obj_size) + res = min_obj_size; +#endif // _TARGET_WIN64_ + + return (res > 0x10000) ? AlignLarge(res) : Align(res); +} + +GCRootImpl::MTInfo *GCRootImpl::GetMTInfo(TADDR mt) +{ + // Remove lower bits in case we are in mark phase + mt &= ~3; + + // Do we already have this MethodTable? + std::unordered_map<TADDR, MTInfo *>::iterator itr = mMTs.find(mt); + + if (itr != mMTs.end()) + return itr->second; + + MTInfo *curr = new MTInfo; + curr->MethodTable = mt; + + // Get Base/Component size. + DacpMethodTableData dmtd; + + if (dmtd.Request(g_sos, mt) != S_OK) + { + delete curr; + return NULL; + } + + // Fill out size info. + curr->BaseSize = (size_t)dmtd.BaseSize; + curr->ComponentSize = (size_t)dmtd.ComponentSize; + curr->ContainsPointers = dmtd.bContainsPointers ? true : false; + + // If this method table contains pointers, fill out and cache the GCDesc. + if (curr->ContainsPointers) + { + int nEntries; + + if (FAILED(MOVE(nEntries, mt-sizeof(TADDR)))) + { + ExtOut("Failed to request number of entries."); + delete curr; + return NULL; + } + + if (nEntries < 0) + { + curr->ArrayOfVC = true; + nEntries = -nEntries; + } + else + { + curr->ArrayOfVC = false; + } + + size_t nSlots = 1 + nEntries * sizeof(CGCDescSeries)/sizeof(TADDR); + curr->Buffer = new TADDR[nSlots]; + + if (curr->Buffer == NULL) + { + ReportOOM(); + delete curr; + return NULL; + } + + if (FAILED(g_ExtData->ReadVirtual(TO_CDADDR(mt - nSlots*sizeof(TADDR)), curr->Buffer, (ULONG)(nSlots*sizeof(TADDR)), NULL))) + { + ExtOut("Failed to read GCDesc for MethodTable %p.\n", SOS_PTR(mt)); + delete curr; + return NULL; + } + + // Construct the GCDesc map and series. + curr->GCDesc = (CGCDesc *)(curr->Buffer+nSlots); + } + + mMTs[mt] = curr; + return curr; +} + + +TADDR GCRootImpl::ReadPointer(TADDR location) +{ + // Reads a pointer from the cache, but doesn't update the cache if it wasn't in it. + TADDR obj = NULL; + bool res = mCache.Read(location, &obj, false); + + if (!res) + return NULL; + + return obj; +} + +TADDR GCRootImpl::ReadPointerCached(TADDR location) +{ + // Reads a pointer from the cache, but updates the cache if it wasn't in it. + TADDR obj = NULL; + bool res = mCache.Read(location, &obj, true); + + if (!res) + return NULL; + + return obj; +} + +/////////////////////////////////////////////////////////////////////////////// + +UINT FindAllPinnedAndStrong(DWORD_PTR handlearray[], UINT arraySize) +{ + unsigned int fetched = 0; + SOSHandleData data[64]; + UINT pos = 0; + + // We do not call GetHandleEnumByType here with a list of strong handles since we would be + // statically setting the list of strong handles, which could change in a future release. + // Instead we rely on the dac to provide whether a handle is strong or not. + ToRelease<ISOSHandleEnum> handles; + HRESULT hr = g_sos->GetHandleEnum(&handles); + if (FAILED(hr)) + { + // This should basically never happen unless there's an OOM. + ExtOut("Failed to enumerate GC handles. HRESULT=%x.\n", hr); + return 0; + } + + do + { + hr = handles->Next(_countof(data), data, &fetched); + + if (FAILED(hr)) + { + ExtOut("Failed to enumerate GC handles. HRESULT=%x.\n", hr); + break; + } + + for (unsigned int i = 0; i < fetched; ++i) + { + if (pos >= arraySize) + { + ExtOut("Buffer overflow while enumerating handles.\n"); + return pos; + } + + if (data[i].StrongReference) + { + handlearray[pos++] = (DWORD_PTR)data[i].Handle; + } + } + } while (fetched == _countof(data)); + + return pos; +} + + + +void PrintNotReachableInRange(TADDR rngStart, TADDR rngEnd, BOOL bExcludeReadyForFinalization, HeapStat* hpstat, BOOL bShort) +{ + GCRootImpl gcroot; + const std::unordered_set<TADDR> &liveObjs = gcroot.GetLiveObjects(bExcludeReadyForFinalization == TRUE); + + LinearReadCache cache(512); + cache.EnsureRangeInCache(rngStart, (unsigned int)(rngEnd-rngStart)); + + for (TADDR p = rngStart; p < rngEnd; p += sizeof(TADDR)) + { + if (IsInterrupt()) + break; + + TADDR header = 0; + TADDR obj = 0; + TADDR taddrMT = 0; + + bool read = cache.Read(p-sizeof(SIZEOF_OBJHEADER), &header); + read = read && cache.Read(p, &obj); + if (read && ((header & BIT_SBLK_FINALIZER_RUN) == 0) && liveObjs.find(obj) == liveObjs.end()) + { + if (bShort) + { + DMLOut("%s\n", DMLObject(obj)); + } + else + { + DMLOut("%s ", DMLObject(obj)); + if (SUCCEEDED(GetMTOfObject(obj, &taddrMT)) && taddrMT) + { + size_t s = ObjectSize(obj); + if (hpstat) + { + hpstat->Add(taddrMT, (DWORD)s); + } + } + } + } + } + + if (!bShort) + ExtOut("\n"); +} + + +//////////////////////////////////////////////////////////////////////////////// +// +// Some defines for cards taken from gc code +// +#define card_word_width ((size_t)32) + +// +// The value of card_size is determined empirically according to the average size of an object +// In the code we also rely on the assumption that one card_table entry (DWORD) covers an entire os page +// +#if defined (_TARGET_WIN64_) +#define card_size ((size_t)(2*DT_OS_PAGE_SIZE/card_word_width)) +#else +#define card_size ((size_t)(DT_OS_PAGE_SIZE/card_word_width)) +#endif //_TARGET_WIN64_ + +// so card_size = 128 on x86, 256 on x64 + +inline +size_t card_word (size_t card) +{ + return card / card_word_width; +} + +inline +unsigned card_bit (size_t card) +{ + return (unsigned)(card % card_word_width); +} + +inline +size_t card_of ( BYTE* object) +{ + return (size_t)(object) / card_size; +} + +BOOL CardIsSet(const DacpGcHeapDetails &heap, TADDR objAddr) +{ + // The card table has to be translated to look at the refcount, etc. + // g_card_table[card_word(card_of(g_lowest_address))]. + + TADDR card_table = TO_TADDR(heap.card_table); + card_table = card_table + card_word(card_of((BYTE *)heap.lowest_address))*sizeof(DWORD); + + do + { + TADDR card_table_lowest_addr; + TADDR card_table_next; + + if (MOVE(card_table_lowest_addr, ALIGN_DOWN(card_table, 0x1000) + sizeof(PVOID)) != S_OK) + { + ExtErr("Error getting card table lowest address\n"); + return FALSE; + } + + if (MOVE(card_table_next, card_table - sizeof(PVOID)) != S_OK) + { + ExtErr("Error getting next card table\n"); + return FALSE; + } + + size_t card = (objAddr - card_table_lowest_addr) / card_size; + DWORD value; + if (MOVE(value, card_table + card_word(card)*sizeof(DWORD)) != S_OK) + { + ExtErr("Error reading card bits\n"); + return FALSE; + } + + if (value & 1<<card_bit(card)) + return TRUE; + + card_table = card_table_next; + } + while(card_table); + + return FALSE; +} + +BOOL NeedCard(TADDR parent, TADDR child) +{ + int iChildGen = g_snapshot.GetGeneration(child); + + if (iChildGen == 2) + return FALSE; + + int iParentGen = g_snapshot.GetGeneration(parent); + + return (iChildGen < iParentGen); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Some defines for mark_array taken from gc code +// + +#define mark_bit_pitch 8 +#define mark_word_width 32 +#define mark_word_size (mark_word_width * mark_bit_pitch) +#define heap_segment_flags_swept 16 + +inline +size_t mark_bit_bit_of(CLRDATA_ADDRESS add) +{ + return (size_t)((add / mark_bit_pitch) % mark_word_width); +} + +inline +size_t mark_word_of(CLRDATA_ADDRESS add) +{ + return (size_t)(add / mark_word_size); +} + +inline BOOL mark_array_marked(const DacpGcHeapDetails &heap, CLRDATA_ADDRESS add) +{ + + DWORD entry = 0; + HRESULT hr = MOVE(entry, heap.mark_array + sizeof(DWORD) * mark_word_of(add)); + + if (FAILED(hr)) + ExtOut("Failed to read card table entry.\n"); + + return entry & (1 << mark_bit_bit_of(add)); +} + +BOOL background_object_marked(const DacpGcHeapDetails &heap, CLRDATA_ADDRESS o) +{ + BOOL m = TRUE; + + if ((o >= heap.background_saved_lowest_address) && (o < heap.background_saved_highest_address)) + m = mark_array_marked(heap, o); + + return m; +} + +BOOL fgc_should_consider_object(const DacpGcHeapDetails &heap, + CLRDATA_ADDRESS o, + const DacpHeapSegmentData &seg, + BOOL consider_bgc_mark_p, + BOOL check_current_sweep_p, + BOOL check_saved_sweep_p) +{ + // the logic for this function must be kept in sync with the analogous function in gc.cpp + BOOL no_bgc_mark_p = FALSE; + + if (consider_bgc_mark_p) + { + if (check_current_sweep_p && (o < heap.next_sweep_obj)) + { + no_bgc_mark_p = TRUE; + } + + if (!no_bgc_mark_p) + { + if(check_saved_sweep_p && (o >= heap.saved_sweep_ephemeral_start)) + { + no_bgc_mark_p = TRUE; + } + + if (!check_saved_sweep_p) + { + CLRDATA_ADDRESS background_allocated = seg.background_allocated; + if (o >= background_allocated) + { + no_bgc_mark_p = TRUE; + } + } + } + } + else + { + no_bgc_mark_p = TRUE; + } + + return no_bgc_mark_p ? TRUE : background_object_marked(heap, o); +} + +enum c_gc_state +{ + c_gc_state_marking, + c_gc_state_planning, + c_gc_state_free +}; + +inline BOOL in_range_for_segment(const DacpHeapSegmentData &seg, CLRDATA_ADDRESS addr) +{ + return (addr >= seg.mem) && (addr < seg.reserved); +} + +void should_check_bgc_mark(const DacpGcHeapDetails &heap, + const DacpHeapSegmentData &seg, + BOOL* consider_bgc_mark_p, + BOOL* check_current_sweep_p, + BOOL* check_saved_sweep_p) +{ + // the logic for this function must be kept in sync with the analogous function in gc.cpp + *consider_bgc_mark_p = FALSE; + *check_current_sweep_p = FALSE; + *check_saved_sweep_p = FALSE; + + if (heap.current_c_gc_state == c_gc_state_planning) + { + // We are doing the next_sweep_obj comparison here because we have yet to + // turn on the swept flag for the segment but in_range_for_segment will return + // FALSE if the address is the same as reserved. + if ((seg.flags & heap_segment_flags_swept) || (heap.next_sweep_obj == seg.reserved)) + { + // this seg was already swept. + } + else + { + *consider_bgc_mark_p = TRUE; + + if (seg.segmentAddr == heap.saved_sweep_ephemeral_seg) + { + *check_saved_sweep_p = TRUE; + } + + if (in_range_for_segment(seg, heap.next_sweep_obj)) + { + *check_current_sweep_p = TRUE; + } + } + } +} + +// TODO: FACTOR TOGETHER THE OBJECT MEMBER WALKING CODE FROM +// TODO: VerifyObjectMember(), GetListOfRefs(), HeapTraverser::PrintRefs() +BOOL VerifyObjectMember(const DacpGcHeapDetails &heap, DWORD_PTR objAddr) +{ + BOOL ret = TRUE; + BOOL bCheckCard = TRUE; + size_t size = 0; + { + DWORD_PTR dwAddrCard = objAddr; + while (dwAddrCard < objAddr + size) + { + if (CardIsSet(heap, dwAddrCard)) + { + bCheckCard = FALSE; + break; + } + dwAddrCard += card_size; + } + + if (bCheckCard) + { + dwAddrCard = objAddr + size - 2*sizeof(PVOID); + if (CardIsSet(heap, dwAddrCard)) + { + bCheckCard = FALSE; + } + } + } + + for (sos::RefIterator itr(TO_TADDR(objAddr)); itr; ++itr) + { + TADDR dwAddr1 = (DWORD_PTR)*itr; + if (dwAddr1) + { + TADDR dwChild = dwAddr1; + // Try something more efficient than IsObject here. Is the methodtable valid? + size_t s; + BOOL bPointers; + TADDR dwAddrMethTable; + if (FAILED(GetMTOfObject(dwAddr1, &dwAddrMethTable)) || + (GetSizeEfficient(dwAddr1, dwAddrMethTable, FALSE, s, bPointers) == FALSE)) + { + DMLOut("object %s: bad member %p at %p\n", DMLObject(objAddr), SOS_PTR(dwAddr1), SOS_PTR(itr.GetOffset())); + ret = FALSE; + } + + if (IsMTForFreeObj(dwAddrMethTable)) + { + DMLOut("object %s contains free object %p at %p\n", DMLObject(objAddr), + SOS_PTR(dwAddr1), SOS_PTR(objAddr+itr.GetOffset())); + ret = FALSE; + } + + // verify card table + if (bCheckCard && NeedCard(objAddr+itr.GetOffset(), dwAddr1)) + { + DMLOut("object %s:%s missing card_table entry for %p\n", + DMLObject(objAddr), (dwChild == dwAddr1) ? "" : " maybe", + SOS_PTR(objAddr+itr.GetOffset())); + ret = FALSE; + } + } + } + + return ret; +} + +// search for can_verify_deep in gc.cpp for examples of how these functions are used. +BOOL VerifyObject(const DacpGcHeapDetails &heap, const DacpHeapSegmentData &seg, DWORD_PTR objAddr, DWORD_PTR MTAddr, size_t objSize, + BOOL bVerifyMember) +{ + if (IsMTForFreeObj(MTAddr)) + { + return TRUE; + } + + if (objSize < min_obj_size) + { + DMLOut("object %s: size %d too small\n", DMLObject(objAddr), objSize); + return FALSE; + } + + // If we requested to verify the object's members, the GC may be in a state where that's not possible. + // Here we check to see if the object in question needs to have its members updated. If so, we turn off + // verification for the object. + if (bVerifyMember) + { + BOOL consider_bgc_mark = FALSE, check_current_sweep = FALSE, check_saved_sweep = FALSE; + should_check_bgc_mark(heap, seg, &consider_bgc_mark, &check_current_sweep, &check_saved_sweep); + bVerifyMember = fgc_should_consider_object(heap, objAddr, seg, consider_bgc_mark, check_current_sweep, check_saved_sweep); + } + + return bVerifyMember ? VerifyObjectMember(heap, objAddr) : TRUE; +} + + +BOOL FindSegment(const DacpGcHeapDetails &heap, DacpHeapSegmentData &seg, CLRDATA_ADDRESS addr) +{ + CLRDATA_ADDRESS dwAddrSeg = heap.generation_table[GetMaxGeneration()].start_segment; + + // Request the inital segment. + if (seg.Request(g_sos, dwAddrSeg, heap) != S_OK) + { + ExtOut("Error requesting heap segment %p.\n", SOS_PTR(dwAddrSeg)); + return FALSE; + } + + // Loop while the object is not in range of the segment. + while (addr < TO_TADDR(seg.mem) || + addr >= (dwAddrSeg == heap.ephemeral_heap_segment ? heap.alloc_allocated : TO_TADDR(seg.allocated))) + { + // get the next segment + dwAddrSeg = seg.next; + + // We reached the last segment without finding the object. + if (dwAddrSeg == NULL) + return FALSE; + + if (seg.Request(g_sos, dwAddrSeg, heap) != S_OK) + { + ExtOut("Error requesting heap segment %p.\n", SOS_PTR(dwAddrSeg)); + return FALSE; + } + } + + return TRUE; +} + +BOOL VerifyObject(const DacpGcHeapDetails &heap, DWORD_PTR objAddr, DWORD_PTR MTAddr, size_t objSize, BOOL bVerifyMember) +{ + // This is only used by the other VerifyObject function if bVerifyMember is true, + // so we only intialize it if we need it for verifying object members. + DacpHeapSegmentData seg; + + if (bVerifyMember) + { + // if we fail to find the segment, we cannot verify the object's members + bVerifyMember = FindSegment(heap, seg, objAddr); + } + + return VerifyObject(heap, seg, objAddr, MTAddr, objSize, bVerifyMember); +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +typedef void (*TYPETREEVISIT)(size_t methodTable, size_t ID, LPVOID token); + +// TODO remove this. MethodTableCache already maps method tables to +// various information. We don't need TypeTree to do this too. +// Straightfoward to do, but low priority. +class TypeTree +{ +private: + size_t methodTable; + size_t ID; + TypeTree *pLeft; + TypeTree *pRight; + +public: + TypeTree(size_t MT) : methodTable(MT),ID(0),pLeft(NULL),pRight(NULL) { } + + BOOL isIn(size_t MT, size_t *pID) + { + TypeTree *pCur = this; + + while (pCur) + { + if (MT == pCur->methodTable) + { + if (pID) + *pID = pCur->ID; + return TRUE; + } + else if (MT < pCur->methodTable) + pCur = pCur->pLeft; + else + pCur = pCur->pRight; + } + + return FALSE; + } + + BOOL insert(size_t MT) + { + TypeTree *pCur = this; + + while (pCur) + { + if (MT == pCur->methodTable) + return TRUE; + else if ((MT < pCur->methodTable)) + { + if (pCur->pLeft) + pCur = pCur->pLeft; + else + break; + } + else if (pCur->pRight) + pCur = pCur->pRight; + else + break; + } + + // If we got here, we need to append at the current node. + TypeTree *pNewNode = new TypeTree(MT); + if (pNewNode == NULL) + return FALSE; + + if (MT < pCur->methodTable) + pCur->pLeft = pNewNode; + else + pCur->pRight = pNewNode; + + return TRUE; + } + + static void destroy(TypeTree *pStart) + { + TypeTree *pCur = pStart; + + if (pCur) + { + destroy(pCur->pLeft); + destroy(pCur->pRight); + delete [] pCur; + } + } + + static void visit_inorder(TypeTree *pStart, TYPETREEVISIT pFunc, LPVOID token) + { + TypeTree *pCur = pStart; + + if (pCur) + { + visit_inorder(pCur->pLeft, pFunc, token); + pFunc (pCur->methodTable, pCur->ID, token); + visit_inorder(pCur->pRight, pFunc, token); + } + } + + static void setTypeIDs(TypeTree *pStart, size_t *pCurID) + { + TypeTree *pCur = pStart; + + if (pCur) + { + setTypeIDs(pCur->pLeft, pCurID); + pCur->ID = *pCurID; + (*pCurID)++; + setTypeIDs(pCur->pRight, pCurID); + } + } + +}; + +/////////////////////////////////////////////////////////////////////////////// +// + +HeapTraverser::HeapTraverser(bool verify) +{ + m_format = 0; + m_file = NULL; + m_objVisited = 0; + m_pTypeTree = NULL; + m_curNID = 1; + m_verify = verify; +} + +HeapTraverser::~HeapTraverser() +{ + if (m_pTypeTree) { + TypeTree::destroy(m_pTypeTree); + m_pTypeTree = NULL; + } +} + +BOOL HeapTraverser::Initialize() +{ + if (!GCHeapsTraverse (HeapTraverser::GatherTypes, this, m_verify)) + { + ExtOut("Error during heap traverse\n"); + return FALSE; + } + + GCRootImpl::GetDependentHandleMap(mDependentHandleMap); + + size_t startID = 1; + TypeTree::setTypeIDs(m_pTypeTree, &startID); + + return TRUE; +} + +BOOL HeapTraverser::CreateReport (FILE *fp, int format) +{ + if (fp == NULL || (format!=FORMAT_XML && format != FORMAT_CLRPROFILER)) + { + return FALSE; + } + + m_file = fp; + m_format = format; + + PrintSection(TYPE_START,TRUE); + + PrintSection(TYPE_TYPES,TRUE); + TypeTree::visit_inorder(m_pTypeTree, HeapTraverser::PrintOutTree, this); + PrintSection(TYPE_TYPES,FALSE); + + ExtOut("tracing roots...\n"); + PrintSection(TYPE_ROOTS,TRUE); + PrintRootHead(); + + TraceHandles(); + FindGCRootOnStacks(); + + PrintRootTail(); + PrintSection(TYPE_ROOTS,FALSE); + + // now print type tree + PrintSection(TYPE_OBJECTS,TRUE); + ExtOut("\nWalking heap...\n"); + m_objVisited = 0; // for UI updates + GCHeapsTraverse (HeapTraverser::PrintHeap, this, FALSE); // Never verify on the second pass + PrintSection(TYPE_OBJECTS,FALSE); + + PrintSection(TYPE_START,FALSE); + + m_file = NULL; + return TRUE; +} + +void HeapTraverser::insert(size_t mTable) +{ + if (m_pTypeTree == NULL) + { + m_pTypeTree = new TypeTree(mTable); + if (m_pTypeTree == NULL) + { + ReportOOM(); + return; + } + } + else + { + m_pTypeTree->insert(mTable); + } +} + +size_t HeapTraverser::getID(size_t mTable) +{ + if (m_pTypeTree == NULL) + { + return 0; + } + // IDs start at 1, so we can return 0 if not found. + size_t ret; + if (m_pTypeTree->isIn(mTable,&ret)) + { + return ret; + } + + return 0; +} + +#ifndef FEATURE_PAL +void replace(std::wstring &str, const WCHAR *toReplace, const WCHAR *replaceWith) +{ + const size_t replaceLen = _wcslen(toReplace); + const size_t replaceWithLen = _wcslen(replaceWith); + + size_t i = str.find(toReplace); + while (i != std::wstring::npos) + { + str.replace(i, replaceLen, replaceWith); + i = str.find(toReplace, i + replaceWithLen); + } +} +#endif + +void HeapTraverser::PrintType(size_t ID,LPCWSTR name) +{ + if (m_format==FORMAT_XML) + { +#ifndef FEATURE_PAL + // Sanitize name based on XML spec. + std::wstring wname = name; + replace(wname, W("&"), W("&")); + replace(wname, W("\""), W(""")); + replace(wname, W("'"), W("'")); + replace(wname, W("<"), W("<")); + replace(wname, W(">"), W(">")); + name = wname.c_str(); +#endif + fprintf(m_file, + "<type id=\"%d\" name=\"%S\"/>\n", + ID, name); + } + else if (m_format==FORMAT_CLRPROFILER) + { + fprintf(m_file, + "t %d 0 %S\n", + ID,name); + } +} + +void HeapTraverser::PrintObjectHead(size_t objAddr,size_t typeID,size_t Size) +{ + if (m_format==FORMAT_XML) + { + fprintf(m_file, + "<object address=\"0x%p\" typeid=\"%d\" size=\"%d\">\n", + (PBYTE)objAddr,typeID, Size); + } + else if (m_format==FORMAT_CLRPROFILER) + { + fprintf(m_file, + "n %d 1 %d %d\n", + m_curNID,typeID,Size); + + fprintf(m_file, + "! 1 0x%p %d\n", + (PBYTE)objAddr,m_curNID); + + m_curNID++; + + fprintf(m_file, + "o 0x%p %d %d ", + (PBYTE)objAddr,typeID,Size); + } +} + +void HeapTraverser::PrintObjectMember(size_t memberValue, bool dependentHandle) +{ + if (m_format==FORMAT_XML) + { + fprintf(m_file, + " <member address=\"0x%p\"%s/>\n", + (PBYTE)memberValue, dependentHandle ? " dependentHandle=\"1\"" : ""); + } + else if (m_format==FORMAT_CLRPROFILER) + { + fprintf(m_file, + " 0x%p", + (PBYTE)memberValue); + } +} + +void HeapTraverser::PrintObjectTail() +{ + if (m_format==FORMAT_XML) + { + fprintf(m_file, + "</object>\n"); + } + else if (m_format==FORMAT_CLRPROFILER) + { + fprintf(m_file, + "\n"); + } +} + +void HeapTraverser::PrintRootHead() +{ + if (m_format==FORMAT_CLRPROFILER) + { + fprintf(m_file, + "r "); + } +} + +void HeapTraverser::PrintRoot(LPCWSTR kind,size_t Value) +{ + if (m_format==FORMAT_XML) + { + fprintf(m_file, + "<root kind=\"%S\" address=\"0x%p\"/>\n", + kind, + (PBYTE)Value); + } + else if (m_format==FORMAT_CLRPROFILER) + { + fprintf(m_file, + "0x%p ", + (PBYTE)Value); + } +} + +void HeapTraverser::PrintRootTail() +{ + if (m_format==FORMAT_CLRPROFILER) + { + fprintf(m_file, + "\n"); + } +} + +void HeapTraverser::PrintSection(int Type,BOOL bOpening) +{ + const char *const pTypes[] = {"<gcheap>","<types>","<roots>","<objects>"}; + const char *const pTypeEnds[] = {"</gcheap>","</types>","</roots>","</objects>"}; + + if (m_format==FORMAT_XML) + { + if ((Type >= 0) && (Type < TYPE_HIGHEST)) + { + fprintf(m_file,"%s\n",bOpening ? pTypes[Type] : pTypeEnds[Type]); + } + else + { + ExtOut ("INVALID TYPE %d\n", Type); + } + } + else if (m_format==FORMAT_CLRPROFILER) + { + if ((Type == TYPE_START) && !bOpening) // a final newline is needed + { + fprintf(m_file,"\n"); + } + } +} + +void HeapTraverser::FindGCRootOnStacks() +{ + ArrayHolder<DWORD_PTR> threadList = NULL; + int numThreads = 0; + + // GetThreadList calls ReportOOM so we don't need to do that here. + HRESULT hr = GetThreadList(&threadList, &numThreads); + if (FAILED(hr) || !threadList) + { + ExtOut("Failed to enumerate threads in the process.\n"); + return; + } + + int total = 0; + DacpThreadData vThread; + for (int i = 0; i < numThreads; i++) + { + if (FAILED(vThread.Request(g_sos, threadList[i]))) + continue; + + if (vThread.osThreadId) + { + unsigned int refCount = 0; + ArrayHolder<SOSStackRefData> refs = NULL; + + if (FAILED(::GetGCRefs(vThread.osThreadId, &refs, &refCount, NULL, NULL))) + { + ExtOut("Failed to walk thread %x\n", vThread.osThreadId); + continue; + } + + for (unsigned int i = 0; i < refCount; ++i) + if (refs[i].Object) + PrintRoot(W("stack"), TO_TADDR(refs[i].Object)); + } + } + +} + + +/* static */ void HeapTraverser::PrintOutTree(size_t methodTable, size_t ID, + LPVOID token) +{ + HeapTraverser *pHolder = (HeapTraverser *) token; + NameForMT_s(methodTable, g_mdName, mdNameLen); + pHolder->PrintType(ID,g_mdName); +} + + +/* static */ void HeapTraverser::PrintHeap(DWORD_PTR objAddr,size_t Size, + DWORD_PTR methodTable, LPVOID token) +{ + if (!IsMTForFreeObj (methodTable)) + { + HeapTraverser *pHolder = (HeapTraverser *) token; + pHolder->m_objVisited++; + size_t ID = pHolder->getID(methodTable); + + pHolder->PrintObjectHead(objAddr, ID, Size); + pHolder->PrintRefs(objAddr, methodTable, Size); + pHolder->PrintObjectTail(); + + if (pHolder->m_objVisited % 1024 == 0) { + ExtOut("."); + if (pHolder->m_objVisited % (1024*64) == 0) + ExtOut("\r\n"); + } + } +} + +void HeapTraverser::TraceHandles() +{ + unsigned int fetched = 0; + SOSHandleData data[64]; + + ToRelease<ISOSHandleEnum> handles; + HRESULT hr = g_sos->GetHandleEnum(&handles); + if (FAILED(hr)) + return; + + do + { + hr = handles->Next(_countof(data), data, &fetched); + + if (FAILED(hr)) + break; + + for (unsigned int i = 0; i < fetched; ++i) + PrintRoot(W("handle"), (size_t)data[i].Handle); + } while (fetched == _countof(data)); +} + +/* static */ void HeapTraverser::GatherTypes(DWORD_PTR objAddr,size_t Size, + DWORD_PTR methodTable, LPVOID token) +{ + if (!IsMTForFreeObj (methodTable)) + { + HeapTraverser *pHolder = (HeapTraverser *) token; + pHolder->insert(methodTable); + } +} + +void HeapTraverser::PrintRefs(size_t obj, size_t methodTable, size_t size) +{ + DWORD_PTR dwAddr = methodTable; + + // TODO: pass info to callback having to lookup the MethodTableInfo again + MethodTableInfo* info = g_special_mtCache.Lookup((DWORD_PTR)methodTable); + _ASSERTE(info->IsInitialized()); // This is the second pass, so we should be intialized + + if (!info->bContainsPointers) + return; + + // Fetch the GCInfo from the other process + CGCDesc *map = info->GCInfo; + if (map == NULL) + { + INT_PTR nEntries; + move_xp (nEntries, dwAddr-sizeof(PVOID)); + bool arrayOfVC = false; + if (nEntries<0) + { + arrayOfVC = true; + nEntries = -nEntries; + } + + size_t nSlots = 1+nEntries*sizeof(CGCDescSeries)/sizeof(DWORD_PTR); + info->GCInfoBuffer = new DWORD_PTR[nSlots]; + if (info->GCInfoBuffer == NULL) + { + ReportOOM(); + return; + } + + if (FAILED(rvCache->Read(TO_CDADDR(dwAddr - nSlots*sizeof(DWORD_PTR)), + info->GCInfoBuffer, (ULONG) (nSlots*sizeof(DWORD_PTR)), NULL))) + return; + + map = info->GCInfo = (CGCDesc*)(info->GCInfoBuffer+nSlots); + info->ArrayOfVC = arrayOfVC; + } + + mCache.EnsureRangeInCache((TADDR)obj, (unsigned int)size); + for (sos::RefIterator itr(obj, info->GCInfo, info->ArrayOfVC, &mCache); itr; ++itr) + { + if (*itr && (!m_verify || sos::IsObject(*itr))) + PrintObjectMember(*itr, false); + } + + std::unordered_map<TADDR, std::list<TADDR>>::iterator itr = mDependentHandleMap.find((TADDR)obj); + if (itr != mDependentHandleMap.end()) + { + for (std::list<TADDR>::iterator litr = itr->second.begin(); litr != itr->second.end(); ++litr) + { + PrintObjectMember(*litr, true); + } + } +} + + +void sos::ObjectIterator::BuildError(char *out, size_t count, const char *format, ...) const +{ + if (out == NULL || count == 0) + return; + + va_list args; + va_start(args, format); + + int written = vsprintf_s(out, count, format, args); + if (written > 0 && mLastObj) + sprintf_s(out+written, count-written, "\nLast good object: %p.\n", (int*)mLastObj); + + va_end(args); +} + +bool sos::ObjectIterator::VerifyObjectMembers(char *reason, size_t count) const +{ + if (!mCurrObj.HasPointers()) + return true; + + size_t size = mCurrObj.GetSize(); + size_t objAddr = (size_t)mCurrObj.GetAddress(); + TADDR mt = mCurrObj.GetMT(); + + INT_PTR nEntries; + MOVE(nEntries, mt-sizeof(PVOID)); + if (nEntries < 0) + nEntries = -nEntries; + + size_t nSlots = 1 + nEntries * sizeof(CGCDescSeries)/sizeof(DWORD_PTR); + ArrayHolder<DWORD_PTR> buffer = new DWORD_PTR[nSlots]; + + if (FAILED(g_ExtData->ReadVirtual(TO_CDADDR(mt - nSlots*sizeof(DWORD_PTR)), + buffer, (ULONG) (nSlots*sizeof(DWORD_PTR)), NULL))) + { + BuildError(reason, count, "Object %s has a bad GCDesc.", DMLObject(objAddr)); + return false; + } + + CGCDesc *map = (CGCDesc *)(buffer+nSlots); + CGCDescSeries* cur = map->GetHighestSeries(); + CGCDescSeries* last = map->GetLowestSeries(); + + const size_t bufferSize = sizeof(size_t)*128; + size_t objBuffer[bufferSize/sizeof(size_t)]; + size_t dwBeginAddr = (size_t)objAddr; + size_t bytesInBuffer = bufferSize; + if (size < bytesInBuffer) + bytesInBuffer = size; + + + if (FAILED(g_ExtData->ReadVirtual(TO_CDADDR(dwBeginAddr), objBuffer, (ULONG) bytesInBuffer,NULL))) + { + BuildError(reason, count, "Object %s: Failed to read members.", DMLObject(objAddr)); + return false; + } + + BOOL bCheckCard = TRUE; + { + DWORD_PTR dwAddrCard = (DWORD_PTR)objAddr; + while (dwAddrCard < objAddr + size) + { + if (CardIsSet (mHeaps[mCurrHeap], dwAddrCard)) + { + bCheckCard = FALSE; + break; + } + dwAddrCard += card_size; + } + if (bCheckCard) + { + dwAddrCard = objAddr + size - 2*sizeof(PVOID); + if (CardIsSet (mHeaps[mCurrHeap], dwAddrCard)) + { + bCheckCard = FALSE; + } + } + } + + if (cur >= last) + { + do + { + BYTE** parm = (BYTE**)((objAddr) + cur->GetSeriesOffset()); + BYTE** ppstop = + (BYTE**)((BYTE*)parm + cur->GetSeriesSize() + (size)); + while (parm < ppstop) + { + CheckInterrupt(); + size_t dwAddr1; + + // Do we run out of cache? + if ((size_t)parm >= dwBeginAddr+bytesInBuffer) + { + // dwBeginAddr += bytesInBuffer; + dwBeginAddr = (size_t)parm; + if (dwBeginAddr >= objAddr + size) + { + return true; + } + bytesInBuffer = bufferSize; + if (objAddr+size-dwBeginAddr < bytesInBuffer) + { + bytesInBuffer = objAddr+size-dwBeginAddr; + } + if (FAILED(g_ExtData->ReadVirtual(TO_CDADDR(dwBeginAddr), objBuffer, (ULONG) bytesInBuffer, NULL))) + { + BuildError(reason, count, "Object %s: Failed to read members.", DMLObject(objAddr)); + return false; + } + } + dwAddr1 = objBuffer[((size_t)parm-dwBeginAddr)/sizeof(size_t)]; + if (dwAddr1) { + DWORD_PTR dwChild = dwAddr1; + // Try something more efficient than IsObject here. Is the methodtable valid? + size_t s; + BOOL bPointers; + DWORD_PTR dwAddrMethTable; + if (FAILED(GetMTOfObject(dwAddr1, &dwAddrMethTable)) || + (GetSizeEfficient(dwAddr1, dwAddrMethTable, FALSE, s, bPointers) == FALSE)) + { + BuildError(reason, count, "object %s: bad member %p at %p", DMLObject(objAddr), + SOS_PTR(dwAddr1), SOS_PTR(objAddr+(size_t)parm-objAddr)); + + return false; + } + + if (IsMTForFreeObj(dwAddrMethTable)) + { + sos::Throw<HeapCorruption>("object %s contains free object %p at %p", DMLObject(objAddr), + SOS_PTR(dwAddr1), SOS_PTR(objAddr+(size_t)parm-objAddr)); + } + + // verify card table + if (bCheckCard && + NeedCard(objAddr+(size_t)parm-objAddr,dwChild)) + { + BuildError(reason, count, "Object %s: %s missing card_table entry for %p", + DMLObject(objAddr), (dwChild == dwAddr1)? "" : " maybe", + SOS_PTR(objAddr+(size_t)parm-objAddr)); + + return false; + } + } + parm++; + } + cur--; + CheckInterrupt(); + + } while (cur >= last); + } + else + { + int cnt = (int) map->GetNumSeries(); + BYTE** parm = (BYTE**)((objAddr) + cur->startoffset); + while ((BYTE*)parm < (BYTE*)((objAddr)+(size)-plug_skew)) + { + for (int __i = 0; __i > cnt; __i--) + { + CheckInterrupt(); + + unsigned skip = cur->val_serie[__i].skip; + unsigned nptrs = cur->val_serie[__i].nptrs; + BYTE** ppstop = parm + nptrs; + do + { + size_t dwAddr1; + // Do we run out of cache? + if ((size_t)parm >= dwBeginAddr+bytesInBuffer) + { + // dwBeginAddr += bytesInBuffer; + dwBeginAddr = (size_t)parm; + if (dwBeginAddr >= objAddr + size) + return true; + + bytesInBuffer = bufferSize; + if (objAddr+size-dwBeginAddr < bytesInBuffer) + bytesInBuffer = objAddr+size-dwBeginAddr; + + if (FAILED(g_ExtData->ReadVirtual(TO_CDADDR(dwBeginAddr), objBuffer, (ULONG) bytesInBuffer, NULL))) + { + BuildError(reason, count, "Object %s: Failed to read members.", DMLObject(objAddr)); + return false; + } + } + dwAddr1 = objBuffer[((size_t)parm-dwBeginAddr)/sizeof(size_t)]; + { + if (dwAddr1) + { + DWORD_PTR dwChild = dwAddr1; + // Try something more efficient than IsObject here. Is the methodtable valid? + size_t s; + BOOL bPointers; + DWORD_PTR dwAddrMethTable; + if (FAILED(GetMTOfObject(dwAddr1, &dwAddrMethTable)) || + (GetSizeEfficient(dwAddr1, dwAddrMethTable, FALSE, s, bPointers) == FALSE)) + { + BuildError(reason, count, "Object %s: Bad member %p at %p.\n", DMLObject(objAddr), + SOS_PTR(dwAddr1), SOS_PTR(objAddr+(size_t)parm-objAddr)); + + return false; + } + + if (IsMTForFreeObj(dwAddrMethTable)) + { + BuildError(reason, count, "Object %s contains free object %p at %p.", DMLObject(objAddr), + SOS_PTR(dwAddr1), SOS_PTR(objAddr+(size_t)parm-objAddr)); + return false; + } + + // verify card table + if (bCheckCard && + NeedCard (objAddr+(size_t)parm-objAddr,dwAddr1)) + { + BuildError(reason, count, "Object %s:%s missing card_table entry for %p", + DMLObject(objAddr), (dwChild == dwAddr1) ? "" : " maybe", + SOS_PTR(objAddr+(size_t)parm-objAddr)); + + return false; + } + } + } + parm++; + CheckInterrupt(); + } while (parm < ppstop); + parm = (BYTE**)((BYTE*)parm + skip); + } + } + } + + return true; +} + +bool sos::ObjectIterator::Verify(char *reason, size_t count) const +{ + try + { + TADDR mt = mCurrObj.GetMT(); + + if (MethodTable::GetFreeMT() == mt) + { + return true; + } + + size_t size = mCurrObj.GetSize(); + if (size < min_obj_size) + { + BuildError(reason, count, "Object %s: Size %d is too small.", DMLObject(mCurrObj.GetAddress()), size); + return false; + } + + if (mCurrObj.GetAddress() + mCurrObj.GetSize() > mSegmentEnd) + { + BuildError(reason, count, "Object %s is too large. End of segment at %p.", DMLObject(mCurrObj), mSegmentEnd); + return false; + } + + BOOL bVerifyMember = TRUE; + + // If we requested to verify the object's members, the GC may be in a state where that's not possible. + // Here we check to see if the object in question needs to have its members updated. If so, we turn off + // verification for the object. + BOOL consider_bgc_mark = FALSE, check_current_sweep = FALSE, check_saved_sweep = FALSE; + should_check_bgc_mark(mHeaps[mCurrHeap], mSegment, &consider_bgc_mark, &check_current_sweep, &check_saved_sweep); + bVerifyMember = fgc_should_consider_object(mHeaps[mCurrHeap], mCurrObj.GetAddress(), mSegment, + consider_bgc_mark, check_current_sweep, check_saved_sweep); + + if (bVerifyMember) + return VerifyObjectMembers(reason, count); + } + catch(const sos::Exception &e) + { + BuildError(reason, count, e.GetMesssage()); + return false; + } + + return true; +} + +bool sos::ObjectIterator::Verify() const +{ + char *c = NULL; + return Verify(c, 0); +} diff --git a/src/ToolBox/SOS/Strike/inc/.gitmirror b/src/ToolBox/SOS/Strike/inc/.gitmirror new file mode 100644 index 0000000000..f507630f94 --- /dev/null +++ b/src/ToolBox/SOS/Strike/inc/.gitmirror @@ -0,0 +1 @@ +Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror.
\ No newline at end of file diff --git a/src/ToolBox/SOS/Strike/inc/dbgeng.h b/src/ToolBox/SOS/Strike/inc/dbgeng.h new file mode 100644 index 0000000000..73e4d19f99 --- /dev/null +++ b/src/ToolBox/SOS/Strike/inc/dbgeng.h @@ -0,0 +1,16122 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +//---------------------------------------------------------------------------- +// +// Debugger engine interfaces. +// + +// +//---------------------------------------------------------------------------- + +#ifndef __DBGENG_H__ +#define __DBGENG_H__ + +#include <stdarg.h> +#include <objbase.h> + +#ifndef _WDBGEXTS_ +typedef struct _WINDBG_EXTENSION_APIS32* PWINDBG_EXTENSION_APIS32; +typedef struct _WINDBG_EXTENSION_APIS64* PWINDBG_EXTENSION_APIS64; +#endif + +#ifndef _CRASHLIB_ +typedef struct _MEMORY_BASIC_INFORMATION64* PMEMORY_BASIC_INFORMATION64; +#endif + +#ifndef __specstrings +// Should include SpecStrings.h to get proper definitions. +#define __in +#define __in_opt +#define __in_bcount(x) +#define __in_bcount_opt(x) +#define __in_ecount(x) +#define __in_ecount_opt(x) +#define __out +#define __out_opt +#define __out_bcount(x) +#define __out_bcount_opt(x) +#define __out_ecount(x) +#define __out_ecount_opt(x) +#define __out_xcount(x) +#define __inout +#define __inout_opt +#define __reserved +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +//---------------------------------------------------------------------------- +// +// GUIDs and interface forward declarations. +// +//---------------------------------------------------------------------------- + +/* f2df5f53-071f-47bd-9de6-5734c3fed689 */ +DEFINE_GUID(IID_IDebugAdvanced, 0xf2df5f53, 0x071f, 0x47bd, + 0x9d, 0xe6, 0x57, 0x34, 0xc3, 0xfe, 0xd6, 0x89); +/* 716d14c9-119b-4ba5-af1f-0890e672416a */ +DEFINE_GUID(IID_IDebugAdvanced2, 0x716d14c9, 0x119b, 0x4ba5, + 0xaf, 0x1f, 0x08, 0x90, 0xe6, 0x72, 0x41, 0x6a); +/* cba4abb4-84c4-444d-87ca-a04e13286739 */ +DEFINE_GUID(IID_IDebugAdvanced3, 0xcba4abb4, 0x84c4, 0x444d, + 0x87, 0xca, 0xa0, 0x4e, 0x13, 0x28, 0x67, 0x39); +/* 5bd9d474-5975-423a-b88b-65a8e7110e65 */ +DEFINE_GUID(IID_IDebugBreakpoint, 0x5bd9d474, 0x5975, 0x423a, + 0xb8, 0x8b, 0x65, 0xa8, 0xe7, 0x11, 0x0e, 0x65); +/* 1b278d20-79f2-426e-a3f9-c1ddf375d48e */ +DEFINE_GUID(IID_IDebugBreakpoint2, 0x1b278d20, 0x79f2, 0x426e, + 0xa3, 0xf9, 0xc1, 0xdd, 0xf3, 0x75, 0xd4, 0x8e); +/* 27fe5639-8407-4f47-8364-ee118fb08ac8 */ +DEFINE_GUID(IID_IDebugClient, 0x27fe5639, 0x8407, 0x4f47, + 0x83, 0x64, 0xee, 0x11, 0x8f, 0xb0, 0x8a, 0xc8); +/* edbed635-372e-4dab-bbfe-ed0d2f63be81 */ +DEFINE_GUID(IID_IDebugClient2, 0xedbed635, 0x372e, 0x4dab, + 0xbb, 0xfe, 0xed, 0x0d, 0x2f, 0x63, 0xbe, 0x81); +/* dd492d7f-71b8-4ad6-a8dc-1c887479ff91 */ +DEFINE_GUID(IID_IDebugClient3, 0xdd492d7f, 0x71b8, 0x4ad6, + 0xa8, 0xdc, 0x1c, 0x88, 0x74, 0x79, 0xff, 0x91); +/* ca83c3de-5089-4cf8-93c8-d892387f2a5e */ +DEFINE_GUID(IID_IDebugClient4, 0xca83c3de, 0x5089, 0x4cf8, + 0x93, 0xc8, 0xd8, 0x92, 0x38, 0x7f, 0x2a, 0x5e); +/* e3acb9d7-7ec2-4f0c-a0da-e81e0cbbe628 */ +DEFINE_GUID(IID_IDebugClient5, 0xe3acb9d7, 0x7ec2, 0x4f0c, + 0xa0, 0xda, 0xe8, 0x1e, 0x0c, 0xbb, 0xe6, 0x28); +/* 5182e668-105e-416e-ad92-24ef800424ba */ +DEFINE_GUID(IID_IDebugControl, 0x5182e668, 0x105e, 0x416e, + 0xad, 0x92, 0x24, 0xef, 0x80, 0x04, 0x24, 0xba); +/* d4366723-44df-4bed-8c7e-4c05424f4588 */ +DEFINE_GUID(IID_IDebugControl2, 0xd4366723, 0x44df, 0x4bed, + 0x8c, 0x7e, 0x4c, 0x05, 0x42, 0x4f, 0x45, 0x88); +/* 7df74a86-b03f-407f-90ab-a20dadcead08 */ +DEFINE_GUID(IID_IDebugControl3, 0x7df74a86, 0xb03f, 0x407f, + 0x90, 0xab, 0xa2, 0x0d, 0xad, 0xce, 0xad, 0x08); +/* 94e60ce9-9b41-4b19-9fc0-6d9eb35272b3 */ +DEFINE_GUID(IID_IDebugControl4, 0x94e60ce9, 0x9b41, 0x4b19, + 0x9f, 0xc0, 0x6d, 0x9e, 0xb3, 0x52, 0x72, 0xb3); +/* 88f7dfab-3ea7-4c3a-aefb-c4e8106173aa */ +DEFINE_GUID(IID_IDebugDataSpaces, 0x88f7dfab, 0x3ea7, 0x4c3a, + 0xae, 0xfb, 0xc4, 0xe8, 0x10, 0x61, 0x73, 0xaa); +/* 7a5e852f-96e9-468f-ac1b-0b3addc4a049 */ +DEFINE_GUID(IID_IDebugDataSpaces2, 0x7a5e852f, 0x96e9, 0x468f, + 0xac, 0x1b, 0x0b, 0x3a, 0xdd, 0xc4, 0xa0, 0x49); +/* 23f79d6c-8aaf-4f7c-a607-9995f5407e63 */ +DEFINE_GUID(IID_IDebugDataSpaces3, 0x23f79d6c, 0x8aaf, 0x4f7c, + 0xa6, 0x07, 0x99, 0x95, 0xf5, 0x40, 0x7e, 0x63); +/* d98ada1f-29e9-4ef5-a6c0-e53349883212 */ +DEFINE_GUID(IID_IDebugDataSpaces4, 0xd98ada1f, 0x29e9, 0x4ef5, + 0xa6, 0xc0, 0xe5, 0x33, 0x49, 0x88, 0x32, 0x12); +/* 337be28b-5036-4d72-b6bf-c45fbb9f2eaa */ +DEFINE_GUID(IID_IDebugEventCallbacks, 0x337be28b, 0x5036, 0x4d72, + 0xb6, 0xbf, 0xc4, 0x5f, 0xbb, 0x9f, 0x2e, 0xaa); +/* 0690e046-9c23-45ac-a04f-987ac29ad0d3 */ +DEFINE_GUID(IID_IDebugEventCallbacksWide, 0x0690e046, 0x9c23, 0x45ac, + 0xa0, 0x4f, 0x98, 0x7a, 0xc2, 0x9a, 0xd0, 0xd3); +/* 9f50e42c-f136-499e-9a97-73036c94ed2d */ +DEFINE_GUID(IID_IDebugInputCallbacks, 0x9f50e42c, 0xf136, 0x499e, + 0x9a, 0x97, 0x73, 0x03, 0x6c, 0x94, 0xed, 0x2d); +/* 4bf58045-d654-4c40-b0af-683090f356dc */ +DEFINE_GUID(IID_IDebugOutputCallbacks, 0x4bf58045, 0xd654, 0x4c40, + 0xb0, 0xaf, 0x68, 0x30, 0x90, 0xf3, 0x56, 0xdc); +/* 4c7fd663-c394-4e26-8ef1-34ad5ed3764c */ +DEFINE_GUID(IID_IDebugOutputCallbacksWide, 0x4c7fd663, 0xc394, 0x4e26, + 0x8e, 0xf1, 0x34, 0xad, 0x5e, 0xd3, 0x76, 0x4c); +/* 67721fe9-56d2-4a44-a325-2b65513ce6eb */ +DEFINE_GUID(IID_IDebugOutputCallbacks2, 0x67721fe9, 0x56d2, 0x4a44, + 0xa3, 0x25, 0x2b, 0x65, 0x51, 0x3c, 0xe6, 0xeb); +/* ce289126-9e84-45a7-937e-67bb18691493 */ +DEFINE_GUID(IID_IDebugRegisters, 0xce289126, 0x9e84, 0x45a7, + 0x93, 0x7e, 0x67, 0xbb, 0x18, 0x69, 0x14, 0x93); +/* 1656afa9-19c6-4e3a-97e7-5dc9160cf9c4 */ +DEFINE_GUID(IID_IDebugRegisters2, 0x1656afa9, 0x19c6, 0x4e3a, + 0x97, 0xe7, 0x5d, 0xc9, 0x16, 0x0c, 0xf9, 0xc4); +/* f2528316-0f1a-4431-aeed-11d096e1e2ab */ +DEFINE_GUID(IID_IDebugSymbolGroup, 0xf2528316, 0x0f1a, 0x4431, + 0xae, 0xed, 0x11, 0xd0, 0x96, 0xe1, 0xe2, 0xab); +/* 6a7ccc5f-fb5e-4dcc-b41c-6c20307bccc7 */ +DEFINE_GUID(IID_IDebugSymbolGroup2, 0x6a7ccc5f, 0xfb5e, 0x4dcc, + 0xb4, 0x1c, 0x6c, 0x20, 0x30, 0x7b, 0xcc, 0xc7); +/* 8c31e98c-983a-48a5-9016-6fe5d667a950 */ +DEFINE_GUID(IID_IDebugSymbols, 0x8c31e98c, 0x983a, 0x48a5, + 0x90, 0x16, 0x6f, 0xe5, 0xd6, 0x67, 0xa9, 0x50); +/* 3a707211-afdd-4495-ad4f-56fecdf8163f */ +DEFINE_GUID(IID_IDebugSymbols2, 0x3a707211, 0xafdd, 0x4495, + 0xad, 0x4f, 0x56, 0xfe, 0xcd, 0xf8, 0x16, 0x3f); +/* f02fbecc-50ac-4f36-9ad9-c975e8f32ff8 */ +DEFINE_GUID(IID_IDebugSymbols3, 0xf02fbecc, 0x50ac, 0x4f36, + 0x9a, 0xd9, 0xc9, 0x75, 0xe8, 0xf3, 0x2f, 0xf8); +/* 6b86fe2c-2c4f-4f0c-9da2-174311acc327 */ +DEFINE_GUID(IID_IDebugSystemObjects, 0x6b86fe2c, 0x2c4f, 0x4f0c, + 0x9d, 0xa2, 0x17, 0x43, 0x11, 0xac, 0xc3, 0x27); +/* 0ae9f5ff-1852-4679-b055-494bee6407ee */ +DEFINE_GUID(IID_IDebugSystemObjects2, 0x0ae9f5ff, 0x1852, 0x4679, + 0xb0, 0x55, 0x49, 0x4b, 0xee, 0x64, 0x07, 0xee); +/* e9676e2f-e286-4ea3-b0f9-dfe5d9fc330e */ +DEFINE_GUID(IID_IDebugSystemObjects3, 0xe9676e2f, 0xe286, 0x4ea3, + 0xb0, 0xf9, 0xdf, 0xe5, 0xd9, 0xfc, 0x33, 0x0e); +/* 489468e6-7d0f-4af5-87ab-25207454d553 */ +DEFINE_GUID(IID_IDebugSystemObjects4, 0x489468e6, 0x7d0f, 0x4af5, + 0x87, 0xab, 0x25, 0x20, 0x74, 0x54, 0xd5, 0x53); + +typedef interface DECLSPEC_UUID("f2df5f53-071f-47bd-9de6-5734c3fed689") + IDebugAdvanced* PDEBUG_ADVANCED; +typedef interface DECLSPEC_UUID("716d14c9-119b-4ba5-af1f-0890e672416a") + IDebugAdvanced2* PDEBUG_ADVANCED2; +typedef interface DECLSPEC_UUID("cba4abb4-84c4-444d-87ca-a04e13286739") + IDebugAdvanced3* PDEBUG_ADVANCED3; +typedef interface DECLSPEC_UUID("5bd9d474-5975-423a-b88b-65a8e7110e65") + IDebugBreakpoint* PDEBUG_BREAKPOINT; +typedef interface DECLSPEC_UUID("1b278d20-79f2-426e-a3f9-c1ddf375d48e") + IDebugBreakpoint2* PDEBUG_BREAKPOINT2; +typedef interface DECLSPEC_UUID("27fe5639-8407-4f47-8364-ee118fb08ac8") + IDebugClient* PDEBUG_CLIENT; +typedef interface DECLSPEC_UUID("edbed635-372e-4dab-bbfe-ed0d2f63be81") + IDebugClient2* PDEBUG_CLIENT2; +typedef interface DECLSPEC_UUID("dd492d7f-71b8-4ad6-a8dc-1c887479ff91") + IDebugClient3* PDEBUG_CLIENT3; +typedef interface DECLSPEC_UUID("ca83c3de-5089-4cf8-93c8-d892387f2a5e") + IDebugClient4* PDEBUG_CLIENT4; +typedef interface DECLSPEC_UUID("e3acb9d7-7ec2-4f0c-a0da-e81e0cbbe628") + IDebugClient5* PDEBUG_CLIENT5; +typedef interface DECLSPEC_UUID("5182e668-105e-416e-ad92-24ef800424ba") + IDebugControl* PDEBUG_CONTROL; +typedef interface DECLSPEC_UUID("d4366723-44df-4bed-8c7e-4c05424f4588") + IDebugControl2* PDEBUG_CONTROL2; +typedef interface DECLSPEC_UUID("7df74a86-b03f-407f-90ab-a20dadcead08") + IDebugControl3* PDEBUG_CONTROL3; +typedef interface DECLSPEC_UUID("94e60ce9-9b41-4b19-9fc0-6d9eb35272b3") + IDebugControl4* PDEBUG_CONTROL4; +typedef interface DECLSPEC_UUID("88f7dfab-3ea7-4c3a-aefb-c4e8106173aa") + IDebugDataSpaces* PDEBUG_DATA_SPACES; +typedef interface DECLSPEC_UUID("7a5e852f-96e9-468f-ac1b-0b3addc4a049") + IDebugDataSpaces2* PDEBUG_DATA_SPACES2; +typedef interface DECLSPEC_UUID("23f79d6c-8aaf-4f7c-a607-9995f5407e63") + IDebugDataSpaces3* PDEBUG_DATA_SPACES3; +typedef interface DECLSPEC_UUID("d98ada1f-29e9-4ef5-a6c0-e53349883212") + IDebugDataSpaces4* PDEBUG_DATA_SPACES4; +typedef interface DECLSPEC_UUID("337be28b-5036-4d72-b6bf-c45fbb9f2eaa") + IDebugEventCallbacks* PDEBUG_EVENT_CALLBACKS; +typedef interface DECLSPEC_UUID("0690e046-9c23-45ac-a04f-987ac29ad0d3") + IDebugEventCallbacksWide* PDEBUG_EVENT_CALLBACKS_WIDE; +typedef interface DECLSPEC_UUID("9f50e42c-f136-499e-9a97-73036c94ed2d") + IDebugInputCallbacks* PDEBUG_INPUT_CALLBACKS; +typedef interface DECLSPEC_UUID("4bf58045-d654-4c40-b0af-683090f356dc") + IDebugOutputCallbacks* PDEBUG_OUTPUT_CALLBACKS; +typedef interface DECLSPEC_UUID("4c7fd663-c394-4e26-8ef1-34ad5ed3764c") + IDebugOutputCallbacksWide* PDEBUG_OUTPUT_CALLBACKS_WIDE; +typedef interface DECLSPEC_UUID("67721fe9-56d2-4a44-a325-2b65513ce6eb") + IDebugOutputCallbacks2* PDEBUG_OUTPUT_CALLBACKS2; +typedef interface DECLSPEC_UUID("ce289126-9e84-45a7-937e-67bb18691493") + IDebugRegisters* PDEBUG_REGISTERS; +typedef interface DECLSPEC_UUID("1656afa9-19c6-4e3a-97e7-5dc9160cf9c4") + IDebugRegisters2* PDEBUG_REGISTERS2; +typedef interface DECLSPEC_UUID("f2528316-0f1a-4431-aeed-11d096e1e2ab") + IDebugSymbolGroup* PDEBUG_SYMBOL_GROUP; +typedef interface DECLSPEC_UUID("6a7ccc5f-fb5e-4dcc-b41c-6c20307bccc7") + IDebugSymbolGroup2* PDEBUG_SYMBOL_GROUP2; +typedef interface DECLSPEC_UUID("8c31e98c-983a-48a5-9016-6fe5d667a950") + IDebugSymbols* PDEBUG_SYMBOLS; +typedef interface DECLSPEC_UUID("3a707211-afdd-4495-ad4f-56fecdf8163f") + IDebugSymbols2* PDEBUG_SYMBOLS2; +typedef interface DECLSPEC_UUID("f02fbecc-50ac-4f36-9ad9-c975e8f32ff8") + IDebugSymbols3* PDEBUG_SYMBOLS3; +typedef interface DECLSPEC_UUID("6b86fe2c-2c4f-4f0c-9da2-174311acc327") + IDebugSystemObjects* PDEBUG_SYSTEM_OBJECTS; +typedef interface DECLSPEC_UUID("0ae9f5ff-1852-4679-b055-494bee6407ee") + IDebugSystemObjects2* PDEBUG_SYSTEM_OBJECTS2; +typedef interface DECLSPEC_UUID("e9676e2f-e286-4ea3-b0f9-dfe5d9fc330e") + IDebugSystemObjects3* PDEBUG_SYSTEM_OBJECTS3; +typedef interface DECLSPEC_UUID("489468e6-7d0f-4af5-87ab-25207454d553") + IDebugSystemObjects4* PDEBUG_SYSTEM_OBJECTS4; + +//---------------------------------------------------------------------------- +// +// Macros. +// +//---------------------------------------------------------------------------- + +// Extends a 32-bit address into a 64-bit address. +#define DEBUG_EXTEND64(Addr) ((ULONG64)(LONG64)(LONG)(Addr)) + +//---------------------------------------------------------------------------- +// +// Client creation functions. +// +//---------------------------------------------------------------------------- + +// RemoteOptions specifies connection types and +// their parameters. Supported strings are: +// npipe:Server=<Machine>,Pipe=<Pipe name> +// tcp:Server=<Machine>,Port=<IP port> +STDAPI +DebugConnect( + __in PCSTR RemoteOptions, + __in REFIID InterfaceId, + __out PVOID* Interface + ); + +STDAPI +DebugConnectWide( + __in PCWSTR RemoteOptions, + __in REFIID InterfaceId, + __out PVOID* Interface + ); + +STDAPI +DebugCreate( + __in REFIID InterfaceId, + __out PVOID* Interface + ); + +//---------------------------------------------------------------------------- +// +// IDebugAdvanced. +// +//---------------------------------------------------------------------------- + +typedef struct _DEBUG_OFFSET_REGION +{ + ULONG64 Base; + ULONG64 Size; +} DEBUG_OFFSET_REGION, *PDEBUG_OFFSET_REGION; + +#undef INTERFACE +#define INTERFACE IDebugAdvanced +DECLARE_INTERFACE_(IDebugAdvanced, IUnknown) +{ + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + __in REFIID InterfaceId, + __out PVOID* Interface + ) PURE; + STDMETHOD_(ULONG, AddRef)( + THIS + ) PURE; + STDMETHOD_(ULONG, Release)( + THIS + ) PURE; + + // IDebugAdvanced. + + // Get/SetThreadContext offer control over + // the full processor context for a thread. + // Higher-level functions, such as the + // IDebugRegisters interface, allow similar + // access in simpler and more generic ways. + // Get/SetThreadContext are useful when + // large amounts of thread context must + // be changed and processor-specific code + // is not a problem. + STDMETHOD(GetThreadContext)( + THIS_ + __out_bcount(ContextSize) /* align_is(16) */ PVOID Context, + __in ULONG ContextSize + ) PURE; + STDMETHOD(SetThreadContext)( + THIS_ + __in_bcount(ContextSize) /* align_is(16) */ PVOID Context, + __in ULONG ContextSize + ) PURE; +}; + +typedef struct _DEBUG_READ_USER_MINIDUMP_STREAM +{ + IN ULONG StreamType; + IN ULONG Flags; + IN ULONG64 Offset; + OUT PVOID Buffer; + IN ULONG BufferSize; + OUT ULONG BufferUsed; +} DEBUG_READ_USER_MINIDUMP_STREAM, *PDEBUG_READ_USER_MINIDUMP_STREAM; + +#define DEBUG_GET_TEXT_COMPLETIONS_NO_DOT_COMMANDS 0x00000001 +#define DEBUG_GET_TEXT_COMPLETIONS_NO_EXTENSION_COMMANDS 0x00000002 +#define DEBUG_GET_TEXT_COMPLETIONS_NO_SYMBOLS 0x00000004 + +typedef struct _DEBUG_GET_TEXT_COMPLETIONS_IN +{ + ULONG Flags; + ULONG MatchCountLimit; + ULONG64 Reserved[3]; + // Input text string follows. +} DEBUG_GET_TEXT_COMPLETIONS_IN, *PDEBUG_GET_TEXT_COMPLETIONS_IN; + +#define DEBUG_GET_TEXT_COMPLETIONS_IS_DOT_COMMAND 0x00000001 +#define DEBUG_GET_TEXT_COMPLETIONS_IS_EXTENSION_COMMAND 0x00000002 +#define DEBUG_GET_TEXT_COMPLETIONS_IS_SYMBOL 0x00000004 + +typedef struct _DEBUG_GET_TEXT_COMPLETIONS_OUT +{ + ULONG Flags; + // Char index in input string where completions start. + ULONG ReplaceIndex; + ULONG MatchCount; + ULONG Reserved1; + ULONG64 Reserved2[2]; + // Completions follow. + // Completion data is zero-terminated strings ended + // by a final zero double-terminator. +} DEBUG_GET_TEXT_COMPLETIONS_OUT, *PDEBUG_GET_TEXT_COMPLETIONS_OUT; + +typedef struct _DEBUG_CACHED_SYMBOL_INFO +{ + ULONG64 ModBase; + ULONG64 Arg1; + ULONG64 Arg2; + ULONG Id; + ULONG Arg3; +} DEBUG_CACHED_SYMBOL_INFO, *PDEBUG_CACHED_SYMBOL_INFO; + +// +// Request requests. +// + +// InBuffer - Unused. +// OutBuffer - Unused. +#define DEBUG_REQUEST_SOURCE_PATH_HAS_SOURCE_SERVER 0 + +// InBuffer - Unused. +// OutBuffer - Machine-specific CONTEXT. +#define DEBUG_REQUEST_TARGET_EXCEPTION_CONTEXT 1 + +// InBuffer - Unused. +// OutBuffer - ULONG system ID of thread. +#define DEBUG_REQUEST_TARGET_EXCEPTION_THREAD 2 + +// InBuffer - Unused. +// OutBuffer - EXCEPTION_RECORD64. +#define DEBUG_REQUEST_TARGET_EXCEPTION_RECORD 3 + +// InBuffer - Unused. +// OutBuffer - DEBUG_CREATE_PROCESS_OPTIONS. +#define DEBUG_REQUEST_GET_ADDITIONAL_CREATE_OPTIONS 4 + +// InBuffer - DEBUG_CREATE_PROCESS_OPTIONS. +// OutBuffer - Unused. +#define DEBUG_REQUEST_SET_ADDITIONAL_CREATE_OPTIONS 5 + +// InBuffer - Unused. +// OutBuffer - ULONG[2] major/minor. +#define DEBUG_REQUEST_GET_WIN32_MAJOR_MINOR_VERSIONS 6 + +// InBuffer - DEBUG_READ_USER_MINIDUMP_STREAM. +// OutBuffer - Unused. +#define DEBUG_REQUEST_READ_USER_MINIDUMP_STREAM 7 + +// InBuffer - Unused. +// OutBuffer - Unused. +#define DEBUG_REQUEST_TARGET_CAN_DETACH 8 + +// InBuffer - PTSTR. +// OutBuffer - Unused. +#define DEBUG_REQUEST_SET_LOCAL_IMPLICIT_COMMAND_LINE 9 + +// InBuffer - Unused. +// OutBuffer - Event code stream offset. +#define DEBUG_REQUEST_GET_CAPTURED_EVENT_CODE_OFFSET 10 + +// InBuffer - Unused. +// OutBuffer - Event code stream information. +#define DEBUG_REQUEST_READ_CAPTURED_EVENT_CODE_STREAM 11 + +// InBuffer - Input data block. +// OutBuffer - Processed data block. +#define DEBUG_REQUEST_EXT_TYPED_DATA_ANSI 12 + +// InBuffer - Unused. +// OutBuffer - Returned path. +#define DEBUG_REQUEST_GET_EXTENSION_SEARCH_PATH_WIDE 13 + +// InBuffer - DEBUG_GET_TEXT_COMPLETIONS_IN. +// OutBuffer - DEBUG_GET_TEXT_COMPLETIONS_OUT. +#define DEBUG_REQUEST_GET_TEXT_COMPLETIONS_WIDE 14 + +// InBuffer - ULONG64 cookie. +// OutBuffer - DEBUG_CACHED_SYMBOL_INFO. +#define DEBUG_REQUEST_GET_CACHED_SYMBOL_INFO 15 + +// InBuffer - DEBUG_CACHED_SYMBOL_INFO. +// OutBuffer - ULONG64 cookie. +#define DEBUG_REQUEST_ADD_CACHED_SYMBOL_INFO 16 + +// InBuffer - ULONG64 cookie. +// OutBuffer - Unused. +#define DEBUG_REQUEST_REMOVE_CACHED_SYMBOL_INFO 17 + +// InBuffer - DEBUG_GET_TEXT_COMPLETIONS_IN. +// OutBuffer - DEBUG_GET_TEXT_COMPLETIONS_OUT. +#define DEBUG_REQUEST_GET_TEXT_COMPLETIONS_ANSI 18 + +// InBuffer - Unused. +// OutBuffer - Unused. +#define DEBUG_REQUEST_CURRENT_OUTPUT_CALLBACKS_ARE_DML_AWARE 19 + +// InBuffer - ULONG64 offset. +// OutBuffer - Unwind information. +#define DEBUG_REQUEST_GET_OFFSET_UNWIND_INFORMATION 20 + +// InBuffer - Unused +// OutBuffer - returned DUMP_HEADER32/DUMP_HEADER64 structure. +#define DEBUG_REQUEST_GET_DUMP_HEADER 21 + +// InBuffer - DUMP_HEADER32/DUMP_HEADER64 structure. +// OutBuffer - Unused +#define DEBUG_REQUEST_SET_DUMP_HEADER 22 + +// +// GetSourceFileInformation requests. +// + +// Arg64 - Module base. +// Arg32 - Unused. +#define DEBUG_SRCFILE_SYMBOL_TOKEN 0 + +// Arg64 - Module base. +// Arg32 - Unused. +#define DEBUG_SRCFILE_SYMBOL_TOKEN_SOURCE_COMMAND_WIDE 1 + +// +// GetSymbolInformation requests. +// + +// Arg64 - Unused. +// Arg32 - Breakpoint ID. +// Buffer - ULONG line number. +// String - File name. +#define DEBUG_SYMINFO_BREAKPOINT_SOURCE_LINE 0 + +// Arg64 - Module base. +// Arg32 - Unused. +// Buffer - IMAGEHLP_MODULEW64. +// String - Unused. +#define DEBUG_SYMINFO_IMAGEHLP_MODULEW64 1 + +// Arg64 - Offset. +// Arg32 - Symbol tag. +// Buffer - Unicode symbol name strings. Could have multiple strings. +// String - Unused, strings are returned in Buffer as there +// may be more than one. +#define DEBUG_SYMINFO_GET_SYMBOL_NAME_BY_OFFSET_AND_TAG_WIDE 2 + +// Arg64 - Module base. +// Arg32 - Symbol tag. +// Buffer - Array of symbol addresses. +// String - Concatenated symbol strings. Individual symbol +// strings are zero-terminated and the final string in +// a symbol is double-zero-terminated. +#define DEBUG_SYMINFO_GET_MODULE_SYMBOL_NAMES_AND_OFFSETS 3 + +// +// GetSystemObjectInformation requests. +// + +// Arg64 - Unused. +// Arg32 - Debugger thread ID. +// Buffer - DEBUG_THREAD_BASIC_INFORMATION. +#define DEBUG_SYSOBJINFO_THREAD_BASIC_INFORMATION 0 + +// Arg64 - Unused. +// Arg32 - Debugger thread ID. +// Buffer - Unicode name string. +#define DEBUG_SYSOBJINFO_THREAD_NAME_WIDE 1 + +// Arg64 - Unused. +// Arg32 - Unused. +// Buffer - ULONG cookie value. +#define DEBUG_SYSOBJINFO_CURRENT_PROCESS_COOKIE 2 + +#define DEBUG_TBINFO_EXIT_STATUS 0x00000001 +#define DEBUG_TBINFO_PRIORITY_CLASS 0x00000002 +#define DEBUG_TBINFO_PRIORITY 0x00000004 +#define DEBUG_TBINFO_TIMES 0x00000008 +#define DEBUG_TBINFO_START_OFFSET 0x00000010 +#define DEBUG_TBINFO_AFFINITY 0x00000020 +#define DEBUG_TBINFO_ALL 0x0000003f + +typedef struct _DEBUG_THREAD_BASIC_INFORMATION +{ + // Valid members have a DEBUG_TBINFO bit set in Valid. + ULONG Valid; + ULONG ExitStatus; + ULONG PriorityClass; + ULONG Priority; + ULONG64 CreateTime; + ULONG64 ExitTime; + ULONG64 KernelTime; + ULONG64 UserTime; + ULONG64 StartOffset; + ULONG64 Affinity; +} DEBUG_THREAD_BASIC_INFORMATION, *PDEBUG_THREAD_BASIC_INFORMATION; + +#undef INTERFACE +#define INTERFACE IDebugAdvanced2 +DECLARE_INTERFACE_(IDebugAdvanced2, IUnknown) +{ + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + __in REFIID InterfaceId, + __out PVOID* Interface + ) PURE; + STDMETHOD_(ULONG, AddRef)( + THIS + ) PURE; + STDMETHOD_(ULONG, Release)( + THIS + ) PURE; + + // IDebugAdvanced. + + // Get/SetThreadContext offer control over + // the full processor context for a thread. + // Higher-level functions, such as the + // IDebugRegisters interface, allow similar + // access in simpler and more generic ways. + // Get/SetThreadContext are useful when + // large amounts of thread context must + // be changed and processor-specific code + // is not a problem. + STDMETHOD(GetThreadContext)( + THIS_ + __out_bcount(ContextSize) /* align_is(16) */ PVOID Context, + __in ULONG ContextSize + ) PURE; + STDMETHOD(SetThreadContext)( + THIS_ + __in_bcount(ContextSize) /* align_is(16) */ PVOID Context, + __in ULONG ContextSize + ) PURE; + + // IDebugAdvanced2. + + // + // Generalized open-ended methods for querying + // and manipulation. The open-ended nature of + // these methods makes it easy to add new requests, + // although at a cost in convenience of calling. + // Sufficiently common requests may have more specific, + // simpler methods elsewhere. + // + + STDMETHOD(Request)( + THIS_ + __in ULONG Request, + __in_bcount_opt(InBufferSize) PVOID InBuffer, + __in ULONG InBufferSize, + __out_bcount_opt(OutBufferSize) PVOID OutBuffer, + __in ULONG OutBufferSize, + __out_opt PULONG OutSize + ) PURE; + + STDMETHOD(GetSourceFileInformation)( + THIS_ + __in ULONG Which, + __in PSTR SourceFile, + __in ULONG64 Arg64, + __in ULONG Arg32, + __out_bcount_opt(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG InfoSize + ) PURE; + STDMETHOD(FindSourceFileAndToken)( + THIS_ + __in ULONG StartElement, + __in ULONG64 ModAddr, + __in PCSTR File, + __in ULONG Flags, + __in_bcount_opt(FileTokenSize) PVOID FileToken, + __in ULONG FileTokenSize, + __out_opt PULONG FoundElement, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG FoundSize + ) PURE; + + STDMETHOD(GetSymbolInformation)( + THIS_ + __in ULONG Which, + __in ULONG64 Arg64, + __in ULONG Arg32, + __out_bcount_opt(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG InfoSize, + __out_ecount_opt(StringBufferSize) PSTR StringBuffer, + __in ULONG StringBufferSize, + __out_opt PULONG StringSize + ) PURE; + + STDMETHOD(GetSystemObjectInformation)( + THIS_ + __in ULONG Which, + __in ULONG64 Arg64, + __in ULONG Arg32, + __out_bcount_opt(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG InfoSize + ) PURE; +}; + +#undef INTERFACE +#define INTERFACE IDebugAdvanced3 +DECLARE_INTERFACE_(IDebugAdvanced3, IUnknown) +{ + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + __in REFIID InterfaceId, + __out PVOID* Interface + ) PURE; + STDMETHOD_(ULONG, AddRef)( + THIS + ) PURE; + STDMETHOD_(ULONG, Release)( + THIS + ) PURE; + + // IDebugAdvanced. + + // Get/SetThreadContext offer control over + // the full processor context for a thread. + // Higher-level functions, such as the + // IDebugRegisters interface, allow similar + // access in simpler and more generic ways. + // Get/SetThreadContext are useful when + // large amounts of thread context must + // be changed and processor-specific code + // is not a problem. + STDMETHOD(GetThreadContext)( + THIS_ + __out_bcount(ContextSize) /* align_is(16) */ PVOID Context, + __in ULONG ContextSize + ) PURE; + STDMETHOD(SetThreadContext)( + THIS_ + __in_bcount(ContextSize) /* align_is(16) */ PVOID Context, + __in ULONG ContextSize + ) PURE; + + // IDebugAdvanced2. + + // + // Generalized open-ended methods for querying + // and manipulation. The open-ended nature of + // these methods makes it easy to add new requests, + // although at a cost in convenience of calling. + // Sufficiently common requests may have more specific, + // simpler methods elsewhere. + // + + STDMETHOD(Request)( + THIS_ + __in ULONG Request, + __in_bcount_opt(InBufferSize) PVOID InBuffer, + __in ULONG InBufferSize, + __out_bcount_opt(OutBufferSize) PVOID OutBuffer, + __in ULONG OutBufferSize, + __out_opt PULONG OutSize + ) PURE; + + STDMETHOD(GetSourceFileInformation)( + THIS_ + __in ULONG Which, + __in PSTR SourceFile, + __in ULONG64 Arg64, + __in ULONG Arg32, + __out_bcount_opt(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG InfoSize + ) PURE; + STDMETHOD(FindSourceFileAndToken)( + THIS_ + __in ULONG StartElement, + __in ULONG64 ModAddr, + __in PCSTR File, + __in ULONG Flags, + __in_bcount_opt(FileTokenSize) PVOID FileToken, + __in ULONG FileTokenSize, + __out_opt PULONG FoundElement, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG FoundSize + ) PURE; + + STDMETHOD(GetSymbolInformation)( + THIS_ + __in ULONG Which, + __in ULONG64 Arg64, + __in ULONG Arg32, + __out_bcount_opt(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG InfoSize, + __out_ecount_opt(StringBufferSize) PSTR StringBuffer, + __in ULONG StringBufferSize, + __out_opt PULONG StringSize + ) PURE; + + STDMETHOD(GetSystemObjectInformation)( + THIS_ + __in ULONG Which, + __in ULONG64 Arg64, + __in ULONG Arg32, + __out_bcount_opt(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG InfoSize + ) PURE; + + // IDebugAdvanced3. + + STDMETHOD(GetSourceFileInformationWide)( + THIS_ + __in ULONG Which, + __in PWSTR SourceFile, + __in ULONG64 Arg64, + __in ULONG Arg32, + __out_bcount_opt(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG InfoSize + ) PURE; + STDMETHOD(FindSourceFileAndTokenWide)( + THIS_ + __in ULONG StartElement, + __in ULONG64 ModAddr, + __in PCWSTR File, + __in ULONG Flags, + __in_bcount_opt(FileTokenSize) PVOID FileToken, + __in ULONG FileTokenSize, + __out_opt PULONG FoundElement, + __out_ecount_opt(BufferSize) PWSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG FoundSize + ) PURE; + + STDMETHOD(GetSymbolInformationWide)( + THIS_ + __in ULONG Which, + __in ULONG64 Arg64, + __in ULONG Arg32, + __out_bcount_opt(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG InfoSize, + __out_ecount_opt(StringBufferSize) PWSTR StringBuffer, + __in ULONG StringBufferSize, + __out_opt PULONG StringSize + ) PURE; +}; + +//---------------------------------------------------------------------------- +// +// IDebugBreakpoint. +// +//---------------------------------------------------------------------------- + +// Types of breakpoints. +#define DEBUG_BREAKPOINT_CODE 0 +#define DEBUG_BREAKPOINT_DATA 1 +#define DEBUG_BREAKPOINT_TIME 2 + +// Breakpoint flags. +// Go-only breakpoints are only active when +// the engine is in unrestricted execution +// mode. They do not fire when the engine +// is stepping. +#define DEBUG_BREAKPOINT_GO_ONLY 0x00000001 +// A breakpoint is flagged as deferred as long as +// its offset expression cannot be evaluated. +// A deferred breakpoint is not active. +#define DEBUG_BREAKPOINT_DEFERRED 0x00000002 +#define DEBUG_BREAKPOINT_ENABLED 0x00000004 +// The adder-only flag does not affect breakpoint +// operation. It is just a marker to restrict +// output and notifications for the breakpoint to +// the client that added the breakpoint. Breakpoint +// callbacks for adder-only breaks will only be delivered +// to the adding client. The breakpoint can not +// be enumerated and accessed by other clients. +#define DEBUG_BREAKPOINT_ADDER_ONLY 0x00000008 +// One-shot breakpoints automatically clear themselves +// the first time they are hit. +#define DEBUG_BREAKPOINT_ONE_SHOT 0x00000010 + +// Data breakpoint access types. +// Different architectures support different +// sets of these bits. +#define DEBUG_BREAK_READ 0x00000001 +#define DEBUG_BREAK_WRITE 0x00000002 +#define DEBUG_BREAK_EXECUTE 0x00000004 +#define DEBUG_BREAK_IO 0x00000008 + +// Structure for querying breakpoint information +// all at once. +typedef struct _DEBUG_BREAKPOINT_PARAMETERS +{ + ULONG64 Offset; + ULONG Id; + ULONG BreakType; + ULONG ProcType; + ULONG Flags; + ULONG DataSize; + ULONG DataAccessType; + ULONG PassCount; + ULONG CurrentPassCount; + ULONG MatchThread; + ULONG CommandSize; + ULONG OffsetExpressionSize; +} DEBUG_BREAKPOINT_PARAMETERS, *PDEBUG_BREAKPOINT_PARAMETERS; + +#undef INTERFACE +#define INTERFACE IDebugBreakpoint +DECLARE_INTERFACE_(IDebugBreakpoint, IUnknown) +{ + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + __in REFIID InterfaceId, + __out PVOID* Interface + ) PURE; + STDMETHOD_(ULONG, AddRef)( + THIS + ) PURE; + STDMETHOD_(ULONG, Release)( + THIS + ) PURE; + + // IDebugBreakpoint. + + // Retrieves debugger engine unique ID + // for the breakpoint. This ID is + // fixed as long as the breakpoint exists + // but after that may be reused. + STDMETHOD(GetId)( + THIS_ + __out PULONG Id + ) PURE; + // Retrieves the type of break and + // processor type for the breakpoint. + STDMETHOD(GetType)( + THIS_ + __out PULONG BreakType, + __out PULONG ProcType + ) PURE; + // Returns the client that called AddBreakpoint. + STDMETHOD(GetAdder)( + THIS_ + __out PDEBUG_CLIENT* Adder + ) PURE; + + STDMETHOD(GetFlags)( + THIS_ + __out PULONG Flags + ) PURE; + // Only certain flags can be changed. Flags + // are: GO_ONLY, ENABLE. + // Sets the given flags. + STDMETHOD(AddFlags)( + THIS_ + __in ULONG Flags + ) PURE; + // Clears the given flags. + STDMETHOD(RemoveFlags)( + THIS_ + __in ULONG Flags + ) PURE; + // Sets the flags. + STDMETHOD(SetFlags)( + THIS_ + __in ULONG Flags + ) PURE; + + // Controls the offset of the breakpoint. The + // interpretation of the offset value depends on + // the type of breakpoint and its settings. It + // may be a code address, a data address, an + // I/O port, etc. + STDMETHOD(GetOffset)( + THIS_ + __out PULONG64 Offset + ) PURE; + STDMETHOD(SetOffset)( + THIS_ + __in ULONG64 Offset + ) PURE; + + // Data breakpoint methods will fail if the + // target platform does not support the + // parameters used. + // These methods only function for breakpoints + // created as data breakpoints. + STDMETHOD(GetDataParameters)( + THIS_ + __out PULONG Size, + __out PULONG AccessType + ) PURE; + STDMETHOD(SetDataParameters)( + THIS_ + __in ULONG Size, + __in ULONG AccessType + ) PURE; + + // Pass count defaults to one. + STDMETHOD(GetPassCount)( + THIS_ + __out PULONG Count + ) PURE; + STDMETHOD(SetPassCount)( + THIS_ + __in ULONG Count + ) PURE; + // Gets the current number of times + // the breakpoint has been hit since + // it was last triggered. + STDMETHOD(GetCurrentPassCount)( + THIS_ + __out PULONG Count + ) PURE; + + // If a match thread is set this breakpoint will + // only trigger if it occurs on the match thread. + // Otherwise it triggers for all threads. + // Thread restrictions are not currently supported + // in kernel mode. + STDMETHOD(GetMatchThreadId)( + THIS_ + __out PULONG Id + ) PURE; + STDMETHOD(SetMatchThreadId)( + THIS_ + __in ULONG Thread + ) PURE; + + // The command for a breakpoint is automatically + // executed by the engine before the event + // is propagated. If the breakpoint continues + // execution the event will begin with a continue + // status. If the breakpoint does not continue + // the event will begin with a break status. + // This allows breakpoint commands to participate + // in the normal event status voting. + // Breakpoint commands are only executed until + // the first command that alters the execution + // status, such as g, p and t. + STDMETHOD(GetCommand)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG CommandSize + ) PURE; + STDMETHOD(SetCommand)( + THIS_ + __in PCSTR Command + ) PURE; + + // Offset expressions are evaluated immediately + // and at module load and unload events. If the + // evaluation is successful the breakpoints + // offset is updated and the breakpoint is + // handled normally. If the expression cannot + // be evaluated the breakpoint is deferred. + // Currently the only offset expression + // supported is a module-relative symbol + // of the form <Module>!<Symbol>. + STDMETHOD(GetOffsetExpression)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG ExpressionSize + ) PURE; + STDMETHOD(SetOffsetExpression)( + THIS_ + __in PCSTR Expression + ) PURE; + + STDMETHOD(GetParameters)( + THIS_ + __out PDEBUG_BREAKPOINT_PARAMETERS Params + ) PURE; +}; + +#undef INTERFACE +#define INTERFACE IDebugBreakpoint2 +DECLARE_INTERFACE_(IDebugBreakpoint2, IUnknown) +{ + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + __in REFIID InterfaceId, + __out PVOID* Interface + ) PURE; + STDMETHOD_(ULONG, AddRef)( + THIS + ) PURE; + STDMETHOD_(ULONG, Release)( + THIS + ) PURE; + + // IDebugBreakpoint. + + // Retrieves debugger engine unique ID + // for the breakpoint. This ID is + // fixed as long as the breakpoint exists + // but after that may be reused. + STDMETHOD(GetId)( + THIS_ + __out PULONG Id + ) PURE; + // Retrieves the type of break and + // processor type for the breakpoint. + STDMETHOD(GetType)( + THIS_ + __out PULONG BreakType, + __out PULONG ProcType + ) PURE; + // Returns the client that called AddBreakpoint. + STDMETHOD(GetAdder)( + THIS_ + __out PDEBUG_CLIENT* Adder + ) PURE; + + STDMETHOD(GetFlags)( + THIS_ + __out PULONG Flags + ) PURE; + // Only certain flags can be changed. Flags + // are: GO_ONLY, ENABLE. + // Sets the given flags. + STDMETHOD(AddFlags)( + THIS_ + __in ULONG Flags + ) PURE; + // Clears the given flags. + STDMETHOD(RemoveFlags)( + THIS_ + __in ULONG Flags + ) PURE; + // Sets the flags. + STDMETHOD(SetFlags)( + THIS_ + __in ULONG Flags + ) PURE; + + // Controls the offset of the breakpoint. The + // interpretation of the offset value depends on + // the type of breakpoint and its settings. It + // may be a code address, a data address, an + // I/O port, etc. + STDMETHOD(GetOffset)( + THIS_ + __out PULONG64 Offset + ) PURE; + STDMETHOD(SetOffset)( + THIS_ + __in ULONG64 Offset + ) PURE; + + // Data breakpoint methods will fail if the + // target platform does not support the + // parameters used. + // These methods only function for breakpoints + // created as data breakpoints. + STDMETHOD(GetDataParameters)( + THIS_ + __out PULONG Size, + __out PULONG AccessType + ) PURE; + STDMETHOD(SetDataParameters)( + THIS_ + __in ULONG Size, + __in ULONG AccessType + ) PURE; + + // Pass count defaults to one. + STDMETHOD(GetPassCount)( + THIS_ + __out PULONG Count + ) PURE; + STDMETHOD(SetPassCount)( + THIS_ + __in ULONG Count + ) PURE; + // Gets the current number of times + // the breakpoint has been hit since + // it was last triggered. + STDMETHOD(GetCurrentPassCount)( + THIS_ + __out PULONG Count + ) PURE; + + // If a match thread is set this breakpoint will + // only trigger if it occurs on the match thread. + // Otherwise it triggers for all threads. + // Thread restrictions are not currently supported + // in kernel mode. + STDMETHOD(GetMatchThreadId)( + THIS_ + __out PULONG Id + ) PURE; + STDMETHOD(SetMatchThreadId)( + THIS_ + __in ULONG Thread + ) PURE; + + // The command for a breakpoint is automatically + // executed by the engine before the event + // is propagated. If the breakpoint continues + // execution the event will begin with a continue + // status. If the breakpoint does not continue + // the event will begin with a break status. + // This allows breakpoint commands to participate + // in the normal event status voting. + // Breakpoint commands are only executed until + // the first command that alters the execution + // status, such as g, p and t. + STDMETHOD(GetCommand)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG CommandSize + ) PURE; + STDMETHOD(SetCommand)( + THIS_ + __in PCSTR Command + ) PURE; + + // Offset expressions are evaluated immediately + // and at module load and unload events. If the + // evaluation is successful the breakpoints + // offset is updated and the breakpoint is + // handled normally. If the expression cannot + // be evaluated the breakpoint is deferred. + // Currently the only offset expression + // supported is a module-relative symbol + // of the form <Module>!<Symbol>. + STDMETHOD(GetOffsetExpression)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG ExpressionSize + ) PURE; + STDMETHOD(SetOffsetExpression)( + THIS_ + __in PCSTR Expression + ) PURE; + + STDMETHOD(GetParameters)( + THIS_ + __out PDEBUG_BREAKPOINT_PARAMETERS Params + ) PURE; + + // IDebugBreakpoint2. + + STDMETHOD(GetCommandWide)( + THIS_ + __out_ecount_opt(BufferSize) PWSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG CommandSize + ) PURE; + STDMETHOD(SetCommandWide)( + THIS_ + __in PCWSTR Command + ) PURE; + + STDMETHOD(GetOffsetExpressionWide)( + THIS_ + __out_ecount_opt(BufferSize) PWSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG ExpressionSize + ) PURE; + STDMETHOD(SetOffsetExpressionWide)( + THIS_ + __in PCWSTR Expression + ) PURE; +}; + +//---------------------------------------------------------------------------- +// +// IDebugClient. +// +//---------------------------------------------------------------------------- + +// Kernel attach flags. +#define DEBUG_ATTACH_KERNEL_CONNECTION 0x00000000 +// Attach to the local machine. If this flag is not set +// a connection is made to a separate target machine using +// the given connection options. +#define DEBUG_ATTACH_LOCAL_KERNEL 0x00000001 +// Attach to an eXDI driver. +#define DEBUG_ATTACH_EXDI_DRIVER 0x00000002 + +// GetRunningProcessSystemIdByExecutableName flags. +// By default the match allows a tail match on +// just the filename. The match returns the first hit +// even if multiple matches exist. +#define DEBUG_GET_PROC_DEFAULT 0x00000000 +// The name must match fully. +#define DEBUG_GET_PROC_FULL_MATCH 0x00000001 +// The match must be the only match. +#define DEBUG_GET_PROC_ONLY_MATCH 0x00000002 +// The name is a service name instead of an executable name. +#define DEBUG_GET_PROC_SERVICE_NAME 0x00000004 + +// GetRunningProcessDescription flags. +#define DEBUG_PROC_DESC_DEFAULT 0x00000000 +// Return only filenames, not full paths. +#define DEBUG_PROC_DESC_NO_PATHS 0x00000001 +// Dont look up service names. +#define DEBUG_PROC_DESC_NO_SERVICES 0x00000002 +// Dont look up MTS package names. +#define DEBUG_PROC_DESC_NO_MTS_PACKAGES 0x00000004 +// Dont retrieve the command line. +#define DEBUG_PROC_DESC_NO_COMMAND_LINE 0x00000008 +// Dont retrieve the session ID. +#define DEBUG_PROC_DESC_NO_SESSION_ID 0x00000010 +// Dont retrieve the process's user name. +#define DEBUG_PROC_DESC_NO_USER_NAME 0x00000020 + +// +// Attach flags. +// + +// Call DebugActiveProcess when attaching. +#define DEBUG_ATTACH_DEFAULT 0x00000000 +// When attaching to a process just examine +// the process state and suspend the threads. +// DebugActiveProcess is not called so the process +// is not actually being debugged. This is useful +// for debugging processes holding locks which +// interfere with the operation of DebugActiveProcess +// or in situations where it is not desirable to +// actually set up as a debugger. +#define DEBUG_ATTACH_NONINVASIVE 0x00000001 +// Attempt to attach to a process that was abandoned +// when being debugged. This is only supported in +// some system versions. +// This flag also allows multiple debuggers to +// attach to the same process, which can result +// in numerous problems unless very carefully +// managed. +#define DEBUG_ATTACH_EXISTING 0x00000002 +// When attaching non-invasively, do not suspend +// threads. It is the callers responsibility +// to either suspend the threads itself or be +// aware that the attach state may not reflect +// the current state of the process if threads +// are still running. +#define DEBUG_ATTACH_NONINVASIVE_NO_SUSPEND 0x00000004 +// When doing an invasive attach do not inject +// a break-in thread to generate the initial break-in +// event. This can be useful to save resources when +// an initial break is not necessary or when injecting +// a thread might affect the debuggee's state. This +// option is only supported on Windows XP and above. +#define DEBUG_ATTACH_INVASIVE_NO_INITIAL_BREAK 0x00000008 +// When doing an invasive attach resume all threads at the +// time of attach. This makes it possible to attach +// to a process created suspended and cause it to start running. +#define DEBUG_ATTACH_INVASIVE_RESUME_PROCESS 0x00000010 +// When doing a non-invasive attach the engine must +// recover information for all debuggee elements. The +// engine may not have permissions for all elements, +// for example it may not be able to open all threads, +// and that would ordinarily block the attach. This +// flag allows unusable elements to be ignored. +#define DEBUG_ATTACH_NONINVASIVE_ALLOW_PARTIAL 0x00000020 + + +// +// Process creation flags to merge with Win32 flags. +// + +// On Windows XP this flag prevents the debug +// heap from being used in the new process. +#define DEBUG_CREATE_PROCESS_NO_DEBUG_HEAP CREATE_UNICODE_ENVIRONMENT +// Indicates that the native NT RTL process creation +// routines should be used instead of Win32. This +// is only meaningful for special processes that run +// as NT native processes. +#define DEBUG_CREATE_PROCESS_THROUGH_RTL STACK_SIZE_PARAM_IS_A_RESERVATION + +// +// Process creation flags specific to the debugger engine. +// + +#define DEBUG_ECREATE_PROCESS_DEFAULT 0x00000000 +#define DEBUG_ECREATE_PROCESS_INHERIT_HANDLES 0x00000001 +#define DEBUG_ECREATE_PROCESS_USE_VERIFIER_FLAGS 0x00000002 +#define DEBUG_ECREATE_PROCESS_USE_IMPLICIT_COMMAND_LINE 0x00000004 + +typedef struct _DEBUG_CREATE_PROCESS_OPTIONS +{ + // Win32 create flags. + ULONG CreateFlags; + // DEBUG_ECREATE_PROCESS_* flags. + ULONG EngCreateFlags; + // Application Verifier flags, + // if DEBUG_ECREATE_PROCESS_USE_VERIFIER_FLAGS is set. + ULONG VerifierFlags; + // Must be zero. + ULONG Reserved; +} DEBUG_CREATE_PROCESS_OPTIONS, *PDEBUG_CREATE_PROCESS_OPTIONS; + +// +// Process options. +// + +// Indicates that the debuggee process should be +// automatically detached when the debugger exits. +// A debugger can explicitly detach on exit or this +// flag can be set so that detach occurs regardless +// of how the debugger exits. +// This is only supported on some system versions. +#define DEBUG_PROCESS_DETACH_ON_EXIT 0x00000001 +// Indicates that processes created by the current +// process should not be debugged. +// Modifying this flag is only supported on some +// system versions. +#define DEBUG_PROCESS_ONLY_THIS_PROCESS 0x00000002 + +// ConnectSession flags. +// Default connect. +#define DEBUG_CONNECT_SESSION_DEFAULT 0x00000000 +// Do not output the debugger version. +#define DEBUG_CONNECT_SESSION_NO_VERSION 0x00000001 +// Do not announce the connection. +#define DEBUG_CONNECT_SESSION_NO_ANNOUNCE 0x00000002 + +// OutputServers flags. +// Debugger servers from StartSever. +#define DEBUG_SERVERS_DEBUGGER 0x00000001 +// Process servers from StartProcessServer. +#define DEBUG_SERVERS_PROCESS 0x00000002 +#define DEBUG_SERVERS_ALL 0x00000003 + +// EndSession flags. +// Perform cleanup for the session. +#define DEBUG_END_PASSIVE 0x00000000 +// Actively terminate the session and then perform cleanup. +#define DEBUG_END_ACTIVE_TERMINATE 0x00000001 +// If possible, detach from all processes and then perform cleanup. +#define DEBUG_END_ACTIVE_DETACH 0x00000002 +// Perform whatever cleanup is possible that doesn't require +// acquiring any locks. This is useful for situations where +// a thread is currently using the engine but the application +// needs to exit and still wants to give the engine +// the opportunity to clean up as much as possible. +// This may leave the engine in an indeterminate state so +// further engine calls should not be made. +// When making a reentrant EndSession call from a remote +// client it is the callers responsibility to ensure +// that the server can process the request. It is best +// to avoid making such calls. +#define DEBUG_END_REENTRANT 0x00000003 +// Notify a server that a remote client is disconnecting. +// This isnt required but if it isnt called then +// no disconnect messages will be generated by the server. +#define DEBUG_END_DISCONNECT 0x00000004 + +// Output mask bits. +// Normal output. +#define DEBUG_OUTPUT_NORMAL 0x00000001 +// Error output. +#define DEBUG_OUTPUT_ERROR 0x00000002 +// Warnings. +#define DEBUG_OUTPUT_WARNING 0x00000004 +// Additional output. +#define DEBUG_OUTPUT_VERBOSE 0x00000008 +// Prompt output. +#define DEBUG_OUTPUT_PROMPT 0x00000010 +// Register dump before prompt. +#define DEBUG_OUTPUT_PROMPT_REGISTERS 0x00000020 +// Warnings specific to extension operation. +#define DEBUG_OUTPUT_EXTENSION_WARNING 0x00000040 +// Debuggee debug output, such as from OutputDebugString. +#define DEBUG_OUTPUT_DEBUGGEE 0x00000080 +// Debuggee-generated prompt, such as from DbgPrompt. +#define DEBUG_OUTPUT_DEBUGGEE_PROMPT 0x00000100 +// Symbol messages, such as for !sym noisy. +#define DEBUG_OUTPUT_SYMBOLS 0x00000200 + +// Internal debugger output, used mainly +// for debugging the debugger. Output +// may only occur in debug builds. +// KD protocol output. +#define DEBUG_IOUTPUT_KD_PROTOCOL 0x80000000 +// Remoting output. +#define DEBUG_IOUTPUT_REMOTING 0x40000000 +// Breakpoint output. +#define DEBUG_IOUTPUT_BREAKPOINT 0x20000000 +// Event output. +#define DEBUG_IOUTPUT_EVENT 0x10000000 + +// OutputIdentity flags. +#define DEBUG_OUTPUT_IDENTITY_DEFAULT 0x00000000 + +#undef INTERFACE +#define INTERFACE IDebugClient +DECLARE_INTERFACE_(IDebugClient, IUnknown) +{ + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + __in REFIID InterfaceId, + __out PVOID* Interface + ) PURE; + STDMETHOD_(ULONG, AddRef)( + THIS + ) PURE; + STDMETHOD_(ULONG, Release)( + THIS + ) PURE; + + // IDebugClient. + + // The following set of methods start + // the different kinds of debuggees. + + // Begins a debug session using the kernel + // debugging protocol. This method selects + // the protocol as the debuggee communication + // mechanism but does not initiate the communication + // itself. + STDMETHOD(AttachKernel)( + THIS_ + __in ULONG Flags, + __in_opt PCSTR ConnectOptions + ) PURE; + STDMETHOD(GetKernelConnectionOptions)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG OptionsSize + ) PURE; + // Updates the connection options for a live + // kernel connection. This can only be used + // to modify parameters for the connection, not + // to switch to a completely different kind of + // connection. + // This method is reentrant. + STDMETHOD(SetKernelConnectionOptions)( + THIS_ + __in PCSTR Options + ) PURE; + + // Starts a process server for remote + // user-mode process control. + // The local process server is server zero. + STDMETHOD(StartProcessServer)( + THIS_ + __in ULONG Flags, + __in PCSTR Options, + __in_opt __reserved PVOID Reserved + ) PURE; + STDMETHOD(ConnectProcessServer)( + THIS_ + __in PCSTR RemoteOptions, + __out PULONG64 Server + ) PURE; + STDMETHOD(DisconnectProcessServer)( + THIS_ + __in ULONG64 Server + ) PURE; + + // Enumerates and describes processes + // accessible through the given process server. + STDMETHOD(GetRunningProcessSystemIds)( + THIS_ + __in ULONG64 Server, + __out_ecount_opt(Count) PULONG Ids, + __in ULONG Count, + __out_opt PULONG ActualCount + ) PURE; + STDMETHOD(GetRunningProcessSystemIdByExecutableName)( + THIS_ + __in ULONG64 Server, + __in PCSTR ExeName, + __in ULONG Flags, + __out PULONG Id + ) PURE; + STDMETHOD(GetRunningProcessDescription)( + THIS_ + __in ULONG64 Server, + __in ULONG SystemId, + __in ULONG Flags, + __out_ecount_opt(ExeNameSize) PSTR ExeName, + __in ULONG ExeNameSize, + __out_opt PULONG ActualExeNameSize, + __out_ecount_opt(DescriptionSize) PSTR Description, + __in ULONG DescriptionSize, + __out_opt PULONG ActualDescriptionSize + ) PURE; + + // Attaches to a running user-mode process. + STDMETHOD(AttachProcess)( + THIS_ + __in ULONG64 Server, + __in ULONG ProcessId, + __in ULONG AttachFlags + ) PURE; + // Creates a new user-mode process for debugging. + // CreateFlags are as given to Win32s CreateProcess. + // One of DEBUG_PROCESS or DEBUG_ONLY_THIS_PROCESS + // must be specified. + STDMETHOD(CreateProcess)( + THIS_ + __in ULONG64 Server, + __in PSTR CommandLine, + __in ULONG CreateFlags + ) PURE; + // Creates or attaches to a user-mode process, or both. + // If CommandLine is NULL this method operates as + // AttachProcess does. If ProcessId is zero it + // operates as CreateProcess does. If CommandLine is + // non-NULL and ProcessId is non-zero the method first + // starts a process with the given information but + // in a suspended state. The engine then attaches to + // the indicated process. Once the attach is successful + // the suspended process is resumed. This provides + // synchronization between the new process and the + // attachment. + STDMETHOD(CreateProcessAndAttach)( + THIS_ + __in ULONG64 Server, + __in_opt PSTR CommandLine, + __in ULONG CreateFlags, + __in ULONG ProcessId, + __in ULONG AttachFlags + ) PURE; + // Gets and sets process control flags. + STDMETHOD(GetProcessOptions)( + THIS_ + __out PULONG Options + ) PURE; + STDMETHOD(AddProcessOptions)( + THIS_ + __in ULONG Options + ) PURE; + STDMETHOD(RemoveProcessOptions)( + THIS_ + __in ULONG Options + ) PURE; + STDMETHOD(SetProcessOptions)( + THIS_ + __in ULONG Options + ) PURE; + + // Opens any kind of user- or kernel-mode dump file + // and begins a debug session with the information + // contained within it. + STDMETHOD(OpenDumpFile)( + THIS_ + __in PCSTR DumpFile + ) PURE; + // Writes a dump file from the current session information. + // The kind of dump file written is determined by the + // kind of session and the type qualifier given. + // For example, if the current session is a kernel + // debug session (DEBUG_CLASS_KERNEL) and the qualifier + // is DEBUG_DUMP_SMALL a small kernel dump will be written. + STDMETHOD(WriteDumpFile)( + THIS_ + __in PCSTR DumpFile, + __in ULONG Qualifier + ) PURE; + + // Indicates that a remote client is ready to + // begin participating in the current session. + // HistoryLimit gives a character limit on + // the amount of output history to be sent. + STDMETHOD(ConnectSession)( + THIS_ + __in ULONG Flags, + __in ULONG HistoryLimit + ) PURE; + // Indicates that the engine should start accepting + // remote connections. Options specifies connection types + // and their parameters. Supported strings are: + // npipe:Pipe=<Pipe name> + // tcp:Port=<IP port> + STDMETHOD(StartServer)( + THIS_ + __in PCSTR Options + ) PURE; + // List the servers running on the given machine. + // Uses the line prefix. + STDMETHOD(OutputServers)( + THIS_ + __in ULONG OutputControl, + __in PCSTR Machine, + __in ULONG Flags + ) PURE; + + // Attempts to terminate all processes in the debuggers list. + STDMETHOD(TerminateProcesses)( + THIS + ) PURE; + // Attempts to detach from all processes in the debuggers list. + // This requires OS support for debugger detach. + STDMETHOD(DetachProcesses)( + THIS + ) PURE; + // Stops the current debug session. If a process + // was created or attached an active EndSession can + // terminate or detach from it. + // If a kernel connection was opened it will be closed but the + // target machine is otherwise unaffected. + STDMETHOD(EndSession)( + THIS_ + __in ULONG Flags + ) PURE; + // If a process was started and ran to completion + // this method can be used to retrieve its exit code. + STDMETHOD(GetExitCode)( + THIS_ + __out PULONG Code + ) PURE; + + // Client event callbacks are called on the thread + // of the client. In order to give thread + // execution to the engine for callbacks all + // client threads should call DispatchCallbacks + // when they are idle. Callbacks are only + // received when a thread calls DispatchCallbacks + // or WaitForEvent. WaitForEvent can only be + // called by the thread that started the debug + // session so all other client threads should + // call DispatchCallbacks when possible. + // DispatchCallbacks returns when ExitDispatch is used + // to interrupt dispatch or when the timeout expires. + // DispatchCallbacks dispatches callbacks for all + // clients associated with the thread calling + // DispatchCallbacks. + // DispatchCallbacks returns S_FALSE when the + // timeout expires. + STDMETHOD(DispatchCallbacks)( + THIS_ + __in ULONG Timeout + ) PURE; + // ExitDispatch can be used to interrupt callback + // dispatch when a client thread is needed by the + // client. This method is reentrant and can + // be called from any thread. + STDMETHOD(ExitDispatch)( + THIS_ + __in PDEBUG_CLIENT Client + ) PURE; + + // Clients are specific to the thread that + // created them. Calls from other threads + // fail immediately. The CreateClient method + // is a notable exception; it allows creation + // of a new client for a new thread. + STDMETHOD(CreateClient)( + THIS_ + __out PDEBUG_CLIENT* Client + ) PURE; + + STDMETHOD(GetInputCallbacks)( + THIS_ + __out PDEBUG_INPUT_CALLBACKS* Callbacks + ) PURE; + STDMETHOD(SetInputCallbacks)( + THIS_ + __in_opt PDEBUG_INPUT_CALLBACKS Callbacks + ) PURE; + + // Output callback interfaces are described separately. + STDMETHOD(GetOutputCallbacks)( + THIS_ + __out PDEBUG_OUTPUT_CALLBACKS* Callbacks + ) PURE; + STDMETHOD(SetOutputCallbacks)( + THIS_ + __in_opt PDEBUG_OUTPUT_CALLBACKS Callbacks + ) PURE; + // Output flags provide control over + // the distribution of output among clients. + // Output masks select which output streams + // should be sent to the output callbacks. + // Only Output calls with a mask that + // contains one of the output mask bits + // will be sent to the output callbacks. + // These methods are reentrant. + // If such access is not synchronized + // disruptions in output may occur. + STDMETHOD(GetOutputMask)( + THIS_ + __out PULONG Mask + ) PURE; + STDMETHOD(SetOutputMask)( + THIS_ + __in ULONG Mask + ) PURE; + // These methods allow access to another clients + // output mask. They are necessary for changing + // a clients output mask when it is + // waiting for events. These methods are reentrant + // and can be called from any thread. + STDMETHOD(GetOtherOutputMask)( + THIS_ + __in PDEBUG_CLIENT Client, + __out PULONG Mask + ) PURE; + STDMETHOD(SetOtherOutputMask)( + THIS_ + __in PDEBUG_CLIENT Client, + __in ULONG Mask + ) PURE; + // Control the width of an output line for + // commands which produce formatted output. + // This setting is just a suggestion. + STDMETHOD(GetOutputWidth)( + THIS_ + __out PULONG Columns + ) PURE; + STDMETHOD(SetOutputWidth)( + THIS_ + __in ULONG Columns + ) PURE; + // Some of the engines output commands produce + // multiple lines of output. A prefix can be + // set that the engine will automatically output + // for each line in that case, allowing a caller + // to control indentation or identifying marks. + // This is not a general setting for any output + // with a newline in it. Methods which use + // the line prefix are marked in their documentation. + STDMETHOD(GetOutputLinePrefix)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG PrefixSize + ) PURE; + STDMETHOD(SetOutputLinePrefix)( + THIS_ + __in_opt PCSTR Prefix + ) PURE; + + // Returns a string describing the machine + // and user this client represents. The + // specific content of the string varies + // with operating system. If the client is + // remotely connected some network information + // may also be present. + STDMETHOD(GetIdentity)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG IdentitySize + ) PURE; + // Format is a printf-like format string + // with one %s where the identity string should go. + STDMETHOD(OutputIdentity)( + THIS_ + __in ULONG OutputControl, + __in ULONG Flags, + __in PCSTR Format + ) PURE; + + // Event callbacks allow a client to + // receive notification about changes + // during the debug session. + STDMETHOD(GetEventCallbacks)( + THIS_ + __out PDEBUG_EVENT_CALLBACKS* Callbacks + ) PURE; + STDMETHOD(SetEventCallbacks)( + THIS_ + __in_opt PDEBUG_EVENT_CALLBACKS Callbacks + ) PURE; + + // The engine sometimes merges compatible callback + // requests to reduce callback overhead. This is + // most noticeable with output as small pieces of + // output are collected into larger groups to + // reduce the overall number of output callback calls. + // A client can use this method to force all pending + // callbacks to be delivered. This is rarely necessary. + STDMETHOD(FlushCallbacks)( + THIS + ) PURE; +}; + +// Per-dump-format control flags. +#define DEBUG_FORMAT_DEFAULT 0x00000000 +// When creating a CAB with secondary images do searches +// for all image files, regardless of whether they're +// needed for the current session or not. +#define DEBUG_FORMAT_CAB_SECONDARY_ALL_IMAGES 0x10000000 +// Write dump to a temporary file, then package it +// into a CAB file and delete the temporary file. +#define DEBUG_FORMAT_WRITE_CAB 0x20000000 +// When creating a CAB add secondary files such as +// current symbols and mapped images. +#define DEBUG_FORMAT_CAB_SECONDARY_FILES 0x40000000 +// Don't overwrite existing files. +#define DEBUG_FORMAT_NO_OVERWRITE 0x80000000 + +#define DEBUG_FORMAT_USER_SMALL_FULL_MEMORY 0x00000001 +#define DEBUG_FORMAT_USER_SMALL_HANDLE_DATA 0x00000002 +#define DEBUG_FORMAT_USER_SMALL_UNLOADED_MODULES 0x00000004 +#define DEBUG_FORMAT_USER_SMALL_INDIRECT_MEMORY 0x00000008 +#define DEBUG_FORMAT_USER_SMALL_DATA_SEGMENTS 0x00000010 +#define DEBUG_FORMAT_USER_SMALL_FILTER_MEMORY 0x00000020 +#define DEBUG_FORMAT_USER_SMALL_FILTER_PATHS 0x00000040 +#define DEBUG_FORMAT_USER_SMALL_PROCESS_THREAD_DATA 0x00000080 +#define DEBUG_FORMAT_USER_SMALL_PRIVATE_READ_WRITE_MEMORY 0x00000100 +#define DEBUG_FORMAT_USER_SMALL_NO_OPTIONAL_DATA 0x00000200 +#define DEBUG_FORMAT_USER_SMALL_FULL_MEMORY_INFO 0x00000400 +#define DEBUG_FORMAT_USER_SMALL_THREAD_INFO 0x00000800 +#define DEBUG_FORMAT_USER_SMALL_CODE_SEGMENTS 0x00001000 +#define DEBUG_FORMAT_USER_SMALL_NO_AUXILIARY_STATE 0x00002000 +#define DEBUG_FORMAT_USER_SMALL_FULL_AUXILIARY_STATE 0x00004000 + +// +// Dump information file types. +// + +// Base dump file, returned when querying for dump files. +#define DEBUG_DUMP_FILE_BASE 0xffffffff +// Single file containing packed page file information. +#define DEBUG_DUMP_FILE_PAGE_FILE_DUMP 0x00000000 + +#undef INTERFACE +#define INTERFACE IDebugClient2 +DECLARE_INTERFACE_(IDebugClient2, IUnknown) +{ + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + __in REFIID InterfaceId, + __out PVOID* Interface + ) PURE; + STDMETHOD_(ULONG, AddRef)( + THIS + ) PURE; + STDMETHOD_(ULONG, Release)( + THIS + ) PURE; + + // IDebugClient. + + // The following set of methods start + // the different kinds of debuggees. + + // Begins a debug session using the kernel + // debugging protocol. This method selects + // the protocol as the debuggee communication + // mechanism but does not initiate the communication + // itself. + STDMETHOD(AttachKernel)( + THIS_ + __in ULONG Flags, + __in_opt PCSTR ConnectOptions + ) PURE; + STDMETHOD(GetKernelConnectionOptions)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG OptionsSize + ) PURE; + // Updates the connection options for a live + // kernel connection. This can only be used + // to modify parameters for the connection, not + // to switch to a completely different kind of + // connection. + // This method is reentrant. + STDMETHOD(SetKernelConnectionOptions)( + THIS_ + __in PCSTR Options + ) PURE; + + // Starts a process server for remote + // user-mode process control. + // The local process server is server zero. + STDMETHOD(StartProcessServer)( + THIS_ + __in ULONG Flags, + __in PCSTR Options, + __in_opt __reserved PVOID Reserved + ) PURE; + STDMETHOD(ConnectProcessServer)( + THIS_ + __in PCSTR RemoteOptions, + __out PULONG64 Server + ) PURE; + STDMETHOD(DisconnectProcessServer)( + THIS_ + __in ULONG64 Server + ) PURE; + + // Enumerates and describes processes + // accessible through the given process server. + STDMETHOD(GetRunningProcessSystemIds)( + THIS_ + __in ULONG64 Server, + __out_ecount_opt(Count) PULONG Ids, + __in ULONG Count, + __out_opt PULONG ActualCount + ) PURE; + STDMETHOD(GetRunningProcessSystemIdByExecutableName)( + THIS_ + __in ULONG64 Server, + __in PCSTR ExeName, + __in ULONG Flags, + __out PULONG Id + ) PURE; + STDMETHOD(GetRunningProcessDescription)( + THIS_ + __in ULONG64 Server, + __in ULONG SystemId, + __in ULONG Flags, + __out_ecount_opt(ExeNameSize) PSTR ExeName, + __in ULONG ExeNameSize, + __out_opt PULONG ActualExeNameSize, + __out_ecount_opt(DescriptionSize) PSTR Description, + __in ULONG DescriptionSize, + __out_opt PULONG ActualDescriptionSize + ) PURE; + + // Attaches to a running user-mode process. + STDMETHOD(AttachProcess)( + THIS_ + __in ULONG64 Server, + __in ULONG ProcessId, + __in ULONG AttachFlags + ) PURE; + // Creates a new user-mode process for debugging. + // CreateFlags are as given to Win32s CreateProcess. + // One of DEBUG_PROCESS or DEBUG_ONLY_THIS_PROCESS + // must be specified. + STDMETHOD(CreateProcess)( + THIS_ + __in ULONG64 Server, + __in PSTR CommandLine, + __in ULONG CreateFlags + ) PURE; + // Creates or attaches to a user-mode process, or both. + // If CommandLine is NULL this method operates as + // AttachProcess does. If ProcessId is zero it + // operates as CreateProcess does. If CommandLine is + // non-NULL and ProcessId is non-zero the method first + // starts a process with the given information but + // in a suspended state. The engine then attaches to + // the indicated process. Once the attach is successful + // the suspended process is resumed. This provides + // synchronization between the new process and the + // attachment. + STDMETHOD(CreateProcessAndAttach)( + THIS_ + __in ULONG64 Server, + __in_opt PSTR CommandLine, + __in ULONG CreateFlags, + __in ULONG ProcessId, + __in ULONG AttachFlags + ) PURE; + // Gets and sets process control flags. + STDMETHOD(GetProcessOptions)( + THIS_ + __out PULONG Options + ) PURE; + STDMETHOD(AddProcessOptions)( + THIS_ + __in ULONG Options + ) PURE; + STDMETHOD(RemoveProcessOptions)( + THIS_ + __in ULONG Options + ) PURE; + STDMETHOD(SetProcessOptions)( + THIS_ + __in ULONG Options + ) PURE; + + // Opens any kind of user- or kernel-mode dump file + // and begins a debug session with the information + // contained within it. + STDMETHOD(OpenDumpFile)( + THIS_ + __in PCSTR DumpFile + ) PURE; + // Writes a dump file from the current session information. + // The kind of dump file written is determined by the + // kind of session and the type qualifier given. + // For example, if the current session is a kernel + // debug session (DEBUG_CLASS_KERNEL) and the qualifier + // is DEBUG_DUMP_SMALL a small kernel dump will be written. + STDMETHOD(WriteDumpFile)( + THIS_ + __in PCSTR DumpFile, + __in ULONG Qualifier + ) PURE; + + // Indicates that a remote client is ready to + // begin participating in the current session. + // HistoryLimit gives a character limit on + // the amount of output history to be sent. + STDMETHOD(ConnectSession)( + THIS_ + __in ULONG Flags, + __in ULONG HistoryLimit + ) PURE; + // Indicates that the engine should start accepting + // remote connections. Options specifies connection types + // and their parameters. Supported strings are: + // npipe:Pipe=<Pipe name> + // tcp:Port=<IP port> + STDMETHOD(StartServer)( + THIS_ + __in PCSTR Options + ) PURE; + // List the servers running on the given machine. + // Uses the line prefix. + STDMETHOD(OutputServers)( + THIS_ + __in ULONG OutputControl, + __in PCSTR Machine, + __in ULONG Flags + ) PURE; + + // Attempts to terminate all processes in the debuggers list. + STDMETHOD(TerminateProcesses)( + THIS + ) PURE; + // Attempts to detach from all processes in the debuggers list. + // This requires OS support for debugger detach. + STDMETHOD(DetachProcesses)( + THIS + ) PURE; + // Stops the current debug session. If a process + // was created or attached an active EndSession can + // terminate or detach from it. + // If a kernel connection was opened it will be closed but the + // target machine is otherwise unaffected. + STDMETHOD(EndSession)( + THIS_ + __in ULONG Flags + ) PURE; + // If a process was started and ran to completion + // this method can be used to retrieve its exit code. + STDMETHOD(GetExitCode)( + THIS_ + __out PULONG Code + ) PURE; + + // Client event callbacks are called on the thread + // of the client. In order to give thread + // execution to the engine for callbacks all + // client threads should call DispatchCallbacks + // when they are idle. Callbacks are only + // received when a thread calls DispatchCallbacks + // or WaitForEvent. WaitForEvent can only be + // called by the thread that started the debug + // session so all other client threads should + // call DispatchCallbacks when possible. + // DispatchCallbacks returns when ExitDispatch is used + // to interrupt dispatch or when the timeout expires. + // DispatchCallbacks dispatches callbacks for all + // clients associated with the thread calling + // DispatchCallbacks. + // DispatchCallbacks returns S_FALSE when the + // timeout expires. + STDMETHOD(DispatchCallbacks)( + THIS_ + __in ULONG Timeout + ) PURE; + // ExitDispatch can be used to interrupt callback + // dispatch when a client thread is needed by the + // client. This method is reentrant and can + // be called from any thread. + STDMETHOD(ExitDispatch)( + THIS_ + __in PDEBUG_CLIENT Client + ) PURE; + + // Clients are specific to the thread that + // created them. Calls from other threads + // fail immediately. The CreateClient method + // is a notable exception; it allows creation + // of a new client for a new thread. + STDMETHOD(CreateClient)( + THIS_ + __out PDEBUG_CLIENT* Client + ) PURE; + + STDMETHOD(GetInputCallbacks)( + THIS_ + __out PDEBUG_INPUT_CALLBACKS* Callbacks + ) PURE; + STDMETHOD(SetInputCallbacks)( + THIS_ + __in_opt PDEBUG_INPUT_CALLBACKS Callbacks + ) PURE; + + // Output callback interfaces are described separately. + STDMETHOD(GetOutputCallbacks)( + THIS_ + __out PDEBUG_OUTPUT_CALLBACKS* Callbacks + ) PURE; + STDMETHOD(SetOutputCallbacks)( + THIS_ + __in_opt PDEBUG_OUTPUT_CALLBACKS Callbacks + ) PURE; + // Output flags provide control over + // the distribution of output among clients. + // Output masks select which output streams + // should be sent to the output callbacks. + // Only Output calls with a mask that + // contains one of the output mask bits + // will be sent to the output callbacks. + // These methods are reentrant. + // If such access is not synchronized + // disruptions in output may occur. + STDMETHOD(GetOutputMask)( + THIS_ + __out PULONG Mask + ) PURE; + STDMETHOD(SetOutputMask)( + THIS_ + __in ULONG Mask + ) PURE; + // These methods allow access to another clients + // output mask. They are necessary for changing + // a clients output mask when it is + // waiting for events. These methods are reentrant + // and can be called from any thread. + STDMETHOD(GetOtherOutputMask)( + THIS_ + __in PDEBUG_CLIENT Client, + __out PULONG Mask + ) PURE; + STDMETHOD(SetOtherOutputMask)( + THIS_ + __in PDEBUG_CLIENT Client, + __in ULONG Mask + ) PURE; + // Control the width of an output line for + // commands which produce formatted output. + // This setting is just a suggestion. + STDMETHOD(GetOutputWidth)( + THIS_ + __out PULONG Columns + ) PURE; + STDMETHOD(SetOutputWidth)( + THIS_ + __in ULONG Columns + ) PURE; + // Some of the engines output commands produce + // multiple lines of output. A prefix can be + // set that the engine will automatically output + // for each line in that case, allowing a caller + // to control indentation or identifying marks. + // This is not a general setting for any output + // with a newline in it. Methods which use + // the line prefix are marked in their documentation. + STDMETHOD(GetOutputLinePrefix)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG PrefixSize + ) PURE; + STDMETHOD(SetOutputLinePrefix)( + THIS_ + __in_opt PCSTR Prefix + ) PURE; + + // Returns a string describing the machine + // and user this client represents. The + // specific content of the string varies + // with operating system. If the client is + // remotely connected some network information + // may also be present. + STDMETHOD(GetIdentity)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG IdentitySize + ) PURE; + // Format is a printf-like format string + // with one %s where the identity string should go. + STDMETHOD(OutputIdentity)( + THIS_ + __in ULONG OutputControl, + __in ULONG Flags, + __in PCSTR Format + ) PURE; + + // Event callbacks allow a client to + // receive notification about changes + // during the debug session. + STDMETHOD(GetEventCallbacks)( + THIS_ + __out PDEBUG_EVENT_CALLBACKS* Callbacks + ) PURE; + STDMETHOD(SetEventCallbacks)( + THIS_ + __in_opt PDEBUG_EVENT_CALLBACKS Callbacks + ) PURE; + + // The engine sometimes merges compatible callback + // requests to reduce callback overhead. This is + // most noticeable with output as small pieces of + // output are collected into larger groups to + // reduce the overall number of output callback calls. + // A client can use this method to force all pending + // callbacks to be delivered. This is rarely necessary. + STDMETHOD(FlushCallbacks)( + THIS + ) PURE; + + // IDebugClient2. + + // Functions similarly to WriteDumpFile with + // the addition of the ability to specify + // per-dump-format write control flags. + // Comment is not supported in all formats. + STDMETHOD(WriteDumpFile2)( + THIS_ + __in PCSTR DumpFile, + __in ULONG Qualifier, + __in ULONG FormatFlags, + __in_opt PCSTR Comment + ) PURE; + // Registers additional files of supporting information + // for a dump file open. This method must be called + // before OpenDumpFile is called. + // The files registered may be opened at the time + // this method is called but generally will not + // be used until OpenDumpFile is called. + STDMETHOD(AddDumpInformationFile)( + THIS_ + __in PCSTR InfoFile, + __in ULONG Type + ) PURE; + + // Requests that the remote process server shut down. + STDMETHOD(EndProcessServer)( + THIS_ + __in ULONG64 Server + ) PURE; + // Waits for a started process server to + // exit. Allows an application running a + // process server to monitor the process + // server so that it can tell when a remote + // client has asked for it to exit. + // Returns S_OK if the process server has + // shut down and S_FALSE for a timeout. + STDMETHOD(WaitForProcessServerEnd)( + THIS_ + __in ULONG Timeout + ) PURE; + + // Returns S_OK if the system is configured + // to allow kernel debugging. + STDMETHOD(IsKernelDebuggerEnabled)( + THIS + ) PURE; + + // Attempts to terminate the current process. + // Exit process events for the process may be generated. + STDMETHOD(TerminateCurrentProcess)( + THIS + ) PURE; + // Attempts to detach from the current process. + // This requires OS support for debugger detach. + STDMETHOD(DetachCurrentProcess)( + THIS + ) PURE; + // Removes the process from the debuggers process + // list without making any other changes. The process + // will still be marked as being debugged and will + // not run. This allows a debugger to be shut down + // and a new debugger attached without taking the + // process out of the debugged state. + // This is only supported on some system versions. + STDMETHOD(AbandonCurrentProcess)( + THIS + ) PURE; +}; + +#undef INTERFACE +#define INTERFACE IDebugClient3 +DECLARE_INTERFACE_(IDebugClient3, IUnknown) +{ + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + __in REFIID InterfaceId, + __out PVOID* Interface + ) PURE; + STDMETHOD_(ULONG, AddRef)( + THIS + ) PURE; + STDMETHOD_(ULONG, Release)( + THIS + ) PURE; + + // IDebugClient. + + // The following set of methods start + // the different kinds of debuggees. + + // Begins a debug session using the kernel + // debugging protocol. This method selects + // the protocol as the debuggee communication + // mechanism but does not initiate the communication + // itself. + STDMETHOD(AttachKernel)( + THIS_ + __in ULONG Flags, + __in_opt PCSTR ConnectOptions + ) PURE; + STDMETHOD(GetKernelConnectionOptions)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG OptionsSize + ) PURE; + // Updates the connection options for a live + // kernel connection. This can only be used + // to modify parameters for the connection, not + // to switch to a completely different kind of + // connection. + // This method is reentrant. + STDMETHOD(SetKernelConnectionOptions)( + THIS_ + __in PCSTR Options + ) PURE; + + // Starts a process server for remote + // user-mode process control. + // The local process server is server zero. + STDMETHOD(StartProcessServer)( + THIS_ + __in ULONG Flags, + __in PCSTR Options, + __in_opt __reserved PVOID Reserved + ) PURE; + STDMETHOD(ConnectProcessServer)( + THIS_ + __in PCSTR RemoteOptions, + __out PULONG64 Server + ) PURE; + STDMETHOD(DisconnectProcessServer)( + THIS_ + __in ULONG64 Server + ) PURE; + + // Enumerates and describes processes + // accessible through the given process server. + STDMETHOD(GetRunningProcessSystemIds)( + THIS_ + __in ULONG64 Server, + __out_ecount_opt(Count) PULONG Ids, + __in ULONG Count, + __out_opt PULONG ActualCount + ) PURE; + STDMETHOD(GetRunningProcessSystemIdByExecutableName)( + THIS_ + __in ULONG64 Server, + __in PCSTR ExeName, + __in ULONG Flags, + __out PULONG Id + ) PURE; + STDMETHOD(GetRunningProcessDescription)( + THIS_ + __in ULONG64 Server, + __in ULONG SystemId, + __in ULONG Flags, + __out_ecount_opt(ExeNameSize) PSTR ExeName, + __in ULONG ExeNameSize, + __out_opt PULONG ActualExeNameSize, + __out_ecount_opt(DescriptionSize) PSTR Description, + __in ULONG DescriptionSize, + __out_opt PULONG ActualDescriptionSize + ) PURE; + + // Attaches to a running user-mode process. + STDMETHOD(AttachProcess)( + THIS_ + __in ULONG64 Server, + __in ULONG ProcessId, + __in ULONG AttachFlags + ) PURE; + // Creates a new user-mode process for debugging. + // CreateFlags are as given to Win32s CreateProcess. + // One of DEBUG_PROCESS or DEBUG_ONLY_THIS_PROCESS + // must be specified. + STDMETHOD(CreateProcess)( + THIS_ + __in ULONG64 Server, + __in PSTR CommandLine, + __in ULONG CreateFlags + ) PURE; + // Creates or attaches to a user-mode process, or both. + // If CommandLine is NULL this method operates as + // AttachProcess does. If ProcessId is zero it + // operates as CreateProcess does. If CommandLine is + // non-NULL and ProcessId is non-zero the method first + // starts a process with the given information but + // in a suspended state. The engine then attaches to + // the indicated process. Once the attach is successful + // the suspended process is resumed. This provides + // synchronization between the new process and the + // attachment. + STDMETHOD(CreateProcessAndAttach)( + THIS_ + __in ULONG64 Server, + __in_opt PSTR CommandLine, + __in ULONG CreateFlags, + __in ULONG ProcessId, + __in ULONG AttachFlags + ) PURE; + // Gets and sets process control flags. + STDMETHOD(GetProcessOptions)( + THIS_ + __out PULONG Options + ) PURE; + STDMETHOD(AddProcessOptions)( + THIS_ + __in ULONG Options + ) PURE; + STDMETHOD(RemoveProcessOptions)( + THIS_ + __in ULONG Options + ) PURE; + STDMETHOD(SetProcessOptions)( + THIS_ + __in ULONG Options + ) PURE; + + // Opens any kind of user- or kernel-mode dump file + // and begins a debug session with the information + // contained within it. + STDMETHOD(OpenDumpFile)( + THIS_ + __in PCSTR DumpFile + ) PURE; + // Writes a dump file from the current session information. + // The kind of dump file written is determined by the + // kind of session and the type qualifier given. + // For example, if the current session is a kernel + // debug session (DEBUG_CLASS_KERNEL) and the qualifier + // is DEBUG_DUMP_SMALL a small kernel dump will be written. + STDMETHOD(WriteDumpFile)( + THIS_ + __in PCSTR DumpFile, + __in ULONG Qualifier + ) PURE; + + // Indicates that a remote client is ready to + // begin participating in the current session. + // HistoryLimit gives a character limit on + // the amount of output history to be sent. + STDMETHOD(ConnectSession)( + THIS_ + __in ULONG Flags, + __in ULONG HistoryLimit + ) PURE; + // Indicates that the engine should start accepting + // remote connections. Options specifies connection types + // and their parameters. Supported strings are: + // npipe:Pipe=<Pipe name> + // tcp:Port=<IP port> + STDMETHOD(StartServer)( + THIS_ + __in PCSTR Options + ) PURE; + // List the servers running on the given machine. + // Uses the line prefix. + STDMETHOD(OutputServers)( + THIS_ + __in ULONG OutputControl, + __in PCSTR Machine, + __in ULONG Flags + ) PURE; + + // Attempts to terminate all processes in the debuggers list. + STDMETHOD(TerminateProcesses)( + THIS + ) PURE; + // Attempts to detach from all processes in the debuggers list. + // This requires OS support for debugger detach. + STDMETHOD(DetachProcesses)( + THIS + ) PURE; + // Stops the current debug session. If a process + // was created or attached an active EndSession can + // terminate or detach from it. + // If a kernel connection was opened it will be closed but the + // target machine is otherwise unaffected. + STDMETHOD(EndSession)( + THIS_ + __in ULONG Flags + ) PURE; + // If a process was started and ran to completion + // this method can be used to retrieve its exit code. + STDMETHOD(GetExitCode)( + THIS_ + __out PULONG Code + ) PURE; + + // Client event callbacks are called on the thread + // of the client. In order to give thread + // execution to the engine for callbacks all + // client threads should call DispatchCallbacks + // when they are idle. Callbacks are only + // received when a thread calls DispatchCallbacks + // or WaitForEvent. WaitForEvent can only be + // called by the thread that started the debug + // session so all other client threads should + // call DispatchCallbacks when possible. + // DispatchCallbacks returns when ExitDispatch is used + // to interrupt dispatch or when the timeout expires. + // DispatchCallbacks dispatches callbacks for all + // clients associated with the thread calling + // DispatchCallbacks. + // DispatchCallbacks returns S_FALSE when the + // timeout expires. + STDMETHOD(DispatchCallbacks)( + THIS_ + __in ULONG Timeout + ) PURE; + // ExitDispatch can be used to interrupt callback + // dispatch when a client thread is needed by the + // client. This method is reentrant and can + // be called from any thread. + STDMETHOD(ExitDispatch)( + THIS_ + __in PDEBUG_CLIENT Client + ) PURE; + + // Clients are specific to the thread that + // created them. Calls from other threads + // fail immediately. The CreateClient method + // is a notable exception; it allows creation + // of a new client for a new thread. + STDMETHOD(CreateClient)( + THIS_ + __out PDEBUG_CLIENT* Client + ) PURE; + + STDMETHOD(GetInputCallbacks)( + THIS_ + __out PDEBUG_INPUT_CALLBACKS* Callbacks + ) PURE; + STDMETHOD(SetInputCallbacks)( + THIS_ + __in_opt PDEBUG_INPUT_CALLBACKS Callbacks + ) PURE; + + // Output callback interfaces are described separately. + STDMETHOD(GetOutputCallbacks)( + THIS_ + __out PDEBUG_OUTPUT_CALLBACKS* Callbacks + ) PURE; + STDMETHOD(SetOutputCallbacks)( + THIS_ + __in_opt PDEBUG_OUTPUT_CALLBACKS Callbacks + ) PURE; + // Output flags provide control over + // the distribution of output among clients. + // Output masks select which output streams + // should be sent to the output callbacks. + // Only Output calls with a mask that + // contains one of the output mask bits + // will be sent to the output callbacks. + // These methods are reentrant. + // If such access is not synchronized + // disruptions in output may occur. + STDMETHOD(GetOutputMask)( + THIS_ + __out PULONG Mask + ) PURE; + STDMETHOD(SetOutputMask)( + THIS_ + __in ULONG Mask + ) PURE; + // These methods allow access to another clients + // output mask. They are necessary for changing + // a clients output mask when it is + // waiting for events. These methods are reentrant + // and can be called from any thread. + STDMETHOD(GetOtherOutputMask)( + THIS_ + __in PDEBUG_CLIENT Client, + __out PULONG Mask + ) PURE; + STDMETHOD(SetOtherOutputMask)( + THIS_ + __in PDEBUG_CLIENT Client, + __in ULONG Mask + ) PURE; + // Control the width of an output line for + // commands which produce formatted output. + // This setting is just a suggestion. + STDMETHOD(GetOutputWidth)( + THIS_ + __out PULONG Columns + ) PURE; + STDMETHOD(SetOutputWidth)( + THIS_ + __in ULONG Columns + ) PURE; + // Some of the engines output commands produce + // multiple lines of output. A prefix can be + // set that the engine will automatically output + // for each line in that case, allowing a caller + // to control indentation or identifying marks. + // This is not a general setting for any output + // with a newline in it. Methods which use + // the line prefix are marked in their documentation. + STDMETHOD(GetOutputLinePrefix)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG PrefixSize + ) PURE; + STDMETHOD(SetOutputLinePrefix)( + THIS_ + __in_opt PCSTR Prefix + ) PURE; + + // Returns a string describing the machine + // and user this client represents. The + // specific content of the string varies + // with operating system. If the client is + // remotely connected some network information + // may also be present. + STDMETHOD(GetIdentity)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG IdentitySize + ) PURE; + // Format is a printf-like format string + // with one %s where the identity string should go. + STDMETHOD(OutputIdentity)( + THIS_ + __in ULONG OutputControl, + __in ULONG Flags, + __in PCSTR Format + ) PURE; + + // Event callbacks allow a client to + // receive notification about changes + // during the debug session. + STDMETHOD(GetEventCallbacks)( + THIS_ + __out PDEBUG_EVENT_CALLBACKS* Callbacks + ) PURE; + STDMETHOD(SetEventCallbacks)( + THIS_ + __in_opt PDEBUG_EVENT_CALLBACKS Callbacks + ) PURE; + + // The engine sometimes merges compatible callback + // requests to reduce callback overhead. This is + // most noticeable with output as small pieces of + // output are collected into larger groups to + // reduce the overall number of output callback calls. + // A client can use this method to force all pending + // callbacks to be delivered. This is rarely necessary. + STDMETHOD(FlushCallbacks)( + THIS + ) PURE; + + // IDebugClient2. + + // Functions similarly to WriteDumpFile with + // the addition of the ability to specify + // per-dump-format write control flags. + // Comment is not supported in all formats. + STDMETHOD(WriteDumpFile2)( + THIS_ + __in PCSTR DumpFile, + __in ULONG Qualifier, + __in ULONG FormatFlags, + __in_opt PCSTR Comment + ) PURE; + // Registers additional files of supporting information + // for a dump file open. This method must be called + // before OpenDumpFile is called. + // The files registered may be opened at the time + // this method is called but generally will not + // be used until OpenDumpFile is called. + STDMETHOD(AddDumpInformationFile)( + THIS_ + __in PCSTR InfoFile, + __in ULONG Type + ) PURE; + + // Requests that the remote process server shut down. + STDMETHOD(EndProcessServer)( + THIS_ + __in ULONG64 Server + ) PURE; + // Waits for a started process server to + // exit. Allows an application running a + // process server to monitor the process + // server so that it can tell when a remote + // client has asked for it to exit. + // Returns S_OK if the process server has + // shut down and S_FALSE for a timeout. + STDMETHOD(WaitForProcessServerEnd)( + THIS_ + __in ULONG Timeout + ) PURE; + + // Returns S_OK if the system is configured + // to allow kernel debugging. + STDMETHOD(IsKernelDebuggerEnabled)( + THIS + ) PURE; + + // Attempts to terminate the current process. + // Exit process events for the process may be generated. + STDMETHOD(TerminateCurrentProcess)( + THIS + ) PURE; + // Attempts to detach from the current process. + // This requires OS support for debugger detach. + STDMETHOD(DetachCurrentProcess)( + THIS + ) PURE; + // Removes the process from the debuggers process + // list without making any other changes. The process + // will still be marked as being debugged and will + // not run. This allows a debugger to be shut down + // and a new debugger attached without taking the + // process out of the debugged state. + // This is only supported on some system versions. + STDMETHOD(AbandonCurrentProcess)( + THIS + ) PURE; + + // IDebugClient3. + + STDMETHOD(GetRunningProcessSystemIdByExecutableNameWide)( + THIS_ + __in ULONG64 Server, + __in PCWSTR ExeName, + __in ULONG Flags, + __out PULONG Id + ) PURE; + STDMETHOD(GetRunningProcessDescriptionWide)( + THIS_ + __in ULONG64 Server, + __in ULONG SystemId, + __in ULONG Flags, + __out_ecount_opt(ExeNameSize) PWSTR ExeName, + __in ULONG ExeNameSize, + __out_opt PULONG ActualExeNameSize, + __out_ecount_opt(DescriptionSize) PWSTR Description, + __in ULONG DescriptionSize, + __out_opt PULONG ActualDescriptionSize + ) PURE; + + STDMETHOD(CreateProcessWide)( + THIS_ + __in ULONG64 Server, + __in PWSTR CommandLine, + __in ULONG CreateFlags + ) PURE; + STDMETHOD(CreateProcessAndAttachWide)( + THIS_ + __in ULONG64 Server, + __in_opt PWSTR CommandLine, + __in ULONG CreateFlags, + __in ULONG ProcessId, + __in ULONG AttachFlags + ) PURE; +}; + +// +// Special indices for GetDumpFile to return +// alternate filenames. +// + +// Special index that returns the name of the last .dmp file +// that failed to load (whether directly or from inside a +// .cab file). +#define DEBUG_DUMP_FILE_LOAD_FAILED_INDEX 0xffffffff +// Index that returns last cab file opened, this is needed to +// get the name of original CAB file since debugger returns the +// extracted dump file in the GetDumpFile method. +#define DEBUG_DUMP_FILE_ORIGINAL_CAB_INDEX 0xfffffffe + +#undef INTERFACE +#define INTERFACE IDebugClient4 +DECLARE_INTERFACE_(IDebugClient4, IUnknown) +{ + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + __in REFIID InterfaceId, + __out PVOID* Interface + ) PURE; + STDMETHOD_(ULONG, AddRef)( + THIS + ) PURE; + STDMETHOD_(ULONG, Release)( + THIS + ) PURE; + + // IDebugClient. + + // The following set of methods start + // the different kinds of debuggees. + + // Begins a debug session using the kernel + // debugging protocol. This method selects + // the protocol as the debuggee communication + // mechanism but does not initiate the communication + // itself. + STDMETHOD(AttachKernel)( + THIS_ + __in ULONG Flags, + __in_opt PCSTR ConnectOptions + ) PURE; + STDMETHOD(GetKernelConnectionOptions)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG OptionsSize + ) PURE; + // Updates the connection options for a live + // kernel connection. This can only be used + // to modify parameters for the connection, not + // to switch to a completely different kind of + // connection. + // This method is reentrant. + STDMETHOD(SetKernelConnectionOptions)( + THIS_ + __in PCSTR Options + ) PURE; + + // Starts a process server for remote + // user-mode process control. + // The local process server is server zero. + STDMETHOD(StartProcessServer)( + THIS_ + __in ULONG Flags, + __in PCSTR Options, + __in_opt __reserved PVOID Reserved + ) PURE; + STDMETHOD(ConnectProcessServer)( + THIS_ + __in PCSTR RemoteOptions, + __out PULONG64 Server + ) PURE; + STDMETHOD(DisconnectProcessServer)( + THIS_ + __in ULONG64 Server + ) PURE; + + // Enumerates and describes processes + // accessible through the given process server. + STDMETHOD(GetRunningProcessSystemIds)( + THIS_ + __in ULONG64 Server, + __out_ecount_opt(Count) PULONG Ids, + __in ULONG Count, + __out_opt PULONG ActualCount + ) PURE; + STDMETHOD(GetRunningProcessSystemIdByExecutableName)( + THIS_ + __in ULONG64 Server, + __in PCSTR ExeName, + __in ULONG Flags, + __out PULONG Id + ) PURE; + STDMETHOD(GetRunningProcessDescription)( + THIS_ + __in ULONG64 Server, + __in ULONG SystemId, + __in ULONG Flags, + __out_ecount_opt(ExeNameSize) PSTR ExeName, + __in ULONG ExeNameSize, + __out_opt PULONG ActualExeNameSize, + __out_ecount_opt(DescriptionSize) PSTR Description, + __in ULONG DescriptionSize, + __out_opt PULONG ActualDescriptionSize + ) PURE; + + // Attaches to a running user-mode process. + STDMETHOD(AttachProcess)( + THIS_ + __in ULONG64 Server, + __in ULONG ProcessId, + __in ULONG AttachFlags + ) PURE; + // Creates a new user-mode process for debugging. + // CreateFlags are as given to Win32s CreateProcess. + // One of DEBUG_PROCESS or DEBUG_ONLY_THIS_PROCESS + // must be specified. + STDMETHOD(CreateProcess)( + THIS_ + __in ULONG64 Server, + __in PSTR CommandLine, + __in ULONG CreateFlags + ) PURE; + // Creates or attaches to a user-mode process, or both. + // If CommandLine is NULL this method operates as + // AttachProcess does. If ProcessId is zero it + // operates as CreateProcess does. If CommandLine is + // non-NULL and ProcessId is non-zero the method first + // starts a process with the given information but + // in a suspended state. The engine then attaches to + // the indicated process. Once the attach is successful + // the suspended process is resumed. This provides + // synchronization between the new process and the + // attachment. + STDMETHOD(CreateProcessAndAttach)( + THIS_ + __in ULONG64 Server, + __in_opt PSTR CommandLine, + __in ULONG CreateFlags, + __in ULONG ProcessId, + __in ULONG AttachFlags + ) PURE; + // Gets and sets process control flags. + STDMETHOD(GetProcessOptions)( + THIS_ + __out PULONG Options + ) PURE; + STDMETHOD(AddProcessOptions)( + THIS_ + __in ULONG Options + ) PURE; + STDMETHOD(RemoveProcessOptions)( + THIS_ + __in ULONG Options + ) PURE; + STDMETHOD(SetProcessOptions)( + THIS_ + __in ULONG Options + ) PURE; + + // Opens any kind of user- or kernel-mode dump file + // and begins a debug session with the information + // contained within it. + STDMETHOD(OpenDumpFile)( + THIS_ + __in PCSTR DumpFile + ) PURE; + // Writes a dump file from the current session information. + // The kind of dump file written is determined by the + // kind of session and the type qualifier given. + // For example, if the current session is a kernel + // debug session (DEBUG_CLASS_KERNEL) and the qualifier + // is DEBUG_DUMP_SMALL a small kernel dump will be written. + STDMETHOD(WriteDumpFile)( + THIS_ + __in PCSTR DumpFile, + __in ULONG Qualifier + ) PURE; + + // Indicates that a remote client is ready to + // begin participating in the current session. + // HistoryLimit gives a character limit on + // the amount of output history to be sent. + STDMETHOD(ConnectSession)( + THIS_ + __in ULONG Flags, + __in ULONG HistoryLimit + ) PURE; + // Indicates that the engine should start accepting + // remote connections. Options specifies connection types + // and their parameters. Supported strings are: + // npipe:Pipe=<Pipe name> + // tcp:Port=<IP port> + STDMETHOD(StartServer)( + THIS_ + __in PCSTR Options + ) PURE; + // List the servers running on the given machine. + // Uses the line prefix. + STDMETHOD(OutputServers)( + THIS_ + __in ULONG OutputControl, + __in PCSTR Machine, + __in ULONG Flags + ) PURE; + + // Attempts to terminate all processes in the debuggers list. + STDMETHOD(TerminateProcesses)( + THIS + ) PURE; + // Attempts to detach from all processes in the debuggers list. + // This requires OS support for debugger detach. + STDMETHOD(DetachProcesses)( + THIS + ) PURE; + // Stops the current debug session. If a process + // was created or attached an active EndSession can + // terminate or detach from it. + // If a kernel connection was opened it will be closed but the + // target machine is otherwise unaffected. + STDMETHOD(EndSession)( + THIS_ + __in ULONG Flags + ) PURE; + // If a process was started and ran to completion + // this method can be used to retrieve its exit code. + STDMETHOD(GetExitCode)( + THIS_ + __out PULONG Code + ) PURE; + + // Client event callbacks are called on the thread + // of the client. In order to give thread + // execution to the engine for callbacks all + // client threads should call DispatchCallbacks + // when they are idle. Callbacks are only + // received when a thread calls DispatchCallbacks + // or WaitForEvent. WaitForEvent can only be + // called by the thread that started the debug + // session so all other client threads should + // call DispatchCallbacks when possible. + // DispatchCallbacks returns when ExitDispatch is used + // to interrupt dispatch or when the timeout expires. + // DispatchCallbacks dispatches callbacks for all + // clients associated with the thread calling + // DispatchCallbacks. + // DispatchCallbacks returns S_FALSE when the + // timeout expires. + STDMETHOD(DispatchCallbacks)( + THIS_ + __in ULONG Timeout + ) PURE; + // ExitDispatch can be used to interrupt callback + // dispatch when a client thread is needed by the + // client. This method is reentrant and can + // be called from any thread. + STDMETHOD(ExitDispatch)( + THIS_ + __in PDEBUG_CLIENT Client + ) PURE; + + // Clients are specific to the thread that + // created them. Calls from other threads + // fail immediately. The CreateClient method + // is a notable exception; it allows creation + // of a new client for a new thread. + STDMETHOD(CreateClient)( + THIS_ + __out PDEBUG_CLIENT* Client + ) PURE; + + STDMETHOD(GetInputCallbacks)( + THIS_ + __out PDEBUG_INPUT_CALLBACKS* Callbacks + ) PURE; + STDMETHOD(SetInputCallbacks)( + THIS_ + __in_opt PDEBUG_INPUT_CALLBACKS Callbacks + ) PURE; + + // Output callback interfaces are described separately. + STDMETHOD(GetOutputCallbacks)( + THIS_ + __out PDEBUG_OUTPUT_CALLBACKS* Callbacks + ) PURE; + STDMETHOD(SetOutputCallbacks)( + THIS_ + __in_opt PDEBUG_OUTPUT_CALLBACKS Callbacks + ) PURE; + // Output flags provide control over + // the distribution of output among clients. + // Output masks select which output streams + // should be sent to the output callbacks. + // Only Output calls with a mask that + // contains one of the output mask bits + // will be sent to the output callbacks. + // These methods are reentrant. + // If such access is not synchronized + // disruptions in output may occur. + STDMETHOD(GetOutputMask)( + THIS_ + __out PULONG Mask + ) PURE; + STDMETHOD(SetOutputMask)( + THIS_ + __in ULONG Mask + ) PURE; + // These methods allow access to another clients + // output mask. They are necessary for changing + // a clients output mask when it is + // waiting for events. These methods are reentrant + // and can be called from any thread. + STDMETHOD(GetOtherOutputMask)( + THIS_ + __in PDEBUG_CLIENT Client, + __out PULONG Mask + ) PURE; + STDMETHOD(SetOtherOutputMask)( + THIS_ + __in PDEBUG_CLIENT Client, + __in ULONG Mask + ) PURE; + // Control the width of an output line for + // commands which produce formatted output. + // This setting is just a suggestion. + STDMETHOD(GetOutputWidth)( + THIS_ + __out PULONG Columns + ) PURE; + STDMETHOD(SetOutputWidth)( + THIS_ + __in ULONG Columns + ) PURE; + // Some of the engines output commands produce + // multiple lines of output. A prefix can be + // set that the engine will automatically output + // for each line in that case, allowing a caller + // to control indentation or identifying marks. + // This is not a general setting for any output + // with a newline in it. Methods which use + // the line prefix are marked in their documentation. + STDMETHOD(GetOutputLinePrefix)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG PrefixSize + ) PURE; + STDMETHOD(SetOutputLinePrefix)( + THIS_ + __in_opt PCSTR Prefix + ) PURE; + + // Returns a string describing the machine + // and user this client represents. The + // specific content of the string varies + // with operating system. If the client is + // remotely connected some network information + // may also be present. + STDMETHOD(GetIdentity)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG IdentitySize + ) PURE; + // Format is a printf-like format string + // with one %s where the identity string should go. + STDMETHOD(OutputIdentity)( + THIS_ + __in ULONG OutputControl, + __in ULONG Flags, + __in PCSTR Format + ) PURE; + + // Event callbacks allow a client to + // receive notification about changes + // during the debug session. + STDMETHOD(GetEventCallbacks)( + THIS_ + __out PDEBUG_EVENT_CALLBACKS* Callbacks + ) PURE; + STDMETHOD(SetEventCallbacks)( + THIS_ + __in_opt PDEBUG_EVENT_CALLBACKS Callbacks + ) PURE; + + // The engine sometimes merges compatible callback + // requests to reduce callback overhead. This is + // most noticeable with output as small pieces of + // output are collected into larger groups to + // reduce the overall number of output callback calls. + // A client can use this method to force all pending + // callbacks to be delivered. This is rarely necessary. + STDMETHOD(FlushCallbacks)( + THIS + ) PURE; + + // IDebugClient2. + + // Functions similarly to WriteDumpFile with + // the addition of the ability to specify + // per-dump-format write control flags. + // Comment is not supported in all formats. + STDMETHOD(WriteDumpFile2)( + THIS_ + __in PCSTR DumpFile, + __in ULONG Qualifier, + __in ULONG FormatFlags, + __in_opt PCSTR Comment + ) PURE; + // Registers additional files of supporting information + // for a dump file open. This method must be called + // before OpenDumpFile is called. + // The files registered may be opened at the time + // this method is called but generally will not + // be used until OpenDumpFile is called. + STDMETHOD(AddDumpInformationFile)( + THIS_ + __in PCSTR InfoFile, + __in ULONG Type + ) PURE; + + // Requests that the remote process server shut down. + STDMETHOD(EndProcessServer)( + THIS_ + __in ULONG64 Server + ) PURE; + // Waits for a started process server to + // exit. Allows an application running a + // process server to monitor the process + // server so that it can tell when a remote + // client has asked for it to exit. + // Returns S_OK if the process server has + // shut down and S_FALSE for a timeout. + STDMETHOD(WaitForProcessServerEnd)( + THIS_ + __in ULONG Timeout + ) PURE; + + // Returns S_OK if the system is configured + // to allow kernel debugging. + STDMETHOD(IsKernelDebuggerEnabled)( + THIS + ) PURE; + + // Attempts to terminate the current process. + // Exit process events for the process may be generated. + STDMETHOD(TerminateCurrentProcess)( + THIS + ) PURE; + // Attempts to detach from the current process. + // This requires OS support for debugger detach. + STDMETHOD(DetachCurrentProcess)( + THIS + ) PURE; + // Removes the process from the debuggers process + // list without making any other changes. The process + // will still be marked as being debugged and will + // not run. This allows a debugger to be shut down + // and a new debugger attached without taking the + // process out of the debugged state. + // This is only supported on some system versions. + STDMETHOD(AbandonCurrentProcess)( + THIS + ) PURE; + + // IDebugClient3. + + STDMETHOD(GetRunningProcessSystemIdByExecutableNameWide)( + THIS_ + __in ULONG64 Server, + __in PCWSTR ExeName, + __in ULONG Flags, + __out PULONG Id + ) PURE; + STDMETHOD(GetRunningProcessDescriptionWide)( + THIS_ + __in ULONG64 Server, + __in ULONG SystemId, + __in ULONG Flags, + __out_ecount_opt(ExeNameSize) PWSTR ExeName, + __in ULONG ExeNameSize, + __out_opt PULONG ActualExeNameSize, + __out_ecount_opt(DescriptionSize) PWSTR Description, + __in ULONG DescriptionSize, + __out_opt PULONG ActualDescriptionSize + ) PURE; + + STDMETHOD(CreateProcessWide)( + THIS_ + __in ULONG64 Server, + __in PWSTR CommandLine, + __in ULONG CreateFlags + ) PURE; + STDMETHOD(CreateProcessAndAttachWide)( + THIS_ + __in ULONG64 Server, + __in_opt PWSTR CommandLine, + __in ULONG CreateFlags, + __in ULONG ProcessId, + __in ULONG AttachFlags + ) PURE; + + // IDebugClient4. + + // In the following methods both a filename and a file + // handle can be passed in. If a file handle is given + // the filename may be omitted, although providing it + // allows the debugger to properly report the name when + // queried. + // File handles cannot be used in remote calls. + STDMETHOD(OpenDumpFileWide)( + THIS_ + __in_opt PCWSTR FileName, + __in ULONG64 FileHandle + ) PURE; + STDMETHOD(WriteDumpFileWide)( + THIS_ + __in_opt PCWSTR FileName, + __in ULONG64 FileHandle, + __in ULONG Qualifier, + __in ULONG FormatFlags, + __in_opt PCWSTR Comment + ) PURE; + STDMETHOD(AddDumpInformationFileWide)( + THIS_ + __in_opt PCWSTR FileName, + __in ULONG64 FileHandle, + __in ULONG Type + ) PURE; + // These methods can be used to retrieve + // file information for all targets that + // involve files. + STDMETHOD(GetNumberDumpFiles)( + THIS_ + __out PULONG Number + ) PURE; + STDMETHOD(GetDumpFile)( + THIS_ + __in ULONG Index, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG NameSize, + __out_opt PULONG64 Handle, + __out PULONG Type + ) PURE; + STDMETHOD(GetDumpFileWide)( + THIS_ + __in ULONG Index, + __out_ecount_opt(BufferSize) PWSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG NameSize, + __out_opt PULONG64 Handle, + __out PULONG Type + ) PURE; +}; + +#undef INTERFACE +#define INTERFACE IDebugClient5 +DECLARE_INTERFACE_(IDebugClient5, IUnknown) +{ + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + __in REFIID InterfaceId, + __out PVOID* Interface + ) PURE; + STDMETHOD_(ULONG, AddRef)( + THIS + ) PURE; + STDMETHOD_(ULONG, Release)( + THIS + ) PURE; + + // IDebugClient. + + // The following set of methods start + // the different kinds of debuggees. + + // Begins a debug session using the kernel + // debugging protocol. This method selects + // the protocol as the debuggee communication + // mechanism but does not initiate the communication + // itself. + STDMETHOD(AttachKernel)( + THIS_ + __in ULONG Flags, + __in_opt PCSTR ConnectOptions + ) PURE; + STDMETHOD(GetKernelConnectionOptions)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG OptionsSize + ) PURE; + // Updates the connection options for a live + // kernel connection. This can only be used + // to modify parameters for the connection, not + // to switch to a completely different kind of + // connection. + // This method is reentrant. + STDMETHOD(SetKernelConnectionOptions)( + THIS_ + __in PCSTR Options + ) PURE; + + // Starts a process server for remote + // user-mode process control. + // The local process server is server zero. + STDMETHOD(StartProcessServer)( + THIS_ + __in ULONG Flags, + __in PCSTR Options, + __in_opt __reserved PVOID Reserved + ) PURE; + STDMETHOD(ConnectProcessServer)( + THIS_ + __in PCSTR RemoteOptions, + __out PULONG64 Server + ) PURE; + STDMETHOD(DisconnectProcessServer)( + THIS_ + __in ULONG64 Server + ) PURE; + + // Enumerates and describes processes + // accessible through the given process server. + STDMETHOD(GetRunningProcessSystemIds)( + THIS_ + __in ULONG64 Server, + __out_ecount_opt(Count) PULONG Ids, + __in ULONG Count, + __out_opt PULONG ActualCount + ) PURE; + STDMETHOD(GetRunningProcessSystemIdByExecutableName)( + THIS_ + __in ULONG64 Server, + __in PCSTR ExeName, + __in ULONG Flags, + __out PULONG Id + ) PURE; + STDMETHOD(GetRunningProcessDescription)( + THIS_ + __in ULONG64 Server, + __in ULONG SystemId, + __in ULONG Flags, + __out_ecount_opt(ExeNameSize) PSTR ExeName, + __in ULONG ExeNameSize, + __out_opt PULONG ActualExeNameSize, + __out_ecount_opt(DescriptionSize) PSTR Description, + __in ULONG DescriptionSize, + __out_opt PULONG ActualDescriptionSize + ) PURE; + + // Attaches to a running user-mode process. + STDMETHOD(AttachProcess)( + THIS_ + __in ULONG64 Server, + __in ULONG ProcessId, + __in ULONG AttachFlags + ) PURE; + // Creates a new user-mode process for debugging. + // CreateFlags are as given to Win32s CreateProcess. + // One of DEBUG_PROCESS or DEBUG_ONLY_THIS_PROCESS + // must be specified. + STDMETHOD(CreateProcess)( + THIS_ + __in ULONG64 Server, + __in PSTR CommandLine, + __in ULONG CreateFlags + ) PURE; + // Creates or attaches to a user-mode process, or both. + // If CommandLine is NULL this method operates as + // AttachProcess does. If ProcessId is zero it + // operates as CreateProcess does. If CommandLine is + // non-NULL and ProcessId is non-zero the method first + // starts a process with the given information but + // in a suspended state. The engine then attaches to + // the indicated process. Once the attach is successful + // the suspended process is resumed. This provides + // synchronization between the new process and the + // attachment. + STDMETHOD(CreateProcessAndAttach)( + THIS_ + __in ULONG64 Server, + __in_opt PSTR CommandLine, + __in ULONG CreateFlags, + __in ULONG ProcessId, + __in ULONG AttachFlags + ) PURE; + // Gets and sets process control flags. + STDMETHOD(GetProcessOptions)( + THIS_ + __out PULONG Options + ) PURE; + STDMETHOD(AddProcessOptions)( + THIS_ + __in ULONG Options + ) PURE; + STDMETHOD(RemoveProcessOptions)( + THIS_ + __in ULONG Options + ) PURE; + STDMETHOD(SetProcessOptions)( + THIS_ + __in ULONG Options + ) PURE; + + // Opens any kind of user- or kernel-mode dump file + // and begins a debug session with the information + // contained within it. + STDMETHOD(OpenDumpFile)( + THIS_ + __in PCSTR DumpFile + ) PURE; + // Writes a dump file from the current session information. + // The kind of dump file written is determined by the + // kind of session and the type qualifier given. + // For example, if the current session is a kernel + // debug session (DEBUG_CLASS_KERNEL) and the qualifier + // is DEBUG_DUMP_SMALL a small kernel dump will be written. + STDMETHOD(WriteDumpFile)( + THIS_ + __in PCSTR DumpFile, + __in ULONG Qualifier + ) PURE; + + // Indicates that a remote client is ready to + // begin participating in the current session. + // HistoryLimit gives a character limit on + // the amount of output history to be sent. + STDMETHOD(ConnectSession)( + THIS_ + __in ULONG Flags, + __in ULONG HistoryLimit + ) PURE; + // Indicates that the engine should start accepting + // remote connections. Options specifies connection types + // and their parameters. Supported strings are: + // npipe:Pipe=<Pipe name> + // tcp:Port=<IP port> + STDMETHOD(StartServer)( + THIS_ + __in PCSTR Options + ) PURE; + // List the servers running on the given machine. + // Uses the line prefix. + STDMETHOD(OutputServers)( + THIS_ + __in ULONG OutputControl, + __in PCSTR Machine, + __in ULONG Flags + ) PURE; + + // Attempts to terminate all processes in the debuggers list. + STDMETHOD(TerminateProcesses)( + THIS + ) PURE; + // Attempts to detach from all processes in the debuggers list. + // This requires OS support for debugger detach. + STDMETHOD(DetachProcesses)( + THIS + ) PURE; + // Stops the current debug session. If a process + // was created or attached an active EndSession can + // terminate or detach from it. + // If a kernel connection was opened it will be closed but the + // target machine is otherwise unaffected. + STDMETHOD(EndSession)( + THIS_ + __in ULONG Flags + ) PURE; + // If a process was started and ran to completion + // this method can be used to retrieve its exit code. + STDMETHOD(GetExitCode)( + THIS_ + __out PULONG Code + ) PURE; + + // Client event callbacks are called on the thread + // of the client. In order to give thread + // execution to the engine for callbacks all + // client threads should call DispatchCallbacks + // when they are idle. Callbacks are only + // received when a thread calls DispatchCallbacks + // or WaitForEvent. WaitForEvent can only be + // called by the thread that started the debug + // session so all other client threads should + // call DispatchCallbacks when possible. + // DispatchCallbacks returns when ExitDispatch is used + // to interrupt dispatch or when the timeout expires. + // DispatchCallbacks dispatches callbacks for all + // clients associated with the thread calling + // DispatchCallbacks. + // DispatchCallbacks returns S_FALSE when the + // timeout expires. + STDMETHOD(DispatchCallbacks)( + THIS_ + __in ULONG Timeout + ) PURE; + // ExitDispatch can be used to interrupt callback + // dispatch when a client thread is needed by the + // client. This method is reentrant and can + // be called from any thread. + STDMETHOD(ExitDispatch)( + THIS_ + __in PDEBUG_CLIENT Client + ) PURE; + + // Clients are specific to the thread that + // created them. Calls from other threads + // fail immediately. The CreateClient method + // is a notable exception; it allows creation + // of a new client for a new thread. + STDMETHOD(CreateClient)( + THIS_ + __out PDEBUG_CLIENT* Client + ) PURE; + + STDMETHOD(GetInputCallbacks)( + THIS_ + __out PDEBUG_INPUT_CALLBACKS* Callbacks + ) PURE; + STDMETHOD(SetInputCallbacks)( + THIS_ + __in_opt PDEBUG_INPUT_CALLBACKS Callbacks + ) PURE; + + // Output callback interfaces are described separately. + STDMETHOD(GetOutputCallbacks)( + THIS_ + __out PDEBUG_OUTPUT_CALLBACKS* Callbacks + ) PURE; + STDMETHOD(SetOutputCallbacks)( + THIS_ + __in_opt PDEBUG_OUTPUT_CALLBACKS Callbacks + ) PURE; + // Output flags provide control over + // the distribution of output among clients. + // Output masks select which output streams + // should be sent to the output callbacks. + // Only Output calls with a mask that + // contains one of the output mask bits + // will be sent to the output callbacks. + // These methods are reentrant. + // If such access is not synchronized + // disruptions in output may occur. + STDMETHOD(GetOutputMask)( + THIS_ + __out PULONG Mask + ) PURE; + STDMETHOD(SetOutputMask)( + THIS_ + __in ULONG Mask + ) PURE; + // These methods allow access to another clients + // output mask. They are necessary for changing + // a clients output mask when it is + // waiting for events. These methods are reentrant + // and can be called from any thread. + STDMETHOD(GetOtherOutputMask)( + THIS_ + __in PDEBUG_CLIENT Client, + __out PULONG Mask + ) PURE; + STDMETHOD(SetOtherOutputMask)( + THIS_ + __in PDEBUG_CLIENT Client, + __in ULONG Mask + ) PURE; + // Control the width of an output line for + // commands which produce formatted output. + // This setting is just a suggestion. + STDMETHOD(GetOutputWidth)( + THIS_ + __out PULONG Columns + ) PURE; + STDMETHOD(SetOutputWidth)( + THIS_ + __in ULONG Columns + ) PURE; + // Some of the engines output commands produce + // multiple lines of output. A prefix can be + // set that the engine will automatically output + // for each line in that case, allowing a caller + // to control indentation or identifying marks. + // This is not a general setting for any output + // with a newline in it. Methods which use + // the line prefix are marked in their documentation. + STDMETHOD(GetOutputLinePrefix)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG PrefixSize + ) PURE; + STDMETHOD(SetOutputLinePrefix)( + THIS_ + __in_opt PCSTR Prefix + ) PURE; + + // Returns a string describing the machine + // and user this client represents. The + // specific content of the string varies + // with operating system. If the client is + // remotely connected some network information + // may also be present. + STDMETHOD(GetIdentity)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG IdentitySize + ) PURE; + // Format is a printf-like format string + // with one %s where the identity string should go. + STDMETHOD(OutputIdentity)( + THIS_ + __in ULONG OutputControl, + __in ULONG Flags, + __in PCSTR Format + ) PURE; + + // Event callbacks allow a client to + // receive notification about changes + // during the debug session. + STDMETHOD(GetEventCallbacks)( + THIS_ + __out PDEBUG_EVENT_CALLBACKS* Callbacks + ) PURE; + STDMETHOD(SetEventCallbacks)( + THIS_ + __in_opt PDEBUG_EVENT_CALLBACKS Callbacks + ) PURE; + + // The engine sometimes merges compatible callback + // requests to reduce callback overhead. This is + // most noticeable with output as small pieces of + // output are collected into larger groups to + // reduce the overall number of output callback calls. + // A client can use this method to force all pending + // callbacks to be delivered. This is rarely necessary. + STDMETHOD(FlushCallbacks)( + THIS + ) PURE; + + // IDebugClient2. + + // Functions similarly to WriteDumpFile with + // the addition of the ability to specify + // per-dump-format write control flags. + // Comment is not supported in all formats. + STDMETHOD(WriteDumpFile2)( + THIS_ + __in PCSTR DumpFile, + __in ULONG Qualifier, + __in ULONG FormatFlags, + __in_opt PCSTR Comment + ) PURE; + // Registers additional files of supporting information + // for a dump file open. This method must be called + // before OpenDumpFile is called. + // The files registered may be opened at the time + // this method is called but generally will not + // be used until OpenDumpFile is called. + STDMETHOD(AddDumpInformationFile)( + THIS_ + __in PCSTR InfoFile, + __in ULONG Type + ) PURE; + + // Requests that the remote process server shut down. + STDMETHOD(EndProcessServer)( + THIS_ + __in ULONG64 Server + ) PURE; + // Waits for a started process server to + // exit. Allows an application running a + // process server to monitor the process + // server so that it can tell when a remote + // client has asked for it to exit. + // Returns S_OK if the process server has + // shut down and S_FALSE for a timeout. + STDMETHOD(WaitForProcessServerEnd)( + THIS_ + __in ULONG Timeout + ) PURE; + + // Returns S_OK if the system is configured + // to allow kernel debugging. + STDMETHOD(IsKernelDebuggerEnabled)( + THIS + ) PURE; + + // Attempts to terminate the current process. + // Exit process events for the process may be generated. + STDMETHOD(TerminateCurrentProcess)( + THIS + ) PURE; + // Attempts to detach from the current process. + // This requires OS support for debugger detach. + STDMETHOD(DetachCurrentProcess)( + THIS + ) PURE; + // Removes the process from the debuggers process + // list without making any other changes. The process + // will still be marked as being debugged and will + // not run. This allows a debugger to be shut down + // and a new debugger attached without taking the + // process out of the debugged state. + // This is only supported on some system versions. + STDMETHOD(AbandonCurrentProcess)( + THIS + ) PURE; + + // IDebugClient3. + + STDMETHOD(GetRunningProcessSystemIdByExecutableNameWide)( + THIS_ + __in ULONG64 Server, + __in PCWSTR ExeName, + __in ULONG Flags, + __out PULONG Id + ) PURE; + STDMETHOD(GetRunningProcessDescriptionWide)( + THIS_ + __in ULONG64 Server, + __in ULONG SystemId, + __in ULONG Flags, + __out_ecount_opt(ExeNameSize) PWSTR ExeName, + __in ULONG ExeNameSize, + __out_opt PULONG ActualExeNameSize, + __out_ecount_opt(DescriptionSize) PWSTR Description, + __in ULONG DescriptionSize, + __out_opt PULONG ActualDescriptionSize + ) PURE; + + STDMETHOD(CreateProcessWide)( + THIS_ + __in ULONG64 Server, + __in PWSTR CommandLine, + __in ULONG CreateFlags + ) PURE; + STDMETHOD(CreateProcessAndAttachWide)( + THIS_ + __in ULONG64 Server, + __in_opt PWSTR CommandLine, + __in ULONG CreateFlags, + __in ULONG ProcessId, + __in ULONG AttachFlags + ) PURE; + + // IDebugClient4. + + // In the following methods both a filename and a file + // handle can be passed in. If a file handle is given + // the filename may be omitted, although providing it + // allows the debugger to properly report the name when + // queried. + // File handles cannot be used in remote calls. + STDMETHOD(OpenDumpFileWide)( + THIS_ + __in_opt PCWSTR FileName, + __in ULONG64 FileHandle + ) PURE; + STDMETHOD(WriteDumpFileWide)( + THIS_ + __in_opt PCWSTR FileName, + __in ULONG64 FileHandle, + __in ULONG Qualifier, + __in ULONG FormatFlags, + __in_opt PCWSTR Comment + ) PURE; + STDMETHOD(AddDumpInformationFileWide)( + THIS_ + __in_opt PCWSTR FileName, + __in ULONG64 FileHandle, + __in ULONG Type + ) PURE; + // These methods can be used to retrieve + // file information for all targets that + // involve files. + STDMETHOD(GetNumberDumpFiles)( + THIS_ + __out PULONG Number + ) PURE; + STDMETHOD(GetDumpFile)( + THIS_ + __in ULONG Index, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG NameSize, + __out_opt PULONG64 Handle, + __out PULONG Type + ) PURE; + STDMETHOD(GetDumpFileWide)( + THIS_ + __in ULONG Index, + __out_ecount_opt(BufferSize) PWSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG NameSize, + __out_opt PULONG64 Handle, + __out PULONG Type + ) PURE; + + // IDebugClient5. + + STDMETHOD(AttachKernelWide)( + THIS_ + __in ULONG Flags, + __in_opt PCWSTR ConnectOptions + ) PURE; + STDMETHOD(GetKernelConnectionOptionsWide)( + THIS_ + __out_ecount_opt(BufferSize) PWSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG OptionsSize + ) PURE; + STDMETHOD(SetKernelConnectionOptionsWide)( + THIS_ + __in PCWSTR Options + ) PURE; + + STDMETHOD(StartProcessServerWide)( + THIS_ + __in ULONG Flags, + __in PCWSTR Options, + __in_opt __reserved PVOID Reserved + ) PURE; + STDMETHOD(ConnectProcessServerWide)( + THIS_ + __in PCWSTR RemoteOptions, + __out PULONG64 Server + ) PURE; + + STDMETHOD(StartServerWide)( + THIS_ + __in PCWSTR Options + ) PURE; + STDMETHOD(OutputServersWide)( + THIS_ + __in ULONG OutputControl, + __in PCWSTR Machine, + __in ULONG Flags + ) PURE; + + STDMETHOD(GetOutputCallbacksWide)( + THIS_ + __out PDEBUG_OUTPUT_CALLBACKS_WIDE* Callbacks + ) PURE; + STDMETHOD(SetOutputCallbacksWide)( + THIS_ + __in PDEBUG_OUTPUT_CALLBACKS_WIDE Callbacks + ) PURE; + STDMETHOD(GetOutputLinePrefixWide)( + THIS_ + __out_ecount_opt(BufferSize) PWSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG PrefixSize + ) PURE; + STDMETHOD(SetOutputLinePrefixWide)( + THIS_ + __in_opt PCWSTR Prefix + ) PURE; + + STDMETHOD(GetIdentityWide)( + THIS_ + __out_ecount_opt(BufferSize) PWSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG IdentitySize + ) PURE; + STDMETHOD(OutputIdentityWide)( + THIS_ + __in ULONG OutputControl, + __in ULONG Flags, + __in PCWSTR Format + ) PURE; + + STDMETHOD(GetEventCallbacksWide)( + THIS_ + __out PDEBUG_EVENT_CALLBACKS_WIDE* Callbacks + ) PURE; + STDMETHOD(SetEventCallbacksWide)( + THIS_ + __in PDEBUG_EVENT_CALLBACKS_WIDE Callbacks + ) PURE; + + STDMETHOD(CreateProcess2)( + THIS_ + __in ULONG64 Server, + __in PSTR CommandLine, + __in_bcount(OptionsBufferSize) PVOID OptionsBuffer, + __in ULONG OptionsBufferSize, + __in_opt PCSTR InitialDirectory, + __in_opt PCSTR Environment + ) PURE; + STDMETHOD(CreateProcess2Wide)( + THIS_ + __in ULONG64 Server, + __in PWSTR CommandLine, + __in_bcount(OptionsBufferSize) PVOID OptionsBuffer, + __in ULONG OptionsBufferSize, + __in_opt PCWSTR InitialDirectory, + __in_opt PCWSTR Environment + ) PURE; + STDMETHOD(CreateProcessAndAttach2)( + THIS_ + __in ULONG64 Server, + __in_opt PSTR CommandLine, + __in_bcount(OptionsBufferSize) PVOID OptionsBuffer, + __in ULONG OptionsBufferSize, + __in_opt PCSTR InitialDirectory, + __in_opt PCSTR Environment, + __in ULONG ProcessId, + __in ULONG AttachFlags + ) PURE; + STDMETHOD(CreateProcessAndAttach2Wide)( + THIS_ + __in ULONG64 Server, + __in_opt PWSTR CommandLine, + __in_bcount(OptionsBufferSize) PVOID OptionsBuffer, + __in ULONG OptionsBufferSize, + __in_opt PCWSTR InitialDirectory, + __in_opt PCWSTR Environment, + __in ULONG ProcessId, + __in ULONG AttachFlags + ) PURE; + + // Helpers for saving and restoring the + // current output line prefix. + STDMETHOD(PushOutputLinePrefix)( + THIS_ + __in_opt PCSTR NewPrefix, + __out PULONG64 Handle + ) PURE; + STDMETHOD(PushOutputLinePrefixWide)( + THIS_ + __in_opt PCWSTR NewPrefix, + __out PULONG64 Handle + ) PURE; + STDMETHOD(PopOutputLinePrefix)( + THIS_ + __in ULONG64 Handle + ) PURE; + + // Queries to determine if any clients + // could potentially respond to the given callback. + STDMETHOD(GetNumberInputCallbacks)( + THIS_ + __out PULONG Count + ) PURE; + STDMETHOD(GetNumberOutputCallbacks)( + THIS_ + __out PULONG Count + ) PURE; + STDMETHOD(GetNumberEventCallbacks)( + THIS_ + __in ULONG EventFlags, + __out PULONG Count + ) PURE; + + // Control over locking the session against + // undesired quits. The quit lock string + // cannot be retrieved from a secure session. + STDMETHOD(GetQuitLockString)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG StringSize + ) PURE; + STDMETHOD(SetQuitLockString)( + THIS_ + __in PCSTR String + ) PURE; + STDMETHOD(GetQuitLockStringWide)( + THIS_ + __out_ecount_opt(BufferSize) PWSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG StringSize + ) PURE; + STDMETHOD(SetQuitLockStringWide)( + THIS_ + __in PCWSTR String + ) PURE; +}; + +//---------------------------------------------------------------------------- +// +// IDebugControl. +// +//---------------------------------------------------------------------------- + +// Execution status codes used for waiting, +// for returning current status and for +// event method return values. +#define DEBUG_STATUS_NO_CHANGE 0 +#define DEBUG_STATUS_GO 1 +#define DEBUG_STATUS_GO_HANDLED 2 +#define DEBUG_STATUS_GO_NOT_HANDLED 3 +#define DEBUG_STATUS_STEP_OVER 4 +#define DEBUG_STATUS_STEP_INTO 5 +#define DEBUG_STATUS_BREAK 6 +#define DEBUG_STATUS_NO_DEBUGGEE 7 +#define DEBUG_STATUS_STEP_BRANCH 8 +#define DEBUG_STATUS_IGNORE_EVENT 9 +#define DEBUG_STATUS_RESTART_REQUESTED 10 +#define DEBUG_STATUS_REVERSE_GO 11 +#define DEBUG_STATUS_REVERSE_STEP_BRANCH 12 +#define DEBUG_STATUS_REVERSE_STEP_OVER 13 +#define DEBUG_STATUS_REVERSE_STEP_INTO 14 + +#define DEBUG_STATUS_MASK 0xf + +// This bit is added in DEBUG_CES_EXECUTION_STATUS +// notifications when the engines execution status +// is changing due to operations performed during +// a wait, such as making synchronous callbacks. If +// the bit is not set the execution status is changing +// due to a wait being satisfied. +#define DEBUG_STATUS_INSIDE_WAIT 0x100000000 +// This bit is added in DEBUG_CES_EXECUTION_STATUS +// notifications when the engines execution status +// update is coming after a wait has timed-out. +// It indicates that the execution status change +// was not due to an actual event. +#define DEBUG_STATUS_WAIT_TIMEOUT 0x200000000 + +// Output control flags. +// Output generated by methods called by this +// client will be sent only to this clients +// output callbacks. +#define DEBUG_OUTCTL_THIS_CLIENT 0x00000000 +// Output will be sent to all clients. +#define DEBUG_OUTCTL_ALL_CLIENTS 0x00000001 +// Output will be sent to all clients except +// the client generating the output. +#define DEBUG_OUTCTL_ALL_OTHER_CLIENTS 0x00000002 +// Output will be discarded immediately and will not +// be logged or sent to callbacks. +#define DEBUG_OUTCTL_IGNORE 0x00000003 +// Output will be logged but not sent to callbacks. +#define DEBUG_OUTCTL_LOG_ONLY 0x00000004 +// All send control bits. +#define DEBUG_OUTCTL_SEND_MASK 0x00000007 +// Do not place output from this client in +// the global log file. +#define DEBUG_OUTCTL_NOT_LOGGED 0x00000008 +// Send output to clients regardless of whether the +// mask allows it or not. +#define DEBUG_OUTCTL_OVERRIDE_MASK 0x00000010 +// Text is markup instead of plain text. +#define DEBUG_OUTCTL_DML 0x00000020 + +// Special values which mean leave the output settings +// unchanged. +#define DEBUG_OUTCTL_AMBIENT_DML 0xfffffffe +#define DEBUG_OUTCTL_AMBIENT_TEXT 0xffffffff + +// Old ambient flag which maps to text. +#define DEBUG_OUTCTL_AMBIENT DEBUG_OUTCTL_AMBIENT_TEXT + +// Interrupt types. +// Force a break in if the debuggee is running. +#define DEBUG_INTERRUPT_ACTIVE 0 +// Notify but do not force a break in. +#define DEBUG_INTERRUPT_PASSIVE 1 +// Try and get the current engine operation to +// complete so that the engine will be available +// again. If no wait is active this is the same +// as a passive interrupt. If a wait is active +// this will try to cause the wait to fail without +// breaking in to the debuggee. There is +// no guarantee that issuing an exit interrupt +// will cause the engine to become available +// as not all operations are arbitrarily +// interruptible. +#define DEBUG_INTERRUPT_EXIT 2 + +// OutputCurrentState flags. These flags +// allow a particular type of information +// to be displayed but do not guarantee +// that it will be displayed. Other global +// settings may override these flags or +// the particular state may not be available. +// For example, source line information may +// not be present so source line information +// may not be displayed. +#define DEBUG_CURRENT_DEFAULT 0x0000000f +#define DEBUG_CURRENT_SYMBOL 0x00000001 +#define DEBUG_CURRENT_DISASM 0x00000002 +#define DEBUG_CURRENT_REGISTERS 0x00000004 +#define DEBUG_CURRENT_SOURCE_LINE 0x00000008 + +// +// Disassemble flags. +// + +// Compute the effective address from current register +// information and display it. +#define DEBUG_DISASM_EFFECTIVE_ADDRESS 0x00000001 +// If the current disassembly offset has an exact +// symbol match output the symbol. +#define DEBUG_DISASM_MATCHING_SYMBOLS 0x00000002 +// Output the source line number for each disassembly offset. +#define DEBUG_DISASM_SOURCE_LINE_NUMBER 0x00000004 +// Output the source file name (no path) for each disassembly offset. +#define DEBUG_DISASM_SOURCE_FILE_NAME 0x00000008 + +// Code interpretation levels for stepping +// and other operations. +#define DEBUG_LEVEL_SOURCE 0 +#define DEBUG_LEVEL_ASSEMBLY 1 + +// Engine control flags. +#define DEBUG_ENGOPT_IGNORE_DBGHELP_VERSION 0x00000001 +#define DEBUG_ENGOPT_IGNORE_EXTENSION_VERSIONS 0x00000002 +// If neither allow nor disallow is specified +// the engine will pick one based on what kind +// of debugging is going on. +#define DEBUG_ENGOPT_ALLOW_NETWORK_PATHS 0x00000004 +#define DEBUG_ENGOPT_DISALLOW_NETWORK_PATHS 0x00000008 +#define DEBUG_ENGOPT_NETWORK_PATHS (0x00000004 | 0x00000008) +// Ignore loader-generated first-chance exceptions. +#define DEBUG_ENGOPT_IGNORE_LOADER_EXCEPTIONS 0x00000010 +// Break in on a debuggees initial event. In user-mode +// this will break at the initial system breakpoint +// for every created process. In kernel-mode it +// will attempt break in on the target at the first +// WaitForEvent. +#define DEBUG_ENGOPT_INITIAL_BREAK 0x00000020 +// Break in on the first module load for a debuggee. +#define DEBUG_ENGOPT_INITIAL_MODULE_BREAK 0x00000040 +// Break in on a debuggees final event. In user-mode +// this will break on process exit for every process. +// In kernel-mode it currently does nothing. +#define DEBUG_ENGOPT_FINAL_BREAK 0x00000080 +// By default Execute will repeat the last command +// if it is given an empty string. The flags to +// Execute can override this behavior for a single +// command or this engine option can be used to +// change the default globally. +#define DEBUG_ENGOPT_NO_EXECUTE_REPEAT 0x00000100 +// Disable places in the engine that have fallback +// code when presented with incomplete information. +// 1. Fails minidump module loads unless matching +// executables can be mapped. +#define DEBUG_ENGOPT_FAIL_INCOMPLETE_INFORMATION 0x00000200 +// Allow the debugger to manipulate page protections +// in order to insert code breakpoints on pages that +// do not have write access. This option is not on +// by default as it allows breakpoints to be set +// in potentially hazardous memory areas. +#define DEBUG_ENGOPT_ALLOW_READ_ONLY_BREAKPOINTS 0x00000400 +// When using a software (bp/bu) breakpoint in code +// that will be executed by multiple threads it is +// possible for breakpoint management to cause the +// breakpoint to be missed or for spurious single-step +// exceptions to be generated. This flag suspends +// all but the active thread when doing breakpoint +// management and thereby avoids multithreading +// problems. Care must be taken when using it, though, +// as the suspension of threads can cause deadlocks +// if the suspended threads are holding resources that +// the active thread needs. Additionally, there +// are still rare situations where problems may +// occur, but setting this flag corrects nearly +// all multithreading issues with software breakpoints. +// Thread-restricted stepping and execution supersedes +// this flags effect. +// This flag is ignored in kernel sessions as there +// is no way to restrict processor execution. +#define DEBUG_ENGOPT_SYNCHRONIZE_BREAKPOINTS 0x00000800 +// Disallows executing shell commands through the +// engine with .shell (!!). +#define DEBUG_ENGOPT_DISALLOW_SHELL_COMMANDS 0x00001000 +// Turns on "quiet mode", a somewhat less verbose mode +// of operation supported in the debuggers that were +// superseded by dbgeng.dll. This equates to the KDQUIET +// environment variable. +#define DEBUG_ENGOPT_KD_QUIET_MODE 0x00002000 +// Disables managed code debugging support in the engine. +// If managed support is already in use this flag has no effect. +#define DEBUG_ENGOPT_DISABLE_MANAGED_SUPPORT 0x00004000 +// Disables symbol loading for all modules created +// after this flag is set. +#define DEBUG_ENGOPT_DISABLE_MODULE_SYMBOL_LOAD 0x00008000 +// Disables execution commands. +#define DEBUG_ENGOPT_DISABLE_EXECUTION_COMMANDS 0x00010000 +// Disallows mapping of image files from disk for any use. +// For example, this disallows image mapping for memory +// content when debugging minidumps. +// Does not affect existing mappings, only future attempts. +#define DEBUG_ENGOPT_DISALLOW_IMAGE_FILE_MAPPING 0x00020000 +// Requests that dbgeng run DML-enhanced versions of commands +// and operations by default. +#define DEBUG_ENGOPT_PREFER_DML 0x00040000 +#define DEBUG_ENGOPT_ALL 0x0007FFFF + +// General unspecified ID constant. +#define DEBUG_ANY_ID 0xffffffff + +typedef struct _DEBUG_STACK_FRAME +{ + ULONG64 InstructionOffset; + ULONG64 ReturnOffset; + ULONG64 FrameOffset; + ULONG64 StackOffset; + ULONG64 FuncTableEntry; + ULONG64 Params[4]; + ULONG64 Reserved[6]; + BOOL Virtual; + ULONG FrameNumber; +} DEBUG_STACK_FRAME, *PDEBUG_STACK_FRAME; + +// OutputStackTrace flags. +// Display a small number of arguments for each call. +// These may or may not be the actual arguments depending +// on the architecture, particular function and +// point during the execution of the function. +// If the current code level is assembly arguments +// are dumped as hex values. If the code level is +// source the engine attempts to provide symbolic +// argument information. +#define DEBUG_STACK_ARGUMENTS 0x00000001 +// Displays information about the functions +// frame such as __stdcall arguments, FPO +// information and whatever else is available. +#define DEBUG_STACK_FUNCTION_INFO 0x00000002 +// Displays source line information for each +// frame of the stack trace. +#define DEBUG_STACK_SOURCE_LINE 0x00000004 +// Show return, previous frame and other relevant address +// values for each frame. +#define DEBUG_STACK_FRAME_ADDRESSES 0x00000008 +// Show column names. +#define DEBUG_STACK_COLUMN_NAMES 0x00000010 +// Show non-volatile register context for each +// frame. This is only meaningful for some platforms. +#define DEBUG_STACK_NONVOLATILE_REGISTERS 0x00000020 +// Show frame numbers +#define DEBUG_STACK_FRAME_NUMBERS 0x00000040 +// Show typed source parameters. +#define DEBUG_STACK_PARAMETERS 0x00000080 +// Show just return address in stack frame addresses. +#define DEBUG_STACK_FRAME_ADDRESSES_RA_ONLY 0x00000100 +// Show frame-to-frame memory usage. +#define DEBUG_STACK_FRAME_MEMORY_USAGE 0x00000200 +// Show typed source parameters one to a line. +#define DEBUG_STACK_PARAMETERS_NEWLINE 0x00000400 +// Produce stack output enhanced with DML content. +#define DEBUG_STACK_DML 0x00000800 + +// Classes of debuggee. Each class +// has different qualifiers for specific +// kinds of debuggees. +#define DEBUG_CLASS_UNINITIALIZED 0 +#define DEBUG_CLASS_KERNEL 1 +#define DEBUG_CLASS_USER_WINDOWS 2 +#define DEBUG_CLASS_IMAGE_FILE 3 + +// Generic dump types. These can be used +// with either user or kernel sessions. +// Session-type-specific aliases are also +// provided. +#define DEBUG_DUMP_SMALL 1024 +#define DEBUG_DUMP_DEFAULT 1025 +#define DEBUG_DUMP_FULL 1026 +#define DEBUG_DUMP_IMAGE_FILE 1027 +#define DEBUG_DUMP_TRACE_LOG 1028 +#define DEBUG_DUMP_WINDOWS_CE 1029 + +// Specific types of kernel debuggees. +#define DEBUG_KERNEL_CONNECTION 0 +#define DEBUG_KERNEL_LOCAL 1 +#define DEBUG_KERNEL_EXDI_DRIVER 2 +#define DEBUG_KERNEL_IDNA 3 + +#define DEBUG_KERNEL_SMALL_DUMP DEBUG_DUMP_SMALL +#define DEBUG_KERNEL_DUMP DEBUG_DUMP_DEFAULT +#define DEBUG_KERNEL_FULL_DUMP DEBUG_DUMP_FULL + +#define DEBUG_KERNEL_TRACE_LOG DEBUG_DUMP_TRACE_LOG + +// Specific types of Windows user debuggees. +#define DEBUG_USER_WINDOWS_PROCESS 0 +#define DEBUG_USER_WINDOWS_PROCESS_SERVER 1 +#define DEBUG_USER_WINDOWS_IDNA 2 +#define DEBUG_USER_WINDOWS_SMALL_DUMP DEBUG_DUMP_SMALL +#define DEBUG_USER_WINDOWS_DUMP DEBUG_DUMP_DEFAULT +#define DEBUG_USER_WINDOWS_DUMP_WINDOWS_CE DEBUG_DUMP_WINDOWS_CE + +// Extension flags. +#define DEBUG_EXTENSION_AT_ENGINE 0x00000000 + +// Execute and ExecuteCommandFile flags. +// These flags only apply to the command +// text itself; output from the executed +// command is controlled by the output +// control parameter. +// Default execution. Command is logged +// but not output. +#define DEBUG_EXECUTE_DEFAULT 0x00000000 +// Echo commands during execution. In +// ExecuteCommandFile also echoes the prompt +// for each line of the file. +#define DEBUG_EXECUTE_ECHO 0x00000001 +// Do not log or output commands during execution. +// Overridden by DEBUG_EXECUTE_ECHO. +#define DEBUG_EXECUTE_NOT_LOGGED 0x00000002 +// If this flag is not set an empty string +// to Execute will repeat the last Execute +// string. +#define DEBUG_EXECUTE_NO_REPEAT 0x00000004 + +// Specific event filter types. Some event +// filters have optional arguments to further +// qualify their operation. +#define DEBUG_FILTER_CREATE_THREAD 0x00000000 +#define DEBUG_FILTER_EXIT_THREAD 0x00000001 +#define DEBUG_FILTER_CREATE_PROCESS 0x00000002 +#define DEBUG_FILTER_EXIT_PROCESS 0x00000003 +// Argument is the name of a module to break on. +#define DEBUG_FILTER_LOAD_MODULE 0x00000004 +// Argument is the base address of a specific module to break on. +#define DEBUG_FILTER_UNLOAD_MODULE 0x00000005 +#define DEBUG_FILTER_SYSTEM_ERROR 0x00000006 +// Initial breakpoint and initial module load are one-shot +// events that are triggered at the appropriate points in +// the beginning of a session. Their commands are executed +// and then further processing is controlled by the normal +// exception and load module filters. +#define DEBUG_FILTER_INITIAL_BREAKPOINT 0x00000007 +#define DEBUG_FILTER_INITIAL_MODULE_LOAD 0x00000008 +// The debug output filter allows the debugger to stop +// when output is produced so that the code causing +// output can be tracked down or synchronized with. +// This filter is not supported for live dual-machine +// kernel debugging. +#define DEBUG_FILTER_DEBUGGEE_OUTPUT 0x00000009 + +// Event filter execution options. +// Break in always. +#define DEBUG_FILTER_BREAK 0x00000000 +// Break in on second-chance exceptions. For events +// that are not exceptions this is the same as BREAK. +#define DEBUG_FILTER_SECOND_CHANCE_BREAK 0x00000001 +// Output a message about the event but continue. +#define DEBUG_FILTER_OUTPUT 0x00000002 +// Continue the event. +#define DEBUG_FILTER_IGNORE 0x00000003 +// Used to remove general exception filters. +#define DEBUG_FILTER_REMOVE 0x00000004 + +// Event filter continuation options. These options are +// only used when DEBUG_STATUS_GO is used to continue +// execution. If a specific go status such as +// DEBUG_STATUS_GO_NOT_HANDLED is used it controls +// the continuation. +#define DEBUG_FILTER_GO_HANDLED 0x00000000 +#define DEBUG_FILTER_GO_NOT_HANDLED 0x00000001 + +// Specific event filter settings. +typedef struct _DEBUG_SPECIFIC_FILTER_PARAMETERS +{ + ULONG ExecutionOption; + ULONG ContinueOption; + ULONG TextSize; + ULONG CommandSize; + // If ArgumentSize is zero this filter does + // not have an argument. An empty argument for + // a filter which does have an argument will take + // one byte for the terminator. + ULONG ArgumentSize; +} DEBUG_SPECIFIC_FILTER_PARAMETERS, *PDEBUG_SPECIFIC_FILTER_PARAMETERS; + +// Exception event filter settings. +typedef struct _DEBUG_EXCEPTION_FILTER_PARAMETERS +{ + ULONG ExecutionOption; + ULONG ContinueOption; + ULONG TextSize; + ULONG CommandSize; + ULONG SecondCommandSize; + ULONG ExceptionCode; +} DEBUG_EXCEPTION_FILTER_PARAMETERS, *PDEBUG_EXCEPTION_FILTER_PARAMETERS; + +// Wait flags. +#define DEBUG_WAIT_DEFAULT 0x00000000 + +// Last event information structures. +typedef struct _DEBUG_LAST_EVENT_INFO_BREAKPOINT +{ + ULONG Id; +} DEBUG_LAST_EVENT_INFO_BREAKPOINT, *PDEBUG_LAST_EVENT_INFO_BREAKPOINT; + +typedef struct _DEBUG_LAST_EVENT_INFO_EXCEPTION +{ + EXCEPTION_RECORD64 ExceptionRecord; + ULONG FirstChance; +} DEBUG_LAST_EVENT_INFO_EXCEPTION, *PDEBUG_LAST_EVENT_INFO_EXCEPTION; + +typedef struct _DEBUG_LAST_EVENT_INFO_EXIT_THREAD +{ + ULONG ExitCode; +} DEBUG_LAST_EVENT_INFO_EXIT_THREAD, *PDEBUG_LAST_EVENT_INFO_EXIT_THREAD; + +typedef struct _DEBUG_LAST_EVENT_INFO_EXIT_PROCESS +{ + ULONG ExitCode; +} DEBUG_LAST_EVENT_INFO_EXIT_PROCESS, *PDEBUG_LAST_EVENT_INFO_EXIT_PROCESS; + +typedef struct _DEBUG_LAST_EVENT_INFO_LOAD_MODULE +{ + ULONG64 Base; +} DEBUG_LAST_EVENT_INFO_LOAD_MODULE, *PDEBUG_LAST_EVENT_INFO_LOAD_MODULE; + +typedef struct _DEBUG_LAST_EVENT_INFO_UNLOAD_MODULE +{ + ULONG64 Base; +} DEBUG_LAST_EVENT_INFO_UNLOAD_MODULE, *PDEBUG_LAST_EVENT_INFO_UNLOAD_MODULE; + +typedef struct _DEBUG_LAST_EVENT_INFO_SYSTEM_ERROR +{ + ULONG Error; + ULONG Level; +} DEBUG_LAST_EVENT_INFO_SYSTEM_ERROR, *PDEBUG_LAST_EVENT_INFO_SYSTEM_ERROR; + +// DEBUG_VALUE types. +#define DEBUG_VALUE_INVALID 0 +#define DEBUG_VALUE_INT8 1 +#define DEBUG_VALUE_INT16 2 +#define DEBUG_VALUE_INT32 3 +#define DEBUG_VALUE_INT64 4 +#define DEBUG_VALUE_FLOAT32 5 +#define DEBUG_VALUE_FLOAT64 6 +#define DEBUG_VALUE_FLOAT80 7 +#define DEBUG_VALUE_FLOAT82 8 +#define DEBUG_VALUE_FLOAT128 9 +#define DEBUG_VALUE_VECTOR64 10 +#define DEBUG_VALUE_VECTOR128 11 +// Count of type indices. +#define DEBUG_VALUE_TYPES 12 + +#if defined(_MSC_VER) +#if _MSC_VER >= 800 +#if _MSC_VER >= 1200 +#pragma warning(push) +#endif +#pragma warning(disable:4201) /* Nameless struct/union */ +#endif +#endif + +// We want the DEBUG_VALUE structure to have 8-byte alignment +// and be 32 bytes total. This is tricky because the compiler +// wants to pad the union of values out to a even 8-byte multiple, +// pushing the type out too far. We can't use 4-packing because +// then the 8-byte alignment requirement is lost, so instead +// we shrink the union to 24 bytes and have a reserved field +// before the type field. The same amount of space is available +// and everybody's happy, but the structure is somewhat unusual. + +typedef struct _DEBUG_VALUE +{ + union + { + UCHAR I8; + USHORT I16; + ULONG I32; + struct + { + // Extra NAT indicator for IA64 + // integer registers. NAT will + // always be false for other CPUs. + ULONG64 I64; + BOOL Nat; + }; + float F32; + double F64; + UCHAR F80Bytes[10]; + UCHAR F82Bytes[11]; + UCHAR F128Bytes[16]; + // Vector interpretations. The actual number + // of valid elements depends on the vector length. + UCHAR VI8[16]; + USHORT VI16[8]; + ULONG VI32[4]; + ULONG64 VI64[2]; + float VF32[4]; + double VF64[2]; + struct + { + ULONG LowPart; + ULONG HighPart; + } I64Parts32; + struct + { + ULONG64 LowPart; + LONG64 HighPart; + } F128Parts64; + // Allows raw byte access to content. Array + // can be indexed for as much data as Type + // describes. This array also serves to pad + // the structure out to 32 bytes and reserves + // space for future members. + UCHAR RawBytes[24]; + }; + ULONG TailOfRawBytes; + ULONG Type; +} DEBUG_VALUE, *PDEBUG_VALUE; + +#if defined(_MSC_VER) +#if _MSC_VER >= 800 +#if _MSC_VER >= 1200 +#pragma warning(pop) +#else +#pragma warning(default:4201) /* Nameless struct/union */ +#endif +#endif +#endif + +#undef INTERFACE +#define INTERFACE IDebugControl +DECLARE_INTERFACE_(IDebugControl, IUnknown) +{ + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + __in REFIID InterfaceId, + __out PVOID* Interface + ) PURE; + STDMETHOD_(ULONG, AddRef)( + THIS + ) PURE; + STDMETHOD_(ULONG, Release)( + THIS + ) PURE; + + // IDebugControl. + + // Checks for a user interrupt, such a Ctrl-C + // or stop button. + // This method is reentrant. + STDMETHOD(GetInterrupt)( + THIS + ) PURE; + // Registers a user interrupt. + // This method is reentrant. + STDMETHOD(SetInterrupt)( + THIS_ + __in ULONG Flags + ) PURE; + // Interrupting a user-mode process requires + // access to some system resources that the + // process may hold itself, preventing the + // interrupt from occurring. The engine + // will time-out pending interrupt requests + // and simulate an interrupt if necessary. + // These methods control the interrupt timeout. + STDMETHOD(GetInterruptTimeout)( + THIS_ + __out PULONG Seconds + ) PURE; + STDMETHOD(SetInterruptTimeout)( + THIS_ + __in ULONG Seconds + ) PURE; + + STDMETHOD(GetLogFile)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG FileSize, + __out PBOOL Append + ) PURE; + // Opens a log file which collects all + // output. Output from every client except + // those that explicitly disable logging + // goes into the log. + // Opening a log file closes any log file + // already open. + STDMETHOD(OpenLogFile)( + THIS_ + __in PCSTR File, + __in BOOL Append + ) PURE; + STDMETHOD(CloseLogFile)( + THIS + ) PURE; + // Controls what output is logged. + STDMETHOD(GetLogMask)( + THIS_ + __out PULONG Mask + ) PURE; + STDMETHOD(SetLogMask)( + THIS_ + __in ULONG Mask + ) PURE; + + // Input requests input from all clients. + // The first input that is returned is used + // to satisfy the call. Other returned + // input is discarded. + STDMETHOD(Input)( + THIS_ + __out_ecount(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG InputSize + ) PURE; + // This method is used by clients to return + // input when it is available. It will + // return S_OK if the input is used to + // satisfy an Input call and S_FALSE if + // the input is ignored. + // This method is reentrant. + STDMETHOD(ReturnInput)( + THIS_ + __in PCSTR Buffer + ) PURE; + + // Sends output through clients + // output callbacks if the mask is allowed + // by the current output control mask and + // according to the output distribution + // settings. + STDMETHODV(Output)( + THIS_ + __in ULONG Mask, + __in PCSTR Format, + ... + ) PURE; + STDMETHOD(OutputVaList)( + THIS_ + __in ULONG Mask, + __in PCSTR Format, + __in va_list Args + ) PURE; + // The following methods allow direct control + // over the distribution of the given output + // for situations where something other than + // the default is desired. These methods require + // extra work in the engine so they should + // only be used when necessary. + STDMETHODV(ControlledOutput)( + THIS_ + __in ULONG OutputControl, + __in ULONG Mask, + __in PCSTR Format, + ... + ) PURE; + STDMETHOD(ControlledOutputVaList)( + THIS_ + __in ULONG OutputControl, + __in ULONG Mask, + __in PCSTR Format, + __in va_list Args + ) PURE; + + // Displays the standard command-line prompt + // followed by the given output. If Format + // is NULL no additional output is produced. + // Output is produced under the + // DEBUG_OUTPUT_PROMPT mask. + // This method only outputs the prompt; it + // does not get input. + STDMETHODV(OutputPrompt)( + THIS_ + __in ULONG OutputControl, + __in_opt PCSTR Format, + ... + ) PURE; + STDMETHOD(OutputPromptVaList)( + THIS_ + __in ULONG OutputControl, + __in_opt PCSTR Format, + __in va_list Args + ) PURE; + // Gets the text that would be displayed by OutputPrompt. + STDMETHOD(GetPromptText)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG TextSize + ) PURE; + // Outputs information about the current + // debuggee state such as a register + // summary, disassembly at the current PC, + // closest symbol and others. + // Uses the line prefix. + STDMETHOD(OutputCurrentState)( + THIS_ + __in ULONG OutputControl, + __in ULONG Flags + ) PURE; + + // Outputs the debugger and extension version + // information. This method is reentrant. + // Uses the line prefix. + STDMETHOD(OutputVersionInformation)( + THIS_ + __in ULONG OutputControl + ) PURE; + + // In user-mode debugging sessions the + // engine will set an event when + // exceptions are continued. This can + // be used to synchronize other processes + // with the debuggers handling of events. + // For example, this is used to support + // the e argument to ntsd. + STDMETHOD(GetNotifyEventHandle)( + THIS_ + __out PULONG64 Handle + ) PURE; + STDMETHOD(SetNotifyEventHandle)( + THIS_ + __in ULONG64 Handle + ) PURE; + + STDMETHOD(Assemble)( + THIS_ + __in ULONG64 Offset, + __in PCSTR Instr, + __out PULONG64 EndOffset + ) PURE; + STDMETHOD(Disassemble)( + THIS_ + __in ULONG64 Offset, + __in ULONG Flags, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG DisassemblySize, + __out PULONG64 EndOffset + ) PURE; + // Returns the value of the effective address + // computed for the last Disassemble, if there + // was one. + STDMETHOD(GetDisassembleEffectiveOffset)( + THIS_ + __out PULONG64 Offset + ) PURE; + // Uses the line prefix if necessary. + STDMETHOD(OutputDisassembly)( + THIS_ + __in ULONG OutputControl, + __in ULONG64 Offset, + __in ULONG Flags, + __out PULONG64 EndOffset + ) PURE; + // Produces multiple lines of disassembly output. + // There will be PreviousLines of disassembly before + // the given offset if a valid disassembly exists. + // In all, there will be TotalLines of output produced. + // The first and last line offsets are returned + // specially and all lines offsets can be retrieved + // through LineOffsets. LineOffsets will contain + // offsets for each line where disassembly started. + // When disassembly of a single instruction takes + // multiple lines the initial offset will be followed + // by DEBUG_INVALID_OFFSET. + // Uses the line prefix. + STDMETHOD(OutputDisassemblyLines)( + THIS_ + __in ULONG OutputControl, + __in ULONG PreviousLines, + __in ULONG TotalLines, + __in ULONG64 Offset, + __in ULONG Flags, + __out_opt PULONG OffsetLine, + __out_opt PULONG64 StartOffset, + __out_opt PULONG64 EndOffset, + __out_ecount_opt(TotalLines) PULONG64 LineOffsets + ) PURE; + // Returns the offset of the start of + // the instruction thats the given + // delta away from the instruction + // at the initial offset. + // This routine does not check for + // validity of the instruction or + // the memory containing it. + STDMETHOD(GetNearInstruction)( + THIS_ + __in ULONG64 Offset, + __in LONG Delta, + __out PULONG64 NearOffset + ) PURE; + + // Offsets can be passed in as zero to use the current + // thread state. + STDMETHOD(GetStackTrace)( + THIS_ + __in ULONG64 FrameOffset, + __in ULONG64 StackOffset, + __in ULONG64 InstructionOffset, + __out_ecount(FramesSize) PDEBUG_STACK_FRAME Frames, + __in ULONG FramesSize, + __out_opt PULONG FramesFilled + ) PURE; + // Does a simple stack trace to determine + // what the current return address is. + STDMETHOD(GetReturnOffset)( + THIS_ + __out PULONG64 Offset + ) PURE; + // If Frames is NULL OutputStackTrace will + // use GetStackTrace to get FramesSize frames + // and then output them. The current register + // values for frame, stack and instruction offsets + // are used. + // Uses the line prefix. + STDMETHOD(OutputStackTrace)( + THIS_ + __in ULONG OutputControl, + __in_ecount_opt(FramesSize) PDEBUG_STACK_FRAME Frames, + __in ULONG FramesSize, + __in ULONG Flags + ) PURE; + + // Returns information about the debuggee such + // as user vs. kernel, dump vs. live, etc. + STDMETHOD(GetDebuggeeType)( + THIS_ + __out PULONG Class, + __out PULONG Qualifier + ) PURE; + // Returns the type of physical processors in + // the machine. + // Returns one of the IMAGE_FILE_MACHINE values. + STDMETHOD(GetActualProcessorType)( + THIS_ + __out PULONG Type + ) PURE; + // Returns the type of processor used in the + // current processor context. + STDMETHOD(GetExecutingProcessorType)( + THIS_ + __out PULONG Type + ) PURE; + // Query all the possible processor types that + // may be encountered during this debug session. + STDMETHOD(GetNumberPossibleExecutingProcessorTypes)( + THIS_ + __out PULONG Number + ) PURE; + STDMETHOD(GetPossibleExecutingProcessorTypes)( + THIS_ + __in ULONG Start, + __in ULONG Count, + __out_ecount(Count) PULONG Types + ) PURE; + // Get the number of actual processors in + // the machine. + STDMETHOD(GetNumberProcessors)( + THIS_ + __out PULONG Number + ) PURE; + // PlatformId is one of the VER_PLATFORM values. + // Major and minor are as given in the NT + // kernel debugger protocol. + // ServicePackString and ServicePackNumber indicate the + // system service pack level. ServicePackNumber is not + // available in some sessions where the service pack level + // is only expressed as a string. The service pack information + // will be empty if the system does not have a service pack + // applied. + // The build string is string information identifying the + // particular build of the system. The build string is + // empty if the system has no particular identifying + // information. + STDMETHOD(GetSystemVersion)( + THIS_ + __out PULONG PlatformId, + __out PULONG Major, + __out PULONG Minor, + __out_ecount_opt(ServicePackStringSize) PSTR ServicePackString, + __in ULONG ServicePackStringSize, + __out_opt PULONG ServicePackStringUsed, + __out PULONG ServicePackNumber, + __out_ecount_opt(BuildStringSize) PSTR BuildString, + __in ULONG BuildStringSize, + __out_opt PULONG BuildStringUsed + ) PURE; + // Returns the page size for the currently executing + // processor context. The page size may vary between + // processor types. + STDMETHOD(GetPageSize)( + THIS_ + __out PULONG Size + ) PURE; + // Returns S_OK if the current processor context uses + // 64-bit addresses, otherwise S_FALSE. + STDMETHOD(IsPointer64Bit)( + THIS + ) PURE; + // Reads the bugcheck data area and returns the + // current contents. This method only works + // in kernel debugging sessions. + STDMETHOD(ReadBugCheckData)( + THIS_ + __out PULONG Code, + __out PULONG64 Arg1, + __out PULONG64 Arg2, + __out PULONG64 Arg3, + __out PULONG64 Arg4 + ) PURE; + + // Query all the processor types supported by + // the engine. This is a complete list and is + // not related to the machine running the engine + // or the debuggee. + STDMETHOD(GetNumberSupportedProcessorTypes)( + THIS_ + __out PULONG Number + ) PURE; + STDMETHOD(GetSupportedProcessorTypes)( + THIS_ + __in ULONG Start, + __in ULONG Count, + __out_ecount(Count) PULONG Types + ) PURE; + // Returns a full, descriptive name and an + // abbreviated name for a processor type. + STDMETHOD(GetProcessorTypeNames)( + THIS_ + __in ULONG Type, + __out_ecount_opt(FullNameBufferSize) PSTR FullNameBuffer, + __in ULONG FullNameBufferSize, + __out_opt PULONG FullNameSize, + __out_ecount_opt(AbbrevNameBufferSize) PSTR AbbrevNameBuffer, + __in ULONG AbbrevNameBufferSize, + __out_opt PULONG AbbrevNameSize + ) PURE; + + // Gets and sets the type of processor to + // use when doing things like setting + // breakpoints, accessing registers, + // getting stack traces and so on. + STDMETHOD(GetEffectiveProcessorType)( + THIS_ + __out PULONG Type + ) PURE; + STDMETHOD(SetEffectiveProcessorType)( + THIS_ + __in ULONG Type + ) PURE; + + // Returns information about whether and how + // the debuggee is running. Status will + // be GO if the debuggee is running and + // BREAK if it isnt. + // If no debuggee exists the status is + // NO_DEBUGGEE. + // This method is reentrant. + STDMETHOD(GetExecutionStatus)( + THIS_ + __out PULONG Status + ) PURE; + // Changes the execution status of the + // engine from stopped to running. + // Status must be one of the go or step + // status values. + STDMETHOD(SetExecutionStatus)( + THIS_ + __in ULONG Status + ) PURE; + + // Controls what code interpretation level the debugger + // runs at. The debugger checks the code level when + // deciding whether to step by a source line or + // assembly instruction along with other related operations. + STDMETHOD(GetCodeLevel)( + THIS_ + __out PULONG Level + ) PURE; + STDMETHOD(SetCodeLevel)( + THIS_ + __in ULONG Level + ) PURE; + + // Gets and sets engine control flags. + // These methods are reentrant. + STDMETHOD(GetEngineOptions)( + THIS_ + __out PULONG Options + ) PURE; + STDMETHOD(AddEngineOptions)( + THIS_ + __in ULONG Options + ) PURE; + STDMETHOD(RemoveEngineOptions)( + THIS_ + __in ULONG Options + ) PURE; + STDMETHOD(SetEngineOptions)( + THIS_ + __in ULONG Options + ) PURE; + + // Gets and sets control values for + // handling system error events. + // If the system error level is less + // than or equal to the given levels + // the error may be displayed and + // the default break for the event + // may be set. + STDMETHOD(GetSystemErrorControl)( + THIS_ + __out PULONG OutputLevel, + __out PULONG BreakLevel + ) PURE; + STDMETHOD(SetSystemErrorControl)( + THIS_ + __in ULONG OutputLevel, + __in ULONG BreakLevel + ) PURE; + + // The command processor supports simple + // string replacement macros in Evaluate and + // Execute. There are currently ten macro + // slots available. Slots 0-9 map to + // the command invocations $u0-$u9. + STDMETHOD(GetTextMacro)( + THIS_ + __in ULONG Slot, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG MacroSize + ) PURE; + STDMETHOD(SetTextMacro)( + THIS_ + __in ULONG Slot, + __in PCSTR Macro + ) PURE; + + // Controls the default number radix used + // in expressions and commands. + STDMETHOD(GetRadix)( + THIS_ + __out PULONG Radix + ) PURE; + STDMETHOD(SetRadix)( + THIS_ + __in ULONG Radix + ) PURE; + + // Evaluates the given expression string and + // returns the resulting value. + // If DesiredType is DEBUG_VALUE_INVALID then + // the natural type is used. + // RemainderIndex, if provided, is set to the index + // of the first character in the input string that was + // not used when evaluating the expression. + STDMETHOD(Evaluate)( + THIS_ + __in PCSTR Expression, + __in ULONG DesiredType, + __out PDEBUG_VALUE Value, + __out_opt PULONG RemainderIndex + ) PURE; + // Attempts to convert the input value to a value + // of the requested type in the output value. + // Conversions can fail if no conversion exists. + // Successful conversions may be lossy. + STDMETHOD(CoerceValue)( + THIS_ + __in PDEBUG_VALUE In, + __in ULONG OutType, + __out PDEBUG_VALUE Out + ) PURE; + STDMETHOD(CoerceValues)( + THIS_ + __in ULONG Count, + __in_ecount(Count) PDEBUG_VALUE In, + __in_ecount(Count) PULONG OutTypes, + __out_ecount(Count) PDEBUG_VALUE Out + ) PURE; + + // Executes the given command string. + // If the string has multiple commands + // Execute will not return until all + // of them have been executed. If this + // requires waiting for the debuggee to + // execute an internal wait will be done + // so Execute can take an arbitrary amount + // of time. + STDMETHOD(Execute)( + THIS_ + __in ULONG OutputControl, + __in PCSTR Command, + __in ULONG Flags + ) PURE; + // Executes the given command file by + // reading a line at a time and processing + // it with Execute. + STDMETHOD(ExecuteCommandFile)( + THIS_ + __in ULONG OutputControl, + __in PCSTR CommandFile, + __in ULONG Flags + ) PURE; + + // Breakpoint interfaces are described + // elsewhere in this section. + STDMETHOD(GetNumberBreakpoints)( + THIS_ + __out PULONG Number + ) PURE; + // It is possible for this retrieval function to + // fail even with an index within the number of + // existing breakpoints if the breakpoint is + // a private breakpoint. + STDMETHOD(GetBreakpointByIndex)( + THIS_ + __in ULONG Index, + __out PDEBUG_BREAKPOINT* Bp + ) PURE; + STDMETHOD(GetBreakpointById)( + THIS_ + __in ULONG Id, + __out PDEBUG_BREAKPOINT* Bp + ) PURE; + // If Ids is non-NULL the Count breakpoints + // referred to in the Ids array are returned, + // otherwise breakpoints from index Start to + // Start + Count 1 are returned. + STDMETHOD(GetBreakpointParameters)( + THIS_ + __in ULONG Count, + __in_ecount_opt(Count) PULONG Ids, + __in ULONG Start, + __out_ecount(Count) PDEBUG_BREAKPOINT_PARAMETERS Params + ) PURE; + // Breakpoints are created empty and disabled. + // When their parameters have been set they + // should be enabled by setting the ENABLE flag. + // If DesiredId is DEBUG_ANY_ID then the + // engine picks an unused ID. If DesiredId + // is any other number the engine attempts + // to use the given ID for the breakpoint. + // If another breakpoint exists with that ID + // the call will fail. + STDMETHOD(AddBreakpoint)( + THIS_ + __in ULONG Type, + __in ULONG DesiredId, + __out PDEBUG_BREAKPOINT* Bp + ) PURE; + // Breakpoint interface is invalid after this call. + STDMETHOD(RemoveBreakpoint)( + THIS_ + __in PDEBUG_BREAKPOINT Bp + ) PURE; + + // Control and use extension DLLs. + STDMETHOD(AddExtension)( + THIS_ + __in PCSTR Path, + __in ULONG Flags, + __out PULONG64 Handle + ) PURE; + STDMETHOD(RemoveExtension)( + THIS_ + __in ULONG64 Handle + ) PURE; + STDMETHOD(GetExtensionByPath)( + THIS_ + __in PCSTR Path, + __out PULONG64 Handle + ) PURE; + // If Handle is zero the extension + // chain is walked searching for the + // function. + STDMETHOD(CallExtension)( + THIS_ + __in ULONG64 Handle, + __in PCSTR Function, + __in_opt PCSTR Arguments + ) PURE; + // GetExtensionFunction works like + // GetProcAddress on extension DLLs + // to allow raw function-call-level + // interaction with extension DLLs. + // Such functions do not need to + // follow the standard extension prototype + // if they are not going to be called + // through the text extension interface. + // _EFN_ is automatically prepended to + // the name string given. + // This function cannot be called remotely. + STDMETHOD(GetExtensionFunction)( + THIS_ + __in ULONG64 Handle, + __in PCSTR FuncName, + __out FARPROC* Function + ) PURE; + // These methods return alternate + // extension interfaces in order to allow + // interface-style extension DLLs to mix in + // older extension calls. + // Structure sizes must be initialized before + // the call. + // These methods cannot be called remotely. + STDMETHOD(GetWindbgExtensionApis32)( + THIS_ + __inout PWINDBG_EXTENSION_APIS32 Api + ) PURE; + STDMETHOD(GetWindbgExtensionApis64)( + THIS_ + __inout PWINDBG_EXTENSION_APIS64 Api + ) PURE; + + // The engine provides a simple mechanism + // to filter common events. Arbitrarily complicated + // filtering can be done by registering event callbacks + // but simple event filtering only requires + // setting the options of one of the predefined + // event filters. + // Simple event filters are either for specific + // events and therefore have an enumerant or + // they are for an exception and are based on + // the exceptions code. Exception filters + // are further divided into exceptions specially + // handled by the engine, which is a fixed set, + // and arbitrary exceptions. + // All three groups of filters are indexed together + // with the specific filters first, then the specific + // exception filters and finally the arbitrary + // exception filters. + // The first specific exception is the default + // exception. If an exception event occurs for + // an exception without settings the default + // exception settings are used. + STDMETHOD(GetNumberEventFilters)( + THIS_ + __out PULONG SpecificEvents, + __out PULONG SpecificExceptions, + __out PULONG ArbitraryExceptions + ) PURE; + // Some filters have descriptive text associated with them. + STDMETHOD(GetEventFilterText)( + THIS_ + __in ULONG Index, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG TextSize + ) PURE; + // All filters support executing a command when the + // event occurs. + STDMETHOD(GetEventFilterCommand)( + THIS_ + __in ULONG Index, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG CommandSize + ) PURE; + STDMETHOD(SetEventFilterCommand)( + THIS_ + __in ULONG Index, + __in PCSTR Command + ) PURE; + STDMETHOD(GetSpecificFilterParameters)( + THIS_ + __in ULONG Start, + __in ULONG Count, + __out_ecount(Count) PDEBUG_SPECIFIC_FILTER_PARAMETERS Params + ) PURE; + STDMETHOD(SetSpecificFilterParameters)( + THIS_ + __in ULONG Start, + __in ULONG Count, + __in_ecount(Count) PDEBUG_SPECIFIC_FILTER_PARAMETERS Params + ) PURE; + // Some specific filters have arguments to further + // qualify their operation. + STDMETHOD(GetSpecificFilterArgument)( + THIS_ + __in ULONG Index, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG ArgumentSize + ) PURE; + STDMETHOD(SetSpecificFilterArgument)( + THIS_ + __in ULONG Index, + __in PCSTR Argument + ) PURE; + // If Codes is non-NULL Start is ignored. + STDMETHOD(GetExceptionFilterParameters)( + THIS_ + __in ULONG Count, + __in_ecount_opt(Count) PULONG Codes, + __in ULONG Start, + __out_ecount(Count) PDEBUG_EXCEPTION_FILTER_PARAMETERS Params + ) PURE; + // The codes in the parameter data control the application + // of the parameter data. If a code is not already in + // the set of filters it is added. If the ExecutionOption + // for a code is REMOVE then the filter is removed. + // Specific exception filters cannot be removed. + STDMETHOD(SetExceptionFilterParameters)( + THIS_ + __in ULONG Count, + __in_ecount(Count) PDEBUG_EXCEPTION_FILTER_PARAMETERS Params + ) PURE; + // Exception filters support an additional command for + // second-chance events. + STDMETHOD(GetExceptionFilterSecondCommand)( + THIS_ + __in ULONG Index, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG CommandSize + ) PURE; + STDMETHOD(SetExceptionFilterSecondCommand)( + THIS_ + __in ULONG Index, + __in PCSTR Command + ) PURE; + + // Yields processing to the engine until + // an event occurs. This method may + // only be called by the thread that started + // the debug session. + // When an event occurs the engine carries + // out all event processing such as calling + // callbacks. + // If the callbacks indicate that execution should + // break the wait will return, otherwise it + // goes back to waiting for a new event. + // If the timeout expires, S_FALSE is returned. + // The timeout is not currently supported for + // kernel debugging. + STDMETHOD(WaitForEvent)( + THIS_ + __in ULONG Flags, + __in ULONG Timeout + ) PURE; + + // Retrieves information about the last event that occurred. + // EventType is one of the event callback mask bits. + // ExtraInformation contains additional event-specific + // information. Not all events have additional information. + STDMETHOD(GetLastEventInformation)( + THIS_ + __out PULONG Type, + __out PULONG ProcessId, + __out PULONG ThreadId, + __out_bcount_opt(ExtraInformationSize) PVOID ExtraInformation, + __in ULONG ExtraInformationSize, + __out_opt PULONG ExtraInformationUsed, + __out_ecount_opt(DescriptionSize) PSTR Description, + __in ULONG DescriptionSize, + __out_opt PULONG DescriptionUsed + ) PURE; +}; + +// OutputTextReplacements flags. +#define DEBUG_OUT_TEXT_REPL_DEFAULT 0x00000000 + +#undef INTERFACE +#define INTERFACE IDebugControl2 +DECLARE_INTERFACE_(IDebugControl2, IUnknown) +{ + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + __in REFIID InterfaceId, + __out PVOID* Interface + ) PURE; + STDMETHOD_(ULONG, AddRef)( + THIS + ) PURE; + STDMETHOD_(ULONG, Release)( + THIS + ) PURE; + + // IDebugControl. + + // Checks for a user interrupt, such a Ctrl-C + // or stop button. + // This method is reentrant. + STDMETHOD(GetInterrupt)( + THIS + ) PURE; + // Registers a user interrupt. + // This method is reentrant. + STDMETHOD(SetInterrupt)( + THIS_ + __in ULONG Flags + ) PURE; + // Interrupting a user-mode process requires + // access to some system resources that the + // process may hold itself, preventing the + // interrupt from occurring. The engine + // will time-out pending interrupt requests + // and simulate an interrupt if necessary. + // These methods control the interrupt timeout. + STDMETHOD(GetInterruptTimeout)( + THIS_ + __out PULONG Seconds + ) PURE; + STDMETHOD(SetInterruptTimeout)( + THIS_ + __in ULONG Seconds + ) PURE; + + STDMETHOD(GetLogFile)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG FileSize, + __out PBOOL Append + ) PURE; + // Opens a log file which collects all + // output. Output from every client except + // those that explicitly disable logging + // goes into the log. + // Opening a log file closes any log file + // already open. + STDMETHOD(OpenLogFile)( + THIS_ + __in PCSTR File, + __in BOOL Append + ) PURE; + STDMETHOD(CloseLogFile)( + THIS + ) PURE; + // Controls what output is logged. + STDMETHOD(GetLogMask)( + THIS_ + __out PULONG Mask + ) PURE; + STDMETHOD(SetLogMask)( + THIS_ + __in ULONG Mask + ) PURE; + + // Input requests input from all clients. + // The first input that is returned is used + // to satisfy the call. Other returned + // input is discarded. + STDMETHOD(Input)( + THIS_ + __out_ecount(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG InputSize + ) PURE; + // This method is used by clients to return + // input when it is available. It will + // return S_OK if the input is used to + // satisfy an Input call and S_FALSE if + // the input is ignored. + // This method is reentrant. + STDMETHOD(ReturnInput)( + THIS_ + __in PCSTR Buffer + ) PURE; + + // Sends output through clients + // output callbacks if the mask is allowed + // by the current output control mask and + // according to the output distribution + // settings. + STDMETHODV(Output)( + THIS_ + __in ULONG Mask, + __in PCSTR Format, + ... + ) PURE; + STDMETHOD(OutputVaList)( + THIS_ + __in ULONG Mask, + __in PCSTR Format, + __in va_list Args + ) PURE; + // The following methods allow direct control + // over the distribution of the given output + // for situations where something other than + // the default is desired. These methods require + // extra work in the engine so they should + // only be used when necessary. + STDMETHODV(ControlledOutput)( + THIS_ + __in ULONG OutputControl, + __in ULONG Mask, + __in PCSTR Format, + ... + ) PURE; + STDMETHOD(ControlledOutputVaList)( + THIS_ + __in ULONG OutputControl, + __in ULONG Mask, + __in PCSTR Format, + __in va_list Args + ) PURE; + + // Displays the standard command-line prompt + // followed by the given output. If Format + // is NULL no additional output is produced. + // Output is produced under the + // DEBUG_OUTPUT_PROMPT mask. + // This method only outputs the prompt; it + // does not get input. + STDMETHODV(OutputPrompt)( + THIS_ + __in ULONG OutputControl, + __in_opt PCSTR Format, + ... + ) PURE; + STDMETHOD(OutputPromptVaList)( + THIS_ + __in ULONG OutputControl, + __in_opt PCSTR Format, + __in va_list Args + ) PURE; + // Gets the text that would be displayed by OutputPrompt. + STDMETHOD(GetPromptText)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG TextSize + ) PURE; + // Outputs information about the current + // debuggee state such as a register + // summary, disassembly at the current PC, + // closest symbol and others. + // Uses the line prefix. + STDMETHOD(OutputCurrentState)( + THIS_ + __in ULONG OutputControl, + __in ULONG Flags + ) PURE; + + // Outputs the debugger and extension version + // information. This method is reentrant. + // Uses the line prefix. + STDMETHOD(OutputVersionInformation)( + THIS_ + __in ULONG OutputControl + ) PURE; + + // In user-mode debugging sessions the + // engine will set an event when + // exceptions are continued. This can + // be used to synchronize other processes + // with the debuggers handling of events. + // For example, this is used to support + // the e argument to ntsd. + STDMETHOD(GetNotifyEventHandle)( + THIS_ + __out PULONG64 Handle + ) PURE; + STDMETHOD(SetNotifyEventHandle)( + THIS_ + __in ULONG64 Handle + ) PURE; + + STDMETHOD(Assemble)( + THIS_ + __in ULONG64 Offset, + __in PCSTR Instr, + __out PULONG64 EndOffset + ) PURE; + STDMETHOD(Disassemble)( + THIS_ + __in ULONG64 Offset, + __in ULONG Flags, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG DisassemblySize, + __out PULONG64 EndOffset + ) PURE; + // Returns the value of the effective address + // computed for the last Disassemble, if there + // was one. + STDMETHOD(GetDisassembleEffectiveOffset)( + THIS_ + __out PULONG64 Offset + ) PURE; + // Uses the line prefix if necessary. + STDMETHOD(OutputDisassembly)( + THIS_ + __in ULONG OutputControl, + __in ULONG64 Offset, + __in ULONG Flags, + __out PULONG64 EndOffset + ) PURE; + // Produces multiple lines of disassembly output. + // There will be PreviousLines of disassembly before + // the given offset if a valid disassembly exists. + // In all, there will be TotalLines of output produced. + // The first and last line offsets are returned + // specially and all lines offsets can be retrieved + // through LineOffsets. LineOffsets will contain + // offsets for each line where disassembly started. + // When disassembly of a single instruction takes + // multiple lines the initial offset will be followed + // by DEBUG_INVALID_OFFSET. + // Uses the line prefix. + STDMETHOD(OutputDisassemblyLines)( + THIS_ + __in ULONG OutputControl, + __in ULONG PreviousLines, + __in ULONG TotalLines, + __in ULONG64 Offset, + __in ULONG Flags, + __out_opt PULONG OffsetLine, + __out_opt PULONG64 StartOffset, + __out_opt PULONG64 EndOffset, + __out_ecount_opt(TotalLines) PULONG64 LineOffsets + ) PURE; + // Returns the offset of the start of + // the instruction thats the given + // delta away from the instruction + // at the initial offset. + // This routine does not check for + // validity of the instruction or + // the memory containing it. + STDMETHOD(GetNearInstruction)( + THIS_ + __in ULONG64 Offset, + __in LONG Delta, + __out PULONG64 NearOffset + ) PURE; + + // Offsets can be passed in as zero to use the current + // thread state. + STDMETHOD(GetStackTrace)( + THIS_ + __in ULONG64 FrameOffset, + __in ULONG64 StackOffset, + __in ULONG64 InstructionOffset, + __out_ecount(FramesSize) PDEBUG_STACK_FRAME Frames, + __in ULONG FramesSize, + __out_opt PULONG FramesFilled + ) PURE; + // Does a simple stack trace to determine + // what the current return address is. + STDMETHOD(GetReturnOffset)( + THIS_ + __out PULONG64 Offset + ) PURE; + // If Frames is NULL OutputStackTrace will + // use GetStackTrace to get FramesSize frames + // and then output them. The current register + // values for frame, stack and instruction offsets + // are used. + // Uses the line prefix. + STDMETHOD(OutputStackTrace)( + THIS_ + __in ULONG OutputControl, + __in_ecount_opt(FramesSize) PDEBUG_STACK_FRAME Frames, + __in ULONG FramesSize, + __in ULONG Flags + ) PURE; + + // Returns information about the debuggee such + // as user vs. kernel, dump vs. live, etc. + STDMETHOD(GetDebuggeeType)( + THIS_ + __out PULONG Class, + __out PULONG Qualifier + ) PURE; + // Returns the type of physical processors in + // the machine. + // Returns one of the IMAGE_FILE_MACHINE values. + STDMETHOD(GetActualProcessorType)( + THIS_ + __out PULONG Type + ) PURE; + // Returns the type of processor used in the + // current processor context. + STDMETHOD(GetExecutingProcessorType)( + THIS_ + __out PULONG Type + ) PURE; + // Query all the possible processor types that + // may be encountered during this debug session. + STDMETHOD(GetNumberPossibleExecutingProcessorTypes)( + THIS_ + __out PULONG Number + ) PURE; + STDMETHOD(GetPossibleExecutingProcessorTypes)( + THIS_ + __in ULONG Start, + __in ULONG Count, + __out_ecount(Count) PULONG Types + ) PURE; + // Get the number of actual processors in + // the machine. + STDMETHOD(GetNumberProcessors)( + THIS_ + __out PULONG Number + ) PURE; + // PlatformId is one of the VER_PLATFORM values. + // Major and minor are as given in the NT + // kernel debugger protocol. + // ServicePackString and ServicePackNumber indicate the + // system service pack level. ServicePackNumber is not + // available in some sessions where the service pack level + // is only expressed as a string. The service pack information + // will be empty if the system does not have a service pack + // applied. + // The build string is string information identifying the + // particular build of the system. The build string is + // empty if the system has no particular identifying + // information. + STDMETHOD(GetSystemVersion)( + THIS_ + __out PULONG PlatformId, + __out PULONG Major, + __out PULONG Minor, + __out_ecount_opt(ServicePackStringSize) PSTR ServicePackString, + __in ULONG ServicePackStringSize, + __out_opt PULONG ServicePackStringUsed, + __out PULONG ServicePackNumber, + __out_ecount_opt(BuildStringSize) PSTR BuildString, + __in ULONG BuildStringSize, + __out_opt PULONG BuildStringUsed + ) PURE; + // Returns the page size for the currently executing + // processor context. The page size may vary between + // processor types. + STDMETHOD(GetPageSize)( + THIS_ + __out PULONG Size + ) PURE; + // Returns S_OK if the current processor context uses + // 64-bit addresses, otherwise S_FALSE. + STDMETHOD(IsPointer64Bit)( + THIS + ) PURE; + // Reads the bugcheck data area and returns the + // current contents. This method only works + // in kernel debugging sessions. + STDMETHOD(ReadBugCheckData)( + THIS_ + __out PULONG Code, + __out PULONG64 Arg1, + __out PULONG64 Arg2, + __out PULONG64 Arg3, + __out PULONG64 Arg4 + ) PURE; + + // Query all the processor types supported by + // the engine. This is a complete list and is + // not related to the machine running the engine + // or the debuggee. + STDMETHOD(GetNumberSupportedProcessorTypes)( + THIS_ + __out PULONG Number + ) PURE; + STDMETHOD(GetSupportedProcessorTypes)( + THIS_ + __in ULONG Start, + __in ULONG Count, + __out_ecount(Count) PULONG Types + ) PURE; + // Returns a full, descriptive name and an + // abbreviated name for a processor type. + STDMETHOD(GetProcessorTypeNames)( + THIS_ + __in ULONG Type, + __out_ecount_opt(FullNameBufferSize) PSTR FullNameBuffer, + __in ULONG FullNameBufferSize, + __out_opt PULONG FullNameSize, + __out_ecount_opt(AbbrevNameBufferSize) PSTR AbbrevNameBuffer, + __in ULONG AbbrevNameBufferSize, + __out_opt PULONG AbbrevNameSize + ) PURE; + + // Gets and sets the type of processor to + // use when doing things like setting + // breakpoints, accessing registers, + // getting stack traces and so on. + STDMETHOD(GetEffectiveProcessorType)( + THIS_ + __out PULONG Type + ) PURE; + STDMETHOD(SetEffectiveProcessorType)( + THIS_ + __in ULONG Type + ) PURE; + + // Returns information about whether and how + // the debuggee is running. Status will + // be GO if the debuggee is running and + // BREAK if it isnt. + // If no debuggee exists the status is + // NO_DEBUGGEE. + // This method is reentrant. + STDMETHOD(GetExecutionStatus)( + THIS_ + __out PULONG Status + ) PURE; + // Changes the execution status of the + // engine from stopped to running. + // Status must be one of the go or step + // status values. + STDMETHOD(SetExecutionStatus)( + THIS_ + __in ULONG Status + ) PURE; + + // Controls what code interpretation level the debugger + // runs at. The debugger checks the code level when + // deciding whether to step by a source line or + // assembly instruction along with other related operations. + STDMETHOD(GetCodeLevel)( + THIS_ + __out PULONG Level + ) PURE; + STDMETHOD(SetCodeLevel)( + THIS_ + __in ULONG Level + ) PURE; + + // Gets and sets engine control flags. + // These methods are reentrant. + STDMETHOD(GetEngineOptions)( + THIS_ + __out PULONG Options + ) PURE; + STDMETHOD(AddEngineOptions)( + THIS_ + __in ULONG Options + ) PURE; + STDMETHOD(RemoveEngineOptions)( + THIS_ + __in ULONG Options + ) PURE; + STDMETHOD(SetEngineOptions)( + THIS_ + __in ULONG Options + ) PURE; + + // Gets and sets control values for + // handling system error events. + // If the system error level is less + // than or equal to the given levels + // the error may be displayed and + // the default break for the event + // may be set. + STDMETHOD(GetSystemErrorControl)( + THIS_ + __out PULONG OutputLevel, + __out PULONG BreakLevel + ) PURE; + STDMETHOD(SetSystemErrorControl)( + THIS_ + __in ULONG OutputLevel, + __in ULONG BreakLevel + ) PURE; + + // The command processor supports simple + // string replacement macros in Evaluate and + // Execute. There are currently ten macro + // slots available. Slots 0-9 map to + // the command invocations $u0-$u9. + STDMETHOD(GetTextMacro)( + THIS_ + __in ULONG Slot, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG MacroSize + ) PURE; + STDMETHOD(SetTextMacro)( + THIS_ + __in ULONG Slot, + __in PCSTR Macro + ) PURE; + + // Controls the default number radix used + // in expressions and commands. + STDMETHOD(GetRadix)( + THIS_ + __out PULONG Radix + ) PURE; + STDMETHOD(SetRadix)( + THIS_ + __in ULONG Radix + ) PURE; + + // Evaluates the given expression string and + // returns the resulting value. + // If DesiredType is DEBUG_VALUE_INVALID then + // the natural type is used. + // RemainderIndex, if provided, is set to the index + // of the first character in the input string that was + // not used when evaluating the expression. + STDMETHOD(Evaluate)( + THIS_ + __in PCSTR Expression, + __in ULONG DesiredType, + __out PDEBUG_VALUE Value, + __out_opt PULONG RemainderIndex + ) PURE; + // Attempts to convert the input value to a value + // of the requested type in the output value. + // Conversions can fail if no conversion exists. + // Successful conversions may be lossy. + STDMETHOD(CoerceValue)( + THIS_ + __in PDEBUG_VALUE In, + __in ULONG OutType, + __out PDEBUG_VALUE Out + ) PURE; + STDMETHOD(CoerceValues)( + THIS_ + __in ULONG Count, + __in_ecount(Count) PDEBUG_VALUE In, + __in_ecount(Count) PULONG OutTypes, + __out_ecount(Count) PDEBUG_VALUE Out + ) PURE; + + // Executes the given command string. + // If the string has multiple commands + // Execute will not return until all + // of them have been executed. If this + // requires waiting for the debuggee to + // execute an internal wait will be done + // so Execute can take an arbitrary amount + // of time. + STDMETHOD(Execute)( + THIS_ + __in ULONG OutputControl, + __in PCSTR Command, + __in ULONG Flags + ) PURE; + // Executes the given command file by + // reading a line at a time and processing + // it with Execute. + STDMETHOD(ExecuteCommandFile)( + THIS_ + __in ULONG OutputControl, + __in PCSTR CommandFile, + __in ULONG Flags + ) PURE; + + // Breakpoint interfaces are described + // elsewhere in this section. + STDMETHOD(GetNumberBreakpoints)( + THIS_ + __out PULONG Number + ) PURE; + // It is possible for this retrieval function to + // fail even with an index within the number of + // existing breakpoints if the breakpoint is + // a private breakpoint. + STDMETHOD(GetBreakpointByIndex)( + THIS_ + __in ULONG Index, + __out PDEBUG_BREAKPOINT* Bp + ) PURE; + STDMETHOD(GetBreakpointById)( + THIS_ + __in ULONG Id, + __out PDEBUG_BREAKPOINT* Bp + ) PURE; + // If Ids is non-NULL the Count breakpoints + // referred to in the Ids array are returned, + // otherwise breakpoints from index Start to + // Start + Count 1 are returned. + STDMETHOD(GetBreakpointParameters)( + THIS_ + __in ULONG Count, + __in_ecount_opt(Count) PULONG Ids, + __in ULONG Start, + __out_ecount(Count) PDEBUG_BREAKPOINT_PARAMETERS Params + ) PURE; + // Breakpoints are created empty and disabled. + // When their parameters have been set they + // should be enabled by setting the ENABLE flag. + // If DesiredId is DEBUG_ANY_ID then the + // engine picks an unused ID. If DesiredId + // is any other number the engine attempts + // to use the given ID for the breakpoint. + // If another breakpoint exists with that ID + // the call will fail. + STDMETHOD(AddBreakpoint)( + THIS_ + __in ULONG Type, + __in ULONG DesiredId, + __out PDEBUG_BREAKPOINT* Bp + ) PURE; + // Breakpoint interface is invalid after this call. + STDMETHOD(RemoveBreakpoint)( + THIS_ + __in PDEBUG_BREAKPOINT Bp + ) PURE; + + // Control and use extension DLLs. + STDMETHOD(AddExtension)( + THIS_ + __in PCSTR Path, + __in ULONG Flags, + __out PULONG64 Handle + ) PURE; + STDMETHOD(RemoveExtension)( + THIS_ + __in ULONG64 Handle + ) PURE; + STDMETHOD(GetExtensionByPath)( + THIS_ + __in PCSTR Path, + __out PULONG64 Handle + ) PURE; + // If Handle is zero the extension + // chain is walked searching for the + // function. + STDMETHOD(CallExtension)( + THIS_ + __in ULONG64 Handle, + __in PCSTR Function, + __in_opt PCSTR Arguments + ) PURE; + // GetExtensionFunction works like + // GetProcAddress on extension DLLs + // to allow raw function-call-level + // interaction with extension DLLs. + // Such functions do not need to + // follow the standard extension prototype + // if they are not going to be called + // through the text extension interface. + // This function cannot be called remotely. + STDMETHOD(GetExtensionFunction)( + THIS_ + __in ULONG64 Handle, + __in PCSTR FuncName, + __out FARPROC* Function + ) PURE; + // These methods return alternate + // extension interfaces in order to allow + // interface-style extension DLLs to mix in + // older extension calls. + // Structure sizes must be initialized before + // the call. + // These methods cannot be called remotely. + STDMETHOD(GetWindbgExtensionApis32)( + THIS_ + __inout PWINDBG_EXTENSION_APIS32 Api + ) PURE; + STDMETHOD(GetWindbgExtensionApis64)( + THIS_ + __inout PWINDBG_EXTENSION_APIS64 Api + ) PURE; + + // The engine provides a simple mechanism + // to filter common events. Arbitrarily complicated + // filtering can be done by registering event callbacks + // but simple event filtering only requires + // setting the options of one of the predefined + // event filters. + // Simple event filters are either for specific + // events and therefore have an enumerant or + // they are for an exception and are based on + // the exceptions code. Exception filters + // are further divided into exceptions specially + // handled by the engine, which is a fixed set, + // and arbitrary exceptions. + // All three groups of filters are indexed together + // with the specific filters first, then the specific + // exception filters and finally the arbitrary + // exception filters. + // The first specific exception is the default + // exception. If an exception event occurs for + // an exception without settings the default + // exception settings are used. + STDMETHOD(GetNumberEventFilters)( + THIS_ + __out PULONG SpecificEvents, + __out PULONG SpecificExceptions, + __out PULONG ArbitraryExceptions + ) PURE; + // Some filters have descriptive text associated with them. + STDMETHOD(GetEventFilterText)( + THIS_ + __in ULONG Index, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG TextSize + ) PURE; + // All filters support executing a command when the + // event occurs. + STDMETHOD(GetEventFilterCommand)( + THIS_ + __in ULONG Index, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG CommandSize + ) PURE; + STDMETHOD(SetEventFilterCommand)( + THIS_ + __in ULONG Index, + __in PCSTR Command + ) PURE; + STDMETHOD(GetSpecificFilterParameters)( + THIS_ + __in ULONG Start, + __in ULONG Count, + __out_ecount(Count) PDEBUG_SPECIFIC_FILTER_PARAMETERS Params + ) PURE; + STDMETHOD(SetSpecificFilterParameters)( + THIS_ + __in ULONG Start, + __in ULONG Count, + __in_ecount(Count) PDEBUG_SPECIFIC_FILTER_PARAMETERS Params + ) PURE; + // Some specific filters have arguments to further + // qualify their operation. + STDMETHOD(GetSpecificFilterArgument)( + THIS_ + __in ULONG Index, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG ArgumentSize + ) PURE; + STDMETHOD(SetSpecificFilterArgument)( + THIS_ + __in ULONG Index, + __in PCSTR Argument + ) PURE; + // If Codes is non-NULL Start is ignored. + STDMETHOD(GetExceptionFilterParameters)( + THIS_ + __in ULONG Count, + __in_ecount_opt(Count) PULONG Codes, + __in ULONG Start, + __out_ecount(Count) PDEBUG_EXCEPTION_FILTER_PARAMETERS Params + ) PURE; + // The codes in the parameter data control the application + // of the parameter data. If a code is not already in + // the set of filters it is added. If the ExecutionOption + // for a code is REMOVE then the filter is removed. + // Specific exception filters cannot be removed. + STDMETHOD(SetExceptionFilterParameters)( + THIS_ + __in ULONG Count, + __in_ecount(Count) PDEBUG_EXCEPTION_FILTER_PARAMETERS Params + ) PURE; + // Exception filters support an additional command for + // second-chance events. + STDMETHOD(GetExceptionFilterSecondCommand)( + THIS_ + __in ULONG Index, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG CommandSize + ) PURE; + STDMETHOD(SetExceptionFilterSecondCommand)( + THIS_ + __in ULONG Index, + __in PCSTR Command + ) PURE; + + // Yields processing to the engine until + // an event occurs. This method may + // only be called by the thread that started + // the debug session. + // When an event occurs the engine carries + // out all event processing such as calling + // callbacks. + // If the callbacks indicate that execution should + // break the wait will return, otherwise it + // goes back to waiting for a new event. + // If the timeout expires, S_FALSE is returned. + // The timeout is not currently supported for + // kernel debugging. + STDMETHOD(WaitForEvent)( + THIS_ + __in ULONG Flags, + __in ULONG Timeout + ) PURE; + + // Retrieves information about the last event that occurred. + // EventType is one of the event callback mask bits. + // ExtraInformation contains additional event-specific + // information. Not all events have additional information. + STDMETHOD(GetLastEventInformation)( + THIS_ + __out PULONG Type, + __out PULONG ProcessId, + __out PULONG ThreadId, + __out_bcount_opt(ExtraInformationSize) PVOID ExtraInformation, + __in ULONG ExtraInformationSize, + __out_opt PULONG ExtraInformationUsed, + __out_ecount_opt(DescriptionSize) PSTR Description, + __in ULONG DescriptionSize, + __out_opt PULONG DescriptionUsed + ) PURE; + + // IDebugControl2. + + STDMETHOD(GetCurrentTimeDate)( + THIS_ + __out PULONG TimeDate + ) PURE; + // Retrieves the number of seconds since the + // machine started running. + STDMETHOD(GetCurrentSystemUpTime)( + THIS_ + __out PULONG UpTime + ) PURE; + + // If the current session is a dump session, + // retrieves any extended format information. + STDMETHOD(GetDumpFormatFlags)( + THIS_ + __out PULONG FormatFlags + ) PURE; + + // The debugger has been enhanced to allow + // arbitrary text replacements in addition + // to the simple $u0-$u9 text macros. + // Text replacement takes a given source + // text in commands and converts it to the + // given destination text. Replacements + // are named by their source text so that + // only one replacement for a source text + // string can exist. + STDMETHOD(GetNumberTextReplacements)( + THIS_ + __out PULONG NumRepl + ) PURE; + // If SrcText is non-NULL the replacement + // is looked up by source text, otherwise + // Index is used to get the Nth replacement. + STDMETHOD(GetTextReplacement)( + THIS_ + __in_opt PCSTR SrcText, + __in ULONG Index, + __out_ecount_opt(SrcBufferSize) PSTR SrcBuffer, + __in ULONG SrcBufferSize, + __out_opt PULONG SrcSize, + __out_ecount_opt(DstBufferSize) PSTR DstBuffer, + __in ULONG DstBufferSize, + __out_opt PULONG DstSize + ) PURE; + // Setting the destination text to + // NULL removes the alias. + STDMETHOD(SetTextReplacement)( + THIS_ + __in PCSTR SrcText, + __in_opt PCSTR DstText + ) PURE; + STDMETHOD(RemoveTextReplacements)( + THIS + ) PURE; + // Outputs the complete list of current + // replacements. + STDMETHOD(OutputTextReplacements)( + THIS_ + __in ULONG OutputControl, + __in ULONG Flags + ) PURE; +}; + +// +// Assembly/disassembly options. +// +// The specific effects of these flags varies depending +// on the particular instruction set. +// + +#define DEBUG_ASMOPT_DEFAULT 0x00000000 +// Display additional information in disassembly. +#define DEBUG_ASMOPT_VERBOSE 0x00000001 +// Do not display raw code bytes in disassembly. +#define DEBUG_ASMOPT_NO_CODE_BYTES 0x00000002 +// Do not take the output width into account when +// formatting disassembly. +#define DEBUG_ASMOPT_IGNORE_OUTPUT_WIDTH 0x00000004 +// Display source file line number before each line if available. +#define DEBUG_ASMOPT_SOURCE_LINE_NUMBER 0x00000008 + +// +// Expression syntax options. +// + +// MASM-style expression evaluation. +#define DEBUG_EXPR_MASM 0x00000000 +// C++-style expression evaluation. +#define DEBUG_EXPR_CPLUSPLUS 0x00000001 + +// +// Event index description information. +// + +#define DEBUG_EINDEX_NAME 0x00000000 + +// +// SetNextEventIndex relation options. +// + +// Value increases forward from the first index. +#define DEBUG_EINDEX_FROM_START 0x00000000 +// Value increases backwards from the last index. +#define DEBUG_EINDEX_FROM_END 0x00000001 +// Value is a signed delta from the current index. +#define DEBUG_EINDEX_FROM_CURRENT 0x00000002 + +#undef INTERFACE +#define INTERFACE IDebugControl3 +DECLARE_INTERFACE_(IDebugControl3, IUnknown) +{ + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + __in REFIID InterfaceId, + __out PVOID* Interface + ) PURE; + STDMETHOD_(ULONG, AddRef)( + THIS + ) PURE; + STDMETHOD_(ULONG, Release)( + THIS + ) PURE; + + // IDebugControl. + + // Checks for a user interrupt, such a Ctrl-C + // or stop button. + // This method is reentrant. + STDMETHOD(GetInterrupt)( + THIS + ) PURE; + // Registers a user interrupt. + // This method is reentrant. + STDMETHOD(SetInterrupt)( + THIS_ + __in ULONG Flags + ) PURE; + // Interrupting a user-mode process requires + // access to some system resources that the + // process may hold itself, preventing the + // interrupt from occurring. The engine + // will time-out pending interrupt requests + // and simulate an interrupt if necessary. + // These methods control the interrupt timeout. + STDMETHOD(GetInterruptTimeout)( + THIS_ + __out PULONG Seconds + ) PURE; + STDMETHOD(SetInterruptTimeout)( + THIS_ + __in ULONG Seconds + ) PURE; + + STDMETHOD(GetLogFile)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG FileSize, + __out PBOOL Append + ) PURE; + // Opens a log file which collects all + // output. Output from every client except + // those that explicitly disable logging + // goes into the log. + // Opening a log file closes any log file + // already open. + STDMETHOD(OpenLogFile)( + THIS_ + __in PCSTR File, + __in BOOL Append + ) PURE; + STDMETHOD(CloseLogFile)( + THIS + ) PURE; + // Controls what output is logged. + STDMETHOD(GetLogMask)( + THIS_ + __out PULONG Mask + ) PURE; + STDMETHOD(SetLogMask)( + THIS_ + __in ULONG Mask + ) PURE; + + // Input requests input from all clients. + // The first input that is returned is used + // to satisfy the call. Other returned + // input is discarded. + STDMETHOD(Input)( + THIS_ + __out_ecount(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG InputSize + ) PURE; + // This method is used by clients to return + // input when it is available. It will + // return S_OK if the input is used to + // satisfy an Input call and S_FALSE if + // the input is ignored. + // This method is reentrant. + STDMETHOD(ReturnInput)( + THIS_ + __in PCSTR Buffer + ) PURE; + + // Sends output through clients + // output callbacks if the mask is allowed + // by the current output control mask and + // according to the output distribution + // settings. + STDMETHODV(Output)( + THIS_ + __in ULONG Mask, + __in PCSTR Format, + ... + ) PURE; + STDMETHOD(OutputVaList)( + THIS_ + __in ULONG Mask, + __in PCSTR Format, + __in va_list Args + ) PURE; + // The following methods allow direct control + // over the distribution of the given output + // for situations where something other than + // the default is desired. These methods require + // extra work in the engine so they should + // only be used when necessary. + STDMETHODV(ControlledOutput)( + THIS_ + __in ULONG OutputControl, + __in ULONG Mask, + __in PCSTR Format, + ... + ) PURE; + STDMETHOD(ControlledOutputVaList)( + THIS_ + __in ULONG OutputControl, + __in ULONG Mask, + __in PCSTR Format, + __in va_list Args + ) PURE; + + // Displays the standard command-line prompt + // followed by the given output. If Format + // is NULL no additional output is produced. + // Output is produced under the + // DEBUG_OUTPUT_PROMPT mask. + // This method only outputs the prompt; it + // does not get input. + STDMETHODV(OutputPrompt)( + THIS_ + __in ULONG OutputControl, + __in_opt PCSTR Format, + ... + ) PURE; + STDMETHOD(OutputPromptVaList)( + THIS_ + __in ULONG OutputControl, + __in_opt PCSTR Format, + __in va_list Args + ) PURE; + // Gets the text that would be displayed by OutputPrompt. + STDMETHOD(GetPromptText)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG TextSize + ) PURE; + // Outputs information about the current + // debuggee state such as a register + // summary, disassembly at the current PC, + // closest symbol and others. + // Uses the line prefix. + STDMETHOD(OutputCurrentState)( + THIS_ + __in ULONG OutputControl, + __in ULONG Flags + ) PURE; + + // Outputs the debugger and extension version + // information. This method is reentrant. + // Uses the line prefix. + STDMETHOD(OutputVersionInformation)( + THIS_ + __in ULONG OutputControl + ) PURE; + + // In user-mode debugging sessions the + // engine will set an event when + // exceptions are continued. This can + // be used to synchronize other processes + // with the debuggers handling of events. + // For example, this is used to support + // the e argument to ntsd. + STDMETHOD(GetNotifyEventHandle)( + THIS_ + __out PULONG64 Handle + ) PURE; + STDMETHOD(SetNotifyEventHandle)( + THIS_ + __in ULONG64 Handle + ) PURE; + + STDMETHOD(Assemble)( + THIS_ + __in ULONG64 Offset, + __in PCSTR Instr, + __out PULONG64 EndOffset + ) PURE; + STDMETHOD(Disassemble)( + THIS_ + __in ULONG64 Offset, + __in ULONG Flags, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG DisassemblySize, + __out PULONG64 EndOffset + ) PURE; + // Returns the value of the effective address + // computed for the last Disassemble, if there + // was one. + STDMETHOD(GetDisassembleEffectiveOffset)( + THIS_ + __out PULONG64 Offset + ) PURE; + // Uses the line prefix if necessary. + STDMETHOD(OutputDisassembly)( + THIS_ + __in ULONG OutputControl, + __in ULONG64 Offset, + __in ULONG Flags, + __out PULONG64 EndOffset + ) PURE; + // Produces multiple lines of disassembly output. + // There will be PreviousLines of disassembly before + // the given offset if a valid disassembly exists. + // In all, there will be TotalLines of output produced. + // The first and last line offsets are returned + // specially and all lines offsets can be retrieved + // through LineOffsets. LineOffsets will contain + // offsets for each line where disassembly started. + // When disassembly of a single instruction takes + // multiple lines the initial offset will be followed + // by DEBUG_INVALID_OFFSET. + // Uses the line prefix. + STDMETHOD(OutputDisassemblyLines)( + THIS_ + __in ULONG OutputControl, + __in ULONG PreviousLines, + __in ULONG TotalLines, + __in ULONG64 Offset, + __in ULONG Flags, + __out_opt PULONG OffsetLine, + __out_opt PULONG64 StartOffset, + __out_opt PULONG64 EndOffset, + __out_ecount_opt(TotalLines) PULONG64 LineOffsets + ) PURE; + // Returns the offset of the start of + // the instruction thats the given + // delta away from the instruction + // at the initial offset. + // This routine does not check for + // validity of the instruction or + // the memory containing it. + STDMETHOD(GetNearInstruction)( + THIS_ + __in ULONG64 Offset, + __in LONG Delta, + __out PULONG64 NearOffset + ) PURE; + + // Offsets can be passed in as zero to use the current + // thread state. + STDMETHOD(GetStackTrace)( + THIS_ + __in ULONG64 FrameOffset, + __in ULONG64 StackOffset, + __in ULONG64 InstructionOffset, + __out_ecount(FramesSize) PDEBUG_STACK_FRAME Frames, + __in ULONG FramesSize, + __out_opt PULONG FramesFilled + ) PURE; + // Does a simple stack trace to determine + // what the current return address is. + STDMETHOD(GetReturnOffset)( + THIS_ + __out PULONG64 Offset + ) PURE; + // If Frames is NULL OutputStackTrace will + // use GetStackTrace to get FramesSize frames + // and then output them. The current register + // values for frame, stack and instruction offsets + // are used. + // Uses the line prefix. + STDMETHOD(OutputStackTrace)( + THIS_ + __in ULONG OutputControl, + __in_ecount_opt(FramesSize) PDEBUG_STACK_FRAME Frames, + __in ULONG FramesSize, + __in ULONG Flags + ) PURE; + + // Returns information about the debuggee such + // as user vs. kernel, dump vs. live, etc. + STDMETHOD(GetDebuggeeType)( + THIS_ + __out PULONG Class, + __out PULONG Qualifier + ) PURE; + // Returns the type of physical processors in + // the machine. + // Returns one of the IMAGE_FILE_MACHINE values. + STDMETHOD(GetActualProcessorType)( + THIS_ + __out PULONG Type + ) PURE; + // Returns the type of processor used in the + // current processor context. + STDMETHOD(GetExecutingProcessorType)( + THIS_ + __out PULONG Type + ) PURE; + // Query all the possible processor types that + // may be encountered during this debug session. + STDMETHOD(GetNumberPossibleExecutingProcessorTypes)( + THIS_ + __out PULONG Number + ) PURE; + STDMETHOD(GetPossibleExecutingProcessorTypes)( + THIS_ + __in ULONG Start, + __in ULONG Count, + __out_ecount(Count) PULONG Types + ) PURE; + // Get the number of actual processors in + // the machine. + STDMETHOD(GetNumberProcessors)( + THIS_ + __out PULONG Number + ) PURE; + // PlatformId is one of the VER_PLATFORM values. + // Major and minor are as given in the NT + // kernel debugger protocol. + // ServicePackString and ServicePackNumber indicate the + // system service pack level. ServicePackNumber is not + // available in some sessions where the service pack level + // is only expressed as a string. The service pack information + // will be empty if the system does not have a service pack + // applied. + // The build string is string information identifying the + // particular build of the system. The build string is + // empty if the system has no particular identifying + // information. + STDMETHOD(GetSystemVersion)( + THIS_ + __out PULONG PlatformId, + __out PULONG Major, + __out PULONG Minor, + __out_ecount_opt(ServicePackStringSize) PSTR ServicePackString, + __in ULONG ServicePackStringSize, + __out_opt PULONG ServicePackStringUsed, + __out PULONG ServicePackNumber, + __out_ecount_opt(BuildStringSize) PSTR BuildString, + __in ULONG BuildStringSize, + __out_opt PULONG BuildStringUsed + ) PURE; + // Returns the page size for the currently executing + // processor context. The page size may vary between + // processor types. + STDMETHOD(GetPageSize)( + THIS_ + __out PULONG Size + ) PURE; + // Returns S_OK if the current processor context uses + // 64-bit addresses, otherwise S_FALSE. + STDMETHOD(IsPointer64Bit)( + THIS + ) PURE; + // Reads the bugcheck data area and returns the + // current contents. This method only works + // in kernel debugging sessions. + STDMETHOD(ReadBugCheckData)( + THIS_ + __out PULONG Code, + __out PULONG64 Arg1, + __out PULONG64 Arg2, + __out PULONG64 Arg3, + __out PULONG64 Arg4 + ) PURE; + + // Query all the processor types supported by + // the engine. This is a complete list and is + // not related to the machine running the engine + // or the debuggee. + STDMETHOD(GetNumberSupportedProcessorTypes)( + THIS_ + __out PULONG Number + ) PURE; + STDMETHOD(GetSupportedProcessorTypes)( + THIS_ + __in ULONG Start, + __in ULONG Count, + __out_ecount(Count) PULONG Types + ) PURE; + // Returns a full, descriptive name and an + // abbreviated name for a processor type. + STDMETHOD(GetProcessorTypeNames)( + THIS_ + __in ULONG Type, + __out_ecount_opt(FullNameBufferSize) PSTR FullNameBuffer, + __in ULONG FullNameBufferSize, + __out_opt PULONG FullNameSize, + __out_ecount_opt(AbbrevNameBufferSize) PSTR AbbrevNameBuffer, + __in ULONG AbbrevNameBufferSize, + __out_opt PULONG AbbrevNameSize + ) PURE; + + // Gets and sets the type of processor to + // use when doing things like setting + // breakpoints, accessing registers, + // getting stack traces and so on. + STDMETHOD(GetEffectiveProcessorType)( + THIS_ + __out PULONG Type + ) PURE; + STDMETHOD(SetEffectiveProcessorType)( + THIS_ + __in ULONG Type + ) PURE; + + // Returns information about whether and how + // the debuggee is running. Status will + // be GO if the debuggee is running and + // BREAK if it isnt. + // If no debuggee exists the status is + // NO_DEBUGGEE. + // This method is reentrant. + STDMETHOD(GetExecutionStatus)( + THIS_ + __out PULONG Status + ) PURE; + // Changes the execution status of the + // engine from stopped to running. + // Status must be one of the go or step + // status values. + STDMETHOD(SetExecutionStatus)( + THIS_ + __in ULONG Status + ) PURE; + + // Controls what code interpretation level the debugger + // runs at. The debugger checks the code level when + // deciding whether to step by a source line or + // assembly instruction along with other related operations. + STDMETHOD(GetCodeLevel)( + THIS_ + __out PULONG Level + ) PURE; + STDMETHOD(SetCodeLevel)( + THIS_ + __in ULONG Level + ) PURE; + + // Gets and sets engine control flags. + // These methods are reentrant. + STDMETHOD(GetEngineOptions)( + THIS_ + __out PULONG Options + ) PURE; + STDMETHOD(AddEngineOptions)( + THIS_ + __in ULONG Options + ) PURE; + STDMETHOD(RemoveEngineOptions)( + THIS_ + __in ULONG Options + ) PURE; + STDMETHOD(SetEngineOptions)( + THIS_ + __in ULONG Options + ) PURE; + + // Gets and sets control values for + // handling system error events. + // If the system error level is less + // than or equal to the given levels + // the error may be displayed and + // the default break for the event + // may be set. + STDMETHOD(GetSystemErrorControl)( + THIS_ + __out PULONG OutputLevel, + __out PULONG BreakLevel + ) PURE; + STDMETHOD(SetSystemErrorControl)( + THIS_ + __in ULONG OutputLevel, + __in ULONG BreakLevel + ) PURE; + + // The command processor supports simple + // string replacement macros in Evaluate and + // Execute. There are currently ten macro + // slots available. Slots 0-9 map to + // the command invocations $u0-$u9. + STDMETHOD(GetTextMacro)( + THIS_ + __in ULONG Slot, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG MacroSize + ) PURE; + STDMETHOD(SetTextMacro)( + THIS_ + __in ULONG Slot, + __in PCSTR Macro + ) PURE; + + // Controls the default number radix used + // in expressions and commands. + STDMETHOD(GetRadix)( + THIS_ + __out PULONG Radix + ) PURE; + STDMETHOD(SetRadix)( + THIS_ + __in ULONG Radix + ) PURE; + + // Evaluates the given expression string and + // returns the resulting value. + // If DesiredType is DEBUG_VALUE_INVALID then + // the natural type is used. + // RemainderIndex, if provided, is set to the index + // of the first character in the input string that was + // not used when evaluating the expression. + STDMETHOD(Evaluate)( + THIS_ + __in PCSTR Expression, + __in ULONG DesiredType, + __out PDEBUG_VALUE Value, + __out_opt PULONG RemainderIndex + ) PURE; + // Attempts to convert the input value to a value + // of the requested type in the output value. + // Conversions can fail if no conversion exists. + // Successful conversions may be lossy. + STDMETHOD(CoerceValue)( + THIS_ + __in PDEBUG_VALUE In, + __in ULONG OutType, + __out PDEBUG_VALUE Out + ) PURE; + STDMETHOD(CoerceValues)( + THIS_ + __in ULONG Count, + __in_ecount(Count) PDEBUG_VALUE In, + __in_ecount(Count) PULONG OutTypes, + __out_ecount(Count) PDEBUG_VALUE Out + ) PURE; + + // Executes the given command string. + // If the string has multiple commands + // Execute will not return until all + // of them have been executed. If this + // requires waiting for the debuggee to + // execute an internal wait will be done + // so Execute can take an arbitrary amount + // of time. + STDMETHOD(Execute)( + THIS_ + __in ULONG OutputControl, + __in PCSTR Command, + __in ULONG Flags + ) PURE; + // Executes the given command file by + // reading a line at a time and processing + // it with Execute. + STDMETHOD(ExecuteCommandFile)( + THIS_ + __in ULONG OutputControl, + __in PCSTR CommandFile, + __in ULONG Flags + ) PURE; + + // Breakpoint interfaces are described + // elsewhere in this section. + STDMETHOD(GetNumberBreakpoints)( + THIS_ + __out PULONG Number + ) PURE; + // It is possible for this retrieval function to + // fail even with an index within the number of + // existing breakpoints if the breakpoint is + // a private breakpoint. + STDMETHOD(GetBreakpointByIndex)( + THIS_ + __in ULONG Index, + __out PDEBUG_BREAKPOINT* Bp + ) PURE; + STDMETHOD(GetBreakpointById)( + THIS_ + __in ULONG Id, + __out PDEBUG_BREAKPOINT* Bp + ) PURE; + // If Ids is non-NULL the Count breakpoints + // referred to in the Ids array are returned, + // otherwise breakpoints from index Start to + // Start + Count 1 are returned. + STDMETHOD(GetBreakpointParameters)( + THIS_ + __in ULONG Count, + __in_ecount_opt(Count) PULONG Ids, + __in ULONG Start, + __out_ecount(Count) PDEBUG_BREAKPOINT_PARAMETERS Params + ) PURE; + // Breakpoints are created empty and disabled. + // When their parameters have been set they + // should be enabled by setting the ENABLE flag. + // If DesiredId is DEBUG_ANY_ID then the + // engine picks an unused ID. If DesiredId + // is any other number the engine attempts + // to use the given ID for the breakpoint. + // If another breakpoint exists with that ID + // the call will fail. + STDMETHOD(AddBreakpoint)( + THIS_ + __in ULONG Type, + __in ULONG DesiredId, + __out PDEBUG_BREAKPOINT* Bp + ) PURE; + // Breakpoint interface is invalid after this call. + STDMETHOD(RemoveBreakpoint)( + THIS_ + __in PDEBUG_BREAKPOINT Bp + ) PURE; + + // Control and use extension DLLs. + STDMETHOD(AddExtension)( + THIS_ + __in PCSTR Path, + __in ULONG Flags, + __out PULONG64 Handle + ) PURE; + STDMETHOD(RemoveExtension)( + THIS_ + __in ULONG64 Handle + ) PURE; + STDMETHOD(GetExtensionByPath)( + THIS_ + __in PCSTR Path, + __out PULONG64 Handle + ) PURE; + // If Handle is zero the extension + // chain is walked searching for the + // function. + STDMETHOD(CallExtension)( + THIS_ + __in ULONG64 Handle, + __in PCSTR Function, + __in_opt PCSTR Arguments + ) PURE; + // GetExtensionFunction works like + // GetProcAddress on extension DLLs + // to allow raw function-call-level + // interaction with extension DLLs. + // Such functions do not need to + // follow the standard extension prototype + // if they are not going to be called + // through the text extension interface. + // This function cannot be called remotely. + STDMETHOD(GetExtensionFunction)( + THIS_ + __in ULONG64 Handle, + __in PCSTR FuncName, + __out FARPROC* Function + ) PURE; + // These methods return alternate + // extension interfaces in order to allow + // interface-style extension DLLs to mix in + // older extension calls. + // Structure sizes must be initialized before + // the call. + // These methods cannot be called remotely. + STDMETHOD(GetWindbgExtensionApis32)( + THIS_ + __inout PWINDBG_EXTENSION_APIS32 Api + ) PURE; + STDMETHOD(GetWindbgExtensionApis64)( + THIS_ + __inout PWINDBG_EXTENSION_APIS64 Api + ) PURE; + + // The engine provides a simple mechanism + // to filter common events. Arbitrarily complicated + // filtering can be done by registering event callbacks + // but simple event filtering only requires + // setting the options of one of the predefined + // event filters. + // Simple event filters are either for specific + // events and therefore have an enumerant or + // they are for an exception and are based on + // the exceptions code. Exception filters + // are further divided into exceptions specially + // handled by the engine, which is a fixed set, + // and arbitrary exceptions. + // All three groups of filters are indexed together + // with the specific filters first, then the specific + // exception filters and finally the arbitrary + // exception filters. + // The first specific exception is the default + // exception. If an exception event occurs for + // an exception without settings the default + // exception settings are used. + STDMETHOD(GetNumberEventFilters)( + THIS_ + __out PULONG SpecificEvents, + __out PULONG SpecificExceptions, + __out PULONG ArbitraryExceptions + ) PURE; + // Some filters have descriptive text associated with them. + STDMETHOD(GetEventFilterText)( + THIS_ + __in ULONG Index, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG TextSize + ) PURE; + // All filters support executing a command when the + // event occurs. + STDMETHOD(GetEventFilterCommand)( + THIS_ + __in ULONG Index, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG CommandSize + ) PURE; + STDMETHOD(SetEventFilterCommand)( + THIS_ + __in ULONG Index, + __in PCSTR Command + ) PURE; + STDMETHOD(GetSpecificFilterParameters)( + THIS_ + __in ULONG Start, + __in ULONG Count, + __out_ecount(Count) PDEBUG_SPECIFIC_FILTER_PARAMETERS Params + ) PURE; + STDMETHOD(SetSpecificFilterParameters)( + THIS_ + __in ULONG Start, + __in ULONG Count, + __in_ecount(Count) PDEBUG_SPECIFIC_FILTER_PARAMETERS Params + ) PURE; + // Some specific filters have arguments to further + // qualify their operation. + STDMETHOD(GetSpecificFilterArgument)( + THIS_ + __in ULONG Index, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG ArgumentSize + ) PURE; + STDMETHOD(SetSpecificFilterArgument)( + THIS_ + __in ULONG Index, + __in PCSTR Argument + ) PURE; + // If Codes is non-NULL Start is ignored. + STDMETHOD(GetExceptionFilterParameters)( + THIS_ + __in ULONG Count, + __in_ecount_opt(Count) PULONG Codes, + __in ULONG Start, + __out_ecount(Count) PDEBUG_EXCEPTION_FILTER_PARAMETERS Params + ) PURE; + // The codes in the parameter data control the application + // of the parameter data. If a code is not already in + // the set of filters it is added. If the ExecutionOption + // for a code is REMOVE then the filter is removed. + // Specific exception filters cannot be removed. + STDMETHOD(SetExceptionFilterParameters)( + THIS_ + __in ULONG Count, + __in_ecount(Count) PDEBUG_EXCEPTION_FILTER_PARAMETERS Params + ) PURE; + // Exception filters support an additional command for + // second-chance events. + STDMETHOD(GetExceptionFilterSecondCommand)( + THIS_ + __in ULONG Index, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG CommandSize + ) PURE; + STDMETHOD(SetExceptionFilterSecondCommand)( + THIS_ + __in ULONG Index, + __in PCSTR Command + ) PURE; + + // Yields processing to the engine until + // an event occurs. This method may + // only be called by the thread that started + // the debug session. + // When an event occurs the engine carries + // out all event processing such as calling + // callbacks. + // If the callbacks indicate that execution should + // break the wait will return, otherwise it + // goes back to waiting for a new event. + // If the timeout expires, S_FALSE is returned. + // The timeout is not currently supported for + // kernel debugging. + STDMETHOD(WaitForEvent)( + THIS_ + __in ULONG Flags, + __in ULONG Timeout + ) PURE; + + // Retrieves information about the last event that occurred. + // EventType is one of the event callback mask bits. + // ExtraInformation contains additional event-specific + // information. Not all events have additional information. + STDMETHOD(GetLastEventInformation)( + THIS_ + __out PULONG Type, + __out PULONG ProcessId, + __out PULONG ThreadId, + __out_bcount_opt(ExtraInformationSize) PVOID ExtraInformation, + __in ULONG ExtraInformationSize, + __out_opt PULONG ExtraInformationUsed, + __out_ecount_opt(DescriptionSize) PSTR Description, + __in ULONG DescriptionSize, + __out_opt PULONG DescriptionUsed + ) PURE; + + // IDebugControl2. + + STDMETHOD(GetCurrentTimeDate)( + THIS_ + __out PULONG TimeDate + ) PURE; + // Retrieves the number of seconds since the + // machine started running. + STDMETHOD(GetCurrentSystemUpTime)( + THIS_ + __out PULONG UpTime + ) PURE; + + // If the current session is a dump session, + // retrieves any extended format information. + STDMETHOD(GetDumpFormatFlags)( + THIS_ + __out PULONG FormatFlags + ) PURE; + + // The debugger has been enhanced to allow + // arbitrary text replacements in addition + // to the simple $u0-$u9 text macros. + // Text replacement takes a given source + // text in commands and converts it to the + // given destination text. Replacements + // are named by their source text so that + // only one replacement for a source text + // string can exist. + STDMETHOD(GetNumberTextReplacements)( + THIS_ + __out PULONG NumRepl + ) PURE; + // If SrcText is non-NULL the replacement + // is looked up by source text, otherwise + // Index is used to get the Nth replacement. + STDMETHOD(GetTextReplacement)( + THIS_ + __in_opt PCSTR SrcText, + __in ULONG Index, + __out_ecount_opt(SrcBufferSize) PSTR SrcBuffer, + __in ULONG SrcBufferSize, + __out_opt PULONG SrcSize, + __out_ecount_opt(DstBufferSize) PSTR DstBuffer, + __in ULONG DstBufferSize, + __out_opt PULONG DstSize + ) PURE; + // Setting the destination text to + // NULL removes the alias. + STDMETHOD(SetTextReplacement)( + THIS_ + __in PCSTR SrcText, + __in_opt PCSTR DstText + ) PURE; + STDMETHOD(RemoveTextReplacements)( + THIS + ) PURE; + // Outputs the complete list of current + // replacements. + STDMETHOD(OutputTextReplacements)( + THIS_ + __in ULONG OutputControl, + __in ULONG Flags + ) PURE; + + // IDebugControl3. + + // Control options for assembly and disassembly. + STDMETHOD(GetAssemblyOptions)( + THIS_ + __out PULONG Options + ) PURE; + STDMETHOD(AddAssemblyOptions)( + THIS_ + __in ULONG Options + ) PURE; + STDMETHOD(RemoveAssemblyOptions)( + THIS_ + __in ULONG Options + ) PURE; + STDMETHOD(SetAssemblyOptions)( + THIS_ + __in ULONG Options + ) PURE; + + // Control the expression syntax. + STDMETHOD(GetExpressionSyntax)( + THIS_ + __out PULONG Flags + ) PURE; + STDMETHOD(SetExpressionSyntax)( + THIS_ + __in ULONG Flags + ) PURE; + // Look up a syntax by its abbreviated + // name and set it. + STDMETHOD(SetExpressionSyntaxByName)( + THIS_ + __in PCSTR AbbrevName + ) PURE; + STDMETHOD(GetNumberExpressionSyntaxes)( + THIS_ + __out PULONG Number + ) PURE; + STDMETHOD(GetExpressionSyntaxNames)( + THIS_ + __in ULONG Index, + __out_ecount_opt(FullNameBufferSize) PSTR FullNameBuffer, + __in ULONG FullNameBufferSize, + __out_opt PULONG FullNameSize, + __out_ecount_opt(AbbrevNameBufferSize) PSTR AbbrevNameBuffer, + __in ULONG AbbrevNameBufferSize, + __out_opt PULONG AbbrevNameSize + ) PURE; + + // + // Some debug sessions have only a single + // possible event, such as a snapshot dump + // file; some have dynamic events, such as + // a live debug session; and others may have + // multiple events, such as a dump file that + // contains snapshots from different points + // in time. The following methods allow + // discovery and selection of the available + // events for a session. + // Sessions with one or more static events + // will be able to report all of the events + // when queried. Sessions with dynamic events + // will only report a single event representing + // the current event. + // Switching events constitutes execution and + // changing the current event will alter the + // execution status to a running state, after + // which WaitForEvent must be used to process + // the selected event. + // + + // GetNumberEvents returns S_OK if this is the + // complete set of events possible, such as for + // a static session; or S_FALSE if other events + // may be possible, such as for a dynamic session. + STDMETHOD(GetNumberEvents)( + THIS_ + __out PULONG Events + ) PURE; + // Sessions may have descriptive information for + // the various events available. The amount of + // information varies according to the specific + // session and data. + STDMETHOD(GetEventIndexDescription)( + THIS_ + __in ULONG Index, + __in ULONG Which, + __in_opt PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG DescSize + ) PURE; + STDMETHOD(GetCurrentEventIndex)( + THIS_ + __out PULONG Index + ) PURE; + // SetNextEventIndex works like seek in that + // it can set an absolute or relative index. + // SetNextEventIndex works similarly to SetExecutionStatus + // by putting the session into a running state, after + // which the caller must call WaitForEvent. The + // current event index only changes when WaitForEvent + // is called. + STDMETHOD(SetNextEventIndex)( + THIS_ + __in ULONG Relation, + __in ULONG Value, + __out PULONG NextIndex + ) PURE; +}; + +// +// Log file flags. +// + +#define DEBUG_LOG_DEFAULT 0x00000000 +#define DEBUG_LOG_APPEND 0x00000001 +#define DEBUG_LOG_UNICODE 0x00000002 +#define DEBUG_LOG_DML 0x00000004 + +// +// System version strings. +// + +#define DEBUG_SYSVERSTR_SERVICE_PACK 0x00000000 +#define DEBUG_SYSVERSTR_BUILD 0x00000001 + +// +// GetManagedStatus flags and strings. +// + +#define DEBUG_MANAGED_DISABLED 0x00000000 +#define DEBUG_MANAGED_ALLOWED 0x00000001 +#define DEBUG_MANAGED_DLL_LOADED 0x00000002 + +#define DEBUG_MANSTR_NONE 0x00000000 +#define DEBUG_MANSTR_LOADED_SUPPORT_DLL 0x00000001 +#define DEBUG_MANSTR_LOAD_STATUS 0x00000002 + +// +// ResetManagedStatus flags. +// + +// Reset state to default engine startup state with +// no support loaded. +#define DEBUG_MANRESET_DEFAULT 0x00000000 +// Force managed support DLL load attempt. +#define DEBUG_MANRESET_LOAD_DLL 0x00000001 + +#undef INTERFACE +#define INTERFACE IDebugControl4 +DECLARE_INTERFACE_(IDebugControl4, IUnknown) +{ + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + __in REFIID InterfaceId, + __out PVOID* Interface + ) PURE; + STDMETHOD_(ULONG, AddRef)( + THIS + ) PURE; + STDMETHOD_(ULONG, Release)( + THIS + ) PURE; + + // IDebugControl. + + // Checks for a user interrupt, such a Ctrl-C + // or stop button. + // This method is reentrant. + STDMETHOD(GetInterrupt)( + THIS + ) PURE; + // Registers a user interrupt. + // This method is reentrant. + STDMETHOD(SetInterrupt)( + THIS_ + __in ULONG Flags + ) PURE; + // Interrupting a user-mode process requires + // access to some system resources that the + // process may hold itself, preventing the + // interrupt from occurring. The engine + // will time-out pending interrupt requests + // and simulate an interrupt if necessary. + // These methods control the interrupt timeout. + STDMETHOD(GetInterruptTimeout)( + THIS_ + __out PULONG Seconds + ) PURE; + STDMETHOD(SetInterruptTimeout)( + THIS_ + __in ULONG Seconds + ) PURE; + + STDMETHOD(GetLogFile)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG FileSize, + __out PBOOL Append + ) PURE; + // Opens a log file which collects all + // output. Output from every client except + // those that explicitly disable logging + // goes into the log. + // Opening a log file closes any log file + // already open. + STDMETHOD(OpenLogFile)( + THIS_ + __in PCSTR File, + __in BOOL Append + ) PURE; + STDMETHOD(CloseLogFile)( + THIS + ) PURE; + // Controls what output is logged. + STDMETHOD(GetLogMask)( + THIS_ + __out PULONG Mask + ) PURE; + STDMETHOD(SetLogMask)( + THIS_ + __in ULONG Mask + ) PURE; + + // Input requests input from all clients. + // The first input that is returned is used + // to satisfy the call. Other returned + // input is discarded. + STDMETHOD(Input)( + THIS_ + __out_ecount(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG InputSize + ) PURE; + // This method is used by clients to return + // input when it is available. It will + // return S_OK if the input is used to + // satisfy an Input call and S_FALSE if + // the input is ignored. + // This method is reentrant. + STDMETHOD(ReturnInput)( + THIS_ + __in PCSTR Buffer + ) PURE; + + // Sends output through clients + // output callbacks if the mask is allowed + // by the current output control mask and + // according to the output distribution + // settings. + STDMETHODV(Output)( + THIS_ + __in ULONG Mask, + __in PCSTR Format, + ... + ) PURE; + STDMETHOD(OutputVaList)( + THIS_ + __in ULONG Mask, + __in PCSTR Format, + __in va_list Args + ) PURE; + // The following methods allow direct control + // over the distribution of the given output + // for situations where something other than + // the default is desired. These methods require + // extra work in the engine so they should + // only be used when necessary. + STDMETHODV(ControlledOutput)( + THIS_ + __in ULONG OutputControl, + __in ULONG Mask, + __in PCSTR Format, + ... + ) PURE; + STDMETHOD(ControlledOutputVaList)( + THIS_ + __in ULONG OutputControl, + __in ULONG Mask, + __in PCSTR Format, + __in va_list Args + ) PURE; + + // Displays the standard command-line prompt + // followed by the given output. If Format + // is NULL no additional output is produced. + // Output is produced under the + // DEBUG_OUTPUT_PROMPT mask. + // This method only outputs the prompt; it + // does not get input. + STDMETHODV(OutputPrompt)( + THIS_ + __in ULONG OutputControl, + __in_opt PCSTR Format, + ... + ) PURE; + STDMETHOD(OutputPromptVaList)( + THIS_ + __in ULONG OutputControl, + __in_opt PCSTR Format, + __in va_list Args + ) PURE; + // Gets the text that would be displayed by OutputPrompt. + STDMETHOD(GetPromptText)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG TextSize + ) PURE; + // Outputs information about the current + // debuggee state such as a register + // summary, disassembly at the current PC, + // closest symbol and others. + // Uses the line prefix. + STDMETHOD(OutputCurrentState)( + THIS_ + __in ULONG OutputControl, + __in ULONG Flags + ) PURE; + + // Outputs the debugger and extension version + // information. This method is reentrant. + // Uses the line prefix. + STDMETHOD(OutputVersionInformation)( + THIS_ + __in ULONG OutputControl + ) PURE; + + // In user-mode debugging sessions the + // engine will set an event when + // exceptions are continued. This can + // be used to synchronize other processes + // with the debuggers handling of events. + // For example, this is used to support + // the e argument to ntsd. + STDMETHOD(GetNotifyEventHandle)( + THIS_ + __out PULONG64 Handle + ) PURE; + STDMETHOD(SetNotifyEventHandle)( + THIS_ + __in ULONG64 Handle + ) PURE; + + STDMETHOD(Assemble)( + THIS_ + __in ULONG64 Offset, + __in PCSTR Instr, + __out PULONG64 EndOffset + ) PURE; + STDMETHOD(Disassemble)( + THIS_ + __in ULONG64 Offset, + __in ULONG Flags, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG DisassemblySize, + __out PULONG64 EndOffset + ) PURE; + // Returns the value of the effective address + // computed for the last Disassemble, if there + // was one. + STDMETHOD(GetDisassembleEffectiveOffset)( + THIS_ + __out PULONG64 Offset + ) PURE; + // Uses the line prefix if necessary. + STDMETHOD(OutputDisassembly)( + THIS_ + __in ULONG OutputControl, + __in ULONG64 Offset, + __in ULONG Flags, + __out PULONG64 EndOffset + ) PURE; + // Produces multiple lines of disassembly output. + // There will be PreviousLines of disassembly before + // the given offset if a valid disassembly exists. + // In all, there will be TotalLines of output produced. + // The first and last line offsets are returned + // specially and all lines offsets can be retrieved + // through LineOffsets. LineOffsets will contain + // offsets for each line where disassembly started. + // When disassembly of a single instruction takes + // multiple lines the initial offset will be followed + // by DEBUG_INVALID_OFFSET. + // Uses the line prefix. + STDMETHOD(OutputDisassemblyLines)( + THIS_ + __in ULONG OutputControl, + __in ULONG PreviousLines, + __in ULONG TotalLines, + __in ULONG64 Offset, + __in ULONG Flags, + __out_opt PULONG OffsetLine, + __out_opt PULONG64 StartOffset, + __out_opt PULONG64 EndOffset, + __out_ecount_opt(TotalLines) PULONG64 LineOffsets + ) PURE; + // Returns the offset of the start of + // the instruction thats the given + // delta away from the instruction + // at the initial offset. + // This routine does not check for + // validity of the instruction or + // the memory containing it. + STDMETHOD(GetNearInstruction)( + THIS_ + __in ULONG64 Offset, + __in LONG Delta, + __out PULONG64 NearOffset + ) PURE; + + // Offsets can be passed in as zero to use the current + // thread state. + STDMETHOD(GetStackTrace)( + THIS_ + __in ULONG64 FrameOffset, + __in ULONG64 StackOffset, + __in ULONG64 InstructionOffset, + __out_ecount(FramesSize) PDEBUG_STACK_FRAME Frames, + __in ULONG FramesSize, + __out_opt PULONG FramesFilled + ) PURE; + // Does a simple stack trace to determine + // what the current return address is. + STDMETHOD(GetReturnOffset)( + THIS_ + __out PULONG64 Offset + ) PURE; + // If Frames is NULL OutputStackTrace will + // use GetStackTrace to get FramesSize frames + // and then output them. The current register + // values for frame, stack and instruction offsets + // are used. + // Uses the line prefix. + STDMETHOD(OutputStackTrace)( + THIS_ + __in ULONG OutputControl, + __in_ecount_opt(FramesSize) PDEBUG_STACK_FRAME Frames, + __in ULONG FramesSize, + __in ULONG Flags + ) PURE; + + // Returns information about the debuggee such + // as user vs. kernel, dump vs. live, etc. + STDMETHOD(GetDebuggeeType)( + THIS_ + __out PULONG Class, + __out PULONG Qualifier + ) PURE; + // Returns the type of physical processors in + // the machine. + // Returns one of the IMAGE_FILE_MACHINE values. + STDMETHOD(GetActualProcessorType)( + THIS_ + __out PULONG Type + ) PURE; + // Returns the type of processor used in the + // current processor context. + STDMETHOD(GetExecutingProcessorType)( + THIS_ + __out PULONG Type + ) PURE; + // Query all the possible processor types that + // may be encountered during this debug session. + STDMETHOD(GetNumberPossibleExecutingProcessorTypes)( + THIS_ + __out PULONG Number + ) PURE; + STDMETHOD(GetPossibleExecutingProcessorTypes)( + THIS_ + __in ULONG Start, + __in ULONG Count, + __out_ecount(Count) PULONG Types + ) PURE; + // Get the number of actual processors in + // the machine. + STDMETHOD(GetNumberProcessors)( + THIS_ + __out PULONG Number + ) PURE; + // PlatformId is one of the VER_PLATFORM values. + // Major and minor are as given in the NT + // kernel debugger protocol. + // ServicePackString and ServicePackNumber indicate the + // system service pack level. ServicePackNumber is not + // available in some sessions where the service pack level + // is only expressed as a string. The service pack information + // will be empty if the system does not have a service pack + // applied. + // The build string is string information identifying the + // particular build of the system. The build string is + // empty if the system has no particular identifying + // information. + STDMETHOD(GetSystemVersion)( + THIS_ + __out PULONG PlatformId, + __out PULONG Major, + __out PULONG Minor, + __out_ecount_opt(ServicePackStringSize) PSTR ServicePackString, + __in ULONG ServicePackStringSize, + __out_opt PULONG ServicePackStringUsed, + __out PULONG ServicePackNumber, + __out_ecount_opt(BuildStringSize) PSTR BuildString, + __in ULONG BuildStringSize, + __out_opt PULONG BuildStringUsed + ) PURE; + // Returns the page size for the currently executing + // processor context. The page size may vary between + // processor types. + STDMETHOD(GetPageSize)( + THIS_ + __out PULONG Size + ) PURE; + // Returns S_OK if the current processor context uses + // 64-bit addresses, otherwise S_FALSE. + STDMETHOD(IsPointer64Bit)( + THIS + ) PURE; + // Reads the bugcheck data area and returns the + // current contents. This method only works + // in kernel debugging sessions. + STDMETHOD(ReadBugCheckData)( + THIS_ + __out PULONG Code, + __out PULONG64 Arg1, + __out PULONG64 Arg2, + __out PULONG64 Arg3, + __out PULONG64 Arg4 + ) PURE; + + // Query all the processor types supported by + // the engine. This is a complete list and is + // not related to the machine running the engine + // or the debuggee. + STDMETHOD(GetNumberSupportedProcessorTypes)( + THIS_ + __out PULONG Number + ) PURE; + STDMETHOD(GetSupportedProcessorTypes)( + THIS_ + __in ULONG Start, + __in ULONG Count, + __out_ecount(Count) PULONG Types + ) PURE; + // Returns a full, descriptive name and an + // abbreviated name for a processor type. + STDMETHOD(GetProcessorTypeNames)( + THIS_ + __in ULONG Type, + __out_ecount_opt(FullNameBufferSize) PSTR FullNameBuffer, + __in ULONG FullNameBufferSize, + __out_opt PULONG FullNameSize, + __out_ecount_opt(AbbrevNameBufferSize) PSTR AbbrevNameBuffer, + __in ULONG AbbrevNameBufferSize, + __out_opt PULONG AbbrevNameSize + ) PURE; + + // Gets and sets the type of processor to + // use when doing things like setting + // breakpoints, accessing registers, + // getting stack traces and so on. + STDMETHOD(GetEffectiveProcessorType)( + THIS_ + __out PULONG Type + ) PURE; + STDMETHOD(SetEffectiveProcessorType)( + THIS_ + __in ULONG Type + ) PURE; + + // Returns information about whether and how + // the debuggee is running. Status will + // be GO if the debuggee is running and + // BREAK if it isnt. + // If no debuggee exists the status is + // NO_DEBUGGEE. + // This method is reentrant. + STDMETHOD(GetExecutionStatus)( + THIS_ + __out PULONG Status + ) PURE; + // Changes the execution status of the + // engine from stopped to running. + // Status must be one of the go or step + // status values. + STDMETHOD(SetExecutionStatus)( + THIS_ + __in ULONG Status + ) PURE; + + // Controls what code interpretation level the debugger + // runs at. The debugger checks the code level when + // deciding whether to step by a source line or + // assembly instruction along with other related operations. + STDMETHOD(GetCodeLevel)( + THIS_ + __out PULONG Level + ) PURE; + STDMETHOD(SetCodeLevel)( + THIS_ + __in ULONG Level + ) PURE; + + // Gets and sets engine control flags. + // These methods are reentrant. + STDMETHOD(GetEngineOptions)( + THIS_ + __out PULONG Options + ) PURE; + STDMETHOD(AddEngineOptions)( + THIS_ + __in ULONG Options + ) PURE; + STDMETHOD(RemoveEngineOptions)( + THIS_ + __in ULONG Options + ) PURE; + STDMETHOD(SetEngineOptions)( + THIS_ + __in ULONG Options + ) PURE; + + // Gets and sets control values for + // handling system error events. + // If the system error level is less + // than or equal to the given levels + // the error may be displayed and + // the default break for the event + // may be set. + STDMETHOD(GetSystemErrorControl)( + THIS_ + __out PULONG OutputLevel, + __out PULONG BreakLevel + ) PURE; + STDMETHOD(SetSystemErrorControl)( + THIS_ + __in ULONG OutputLevel, + __in ULONG BreakLevel + ) PURE; + + // The command processor supports simple + // string replacement macros in Evaluate and + // Execute. There are currently ten macro + // slots available. Slots 0-9 map to + // the command invocations $u0-$u9. + STDMETHOD(GetTextMacro)( + THIS_ + __in ULONG Slot, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG MacroSize + ) PURE; + STDMETHOD(SetTextMacro)( + THIS_ + __in ULONG Slot, + __in PCSTR Macro + ) PURE; + + // Controls the default number radix used + // in expressions and commands. + STDMETHOD(GetRadix)( + THIS_ + __out PULONG Radix + ) PURE; + STDMETHOD(SetRadix)( + THIS_ + __in ULONG Radix + ) PURE; + + // Evaluates the given expression string and + // returns the resulting value. + // If DesiredType is DEBUG_VALUE_INVALID then + // the natural type is used. + // RemainderIndex, if provided, is set to the index + // of the first character in the input string that was + // not used when evaluating the expression. + STDMETHOD(Evaluate)( + THIS_ + __in PCSTR Expression, + __in ULONG DesiredType, + __out PDEBUG_VALUE Value, + __out_opt PULONG RemainderIndex + ) PURE; + // Attempts to convert the input value to a value + // of the requested type in the output value. + // Conversions can fail if no conversion exists. + // Successful conversions may be lossy. + STDMETHOD(CoerceValue)( + THIS_ + __in PDEBUG_VALUE In, + __in ULONG OutType, + __out PDEBUG_VALUE Out + ) PURE; + STDMETHOD(CoerceValues)( + THIS_ + __in ULONG Count, + __in_ecount(Count) PDEBUG_VALUE In, + __in_ecount(Count) PULONG OutTypes, + __out_ecount(Count) PDEBUG_VALUE Out + ) PURE; + + // Executes the given command string. + // If the string has multiple commands + // Execute will not return until all + // of them have been executed. If this + // requires waiting for the debuggee to + // execute an internal wait will be done + // so Execute can take an arbitrary amount + // of time. + STDMETHOD(Execute)( + THIS_ + __in ULONG OutputControl, + __in PCSTR Command, + __in ULONG Flags + ) PURE; + // Executes the given command file by + // reading a line at a time and processing + // it with Execute. + STDMETHOD(ExecuteCommandFile)( + THIS_ + __in ULONG OutputControl, + __in PCSTR CommandFile, + __in ULONG Flags + ) PURE; + + // Breakpoint interfaces are described + // elsewhere in this section. + STDMETHOD(GetNumberBreakpoints)( + THIS_ + __out PULONG Number + ) PURE; + // It is possible for this retrieval function to + // fail even with an index within the number of + // existing breakpoints if the breakpoint is + // a private breakpoint. + STDMETHOD(GetBreakpointByIndex)( + THIS_ + __in ULONG Index, + __out PDEBUG_BREAKPOINT* Bp + ) PURE; + STDMETHOD(GetBreakpointById)( + THIS_ + __in ULONG Id, + __out PDEBUG_BREAKPOINT* Bp + ) PURE; + // If Ids is non-NULL the Count breakpoints + // referred to in the Ids array are returned, + // otherwise breakpoints from index Start to + // Start + Count 1 are returned. + STDMETHOD(GetBreakpointParameters)( + THIS_ + __in ULONG Count, + __in_ecount_opt(Count) PULONG Ids, + __in ULONG Start, + __out_ecount(Count) PDEBUG_BREAKPOINT_PARAMETERS Params + ) PURE; + // Breakpoints are created empty and disabled. + // When their parameters have been set they + // should be enabled by setting the ENABLE flag. + // If DesiredId is DEBUG_ANY_ID then the + // engine picks an unused ID. If DesiredId + // is any other number the engine attempts + // to use the given ID for the breakpoint. + // If another breakpoint exists with that ID + // the call will fail. + STDMETHOD(AddBreakpoint)( + THIS_ + __in ULONG Type, + __in ULONG DesiredId, + __out PDEBUG_BREAKPOINT* Bp + ) PURE; + // Breakpoint interface is invalid after this call. + STDMETHOD(RemoveBreakpoint)( + THIS_ + __in PDEBUG_BREAKPOINT Bp + ) PURE; + + // Control and use extension DLLs. + STDMETHOD(AddExtension)( + THIS_ + __in PCSTR Path, + __in ULONG Flags, + __out PULONG64 Handle + ) PURE; + STDMETHOD(RemoveExtension)( + THIS_ + __in ULONG64 Handle + ) PURE; + STDMETHOD(GetExtensionByPath)( + THIS_ + __in PCSTR Path, + __out PULONG64 Handle + ) PURE; + // If Handle is zero the extension + // chain is walked searching for the + // function. + STDMETHOD(CallExtension)( + THIS_ + __in ULONG64 Handle, + __in PCSTR Function, + __in_opt PCSTR Arguments + ) PURE; + // GetExtensionFunction works like + // GetProcAddress on extension DLLs + // to allow raw function-call-level + // interaction with extension DLLs. + // Such functions do not need to + // follow the standard extension prototype + // if they are not going to be called + // through the text extension interface. + // This function cannot be called remotely. + STDMETHOD(GetExtensionFunction)( + THIS_ + __in ULONG64 Handle, + __in PCSTR FuncName, + __out FARPROC* Function + ) PURE; + // These methods return alternate + // extension interfaces in order to allow + // interface-style extension DLLs to mix in + // older extension calls. + // Structure sizes must be initialized before + // the call. + // These methods cannot be called remotely. + STDMETHOD(GetWindbgExtensionApis32)( + THIS_ + __inout PWINDBG_EXTENSION_APIS32 Api + ) PURE; + STDMETHOD(GetWindbgExtensionApis64)( + THIS_ + __inout PWINDBG_EXTENSION_APIS64 Api + ) PURE; + + // The engine provides a simple mechanism + // to filter common events. Arbitrarily complicated + // filtering can be done by registering event callbacks + // but simple event filtering only requires + // setting the options of one of the predefined + // event filters. + // Simple event filters are either for specific + // events and therefore have an enumerant or + // they are for an exception and are based on + // the exceptions code. Exception filters + // are further divided into exceptions specially + // handled by the engine, which is a fixed set, + // and arbitrary exceptions. + // All three groups of filters are indexed together + // with the specific filters first, then the specific + // exception filters and finally the arbitrary + // exception filters. + // The first specific exception is the default + // exception. If an exception event occurs for + // an exception without settings the default + // exception settings are used. + STDMETHOD(GetNumberEventFilters)( + THIS_ + __out PULONG SpecificEvents, + __out PULONG SpecificExceptions, + __out PULONG ArbitraryExceptions + ) PURE; + // Some filters have descriptive text associated with them. + STDMETHOD(GetEventFilterText)( + THIS_ + __in ULONG Index, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG TextSize + ) PURE; + // All filters support executing a command when the + // event occurs. + STDMETHOD(GetEventFilterCommand)( + THIS_ + __in ULONG Index, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG CommandSize + ) PURE; + STDMETHOD(SetEventFilterCommand)( + THIS_ + __in ULONG Index, + __in PCSTR Command + ) PURE; + STDMETHOD(GetSpecificFilterParameters)( + THIS_ + __in ULONG Start, + __in ULONG Count, + __out_ecount(Count) PDEBUG_SPECIFIC_FILTER_PARAMETERS Params + ) PURE; + STDMETHOD(SetSpecificFilterParameters)( + THIS_ + __in ULONG Start, + __in ULONG Count, + __in_ecount(Count) PDEBUG_SPECIFIC_FILTER_PARAMETERS Params + ) PURE; + // Some specific filters have arguments to further + // qualify their operation. + STDMETHOD(GetSpecificFilterArgument)( + THIS_ + __in ULONG Index, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG ArgumentSize + ) PURE; + STDMETHOD(SetSpecificFilterArgument)( + THIS_ + __in ULONG Index, + __in PCSTR Argument + ) PURE; + // If Codes is non-NULL Start is ignored. + STDMETHOD(GetExceptionFilterParameters)( + THIS_ + __in ULONG Count, + __in_ecount_opt(Count) PULONG Codes, + __in ULONG Start, + __out_ecount(Count) PDEBUG_EXCEPTION_FILTER_PARAMETERS Params + ) PURE; + // The codes in the parameter data control the application + // of the parameter data. If a code is not already in + // the set of filters it is added. If the ExecutionOption + // for a code is REMOVE then the filter is removed. + // Specific exception filters cannot be removed. + STDMETHOD(SetExceptionFilterParameters)( + THIS_ + __in ULONG Count, + __in_ecount(Count) PDEBUG_EXCEPTION_FILTER_PARAMETERS Params + ) PURE; + // Exception filters support an additional command for + // second-chance events. + STDMETHOD(GetExceptionFilterSecondCommand)( + THIS_ + __in ULONG Index, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG CommandSize + ) PURE; + STDMETHOD(SetExceptionFilterSecondCommand)( + THIS_ + __in ULONG Index, + __in PCSTR Command + ) PURE; + + // Yields processing to the engine until + // an event occurs. This method may + // only be called by the thread that started + // the debug session. + // When an event occurs the engine carries + // out all event processing such as calling + // callbacks. + // If the callbacks indicate that execution should + // break the wait will return, otherwise it + // goes back to waiting for a new event. + // If the timeout expires, S_FALSE is returned. + // The timeout is not currently supported for + // kernel debugging. + STDMETHOD(WaitForEvent)( + THIS_ + __in ULONG Flags, + __in ULONG Timeout + ) PURE; + + // Retrieves information about the last event that occurred. + // EventType is one of the event callback mask bits. + // ExtraInformation contains additional event-specific + // information. Not all events have additional information. + STDMETHOD(GetLastEventInformation)( + THIS_ + __out PULONG Type, + __out PULONG ProcessId, + __out PULONG ThreadId, + __out_bcount_opt(ExtraInformationSize) PVOID ExtraInformation, + __in ULONG ExtraInformationSize, + __out_opt PULONG ExtraInformationUsed, + __out_ecount_opt(DescriptionSize) PSTR Description, + __in ULONG DescriptionSize, + __out_opt PULONG DescriptionUsed + ) PURE; + + // IDebugControl2. + + STDMETHOD(GetCurrentTimeDate)( + THIS_ + __out PULONG TimeDate + ) PURE; + // Retrieves the number of seconds since the + // machine started running. + STDMETHOD(GetCurrentSystemUpTime)( + THIS_ + __out PULONG UpTime + ) PURE; + + // If the current session is a dump session, + // retrieves any extended format information. + STDMETHOD(GetDumpFormatFlags)( + THIS_ + __out PULONG FormatFlags + ) PURE; + + // The debugger has been enhanced to allow + // arbitrary text replacements in addition + // to the simple $u0-$u9 text macros. + // Text replacement takes a given source + // text in commands and converts it to the + // given destination text. Replacements + // are named by their source text so that + // only one replacement for a source text + // string can exist. + STDMETHOD(GetNumberTextReplacements)( + THIS_ + __out PULONG NumRepl + ) PURE; + // If SrcText is non-NULL the replacement + // is looked up by source text, otherwise + // Index is used to get the Nth replacement. + STDMETHOD(GetTextReplacement)( + THIS_ + __in_opt PCSTR SrcText, + __in ULONG Index, + __out_ecount_opt(SrcBufferSize) PSTR SrcBuffer, + __in ULONG SrcBufferSize, + __out_opt PULONG SrcSize, + __out_ecount_opt(DstBufferSize) PSTR DstBuffer, + __in ULONG DstBufferSize, + __out_opt PULONG DstSize + ) PURE; + // Setting the destination text to + // NULL removes the alias. + STDMETHOD(SetTextReplacement)( + THIS_ + __in PCSTR SrcText, + __in_opt PCSTR DstText + ) PURE; + STDMETHOD(RemoveTextReplacements)( + THIS + ) PURE; + // Outputs the complete list of current + // replacements. + STDMETHOD(OutputTextReplacements)( + THIS_ + __in ULONG OutputControl, + __in ULONG Flags + ) PURE; + + // IDebugControl3. + + // Control options for assembly and disassembly. + STDMETHOD(GetAssemblyOptions)( + THIS_ + __out PULONG Options + ) PURE; + STDMETHOD(AddAssemblyOptions)( + THIS_ + __in ULONG Options + ) PURE; + STDMETHOD(RemoveAssemblyOptions)( + THIS_ + __in ULONG Options + ) PURE; + STDMETHOD(SetAssemblyOptions)( + THIS_ + __in ULONG Options + ) PURE; + + // Control the expression syntax. + STDMETHOD(GetExpressionSyntax)( + THIS_ + __out PULONG Flags + ) PURE; + STDMETHOD(SetExpressionSyntax)( + THIS_ + __in ULONG Flags + ) PURE; + // Look up a syntax by its abbreviated + // name and set it. + STDMETHOD(SetExpressionSyntaxByName)( + THIS_ + __in PCSTR AbbrevName + ) PURE; + STDMETHOD(GetNumberExpressionSyntaxes)( + THIS_ + __out PULONG Number + ) PURE; + STDMETHOD(GetExpressionSyntaxNames)( + THIS_ + __in ULONG Index, + __out_ecount_opt(FullNameBufferSize) PSTR FullNameBuffer, + __in ULONG FullNameBufferSize, + __out_opt PULONG FullNameSize, + __out_ecount_opt(AbbrevNameBufferSize) PSTR AbbrevNameBuffer, + __in ULONG AbbrevNameBufferSize, + __out_opt PULONG AbbrevNameSize + ) PURE; + + // + // Some debug sessions have only a single + // possible event, such as a snapshot dump + // file; some have dynamic events, such as + // a live debug session; and others may have + // multiple events, such as a dump file that + // contains snapshots from different points + // in time. The following methods allow + // discovery and selection of the available + // events for a session. + // Sessions with one or more static events + // will be able to report all of the events + // when queried. Sessions with dynamic events + // will only report a single event representing + // the current event. + // Switching events constitutes execution and + // changing the current event will alter the + // execution status to a running state, after + // which WaitForEvent must be used to process + // the selected event. + // + + // GetNumberEvents returns S_OK if this is the + // complete set of events possible, such as for + // a static session; or S_FALSE if other events + // may be possible, such as for a dynamic session. + STDMETHOD(GetNumberEvents)( + THIS_ + __out PULONG Events + ) PURE; + // Sessions may have descriptive information for + // the various events available. The amount of + // information varies according to the specific + // session and data. + STDMETHOD(GetEventIndexDescription)( + THIS_ + __in ULONG Index, + __in ULONG Which, + __in_opt PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG DescSize + ) PURE; + STDMETHOD(GetCurrentEventIndex)( + THIS_ + __out PULONG Index + ) PURE; + // SetNextEventIndex works like seek in that + // it can set an absolute or relative index. + // SetNextEventIndex works similarly to SetExecutionStatus + // by putting the session into a running state, after + // which the caller must call WaitForEvent. The + // current event index only changes when WaitForEvent + // is called. + STDMETHOD(SetNextEventIndex)( + THIS_ + __in ULONG Relation, + __in ULONG Value, + __out PULONG NextIndex + ) PURE; + + // IDebugControl4. + + STDMETHOD(GetLogFileWide)( + THIS_ + __out_ecount_opt(BufferSize) PWSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG FileSize, + __out PBOOL Append + ) PURE; + STDMETHOD(OpenLogFileWide)( + THIS_ + __in PCWSTR File, + __in BOOL Append + ) PURE; + + STDMETHOD(InputWide)( + THIS_ + __out_ecount(BufferSize) PWSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG InputSize + ) PURE; + STDMETHOD(ReturnInputWide)( + THIS_ + __in PCWSTR Buffer + ) PURE; + + STDMETHODV(OutputWide)( + THIS_ + __in ULONG Mask, + __in PCWSTR Format, + ... + ) PURE; + STDMETHOD(OutputVaListWide)( + THIS_ + __in ULONG Mask, + __in PCWSTR Format, + __in va_list Args + ) PURE; + STDMETHODV(ControlledOutputWide)( + THIS_ + __in ULONG OutputControl, + __in ULONG Mask, + __in PCWSTR Format, + ... + ) PURE; + STDMETHOD(ControlledOutputVaListWide)( + THIS_ + __in ULONG OutputControl, + __in ULONG Mask, + __in PCWSTR Format, + __in va_list Args + ) PURE; + + STDMETHODV(OutputPromptWide)( + THIS_ + __in ULONG OutputControl, + __in_opt PCWSTR Format, + ... + ) PURE; + STDMETHOD(OutputPromptVaListWide)( + THIS_ + __in ULONG OutputControl, + __in_opt PCWSTR Format, + __in va_list Args + ) PURE; + STDMETHOD(GetPromptTextWide)( + THIS_ + __out_ecount_opt(BufferSize) PWSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG TextSize + ) PURE; + + STDMETHOD(AssembleWide)( + THIS_ + __in ULONG64 Offset, + __in PCWSTR Instr, + __out PULONG64 EndOffset + ) PURE; + STDMETHOD(DisassembleWide)( + THIS_ + __in ULONG64 Offset, + __in ULONG Flags, + __out_ecount_opt(BufferSize) PWSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG DisassemblySize, + __out PULONG64 EndOffset + ) PURE; + + STDMETHOD(GetProcessorTypeNamesWide)( + THIS_ + __in ULONG Type, + __out_ecount_opt(FullNameBufferSize) PWSTR FullNameBuffer, + __in ULONG FullNameBufferSize, + __out_opt PULONG FullNameSize, + __out_ecount_opt(AbbrevNameBufferSize) PWSTR AbbrevNameBuffer, + __in ULONG AbbrevNameBufferSize, + __out_opt PULONG AbbrevNameSize + ) PURE; + + STDMETHOD(GetTextMacroWide)( + THIS_ + __in ULONG Slot, + __out_ecount_opt(BufferSize) PWSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG MacroSize + ) PURE; + STDMETHOD(SetTextMacroWide)( + THIS_ + __in ULONG Slot, + __in PCWSTR Macro + ) PURE; + + STDMETHOD(EvaluateWide)( + THIS_ + __in PCWSTR Expression, + __in ULONG DesiredType, + __out PDEBUG_VALUE Value, + __out_opt PULONG RemainderIndex + ) PURE; + + STDMETHOD(ExecuteWide)( + THIS_ + __in ULONG OutputControl, + __in PCWSTR Command, + __in ULONG Flags + ) PURE; + STDMETHOD(ExecuteCommandFileWide)( + THIS_ + __in ULONG OutputControl, + __in PCWSTR CommandFile, + __in ULONG Flags + ) PURE; + + STDMETHOD(GetBreakpointByIndex2)( + THIS_ + __in ULONG Index, + __out PDEBUG_BREAKPOINT2* Bp + ) PURE; + STDMETHOD(GetBreakpointById2)( + THIS_ + __in ULONG Id, + __out PDEBUG_BREAKPOINT2* Bp + ) PURE; + STDMETHOD(AddBreakpoint2)( + THIS_ + __in ULONG Type, + __in ULONG DesiredId, + __out PDEBUG_BREAKPOINT2* Bp + ) PURE; + STDMETHOD(RemoveBreakpoint2)( + THIS_ + __in PDEBUG_BREAKPOINT2 Bp + ) PURE; + + STDMETHOD(AddExtensionWide)( + THIS_ + __in PCWSTR Path, + __in ULONG Flags, + __out PULONG64 Handle + ) PURE; + STDMETHOD(GetExtensionByPathWide)( + THIS_ + __in PCWSTR Path, + __out PULONG64 Handle + ) PURE; + STDMETHOD(CallExtensionWide)( + THIS_ + __in ULONG64 Handle, + __in PCWSTR Function, + __in_opt PCWSTR Arguments + ) PURE; + STDMETHOD(GetExtensionFunctionWide)( + THIS_ + __in ULONG64 Handle, + __in PCWSTR FuncName, + __out FARPROC* Function + ) PURE; + + STDMETHOD(GetEventFilterTextWide)( + THIS_ + __in ULONG Index, + __out_ecount_opt(BufferSize) PWSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG TextSize + ) PURE; + STDMETHOD(GetEventFilterCommandWide)( + THIS_ + __in ULONG Index, + __out_ecount_opt(BufferSize) PWSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG CommandSize + ) PURE; + STDMETHOD(SetEventFilterCommandWide)( + THIS_ + __in ULONG Index, + __in PCWSTR Command + ) PURE; + STDMETHOD(GetSpecificFilterArgumentWide)( + THIS_ + __in ULONG Index, + __out_ecount_opt(BufferSize) PWSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG ArgumentSize + ) PURE; + STDMETHOD(SetSpecificFilterArgumentWide)( + THIS_ + __in ULONG Index, + __in PCWSTR Argument + ) PURE; + STDMETHOD(GetExceptionFilterSecondCommandWide)( + THIS_ + __in ULONG Index, + __out_ecount_opt(BufferSize) PWSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG CommandSize + ) PURE; + STDMETHOD(SetExceptionFilterSecondCommandWide)( + THIS_ + __in ULONG Index, + __in PCWSTR Command + ) PURE; + + STDMETHOD(GetLastEventInformationWide)( + THIS_ + __out PULONG Type, + __out PULONG ProcessId, + __out PULONG ThreadId, + __out_bcount_opt(ExtraInformationSize) PVOID ExtraInformation, + __in ULONG ExtraInformationSize, + __out_opt PULONG ExtraInformationUsed, + __out_ecount_opt(DescriptionSize) PWSTR Description, + __in ULONG DescriptionSize, + __out_opt PULONG DescriptionUsed + ) PURE; + + STDMETHOD(GetTextReplacementWide)( + THIS_ + __in_opt PCWSTR SrcText, + __in ULONG Index, + __out_ecount_opt(SrcBufferSize) PWSTR SrcBuffer, + __in ULONG SrcBufferSize, + __out_opt PULONG SrcSize, + __out_ecount_opt(DstBufferSize) PWSTR DstBuffer, + __in ULONG DstBufferSize, + __out_opt PULONG DstSize + ) PURE; + STDMETHOD(SetTextReplacementWide)( + THIS_ + __in PCWSTR SrcText, + __in_opt PCWSTR DstText + ) PURE; + + STDMETHOD(SetExpressionSyntaxByNameWide)( + THIS_ + __in PCWSTR AbbrevName + ) PURE; + STDMETHOD(GetExpressionSyntaxNamesWide)( + THIS_ + __in ULONG Index, + __out_ecount_opt(FullNameBufferSize) PWSTR FullNameBuffer, + __in ULONG FullNameBufferSize, + __out_opt PULONG FullNameSize, + __out_ecount_opt(AbbrevNameBufferSize) PWSTR AbbrevNameBuffer, + __in ULONG AbbrevNameBufferSize, + __out_opt PULONG AbbrevNameSize + ) PURE; + + STDMETHOD(GetEventIndexDescriptionWide)( + THIS_ + __in ULONG Index, + __in ULONG Which, + __in_opt PWSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG DescSize + ) PURE; + + STDMETHOD(GetLogFile2)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG FileSize, + __out PULONG Flags + ) PURE; + STDMETHOD(OpenLogFile2)( + THIS_ + __in PCSTR File, + __in ULONG Flags + ) PURE; + STDMETHOD(GetLogFile2Wide)( + THIS_ + __out_ecount_opt(BufferSize) PWSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG FileSize, + __out PULONG Flags + ) PURE; + STDMETHOD(OpenLogFile2Wide)( + THIS_ + __in PCWSTR File, + __in ULONG Flags + ) PURE; + + // GetSystemVersion always returns the kd + // major/minor version numbers, which are + // different than the Win32 version numbers. + // GetSystemVersionValues can be used + // to determine the Win32 version values. + STDMETHOD(GetSystemVersionValues)( + THIS_ + __out PULONG PlatformId, + __out PULONG Win32Major, + __out PULONG Win32Minor, + __out_opt PULONG KdMajor, + __out_opt PULONG KdMinor + ) PURE; + // Strings are selected with DEBUG_SYSVERSTR_*. + STDMETHOD(GetSystemVersionString)( + THIS_ + __in ULONG Which, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG StringSize + ) PURE; + STDMETHOD(GetSystemVersionStringWide)( + THIS_ + __in ULONG Which, + __out_ecount_opt(BufferSize) PWSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG StringSize + ) PURE; + + // Stack tracing with a full initial context + // and full context return for each frame. + // The FrameContextsSize parameter is the total + // byte size of FrameContexts. FrameContextsEntrySize + // gives the byte size of each entry in + // FrameContexts. + STDMETHOD(GetContextStackTrace)( + THIS_ + __in_bcount_opt(StartContextSize) PVOID StartContext, + __in ULONG StartContextSize, + __out_ecount_opt(FramesSize) PDEBUG_STACK_FRAME Frames, + __in ULONG FramesSize, + __out_bcount_opt(FrameContextsSize) PVOID FrameContexts, + __in ULONG FrameContextsSize, + __in ULONG FrameContextsEntrySize, + __out_opt PULONG FramesFilled + ) PURE; + STDMETHOD(OutputContextStackTrace)( + THIS_ + __in ULONG OutputControl, + __in_ecount(FramesSize) PDEBUG_STACK_FRAME Frames, + __in ULONG FramesSize, + __in_bcount(FrameContextsSize) PVOID FrameContexts, + __in ULONG FrameContextsSize, + __in ULONG FrameContextsEntrySize, + __in ULONG Flags + ) PURE; + + // Some targets, such as user-mode minidump files, + // have separate "event of interest" information + // stored within them. This method allows + // access to that information. + STDMETHOD(GetStoredEventInformation)( + THIS_ + __out PULONG Type, + __out PULONG ProcessId, + __out PULONG ThreadId, + __out_bcount_opt(ContextSize) PVOID Context, + __in ULONG ContextSize, + __out_opt PULONG ContextUsed, + __out_bcount_opt(ExtraInformationSize) PVOID ExtraInformation, + __in ULONG ExtraInformationSize, + __out_opt PULONG ExtraInformationUsed + ) PURE; + + // Managed debugging support relies on debugging + // functionality provided by the Common Language Runtime. + // This method provides feedback on the engine's + // use of the runtime debugging APIs. + STDMETHOD(GetManagedStatus)( + THIS_ + __out_opt PULONG Flags, + __in ULONG WhichString, + __out_ecount_opt(StringSize) PSTR String, + __in ULONG StringSize, + __out_opt PULONG StringNeeded + ) PURE; + STDMETHOD(GetManagedStatusWide)( + THIS_ + __out_opt PULONG Flags, + __in ULONG WhichString, + __out_ecount_opt(StringSize) PWSTR String, + __in ULONG StringSize, + __out_opt PULONG StringNeeded + ) PURE; + // Clears and reinitializes the engine's + // managed code debugging support. + STDMETHOD(ResetManagedStatus)( + THIS_ + __in ULONG Flags + ) PURE; +}; + +//---------------------------------------------------------------------------- +// +// IDebugDataSpaces. +// +//---------------------------------------------------------------------------- + +// Data space indices for callbacks and other methods. +#define DEBUG_DATA_SPACE_VIRTUAL 0 +#define DEBUG_DATA_SPACE_PHYSICAL 1 +#define DEBUG_DATA_SPACE_CONTROL 2 +#define DEBUG_DATA_SPACE_IO 3 +#define DEBUG_DATA_SPACE_MSR 4 +#define DEBUG_DATA_SPACE_BUS_DATA 5 +#define DEBUG_DATA_SPACE_DEBUGGER_DATA 6 +// Count of data spaces. +#define DEBUG_DATA_SPACE_COUNT 7 + +// Indices for ReadDebuggerData interface +#define DEBUG_DATA_KernBase 24 +#define DEBUG_DATA_BreakpointWithStatusAddr 32 +#define DEBUG_DATA_SavedContextAddr 40 +#define DEBUG_DATA_KiCallUserModeAddr 56 +#define DEBUG_DATA_KeUserCallbackDispatcherAddr 64 +#define DEBUG_DATA_PsLoadedModuleListAddr 72 +#define DEBUG_DATA_PsActiveProcessHeadAddr 80 +#define DEBUG_DATA_PspCidTableAddr 88 +#define DEBUG_DATA_ExpSystemResourcesListAddr 96 +#define DEBUG_DATA_ExpPagedPoolDescriptorAddr 104 +#define DEBUG_DATA_ExpNumberOfPagedPoolsAddr 112 +#define DEBUG_DATA_KeTimeIncrementAddr 120 +#define DEBUG_DATA_KeBugCheckCallbackListHeadAddr 128 +#define DEBUG_DATA_KiBugcheckDataAddr 136 +#define DEBUG_DATA_IopErrorLogListHeadAddr 144 +#define DEBUG_DATA_ObpRootDirectoryObjectAddr 152 +#define DEBUG_DATA_ObpTypeObjectTypeAddr 160 +#define DEBUG_DATA_MmSystemCacheStartAddr 168 +#define DEBUG_DATA_MmSystemCacheEndAddr 176 +#define DEBUG_DATA_MmSystemCacheWsAddr 184 +#define DEBUG_DATA_MmPfnDatabaseAddr 192 +#define DEBUG_DATA_MmSystemPtesStartAddr 200 +#define DEBUG_DATA_MmSystemPtesEndAddr 208 +#define DEBUG_DATA_MmSubsectionBaseAddr 216 +#define DEBUG_DATA_MmNumberOfPagingFilesAddr 224 +#define DEBUG_DATA_MmLowestPhysicalPageAddr 232 +#define DEBUG_DATA_MmHighestPhysicalPageAddr 240 +#define DEBUG_DATA_MmNumberOfPhysicalPagesAddr 248 +#define DEBUG_DATA_MmMaximumNonPagedPoolInBytesAddr 256 +#define DEBUG_DATA_MmNonPagedSystemStartAddr 264 +#define DEBUG_DATA_MmNonPagedPoolStartAddr 272 +#define DEBUG_DATA_MmNonPagedPoolEndAddr 280 +#define DEBUG_DATA_MmPagedPoolStartAddr 288 +#define DEBUG_DATA_MmPagedPoolEndAddr 296 +#define DEBUG_DATA_MmPagedPoolInformationAddr 304 +#define DEBUG_DATA_MmPageSize 312 +#define DEBUG_DATA_MmSizeOfPagedPoolInBytesAddr 320 +#define DEBUG_DATA_MmTotalCommitLimitAddr 328 +#define DEBUG_DATA_MmTotalCommittedPagesAddr 336 +#define DEBUG_DATA_MmSharedCommitAddr 344 +#define DEBUG_DATA_MmDriverCommitAddr 352 +#define DEBUG_DATA_MmProcessCommitAddr 360 +#define DEBUG_DATA_MmPagedPoolCommitAddr 368 +#define DEBUG_DATA_MmExtendedCommitAddr 376 +#define DEBUG_DATA_MmZeroedPageListHeadAddr 384 +#define DEBUG_DATA_MmFreePageListHeadAddr 392 +#define DEBUG_DATA_MmStandbyPageListHeadAddr 400 +#define DEBUG_DATA_MmModifiedPageListHeadAddr 408 +#define DEBUG_DATA_MmModifiedNoWritePageListHeadAddr 416 +#define DEBUG_DATA_MmAvailablePagesAddr 424 +#define DEBUG_DATA_MmResidentAvailablePagesAddr 432 +#define DEBUG_DATA_PoolTrackTableAddr 440 +#define DEBUG_DATA_NonPagedPoolDescriptorAddr 448 +#define DEBUG_DATA_MmHighestUserAddressAddr 456 +#define DEBUG_DATA_MmSystemRangeStartAddr 464 +#define DEBUG_DATA_MmUserProbeAddressAddr 472 +#define DEBUG_DATA_KdPrintCircularBufferAddr 480 +#define DEBUG_DATA_KdPrintCircularBufferEndAddr 488 +#define DEBUG_DATA_KdPrintWritePointerAddr 496 +#define DEBUG_DATA_KdPrintRolloverCountAddr 504 +#define DEBUG_DATA_MmLoadedUserImageListAddr 512 +#define DEBUG_DATA_NtBuildLabAddr 520 +#define DEBUG_DATA_KiNormalSystemCall 528 +#define DEBUG_DATA_KiProcessorBlockAddr 536 +#define DEBUG_DATA_MmUnloadedDriversAddr 544 +#define DEBUG_DATA_MmLastUnloadedDriverAddr 552 +#define DEBUG_DATA_MmTriageActionTakenAddr 560 +#define DEBUG_DATA_MmSpecialPoolTagAddr 568 +#define DEBUG_DATA_KernelVerifierAddr 576 +#define DEBUG_DATA_MmVerifierDataAddr 584 +#define DEBUG_DATA_MmAllocatedNonPagedPoolAddr 592 +#define DEBUG_DATA_MmPeakCommitmentAddr 600 +#define DEBUG_DATA_MmTotalCommitLimitMaximumAddr 608 +#define DEBUG_DATA_CmNtCSDVersionAddr 616 +#define DEBUG_DATA_MmPhysicalMemoryBlockAddr 624 +#define DEBUG_DATA_MmSessionBase 632 +#define DEBUG_DATA_MmSessionSize 640 +#define DEBUG_DATA_MmSystemParentTablePage 648 +#define DEBUG_DATA_MmVirtualTranslationBase 656 +#define DEBUG_DATA_OffsetKThreadNextProcessor 664 +#define DEBUG_DATA_OffsetKThreadTeb 666 +#define DEBUG_DATA_OffsetKThreadKernelStack 668 +#define DEBUG_DATA_OffsetKThreadInitialStack 670 +#define DEBUG_DATA_OffsetKThreadApcProcess 672 +#define DEBUG_DATA_OffsetKThreadState 674 +#define DEBUG_DATA_OffsetKThreadBStore 676 +#define DEBUG_DATA_OffsetKThreadBStoreLimit 678 +#define DEBUG_DATA_SizeEProcess 680 +#define DEBUG_DATA_OffsetEprocessPeb 682 +#define DEBUG_DATA_OffsetEprocessParentCID 684 +#define DEBUG_DATA_OffsetEprocessDirectoryTableBase 686 +#define DEBUG_DATA_SizePrcb 688 +#define DEBUG_DATA_OffsetPrcbDpcRoutine 690 +#define DEBUG_DATA_OffsetPrcbCurrentThread 692 +#define DEBUG_DATA_OffsetPrcbMhz 694 +#define DEBUG_DATA_OffsetPrcbCpuType 696 +#define DEBUG_DATA_OffsetPrcbVendorString 698 +#define DEBUG_DATA_OffsetPrcbProcessorState 700 +#define DEBUG_DATA_OffsetPrcbNumber 702 +#define DEBUG_DATA_SizeEThread 704 +#define DEBUG_DATA_KdPrintCircularBufferPtrAddr 712 +#define DEBUG_DATA_KdPrintBufferSizeAddr 720 +#define DEBUG_DATA_MmBadPagesDetected 800 + +#define DEBUG_DATA_PaeEnabled 100000 +#define DEBUG_DATA_SharedUserData 100008 +#define DEBUG_DATA_ProductType 100016 +#define DEBUG_DATA_SuiteMask 100024 +#define DEBUG_DATA_DumpWriterStatus 100032 +#define DEBUG_DATA_DumpFormatVersion 100040 +#define DEBUG_DATA_DumpWriterVersion 100048 +#define DEBUG_DATA_DumpPowerState 100056 +#define DEBUG_DATA_DumpMmStorage 100064 + +// +// Processor information structures. +// + +typedef struct _DEBUG_PROCESSOR_IDENTIFICATION_ALPHA +{ + ULONG Type; + ULONG Revision; +} DEBUG_PROCESSOR_IDENTIFICATION_ALPHA, *PDEBUG_PROCESSOR_IDENTIFICATION_ALPHA; + +typedef struct _DEBUG_PROCESSOR_IDENTIFICATION_AMD64 +{ + ULONG Family; + ULONG Model; + ULONG Stepping; + CHAR VendorString[16]; +} DEBUG_PROCESSOR_IDENTIFICATION_AMD64, *PDEBUG_PROCESSOR_IDENTIFICATION_AMD64; + +typedef struct _DEBUG_PROCESSOR_IDENTIFICATION_IA64 +{ + ULONG Model; + ULONG Revision; + ULONG Family; + ULONG ArchRev; + CHAR VendorString[16]; +} DEBUG_PROCESSOR_IDENTIFICATION_IA64, *PDEBUG_PROCESSOR_IDENTIFICATION_IA64; + +typedef struct _DEBUG_PROCESSOR_IDENTIFICATION_X86 +{ + ULONG Family; + ULONG Model; + ULONG Stepping; + CHAR VendorString[16]; +} DEBUG_PROCESSOR_IDENTIFICATION_X86, *PDEBUG_PROCESSOR_IDENTIFICATION_X86; + +typedef struct _DEBUG_PROCESSOR_IDENTIFICATION_ARM +{ + ULONG Type; + ULONG Revision; +} DEBUG_PROCESSOR_IDENTIFICATION_ARM, *PDEBUG_PROCESSOR_IDENTIFICATION_ARM; + +typedef union _DEBUG_PROCESSOR_IDENTIFICATION_ALL +{ + DEBUG_PROCESSOR_IDENTIFICATION_ALPHA Alpha; + DEBUG_PROCESSOR_IDENTIFICATION_AMD64 Amd64; + DEBUG_PROCESSOR_IDENTIFICATION_IA64 Ia64; + DEBUG_PROCESSOR_IDENTIFICATION_X86 X86; + DEBUG_PROCESSOR_IDENTIFICATION_ARM Arm; +} DEBUG_PROCESSOR_IDENTIFICATION_ALL, *PDEBUG_PROCESSOR_IDENTIFICATION_ALL; + +// Indices for ReadProcessorSystemData. +#define DEBUG_DATA_KPCR_OFFSET 0 +#define DEBUG_DATA_KPRCB_OFFSET 1 +#define DEBUG_DATA_KTHREAD_OFFSET 2 +#define DEBUG_DATA_BASE_TRANSLATION_VIRTUAL_OFFSET 3 +#define DEBUG_DATA_PROCESSOR_IDENTIFICATION 4 +#define DEBUG_DATA_PROCESSOR_SPEED 5 + +#undef INTERFACE +#define INTERFACE IDebugDataSpaces +DECLARE_INTERFACE_(IDebugDataSpaces, IUnknown) +{ + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + __in REFIID InterfaceId, + __out PVOID* Interface + ) PURE; + STDMETHOD_(ULONG, AddRef)( + THIS + ) PURE; + STDMETHOD_(ULONG, Release)( + THIS + ) PURE; + + // IDebugDataSpaces. + STDMETHOD(ReadVirtual)( + THIS_ + __in ULONG64 Offset, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesRead + ) PURE; + STDMETHOD(WriteVirtual)( + THIS_ + __in ULONG64 Offset, + __in_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesWritten + ) PURE; + // SearchVirtual searches the given virtual + // address range for the given pattern. PatternSize + // gives the byte length of the pattern and PatternGranularity + // controls the granularity of comparisons during + // the search. + // For example, a DWORD-granular search would + // use a pattern granularity of four to search by DWORD + // increments. + STDMETHOD(SearchVirtual)( + THIS_ + __in ULONG64 Offset, + __in ULONG64 Length, + __in_bcount(PatternSize) PVOID Pattern, + __in ULONG PatternSize, + __in ULONG PatternGranularity, + __out PULONG64 MatchOffset + ) PURE; + // These methods are identical to Read/WriteVirtual + // except that they avoid the kernel virtual memory + // cache entirely and are therefore useful for reading + // virtual memory which is inherently volatile, such + // as memory-mapped device areas, without contaminating + // or invalidating the cache. + // In user-mode they are the same as Read/WriteVirtual. + STDMETHOD(ReadVirtualUncached)( + THIS_ + __in ULONG64 Offset, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesRead + ) PURE; + STDMETHOD(WriteVirtualUncached)( + THIS_ + __in ULONG64 Offset, + __in_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesWritten + ) PURE; + // The following two methods are convenience + // methods for accessing pointer values. + // They automatically convert between native pointers + // and canonical 64-bit values as necessary. + // These routines stop at the first failure. + STDMETHOD(ReadPointersVirtual)( + THIS_ + __in ULONG Count, + __in ULONG64 Offset, + __out_ecount(Count) PULONG64 Ptrs + ) PURE; + STDMETHOD(WritePointersVirtual)( + THIS_ + __in ULONG Count, + __in ULONG64 Offset, + __in_ecount(Count) PULONG64 Ptrs + ) PURE; + // All non-virtual data spaces are only + // available when kernel debugging. + STDMETHOD(ReadPhysical)( + THIS_ + __in ULONG64 Offset, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesRead + ) PURE; + STDMETHOD(WritePhysical)( + THIS_ + __in ULONG64 Offset, + __in_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesWritten + ) PURE; + STDMETHOD(ReadControl)( + THIS_ + __in ULONG Processor, + __in ULONG64 Offset, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesRead + ) PURE; + STDMETHOD(WriteControl)( + THIS_ + __in ULONG Processor, + __in ULONG64 Offset, + __in_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesWritten + ) PURE; + STDMETHOD(ReadIo)( + THIS_ + __in ULONG InterfaceType, + __in ULONG BusNumber, + __in ULONG AddressSpace, + __in ULONG64 Offset, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesRead + ) PURE; + STDMETHOD(WriteIo)( + THIS_ + __in ULONG InterfaceType, + __in ULONG BusNumber, + __in ULONG AddressSpace, + __in ULONG64 Offset, + __in_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesWritten + ) PURE; + STDMETHOD(ReadMsr)( + THIS_ + __in ULONG Msr, + __out PULONG64 Value + ) PURE; + STDMETHOD(WriteMsr)( + THIS_ + __in ULONG Msr, + __in ULONG64 Value + ) PURE; + STDMETHOD(ReadBusData)( + THIS_ + __in ULONG BusDataType, + __in ULONG BusNumber, + __in ULONG SlotNumber, + __in ULONG Offset, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesRead + ) PURE; + STDMETHOD(WriteBusData)( + THIS_ + __in ULONG BusDataType, + __in ULONG BusNumber, + __in ULONG SlotNumber, + __in ULONG Offset, + __in_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesWritten + ) PURE; + STDMETHOD(CheckLowMemory)( + THIS + ) PURE; + STDMETHOD(ReadDebuggerData)( + THIS_ + __in ULONG Index, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG DataSize + ) PURE; + STDMETHOD(ReadProcessorSystemData)( + THIS_ + __in ULONG Processor, + __in ULONG Index, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG DataSize + ) PURE; +}; + +// +// Handle data types and structures. +// + +#define DEBUG_HANDLE_DATA_TYPE_BASIC 0 +#define DEBUG_HANDLE_DATA_TYPE_TYPE_NAME 1 +#define DEBUG_HANDLE_DATA_TYPE_OBJECT_NAME 2 +#define DEBUG_HANDLE_DATA_TYPE_HANDLE_COUNT 3 +#define DEBUG_HANDLE_DATA_TYPE_TYPE_NAME_WIDE 4 +#define DEBUG_HANDLE_DATA_TYPE_OBJECT_NAME_WIDE 5 +#define DEBUG_HANDLE_DATA_TYPE_MINI_THREAD_1 6 +#define DEBUG_HANDLE_DATA_TYPE_MINI_MUTANT_1 7 +#define DEBUG_HANDLE_DATA_TYPE_MINI_MUTANT_2 8 +#define DEBUG_HANDLE_DATA_TYPE_PER_HANDLE_OPERATIONS 9 +#define DEBUG_HANDLE_DATA_TYPE_ALL_HANDLE_OPERATIONS 10 +#define DEBUG_HANDLE_DATA_TYPE_MINI_PROCESS_1 11 +#define DEBUG_HANDLE_DATA_TYPE_MINI_PROCESS_2 12 + +typedef struct _DEBUG_HANDLE_DATA_BASIC +{ + ULONG TypeNameSize; + ULONG ObjectNameSize; + ULONG Attributes; + ULONG GrantedAccess; + ULONG HandleCount; + ULONG PointerCount; +} DEBUG_HANDLE_DATA_BASIC, *PDEBUG_HANDLE_DATA_BASIC; + +#undef INTERFACE +#define INTERFACE IDebugDataSpaces2 +DECLARE_INTERFACE_(IDebugDataSpaces2, IUnknown) +{ + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + __in REFIID InterfaceId, + __out PVOID* Interface + ) PURE; + STDMETHOD_(ULONG, AddRef)( + THIS + ) PURE; + STDMETHOD_(ULONG, Release)( + THIS + ) PURE; + + // IDebugDataSpaces. + STDMETHOD(ReadVirtual)( + THIS_ + __in ULONG64 Offset, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesRead + ) PURE; + STDMETHOD(WriteVirtual)( + THIS_ + __in ULONG64 Offset, + __in_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesWritten + ) PURE; + // SearchVirtual searches the given virtual + // address range for the given pattern. PatternSize + // gives the byte length of the pattern and PatternGranularity + // controls the granularity of comparisons during + // the search. + // For example, a DWORD-granular search would + // use a pattern granularity of four to search by DWORD + // increments. + STDMETHOD(SearchVirtual)( + THIS_ + __in ULONG64 Offset, + __in ULONG64 Length, + __in_bcount(PatternSize) PVOID Pattern, + __in ULONG PatternSize, + __in ULONG PatternGranularity, + __out PULONG64 MatchOffset + ) PURE; + // These methods are identical to Read/WriteVirtual + // except that they avoid the kernel virtual memory + // cache entirely and are therefore useful for reading + // virtual memory which is inherently volatile, such + // as memory-mapped device areas, without contaminating + // or invalidating the cache. + // In user-mode they are the same as Read/WriteVirtual. + STDMETHOD(ReadVirtualUncached)( + THIS_ + __in ULONG64 Offset, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesRead + ) PURE; + STDMETHOD(WriteVirtualUncached)( + THIS_ + __in ULONG64 Offset, + __in_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesWritten + ) PURE; + // The following two methods are convenience + // methods for accessing pointer values. + // They automatically convert between native pointers + // and canonical 64-bit values as necessary. + // These routines stop at the first failure. + STDMETHOD(ReadPointersVirtual)( + THIS_ + __in ULONG Count, + __in ULONG64 Offset, + __out_ecount(Count) PULONG64 Ptrs + ) PURE; + STDMETHOD(WritePointersVirtual)( + THIS_ + __in ULONG Count, + __in ULONG64 Offset, + __in_ecount(Count) PULONG64 Ptrs + ) PURE; + // All non-virtual data spaces are only + // available when kernel debugging. + STDMETHOD(ReadPhysical)( + THIS_ + __in ULONG64 Offset, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesRead + ) PURE; + STDMETHOD(WritePhysical)( + THIS_ + __in ULONG64 Offset, + __in_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesWritten + ) PURE; + STDMETHOD(ReadControl)( + THIS_ + __in ULONG Processor, + __in ULONG64 Offset, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesRead + ) PURE; + STDMETHOD(WriteControl)( + THIS_ + __in ULONG Processor, + __in ULONG64 Offset, + __in_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesWritten + ) PURE; + STDMETHOD(ReadIo)( + THIS_ + __in ULONG InterfaceType, + __in ULONG BusNumber, + __in ULONG AddressSpace, + __in ULONG64 Offset, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesRead + ) PURE; + STDMETHOD(WriteIo)( + THIS_ + __in ULONG InterfaceType, + __in ULONG BusNumber, + __in ULONG AddressSpace, + __in ULONG64 Offset, + __in_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesWritten + ) PURE; + STDMETHOD(ReadMsr)( + THIS_ + __in ULONG Msr, + __out PULONG64 Value + ) PURE; + STDMETHOD(WriteMsr)( + THIS_ + __in ULONG Msr, + __in ULONG64 Value + ) PURE; + STDMETHOD(ReadBusData)( + THIS_ + __in ULONG BusDataType, + __in ULONG BusNumber, + __in ULONG SlotNumber, + __in ULONG Offset, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesRead + ) PURE; + STDMETHOD(WriteBusData)( + THIS_ + __in ULONG BusDataType, + __in ULONG BusNumber, + __in ULONG SlotNumber, + __in ULONG Offset, + __in_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesWritten + ) PURE; + STDMETHOD(CheckLowMemory)( + THIS + ) PURE; + STDMETHOD(ReadDebuggerData)( + THIS_ + __in ULONG Index, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG DataSize + ) PURE; + STDMETHOD(ReadProcessorSystemData)( + THIS_ + __in ULONG Processor, + __in ULONG Index, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG DataSize + ) PURE; + + // IDebugDataSpaces2. + + STDMETHOD(VirtualToPhysical)( + THIS_ + __in ULONG64 Virtual, + __out PULONG64 Physical + ) PURE; + // Returns the physical addresses for the + // N levels of the systems paging structures. + // Level zero is the starting base physical + // address for virtual translations. + // Levels one-(N-1) will point to the appropriate + // paging descriptor for the virtual address at + // the given level of the paging hierarchy. The + // exact number of levels depends on many factors. + // The last level will be the fully translated + // physical address, matching what VirtualToPhysical + // returns. If the address can only be partially + // translated S_FALSE is returned. + STDMETHOD(GetVirtualTranslationPhysicalOffsets)( + THIS_ + __in ULONG64 Virtual, + __out_ecount_opt(OffsetsSize) PULONG64 Offsets, + __in ULONG OffsetsSize, + __out_opt PULONG Levels + ) PURE; + + // System handle data is accessible in certain + // debug sessions. The particular data available + // varies from session to session and platform + // to platform. + STDMETHOD(ReadHandleData)( + THIS_ + __in ULONG64 Handle, + __in ULONG DataType, + __out_bcount_opt(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG DataSize + ) PURE; + + // Fills memory with the given pattern. + // The fill stops at the first non-writable byte. + STDMETHOD(FillVirtual)( + THIS_ + __in ULONG64 Start, + __in ULONG Size, + __in_bcount(PatternSize) PVOID Pattern, + __in ULONG PatternSize, + __out_opt PULONG Filled + ) PURE; + STDMETHOD(FillPhysical)( + THIS_ + __in ULONG64 Start, + __in ULONG Size, + __in_bcount(PatternSize) PVOID Pattern, + __in ULONG PatternSize, + __out_opt PULONG Filled + ) PURE; + + // Queries virtual memory mapping information given + // an address similarly to the Win32 API VirtualQuery. + // MEMORY_BASIC_INFORMATION64 is defined in crash.h. + // This method currently only works for user-mode sessions. + STDMETHOD(QueryVirtual)( + THIS_ + __in ULONG64 Offset, + __out PMEMORY_BASIC_INFORMATION64 Info + ) PURE; +}; + +#undef INTERFACE +#define INTERFACE IDebugDataSpaces3 +DECLARE_INTERFACE_(IDebugDataSpaces3, IUnknown) +{ + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + __in REFIID InterfaceId, + __out PVOID* Interface + ) PURE; + STDMETHOD_(ULONG, AddRef)( + THIS + ) PURE; + STDMETHOD_(ULONG, Release)( + THIS + ) PURE; + + // IDebugDataSpaces. + STDMETHOD(ReadVirtual)( + THIS_ + __in ULONG64 Offset, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesRead + ) PURE; + STDMETHOD(WriteVirtual)( + THIS_ + __in ULONG64 Offset, + __in_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesWritten + ) PURE; + // SearchVirtual searches the given virtual + // address range for the given pattern. PatternSize + // gives the byte length of the pattern and PatternGranularity + // controls the granularity of comparisons during + // the search. + // For example, a DWORD-granular search would + // use a pattern granularity of four to search by DWORD + // increments. + STDMETHOD(SearchVirtual)( + THIS_ + __in ULONG64 Offset, + __in ULONG64 Length, + __in_bcount(PatternSize) PVOID Pattern, + __in ULONG PatternSize, + __in ULONG PatternGranularity, + __out PULONG64 MatchOffset + ) PURE; + // These methods are identical to Read/WriteVirtual + // except that they avoid the kernel virtual memory + // cache entirely and are therefore useful for reading + // virtual memory which is inherently volatile, such + // as memory-mapped device areas, without contaminating + // or invalidating the cache. + // In user-mode they are the same as Read/WriteVirtual. + STDMETHOD(ReadVirtualUncached)( + THIS_ + __in ULONG64 Offset, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesRead + ) PURE; + STDMETHOD(WriteVirtualUncached)( + THIS_ + __in ULONG64 Offset, + __in_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesWritten + ) PURE; + // The following two methods are convenience + // methods for accessing pointer values. + // They automatically convert between native pointers + // and canonical 64-bit values as necessary. + // These routines stop at the first failure. + STDMETHOD(ReadPointersVirtual)( + THIS_ + __in ULONG Count, + __in ULONG64 Offset, + __out_ecount(Count) PULONG64 Ptrs + ) PURE; + STDMETHOD(WritePointersVirtual)( + THIS_ + __in ULONG Count, + __in ULONG64 Offset, + __in_ecount(Count) PULONG64 Ptrs + ) PURE; + // All non-virtual data spaces are only + // available when kernel debugging. + STDMETHOD(ReadPhysical)( + THIS_ + __in ULONG64 Offset, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesRead + ) PURE; + STDMETHOD(WritePhysical)( + THIS_ + __in ULONG64 Offset, + __in_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesWritten + ) PURE; + STDMETHOD(ReadControl)( + THIS_ + __in ULONG Processor, + __in ULONG64 Offset, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesRead + ) PURE; + STDMETHOD(WriteControl)( + THIS_ + __in ULONG Processor, + __in ULONG64 Offset, + __in_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesWritten + ) PURE; + STDMETHOD(ReadIo)( + THIS_ + __in ULONG InterfaceType, + __in ULONG BusNumber, + __in ULONG AddressSpace, + __in ULONG64 Offset, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesRead + ) PURE; + STDMETHOD(WriteIo)( + THIS_ + __in ULONG InterfaceType, + __in ULONG BusNumber, + __in ULONG AddressSpace, + __in ULONG64 Offset, + __in_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesWritten + ) PURE; + STDMETHOD(ReadMsr)( + THIS_ + __in ULONG Msr, + __out PULONG64 Value + ) PURE; + STDMETHOD(WriteMsr)( + THIS_ + __in ULONG Msr, + __in ULONG64 Value + ) PURE; + STDMETHOD(ReadBusData)( + THIS_ + __in ULONG BusDataType, + __in ULONG BusNumber, + __in ULONG SlotNumber, + __in ULONG Offset, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesRead + ) PURE; + STDMETHOD(WriteBusData)( + THIS_ + __in ULONG BusDataType, + __in ULONG BusNumber, + __in ULONG SlotNumber, + __in ULONG Offset, + __in_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesWritten + ) PURE; + STDMETHOD(CheckLowMemory)( + THIS + ) PURE; + STDMETHOD(ReadDebuggerData)( + THIS_ + __in ULONG Index, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG DataSize + ) PURE; + STDMETHOD(ReadProcessorSystemData)( + THIS_ + __in ULONG Processor, + __in ULONG Index, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG DataSize + ) PURE; + + // IDebugDataSpaces2. + + STDMETHOD(VirtualToPhysical)( + THIS_ + __in ULONG64 Virtual, + __out PULONG64 Physical + ) PURE; + // Returns the physical addresses for the + // N levels of the systems paging structures. + // Level zero is the starting base physical + // address for virtual translations. + // Levels one-(N-1) will point to the appropriate + // paging descriptor for the virtual address at + // the given level of the paging hierarchy. The + // exact number of levels depends on many factors. + // The last level will be the fully translated + // physical address, matching what VirtualToPhysical + // returns. If the address can only be partially + // translated S_FALSE is returned. + STDMETHOD(GetVirtualTranslationPhysicalOffsets)( + THIS_ + __in ULONG64 Virtual, + __out_ecount_opt(OffsetsSize) PULONG64 Offsets, + __in ULONG OffsetsSize, + __out_opt PULONG Levels + ) PURE; + + // System handle data is accessible in certain + // debug sessions. The particular data available + // varies from session to session and platform + // to platform. + STDMETHOD(ReadHandleData)( + THIS_ + __in ULONG64 Handle, + __in ULONG DataType, + __out_bcount_opt(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG DataSize + ) PURE; + + // Fills memory with the given pattern. + // The fill stops at the first non-writable byte. + STDMETHOD(FillVirtual)( + THIS_ + __in ULONG64 Start, + __in ULONG Size, + __in_bcount(PatternSize) PVOID Pattern, + __in ULONG PatternSize, + __out_opt PULONG Filled + ) PURE; + STDMETHOD(FillPhysical)( + THIS_ + __in ULONG64 Start, + __in ULONG Size, + __in_bcount(PatternSize) PVOID Pattern, + __in ULONG PatternSize, + __out_opt PULONG Filled + ) PURE; + + // Queries virtual memory mapping information given + // an address similarly to the Win32 API VirtualQuery. + // MEMORY_BASIC_INFORMATION64 is defined in crash.h. + // This method currently only works for user-mode sessions. + STDMETHOD(QueryVirtual)( + THIS_ + __in ULONG64 Offset, + __out PMEMORY_BASIC_INFORMATION64 Info + ) PURE; + + // IDebugDataSpaces3. + + // Convenience method for reading an image + // header from virtual memory. Given the + // image base, this method determines where + // the NT headers are, validates the necessary + // markers and converts the headers into + // 64-bit form for consistency. + // A caller can check whether the headers were + // originally 32-bit by checking the optional + // header magic value. + // This method will not read ROM headers. + STDMETHOD(ReadImageNtHeaders)( + THIS_ + __in ULONG64 ImageBase, + __out PIMAGE_NT_HEADERS64 Headers + ) PURE; + + // Some debug sessions have arbitrary additional + // data available. For example, additional dump + // information files may contain extra information + // gathered at the same time as the primary dump. + // Such information is tagged with a unique identifier + // and can only be retrieved via the tag. + // Tagged data cannot be partially available; the + // tagged block is either fully present or completely + // absent. + STDMETHOD(ReadTagged)( + THIS_ + __in LPGUID Tag, + __in ULONG Offset, + __out_bcount_opt(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG TotalSize + ) PURE; + STDMETHOD(StartEnumTagged)( + THIS_ + __out PULONG64 Handle + ) PURE; + STDMETHOD(GetNextTagged)( + THIS_ + __in ULONG64 Handle, + __out LPGUID Tag, + __out PULONG Size + ) PURE; + STDMETHOD(EndEnumTagged)( + THIS_ + __in ULONG64 Handle + ) PURE; +}; + +#define DEBUG_OFFSINFO_VIRTUAL_SOURCE 0x00000001 + +#define DEBUG_VSOURCE_INVALID 0x00000000 +#define DEBUG_VSOURCE_DEBUGGEE 0x00000001 +#define DEBUG_VSOURCE_MAPPED_IMAGE 0x00000002 + +#define DEBUG_VSEARCH_DEFAULT 0x00000000 +#define DEBUG_VSEARCH_WRITABLE_ONLY 0x00000001 + +#define DEBUG_PHYSICAL_DEFAULT 0x00000000 +#define DEBUG_PHYSICAL_CACHED 0x00000001 +#define DEBUG_PHYSICAL_UNCACHED 0x00000002 +#define DEBUG_PHYSICAL_WRITE_COMBINED 0x00000003 + +#undef INTERFACE +#define INTERFACE IDebugDataSpaces4 +DECLARE_INTERFACE_(IDebugDataSpaces4, IUnknown) +{ + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + __in REFIID InterfaceId, + __out PVOID* Interface + ) PURE; + STDMETHOD_(ULONG, AddRef)( + THIS + ) PURE; + STDMETHOD_(ULONG, Release)( + THIS + ) PURE; + + // IDebugDataSpaces. + + STDMETHOD(ReadVirtual)( + THIS_ + __in ULONG64 Offset, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesRead + ) PURE; + STDMETHOD(WriteVirtual)( + THIS_ + __in ULONG64 Offset, + __in_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesWritten + ) PURE; + // SearchVirtual searches the given virtual + // address range for the given pattern. PatternSize + // gives the byte length of the pattern and PatternGranularity + // controls the granularity of comparisons during + // the search. + // For example, a DWORD-granular search would + // use a pattern granularity of four to search by DWORD + // increments. + STDMETHOD(SearchVirtual)( + THIS_ + __in ULONG64 Offset, + __in ULONG64 Length, + __in_bcount(PatternSize) PVOID Pattern, + __in ULONG PatternSize, + __in ULONG PatternGranularity, + __out PULONG64 MatchOffset + ) PURE; + // These methods are identical to Read/WriteVirtual + // except that they avoid the kernel virtual memory + // cache entirely and are therefore useful for reading + // virtual memory which is inherently volatile, such + // as memory-mapped device areas, without contaminating + // or invalidating the cache. + // In user-mode they are the same as Read/WriteVirtual. + STDMETHOD(ReadVirtualUncached)( + THIS_ + __in ULONG64 Offset, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesRead + ) PURE; + STDMETHOD(WriteVirtualUncached)( + THIS_ + __in ULONG64 Offset, + __in_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesWritten + ) PURE; + // The following two methods are convenience + // methods for accessing pointer values. + // They automatically convert between native pointers + // and canonical 64-bit values as necessary. + // These routines stop at the first failure. + STDMETHOD(ReadPointersVirtual)( + THIS_ + __in ULONG Count, + __in ULONG64 Offset, + __out_ecount(Count) PULONG64 Ptrs + ) PURE; + STDMETHOD(WritePointersVirtual)( + THIS_ + __in ULONG Count, + __in ULONG64 Offset, + __in_ecount(Count) PULONG64 Ptrs + ) PURE; + // All non-virtual data spaces are only + // available when kernel debugging. + STDMETHOD(ReadPhysical)( + THIS_ + __in ULONG64 Offset, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesRead + ) PURE; + STDMETHOD(WritePhysical)( + THIS_ + __in ULONG64 Offset, + __in_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesWritten + ) PURE; + STDMETHOD(ReadControl)( + THIS_ + __in ULONG Processor, + __in ULONG64 Offset, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesRead + ) PURE; + STDMETHOD(WriteControl)( + THIS_ + __in ULONG Processor, + __in ULONG64 Offset, + __in_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesWritten + ) PURE; + STDMETHOD(ReadIo)( + THIS_ + __in ULONG InterfaceType, + __in ULONG BusNumber, + __in ULONG AddressSpace, + __in ULONG64 Offset, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesRead + ) PURE; + STDMETHOD(WriteIo)( + THIS_ + __in ULONG InterfaceType, + __in ULONG BusNumber, + __in ULONG AddressSpace, + __in ULONG64 Offset, + __in_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesWritten + ) PURE; + STDMETHOD(ReadMsr)( + THIS_ + __in ULONG Msr, + __out PULONG64 Value + ) PURE; + STDMETHOD(WriteMsr)( + THIS_ + __in ULONG Msr, + __in ULONG64 Value + ) PURE; + STDMETHOD(ReadBusData)( + THIS_ + __in ULONG BusDataType, + __in ULONG BusNumber, + __in ULONG SlotNumber, + __in ULONG Offset, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesRead + ) PURE; + STDMETHOD(WriteBusData)( + THIS_ + __in ULONG BusDataType, + __in ULONG BusNumber, + __in ULONG SlotNumber, + __in ULONG Offset, + __in_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesWritten + ) PURE; + STDMETHOD(CheckLowMemory)( + THIS + ) PURE; + STDMETHOD(ReadDebuggerData)( + THIS_ + __in ULONG Index, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG DataSize + ) PURE; + STDMETHOD(ReadProcessorSystemData)( + THIS_ + __in ULONG Processor, + __in ULONG Index, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG DataSize + ) PURE; + + // IDebugDataSpaces2. + + STDMETHOD(VirtualToPhysical)( + THIS_ + __in ULONG64 Virtual, + __out PULONG64 Physical + ) PURE; + // Returns the physical addresses for the + // N levels of the systems paging structures. + // Level zero is the starting base physical + // address for virtual translations. + // Levels one-(N-1) will point to the appropriate + // paging descriptor for the virtual address at + // the given level of the paging hierarchy. The + // exact number of levels depends on many factors. + // The last level will be the fully translated + // physical address, matching what VirtualToPhysical + // returns. If the address can only be partially + // translated S_FALSE is returned. + STDMETHOD(GetVirtualTranslationPhysicalOffsets)( + THIS_ + __in ULONG64 Virtual, + __out_ecount_opt(OffsetsSize) PULONG64 Offsets, + __in ULONG OffsetsSize, + __out_opt PULONG Levels + ) PURE; + + // System handle data is accessible in certain + // debug sessions. The particular data available + // varies from session to session and platform + // to platform. + STDMETHOD(ReadHandleData)( + THIS_ + __in ULONG64 Handle, + __in ULONG DataType, + __out_bcount_opt(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG DataSize + ) PURE; + + // Fills memory with the given pattern. + // The fill stops at the first non-writable byte. + STDMETHOD(FillVirtual)( + THIS_ + __in ULONG64 Start, + __in ULONG Size, + __in_bcount(PatternSize) PVOID Pattern, + __in ULONG PatternSize, + __out_opt PULONG Filled + ) PURE; + STDMETHOD(FillPhysical)( + THIS_ + __in ULONG64 Start, + __in ULONG Size, + __in_bcount(PatternSize) PVOID Pattern, + __in ULONG PatternSize, + __out_opt PULONG Filled + ) PURE; + + // Queries virtual memory mapping information given + // an address similarly to the Win32 API VirtualQuery. + // MEMORY_BASIC_INFORMATION64 is defined in crash.h. + // This method currently only works for user-mode sessions. + STDMETHOD(QueryVirtual)( + THIS_ + __in ULONG64 Offset, + __out PMEMORY_BASIC_INFORMATION64 Info + ) PURE; + + // IDebugDataSpaces3. + + // Convenience method for reading an image + // header from virtual memory. Given the + // image base, this method determines where + // the NT headers are, validates the necessary + // markers and converts the headers into + // 64-bit form for consistency. + // A caller can check whether the headers were + // originally 32-bit by checking the optional + // header magic value. + // This method will not read ROM headers. + STDMETHOD(ReadImageNtHeaders)( + THIS_ + __in ULONG64 ImageBase, + __out PIMAGE_NT_HEADERS64 Headers + ) PURE; + + // Some debug sessions have arbitrary additional + // data available. For example, additional dump + // information files may contain extra information + // gathered at the same time as the primary dump. + // Such information is tagged with a unique identifier + // and can only be retrieved via the tag. + // Tagged data cannot be partially available; the + // tagged block is either fully present or completely + // absent. + STDMETHOD(ReadTagged)( + THIS_ + __in LPGUID Tag, + __in ULONG Offset, + __out_bcount_opt(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG TotalSize + ) PURE; + STDMETHOD(StartEnumTagged)( + THIS_ + __out PULONG64 Handle + ) PURE; + STDMETHOD(GetNextTagged)( + THIS_ + __in ULONG64 Handle, + __out LPGUID Tag, + __out PULONG Size + ) PURE; + STDMETHOD(EndEnumTagged)( + THIS_ + __in ULONG64 Handle + ) PURE; + + // IDebugDataSpaces4. + + // General information about an address in the given data space. + // Queries are from DEBUG_OFFSINFO_*. + STDMETHOD(GetOffsetInformation)( + THIS_ + __in ULONG Space, + __in ULONG Which, + __in ULONG64 Offset, + __out_bcount_opt(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG InfoSize + ) PURE; + + // Given a particular address, return the + // next address which has a different validity. + // For example, in debug sessions such as a live + // user-mode session where virtual address validity + // changes from page to page this will return the + // page after the given page. In sessions such as + // a user-mode dump file where validity can change + // from byte to byte this will return the start of + // the next region that has different validity. + STDMETHOD(GetNextDifferentlyValidOffsetVirtual)( + THIS_ + __in ULONG64 Offset, + __out PULONG64 NextOffset + ) PURE; + + // Given a particular range of virtual addresses, + // find the first region which is valid memory. + STDMETHOD(GetValidRegionVirtual)( + THIS_ + __in ULONG64 Base, + __in ULONG Size, + __out PULONG64 ValidBase, + __out PULONG ValidSize + ) PURE; + + STDMETHOD(SearchVirtual2)( + THIS_ + __in ULONG64 Offset, + __in ULONG64 Length, + __in ULONG Flags, + __in_bcount(PatternSize) PVOID Pattern, + __in ULONG PatternSize, + __in ULONG PatternGranularity, + __out PULONG64 MatchOffset + ) PURE; + + // Attempts to read a multi-byte string + // starting at the given virtual address. + // The possible string length, including terminator, + // is capped at the given max size. + // If a return buffer is given it will always + // be terminated. + STDMETHOD(ReadMultiByteStringVirtual)( + THIS_ + __in ULONG64 Offset, + __in ULONG MaxBytes, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG StringBytes + ) PURE; + // Reads a multi-byte string and converts + // it to Unicode using the given code page. + STDMETHOD(ReadMultiByteStringVirtualWide)( + THIS_ + __in ULONG64 Offset, + __in ULONG MaxBytes, + __in ULONG CodePage, + __out_ecount_opt(BufferSize) PWSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG StringBytes + ) PURE; + STDMETHOD(ReadUnicodeStringVirtual)( + THIS_ + __in ULONG64 Offset, + __in ULONG MaxBytes, + __in ULONG CodePage, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG StringBytes + ) PURE; + STDMETHOD(ReadUnicodeStringVirtualWide)( + THIS_ + __in ULONG64 Offset, + __in ULONG MaxBytes, + __out_ecount_opt(BufferSize) PWSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG StringBytes + ) PURE; + + STDMETHOD(ReadPhysical2)( + THIS_ + __in ULONG64 Offset, + __in ULONG Flags, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesRead + ) PURE; + STDMETHOD(WritePhysical2)( + THIS_ + __in ULONG64 Offset, + __in ULONG Flags, + __in_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesWritten + ) PURE; +}; + +//---------------------------------------------------------------------------- +// +// IDebugEventCallbacks. +// +//---------------------------------------------------------------------------- + +// Interest mask bits. +#define DEBUG_EVENT_BREAKPOINT 0x00000001 +#define DEBUG_EVENT_EXCEPTION 0x00000002 +#define DEBUG_EVENT_CREATE_THREAD 0x00000004 +#define DEBUG_EVENT_EXIT_THREAD 0x00000008 +#define DEBUG_EVENT_CREATE_PROCESS 0x00000010 +#define DEBUG_EVENT_EXIT_PROCESS 0x00000020 +#define DEBUG_EVENT_LOAD_MODULE 0x00000040 +#define DEBUG_EVENT_UNLOAD_MODULE 0x00000080 +#define DEBUG_EVENT_SYSTEM_ERROR 0x00000100 +#define DEBUG_EVENT_SESSION_STATUS 0x00000200 +#define DEBUG_EVENT_CHANGE_DEBUGGEE_STATE 0x00000400 +#define DEBUG_EVENT_CHANGE_ENGINE_STATE 0x00000800 +#define DEBUG_EVENT_CHANGE_SYMBOL_STATE 0x00001000 + +// SessionStatus flags. +// A debuggee has been discovered for the session. +#define DEBUG_SESSION_ACTIVE 0x00000000 +// The session has been ended by EndSession. +#define DEBUG_SESSION_END_SESSION_ACTIVE_TERMINATE 0x00000001 +#define DEBUG_SESSION_END_SESSION_ACTIVE_DETACH 0x00000002 +#define DEBUG_SESSION_END_SESSION_PASSIVE 0x00000003 +// The debuggee has run to completion. User-mode only. +#define DEBUG_SESSION_END 0x00000004 +// The target machine has rebooted. Kernel-mode only. +#define DEBUG_SESSION_REBOOT 0x00000005 +// The target machine has hibernated. Kernel-mode only. +#define DEBUG_SESSION_HIBERNATE 0x00000006 +// The engine was unable to continue the session. +#define DEBUG_SESSION_FAILURE 0x00000007 + +// ChangeDebuggeeState flags. +// The debuggees state has changed generally, such +// as when the debuggee has been executing. +// Argument is zero. +#define DEBUG_CDS_ALL 0xffffffff +// Registers have changed. If only a single register +// changed, argument is the index of the register. +// Otherwise it is DEBUG_ANY_ID. +#define DEBUG_CDS_REGISTERS 0x00000001 +// Data spaces have changed. If only a single +// space was affected, argument is the data +// space. Otherwise it is DEBUG_ANY_ID. +#define DEBUG_CDS_DATA 0x00000002 + +// ChangeEngineState flags. +// The engine state has changed generally. +// Argument is zero. +#define DEBUG_CES_ALL 0xffffffff +// Current thread changed. This may imply a change +// of system and process also. Argument is the ID of the new +// current thread or DEBUG_ANY_ID if no thread is current. +#define DEBUG_CES_CURRENT_THREAD 0x00000001 +// Effective processor changed. Argument is the +// new processor type. +#define DEBUG_CES_EFFECTIVE_PROCESSOR 0x00000002 +// Breakpoints changed. If only a single breakpoint +// changed, argument is the ID of the breakpoint. +// Otherwise it is DEBUG_ANY_ID. +#define DEBUG_CES_BREAKPOINTS 0x00000004 +// Code interpretation level changed. Argument is +// the new level. +#define DEBUG_CES_CODE_LEVEL 0x00000008 +// Execution status changed. Argument is the new +// execution status. +#define DEBUG_CES_EXECUTION_STATUS 0x00000010 +// Engine options have changed. Argument is the new +// options value. +#define DEBUG_CES_ENGINE_OPTIONS 0x00000020 +// Log file information has changed. Argument +// is TRUE if a log file was opened and FALSE if +// a log file was closed. +#define DEBUG_CES_LOG_FILE 0x00000040 +// Default number radix has changed. Argument +// is the new radix. +#define DEBUG_CES_RADIX 0x00000080 +// Event filters changed. If only a single filter +// changed the argument is the filter's index, +// otherwise it is DEBUG_ANY_ID. +#define DEBUG_CES_EVENT_FILTERS 0x00000100 +// Process options have changed. Argument is the new +// options value. +#define DEBUG_CES_PROCESS_OPTIONS 0x00000200 +// Extensions have been added or removed. +#define DEBUG_CES_EXTENSIONS 0x00000400 +// Systems have been added or removed. The argument +// is the system ID. Systems, unlike processes and +// threads, may be created at any time and not +// just during WaitForEvent. +#define DEBUG_CES_SYSTEMS 0x00000800 +// Assembly/disassembly options have changed. Argument +// is the new options value. +#define DEBUG_CES_ASSEMBLY_OPTIONS 0x00001000 +// Expression syntax has changed. Argument +// is the new syntax value. +#define DEBUG_CES_EXPRESSION_SYNTAX 0x00002000 +// Text replacements have changed. +#define DEBUG_CES_TEXT_REPLACEMENTS 0x00004000 + +// ChangeSymbolState flags. +// Symbol state has changed generally, such +// as after reload operations. Argument is zero. +#define DEBUG_CSS_ALL 0xffffffff +// Modules have been loaded. If only a +// single module changed, argument is the +// base address of the module. Otherwise +// it is zero. +#define DEBUG_CSS_LOADS 0x00000001 +// Modules have been unloaded. If only a +// single module changed, argument is the +// base address of the module. Otherwise +// it is zero. +#define DEBUG_CSS_UNLOADS 0x00000002 +// Current symbol scope changed. +#define DEBUG_CSS_SCOPE 0x00000004 +// Paths have changed. +#define DEBUG_CSS_PATHS 0x00000008 +// Symbol options have changed. Argument is the new +// options value. +#define DEBUG_CSS_SYMBOL_OPTIONS 0x00000010 +// Type options have changed. Argument is the new +// options value. +#define DEBUG_CSS_TYPE_OPTIONS 0x00000020 + +#undef INTERFACE +#define INTERFACE IDebugEventCallbacks +DECLARE_INTERFACE_(IDebugEventCallbacks, IUnknown) +{ + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + __in REFIID InterfaceId, + __out PVOID* Interface + ) PURE; + STDMETHOD_(ULONG, AddRef)( + THIS + ) PURE; + STDMETHOD_(ULONG, Release)( + THIS + ) PURE; + + // IDebugEventCallbacks. + + // The engine calls GetInterestMask once when + // the event callbacks are set for a client. + STDMETHOD(GetInterestMask)( + THIS_ + __out PULONG Mask + ) PURE; + + // A breakpoint event is generated when + // a breakpoint exception is received and + // it can be mapped to an existing breakpoint. + // The callback method is given a reference + // to the breakpoint and should release it when + // it is done with it. + STDMETHOD(Breakpoint)( + THIS_ + __in PDEBUG_BREAKPOINT Bp + ) PURE; + + // Exceptions include breaks which cannot + // be mapped to an existing breakpoint + // instance. + STDMETHOD(Exception)( + THIS_ + __in PEXCEPTION_RECORD64 Exception, + __in ULONG FirstChance + ) PURE; + + // Any of these values can be zero if they + // cannot be provided by the engine. + // Currently the kernel does not return thread + // or process change events. + STDMETHOD(CreateThread)( + THIS_ + __in ULONG64 Handle, + __in ULONG64 DataOffset, + __in ULONG64 StartOffset + ) PURE; + STDMETHOD(ExitThread)( + THIS_ + __in ULONG ExitCode + ) PURE; + + // Any of these values can be zero if they + // cannot be provided by the engine. + STDMETHOD(CreateProcess)( + THIS_ + __in ULONG64 ImageFileHandle, + __in ULONG64 Handle, + __in ULONG64 BaseOffset, + __in ULONG ModuleSize, + __in_opt PCSTR ModuleName, + __in_opt PCSTR ImageName, + __in ULONG CheckSum, + __in ULONG TimeDateStamp, + __in ULONG64 InitialThreadHandle, + __in ULONG64 ThreadDataOffset, + __in ULONG64 StartOffset + ) PURE; + STDMETHOD(ExitProcess)( + THIS_ + __in ULONG ExitCode + ) PURE; + + // Any of these values may be zero. + STDMETHOD(LoadModule)( + THIS_ + __in ULONG64 ImageFileHandle, + __in ULONG64 BaseOffset, + __in ULONG ModuleSize, + __in_opt PCSTR ModuleName, + __in_opt PCSTR ImageName, + __in ULONG CheckSum, + __in ULONG TimeDateStamp + ) PURE; + STDMETHOD(UnloadModule)( + THIS_ + __in_opt PCSTR ImageBaseName, + __in ULONG64 BaseOffset + ) PURE; + + STDMETHOD(SystemError)( + THIS_ + __in ULONG Error, + __in ULONG Level + ) PURE; + + // Session status is synchronous like the other + // wait callbacks but it is called as the state + // of the session is changing rather than at + // specific events so its return value does not + // influence waiting. Implementations should just + // return DEBUG_STATUS_NO_CHANGE. + // Also, because some of the status + // notifications are very early or very + // late in the session lifetime there may not be + // current processes or threads when the notification + // is generated. + STDMETHOD(SessionStatus)( + THIS_ + __in ULONG Status + ) PURE; + + // The following callbacks are informational + // callbacks notifying the provider about + // changes in debug state. The return value + // of these callbacks is ignored. Implementations + // can not call back into the engine. + + // Debuggee state, such as registers or data spaces, + // has changed. + STDMETHOD(ChangeDebuggeeState)( + THIS_ + __in ULONG Flags, + __in ULONG64 Argument + ) PURE; + // Engine state has changed. + STDMETHOD(ChangeEngineState)( + THIS_ + __in ULONG Flags, + __in ULONG64 Argument + ) PURE; + // Symbol state has changed. + STDMETHOD(ChangeSymbolState)( + THIS_ + __in ULONG Flags, + __in ULONG64 Argument + ) PURE; +}; + +#undef INTERFACE +#define INTERFACE IDebugEventCallbacksWide +DECLARE_INTERFACE_(IDebugEventCallbacksWide, IUnknown) +{ + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + __in REFIID InterfaceId, + __out PVOID* Interface + ) PURE; + STDMETHOD_(ULONG, AddRef)( + THIS + ) PURE; + STDMETHOD_(ULONG, Release)( + THIS + ) PURE; + + // IDebugEventCallbacksWide. + + // The engine calls GetInterestMask once when + // the event callbacks are set for a client. + STDMETHOD(GetInterestMask)( + THIS_ + __out PULONG Mask + ) PURE; + + // A breakpoint event is generated when + // a breakpoint exception is received and + // it can be mapped to an existing breakpoint. + // The callback method is given a reference + // to the breakpoint and should release it when + // it is done with it. + STDMETHOD(Breakpoint)( + THIS_ + __in PDEBUG_BREAKPOINT2 Bp + ) PURE; + + // Exceptions include breaks which cannot + // be mapped to an existing breakpoint + // instance. + STDMETHOD(Exception)( + THIS_ + __in PEXCEPTION_RECORD64 Exception, + __in ULONG FirstChance + ) PURE; + + // Any of these values can be zero if they + // cannot be provided by the engine. + // Currently the kernel does not return thread + // or process change events. + STDMETHOD(CreateThread)( + THIS_ + __in ULONG64 Handle, + __in ULONG64 DataOffset, + __in ULONG64 StartOffset + ) PURE; + STDMETHOD(ExitThread)( + THIS_ + __in ULONG ExitCode + ) PURE; + + // Any of these values can be zero if they + // cannot be provided by the engine. + STDMETHOD(CreateProcess)( + THIS_ + __in ULONG64 ImageFileHandle, + __in ULONG64 Handle, + __in ULONG64 BaseOffset, + __in ULONG ModuleSize, + __in_opt PCWSTR ModuleName, + __in_opt PCWSTR ImageName, + __in ULONG CheckSum, + __in ULONG TimeDateStamp, + __in ULONG64 InitialThreadHandle, + __in ULONG64 ThreadDataOffset, + __in ULONG64 StartOffset + ) PURE; + STDMETHOD(ExitProcess)( + THIS_ + __in ULONG ExitCode + ) PURE; + + // Any of these values may be zero. + STDMETHOD(LoadModule)( + THIS_ + __in ULONG64 ImageFileHandle, + __in ULONG64 BaseOffset, + __in ULONG ModuleSize, + __in_opt PCWSTR ModuleName, + __in_opt PCWSTR ImageName, + __in ULONG CheckSum, + __in ULONG TimeDateStamp + ) PURE; + STDMETHOD(UnloadModule)( + THIS_ + __in_opt PCWSTR ImageBaseName, + __in ULONG64 BaseOffset + ) PURE; + + STDMETHOD(SystemError)( + THIS_ + __in ULONG Error, + __in ULONG Level + ) PURE; + + // Session status is synchronous like the other + // wait callbacks but it is called as the state + // of the session is changing rather than at + // specific events so its return value does not + // influence waiting. Implementations should just + // return DEBUG_STATUS_NO_CHANGE. + // Also, because some of the status + // notifications are very early or very + // late in the session lifetime there may not be + // current processes or threads when the notification + // is generated. + STDMETHOD(SessionStatus)( + THIS_ + __in ULONG Status + ) PURE; + + // The following callbacks are informational + // callbacks notifying the provider about + // changes in debug state. The return value + // of these callbacks is ignored. Implementations + // can not call back into the engine. + + // Debuggee state, such as registers or data spaces, + // has changed. + STDMETHOD(ChangeDebuggeeState)( + THIS_ + __in ULONG Flags, + __in ULONG64 Argument + ) PURE; + // Engine state has changed. + STDMETHOD(ChangeEngineState)( + THIS_ + __in ULONG Flags, + __in ULONG64 Argument + ) PURE; + // Symbol state has changed. + STDMETHOD(ChangeSymbolState)( + THIS_ + __in ULONG Flags, + __in ULONG64 Argument + ) PURE; +}; + +//---------------------------------------------------------------------------- +// +// IDebugInputCallbacks. +// +//---------------------------------------------------------------------------- + +#undef INTERFACE +#define INTERFACE IDebugInputCallbacks +DECLARE_INTERFACE_(IDebugInputCallbacks, IUnknown) +{ + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + __in REFIID InterfaceId, + __out PVOID* Interface + ) PURE; + STDMETHOD_(ULONG, AddRef)( + THIS + ) PURE; + STDMETHOD_(ULONG, Release)( + THIS + ) PURE; + + // IDebugInputCallbacks. + + // A call to the StartInput method is a request for + // a line of input from any client. The returned input + // should always be zero-terminated. The buffer size + // provided is only a guideline. A client can return + // more if necessary and the engine will truncate it + // before returning from IDebugControl::Input. + // The return value is ignored. + STDMETHOD(StartInput)( + THIS_ + __in ULONG BufferSize + ) PURE; + // The return value is ignored. + STDMETHOD(EndInput)( + THIS + ) PURE; +}; + +//---------------------------------------------------------------------------- +// +// IDebugOutputCallbacks. +// +//---------------------------------------------------------------------------- + +#undef INTERFACE +#define INTERFACE IDebugOutputCallbacks +DECLARE_INTERFACE_(IDebugOutputCallbacks, IUnknown) +{ + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + __in REFIID InterfaceId, + __out PVOID* Interface + ) PURE; + STDMETHOD_(ULONG, AddRef)( + THIS + ) PURE; + STDMETHOD_(ULONG, Release)( + THIS + ) PURE; + + // IDebugOutputCallbacks. + + // This method is only called if the supplied mask + // is allowed by the clients output control. + // The return value is ignored. + STDMETHOD(Output)( + THIS_ + __in ULONG Mask, + __in PCSTR Text + ) PURE; +}; + +#undef INTERFACE +#define INTERFACE IDebugOutputCallbacksWide +DECLARE_INTERFACE_(IDebugOutputCallbacksWide, IUnknown) +{ + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + __in REFIID InterfaceId, + __out PVOID* Interface + ) PURE; + STDMETHOD_(ULONG, AddRef)( + THIS + ) PURE; + STDMETHOD_(ULONG, Release)( + THIS + ) PURE; + + // IDebugOutputCallbacksWide. + + // This method is only called if the supplied mask + // is allowed by the clients output control. + // The return value is ignored. + STDMETHOD(Output)( + THIS_ + __in ULONG Mask, + __in PCWSTR Text + ) PURE; +}; + +// +// IDebugOutputCallbacks2 interest mask flags. +// + +// Indicates that the callback wants notifications +// of all explicit flushes. +#define DEBUG_OUTCBI_EXPLICIT_FLUSH 0x00000001 +// Indicates that the callback wants +// content in text form. +#define DEBUG_OUTCBI_TEXT 0x00000002 +// Indicates that the callback wants +// content in markup form. +#define DEBUG_OUTCBI_DML 0x00000004 + +#define DEBUG_OUTCBI_ANY_FORMAT 0x00000006 + +// +// Different kinds of output callback notifications +// that can be sent to Output2. +// + +// Plain text content, flags are below, argument is mask. +#define DEBUG_OUTCB_TEXT 0 +// Debugger markup content, flags are below, argument is mask. +#define DEBUG_OUTCB_DML 1 +// Notification of an explicit output flush, flags and argument are zero. +#define DEBUG_OUTCB_EXPLICIT_FLUSH 2 + +// +// Flags for various Output2 callbacks. +// + +// The content string was followed by an +// explicit flush. This flag will be used +// instead of a separate DEBUG_OUTCB_EXPLICIT_FLUSH +// callback when a flush has text to flush, +// thus avoiding two callbacks. +#define DEBUG_OUTCBF_COMBINED_EXPLICIT_FLUSH 0x00000001 + +// The markup content string has embedded tags. +#define DEBUG_OUTCBF_DML_HAS_TAGS 0x00000002 +// The markup content has encoded special characters like ", &, < and >. +#define DEBUG_OUTCBF_DML_HAS_SPECIAL_CHARACTERS 0x00000004 + +#undef INTERFACE +#define INTERFACE IDebugOutputCallbacks2 +DECLARE_INTERFACE_(IDebugOutputCallbacks2, IUnknown) +{ + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + __in REFIID InterfaceId, + __out PVOID* Interface + ) PURE; + STDMETHOD_(ULONG, AddRef)( + THIS + ) PURE; + STDMETHOD_(ULONG, Release)( + THIS + ) PURE; + + // IDebugOutputCallbacks. + + // This method is not used. + STDMETHOD(Output)( + THIS_ + __in ULONG Mask, + __in PCSTR Text + ) PURE; + + // IDebugOutputCallbacks2. + + // The engine calls GetInterestMask once when + // the callbacks are set for a client. + STDMETHOD(GetInterestMask)( + THIS_ + __out PULONG Mask + ) PURE; + + STDMETHOD(Output2)( + THIS_ + __in ULONG Which, + __in ULONG Flags, + __in ULONG64 Arg, + __in_opt PCWSTR Text + ) PURE; +}; + +//---------------------------------------------------------------------------- +// +// IDebugRegisters. +// +//---------------------------------------------------------------------------- + +#define DEBUG_REGISTERS_DEFAULT 0x00000000 +#define DEBUG_REGISTERS_INT32 0x00000001 +#define DEBUG_REGISTERS_INT64 0x00000002 +#define DEBUG_REGISTERS_FLOAT 0x00000004 +#define DEBUG_REGISTERS_ALL 0x00000007 + +#define DEBUG_REGISTER_SUB_REGISTER 0x00000001 + +typedef struct _DEBUG_REGISTER_DESCRIPTION +{ + // DEBUG_VALUE type. + ULONG Type; + ULONG Flags; + + // If this is a subregister the full + // registers description index is + // given in SubregMaster. The length, mask + // and shift describe how the subregisters + // bits fit into the full register. + ULONG SubregMaster; + ULONG SubregLength; + ULONG64 SubregMask; + ULONG SubregShift; + + ULONG Reserved0; +} DEBUG_REGISTER_DESCRIPTION, *PDEBUG_REGISTER_DESCRIPTION; + +#undef INTERFACE +#define INTERFACE IDebugRegisters +DECLARE_INTERFACE_(IDebugRegisters, IUnknown) +{ + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + __in REFIID InterfaceId, + __out PVOID* Interface + ) PURE; + STDMETHOD_(ULONG, AddRef)( + THIS + ) PURE; + STDMETHOD_(ULONG, Release)( + THIS + ) PURE; + + // IDebugRegisters. + STDMETHOD(GetNumberRegisters)( + THIS_ + __out PULONG Number + ) PURE; + STDMETHOD(GetDescription)( + THIS_ + __in ULONG Register, + __out_ecount_opt(NameBufferSize) PSTR NameBuffer, + __in ULONG NameBufferSize, + __out_opt PULONG NameSize, + __out_opt PDEBUG_REGISTER_DESCRIPTION Desc + ) PURE; + STDMETHOD(GetIndexByName)( + THIS_ + __in PCSTR Name, + __out PULONG Index + ) PURE; + + STDMETHOD(GetValue)( + THIS_ + __in ULONG Register, + __out PDEBUG_VALUE Value + ) PURE; + // SetValue makes a best effort at coercing + // the given value into the given registers + // value type. If the given value is larger + // than the register can hold the least + // significant bits will be dropped. Float + // to int and int to float will be done + // if necessary. Subregister bits will be + // inserted into the master register. + STDMETHOD(SetValue)( + THIS_ + __in ULONG Register, + __in PDEBUG_VALUE Value + ) PURE; + // Gets Count register values. If Indices is + // non-NULL it must contain Count register + // indices which control the registers affected. + // If Indices is NULL the registers from Start + // to Start + Count 1 are retrieved. + STDMETHOD(GetValues)( + THIS_ + __in ULONG Count, + __in_ecount_opt(Count) PULONG Indices, + __in ULONG Start, + __out_ecount(Count) PDEBUG_VALUE Values + ) PURE; + STDMETHOD(SetValues)( + THIS_ + __in ULONG Count, + __in_ecount_opt(Count) PULONG Indices, + __in ULONG Start, + __in_ecount(Count) PDEBUG_VALUE Values + ) PURE; + + // Outputs a group of registers in a well-formatted + // way thats specific to the platforms register set. + // Uses the line prefix. + STDMETHOD(OutputRegisters)( + THIS_ + __in ULONG OutputControl, + __in ULONG Flags + ) PURE; + + // Abstracted pieces of processor information. + // The mapping of these values to architectural + // registers is architecture-specific and their + // interpretation and existence may vary. They + // are intended to be directly compatible with + // calls which take this information, such as + // stack walking. + STDMETHOD(GetInstructionOffset)( + THIS_ + __out PULONG64 Offset + ) PURE; + STDMETHOD(GetStackOffset)( + THIS_ + __out PULONG64 Offset + ) PURE; + STDMETHOD(GetFrameOffset)( + THIS_ + __out PULONG64 Offset + ) PURE; +}; + +// +// The engine maintains several separate +// pieces of context information. There is +// the current debuggee context, a possible +// override context, such as from .cxr, +// a context for the current scope frame and so on. +// + +// Get register information from the debuggee. +#define DEBUG_REGSRC_DEBUGGEE 0x00000000 +// Get register information from an explicit +// override context, such as one set by .cxr. +// If there is no override context the request will fail. +#define DEBUG_REGSRC_EXPLICIT 0x00000001 +// Get register information from the current scope +// frame. Note that stack unwinding does not guarantee +// accurate updating of the register context, +// so scope frame register context may not be accurate +// in all cases. +#define DEBUG_REGSRC_FRAME 0x00000002 + +#undef INTERFACE +#define INTERFACE IDebugRegisters2 +DECLARE_INTERFACE_(IDebugRegisters2, IUnknown) +{ + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + __in REFIID InterfaceId, + __out PVOID* Interface + ) PURE; + STDMETHOD_(ULONG, AddRef)( + THIS + ) PURE; + STDMETHOD_(ULONG, Release)( + THIS + ) PURE; + + // IDebugRegisters. + + STDMETHOD(GetNumberRegisters)( + THIS_ + __out PULONG Number + ) PURE; + STDMETHOD(GetDescription)( + THIS_ + __in ULONG Register, + __out_ecount_opt(NameBufferSize) PSTR NameBuffer, + __in ULONG NameBufferSize, + __out_opt PULONG NameSize, + __out_opt PDEBUG_REGISTER_DESCRIPTION Desc + ) PURE; + STDMETHOD(GetIndexByName)( + THIS_ + __in PCSTR Name, + __out PULONG Index + ) PURE; + + STDMETHOD(GetValue)( + THIS_ + __in ULONG Register, + __out PDEBUG_VALUE Value + ) PURE; + // SetValue makes a best effort at coercing + // the given value into the given registers + // value type. If the given value is larger + // than the register can hold the least + // significant bits will be dropped. Float + // to int and int to float will be done + // if necessary. Subregister bits will be + // inserted into the master register. + STDMETHOD(SetValue)( + THIS_ + __in ULONG Register, + __in PDEBUG_VALUE Value + ) PURE; + // Gets Count register values. If Indices is + // non-NULL it must contain Count register + // indices which control the registers affected. + // If Indices is NULL the registers from Start + // to Start + Count 1 are retrieved. + STDMETHOD(GetValues)( + THIS_ + __in ULONG Count, + __in_ecount_opt(Count) PULONG Indices, + __in ULONG Start, + __out_ecount(Count) PDEBUG_VALUE Values + ) PURE; + STDMETHOD(SetValues)( + THIS_ + __in ULONG Count, + __in_ecount_opt(Count) PULONG Indices, + __in ULONG Start, + __in_ecount(Count) PDEBUG_VALUE Values + ) PURE; + + // Outputs a group of registers in a well-formatted + // way thats specific to the platforms register set. + // Uses the line prefix. + STDMETHOD(OutputRegisters)( + THIS_ + __in ULONG OutputControl, + __in ULONG Flags + ) PURE; + + // Abstracted pieces of processor information. + // The mapping of these values to architectural + // registers is architecture-specific and their + // interpretation and existence may vary. They + // are intended to be directly compatible with + // calls which take this information, such as + // stack walking. + STDMETHOD(GetInstructionOffset)( + THIS_ + __out PULONG64 Offset + ) PURE; + STDMETHOD(GetStackOffset)( + THIS_ + __out PULONG64 Offset + ) PURE; + STDMETHOD(GetFrameOffset)( + THIS_ + __out PULONG64 Offset + ) PURE; + + // IDebugRegisters2. + + STDMETHOD(GetDescriptionWide)( + THIS_ + __in ULONG Register, + __out_ecount_opt(NameBufferSize) PWSTR NameBuffer, + __in ULONG NameBufferSize, + __out_opt PULONG NameSize, + __out_opt PDEBUG_REGISTER_DESCRIPTION Desc + ) PURE; + STDMETHOD(GetIndexByNameWide)( + THIS_ + __in PCWSTR Name, + __out PULONG Index + ) PURE; + + // Pseudo-registers are synthetic values derived + // by the engine that are presented in a manner + // similar to regular registers. They are simple + // value holders, similar to actual registers. + // Pseudo-registers are defined for concepts, + // such as current-instruction-pointer or + // current-thread-data. As such they have + // types appropriate for their data. + STDMETHOD(GetNumberPseudoRegisters)( + THIS_ + __out PULONG Number + ) PURE; + STDMETHOD(GetPseudoDescription)( + THIS_ + __in ULONG Register, + __out_ecount_opt(NameBufferSize) PSTR NameBuffer, + __in ULONG NameBufferSize, + __out_opt PULONG NameSize, + __out_opt PULONG64 TypeModule, + __out_opt PULONG TypeId + ) PURE; + STDMETHOD(GetPseudoDescriptionWide)( + THIS_ + __in ULONG Register, + __out_ecount_opt(NameBufferSize) PWSTR NameBuffer, + __in ULONG NameBufferSize, + __out_opt PULONG NameSize, + __out_opt PULONG64 TypeModule, + __out_opt PULONG TypeId + ) PURE; + STDMETHOD(GetPseudoIndexByName)( + THIS_ + __in PCSTR Name, + __out PULONG Index + ) PURE; + STDMETHOD(GetPseudoIndexByNameWide)( + THIS_ + __in PCWSTR Name, + __out PULONG Index + ) PURE; + // Some pseudo-register values are affected + // by the register source, others are not. + STDMETHOD(GetPseudoValues)( + THIS_ + __in ULONG Source, + __in ULONG Count, + __in_ecount_opt(Count) PULONG Indices, + __in ULONG Start, + __out_ecount(Count) PDEBUG_VALUE Values + ) PURE; + // Many pseudo-registers are read-only and cannot be set. + STDMETHOD(SetPseudoValues)( + THIS_ + __in ULONG Source, + __in ULONG Count, + __in_ecount_opt(Count) PULONG Indices, + __in ULONG Start, + __in_ecount(Count) PDEBUG_VALUE Values + ) PURE; + + // These expanded methods allow selection + // of the source of register information. + STDMETHOD(GetValues2)( + THIS_ + __in ULONG Source, + __in ULONG Count, + __in_ecount_opt(Count) PULONG Indices, + __in ULONG Start, + __out_ecount(Count) PDEBUG_VALUE Values + ) PURE; + STDMETHOD(SetValues2)( + THIS_ + __in ULONG Source, + __in ULONG Count, + __in_ecount_opt(Count) PULONG Indices, + __in ULONG Start, + __in_ecount(Count) PDEBUG_VALUE Values + ) PURE; + STDMETHOD(OutputRegisters2)( + THIS_ + __in ULONG OutputControl, + __in ULONG Source, + __in ULONG Flags + ) PURE; + STDMETHOD(GetInstructionOffset2)( + THIS_ + __in ULONG Source, + __out PULONG64 Offset + ) PURE; + STDMETHOD(GetStackOffset2)( + THIS_ + __in ULONG Source, + __out PULONG64 Offset + ) PURE; + STDMETHOD(GetFrameOffset2)( + THIS_ + __in ULONG Source, + __out PULONG64 Offset + ) PURE; +}; + +//---------------------------------------------------------------------------- +// +// IDebugSymbolGroup +// +//---------------------------------------------------------------------------- + +// OutputSymbols flags. +// Default output contains +// <Name>**NAME**<Offset>**OFF**<Value>**VALUE**<Type>**TYPE** +// per symbol. +#define DEBUG_OUTPUT_SYMBOLS_DEFAULT 0x00000000 +#define DEBUG_OUTPUT_SYMBOLS_NO_NAMES 0x00000001 +#define DEBUG_OUTPUT_SYMBOLS_NO_OFFSETS 0x00000002 +#define DEBUG_OUTPUT_SYMBOLS_NO_VALUES 0x00000004 +#define DEBUG_OUTPUT_SYMBOLS_NO_TYPES 0x00000010 + +#define DEBUG_OUTPUT_NAME_END "**NAME**" +#define DEBUG_OUTPUT_OFFSET_END "**OFF**" +#define DEBUG_OUTPUT_VALUE_END "**VALUE**" +#define DEBUG_OUTPUT_TYPE_END "**TYPE**" + +#define DEBUG_OUTPUT_NAME_END_WIDE L"**NAME**" +#define DEBUG_OUTPUT_OFFSET_END_WIDE L"**OFF**" +#define DEBUG_OUTPUT_VALUE_END_WIDE L"**VALUE**" +#define DEBUG_OUTPUT_TYPE_END_WIDE L"**TYPE**" + +#ifdef UNICODE +#define DEBUG_OUTPUT_NAME_END_T DEBUG_OUTPUT_NAME_END_WIDE +#define DEBUG_OUTPUT_OFFSET_END_T DEBUG_OUTPUT_OFFSET_END_WIDE +#define DEBUG_OUTPUT_VALUE_END_T DEBUG_OUTPUT_VALUE_END_WIDE +#define DEBUG_OUTPUT_TYPE_END_T DEBUG_OUTPUT_TYPE_END_WIDE +#else +#define DEBUG_OUTPUT_NAME_END_T DEBUG_OUTPUT_NAME_END +#define DEBUG_OUTPUT_OFFSET_END_T DEBUG_OUTPUT_OFFSET_END +#define DEBUG_OUTPUT_VALUE_END_T DEBUG_OUTPUT_VALUE_END +#define DEBUG_OUTPUT_TYPE_END_T DEBUG_OUTPUT_TYPE_END +#endif + +// DEBUG_SYMBOL_PARAMETERS flags. +// Cumulative expansion level, takes four bits. +#define DEBUG_SYMBOL_EXPANSION_LEVEL_MASK 0x0000000f +// Symbols subelements follow. +#define DEBUG_SYMBOL_EXPANDED 0x00000010 +// Symbols value is read-only. +#define DEBUG_SYMBOL_READ_ONLY 0x00000020 +// Symbol subelements are array elements. +#define DEBUG_SYMBOL_IS_ARRAY 0x00000040 +// Symbol is a float value. +#define DEBUG_SYMBOL_IS_FLOAT 0x00000080 +// Symbol is a scope argument. +#define DEBUG_SYMBOL_IS_ARGUMENT 0x00000100 +// Symbol is a scope argument. +#define DEBUG_SYMBOL_IS_LOCAL 0x00000200 + +typedef struct _DEBUG_SYMBOL_PARAMETERS +{ + ULONG64 Module; + ULONG TypeId; + // ParentSymbol may be DEBUG_ANY_ID when unknown. + ULONG ParentSymbol; + // A subelement of a symbol can be a field, such + // as in structs, unions or classes; or an array + // element count for arrays. + ULONG SubElements; + ULONG Flags; + ULONG64 Reserved; +} DEBUG_SYMBOL_PARAMETERS, *PDEBUG_SYMBOL_PARAMETERS; + +#undef INTERFACE +#define INTERFACE IDebugSymbolGroup +DECLARE_INTERFACE_(IDebugSymbolGroup, IUnknown) +{ + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + __in REFIID InterfaceId, + __out PVOID* Interface + ) PURE; + STDMETHOD_(ULONG, AddRef)( + THIS + ) PURE; + STDMETHOD_(ULONG, Release)( + THIS + ) PURE; + + // IDebugSymbolGroup. + STDMETHOD(GetNumberSymbols)( + THIS_ + __out PULONG Number + ) PURE; + // On input Index indicates the desired insertion + // index. On output Index contains the actual index. + // Use DEBUG_ANY_ID to append a symbol to the end. + STDMETHOD(AddSymbol)( + THIS_ + __in PCSTR Name, + __inout PULONG Index + ) PURE; + STDMETHOD(RemoveSymbolByName)( + THIS_ + __in PCSTR Name + ) PURE; + STDMETHOD(RemoveSymbolByIndex)( + THIS_ + __in ULONG Index + ) PURE; + STDMETHOD(GetSymbolName)( + THIS_ + __in ULONG Index, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG NameSize + ) PURE; + STDMETHOD(GetSymbolParameters)( + THIS_ + __in ULONG Start, + __in ULONG Count, + __out_ecount(Count) PDEBUG_SYMBOL_PARAMETERS Params + ) PURE; + STDMETHOD(ExpandSymbol)( + THIS_ + __in ULONG Index, + __in BOOL Expand + ) PURE; + // Uses the line prefix. + STDMETHOD(OutputSymbols)( + THIS_ + __in ULONG OutputControl, + __in ULONG Flags, + __in ULONG Start, + __in ULONG Count + ) PURE; + STDMETHOD(WriteSymbol)( + THIS_ + __in ULONG Index, + __in PCSTR Value + ) PURE; + STDMETHOD(OutputAsType)( + THIS_ + __in ULONG Index, + __in PCSTR Type + ) PURE; +}; + +#define DEBUG_SYMENT_IS_CODE 0x00000001 +#define DEBUG_SYMENT_IS_DATA 0x00000002 +#define DEBUG_SYMENT_IS_PARAMETER 0x00000004 +#define DEBUG_SYMENT_IS_LOCAL 0x00000008 +#define DEBUG_SYMENT_IS_MANAGED 0x00000010 +#define DEBUG_SYMENT_IS_SYNTHETIC 0x00000020 + +typedef struct _DEBUG_SYMBOL_ENTRY +{ + ULONG64 ModuleBase; + ULONG64 Offset; + ULONG64 Id; + ULONG64 Arg64; + ULONG Size; + ULONG Flags; + ULONG TypeId; + ULONG NameSize; + ULONG Token; + ULONG Tag; + ULONG Arg32; + ULONG Reserved; +} DEBUG_SYMBOL_ENTRY, *PDEBUG_SYMBOL_ENTRY; + +#undef INTERFACE +#define INTERFACE IDebugSymbolGroup2 +DECLARE_INTERFACE_(IDebugSymbolGroup2, IUnknown) +{ + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + __in REFIID InterfaceId, + __out PVOID* Interface + ) PURE; + STDMETHOD_(ULONG, AddRef)( + THIS + ) PURE; + STDMETHOD_(ULONG, Release)( + THIS + ) PURE; + + // IDebugSymbolGroup. + + STDMETHOD(GetNumberSymbols)( + THIS_ + __out PULONG Number + ) PURE; + // On input Index indicates the desired insertion + // index. On output Index contains the actual index. + // Use DEBUG_ANY_ID to append a symbol to the end. + STDMETHOD(AddSymbol)( + THIS_ + __in PCSTR Name, + __inout PULONG Index + ) PURE; + STDMETHOD(RemoveSymbolByName)( + THIS_ + __in PCSTR Name + ) PURE; + STDMETHOD(RemoveSymbolByIndex)( + THIS_ + __in ULONG Index + ) PURE; + STDMETHOD(GetSymbolName)( + THIS_ + __in ULONG Index, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG NameSize + ) PURE; + STDMETHOD(GetSymbolParameters)( + THIS_ + __in ULONG Start, + __in ULONG Count, + __out_ecount(Count) PDEBUG_SYMBOL_PARAMETERS Params + ) PURE; + STDMETHOD(ExpandSymbol)( + THIS_ + __in ULONG Index, + __in BOOL Expand + ) PURE; + // Uses the line prefix. + STDMETHOD(OutputSymbols)( + THIS_ + __in ULONG OutputControl, + __in ULONG Flags, + __in ULONG Start, + __in ULONG Count + ) PURE; + STDMETHOD(WriteSymbol)( + THIS_ + __in ULONG Index, + __in PCSTR Value + ) PURE; + STDMETHOD(OutputAsType)( + THIS_ + __in ULONG Index, + __in PCSTR Type + ) PURE; + + // IDebugSymbolGroup2. + + STDMETHOD(AddSymbolWide)( + THIS_ + __in PCWSTR Name, + __inout PULONG Index + ) PURE; + STDMETHOD(RemoveSymbolByNameWide)( + THIS_ + __in PCWSTR Name + ) PURE; + STDMETHOD(GetSymbolNameWide)( + THIS_ + __in ULONG Index, + __out_ecount_opt(BufferSize) PWSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG NameSize + ) PURE; + STDMETHOD(WriteSymbolWide)( + THIS_ + __in ULONG Index, + __in PCWSTR Value + ) PURE; + STDMETHOD(OutputAsTypeWide)( + THIS_ + __in ULONG Index, + __in PCWSTR Type + ) PURE; + + STDMETHOD(GetSymbolTypeName)( + THIS_ + __in ULONG Index, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG NameSize + ) PURE; + STDMETHOD(GetSymbolTypeNameWide)( + THIS_ + __in ULONG Index, + __out_ecount_opt(BufferSize) PWSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG NameSize + ) PURE; + STDMETHOD(GetSymbolSize)( + THIS_ + __in ULONG Index, + __out PULONG Size + ) PURE; + // If the symbol has an absolute address + // this method will retrieve it. + STDMETHOD(GetSymbolOffset)( + THIS_ + __in ULONG Index, + __out PULONG64 Offset + ) PURE; + // If the symbol is enregistered this + // method will return the register index. + STDMETHOD(GetSymbolRegister)( + THIS_ + __in ULONG Index, + __out PULONG Register + ) PURE; + STDMETHOD(GetSymbolValueText)( + THIS_ + __in ULONG Index, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG NameSize + ) PURE; + STDMETHOD(GetSymbolValueTextWide)( + THIS_ + __in ULONG Index, + __out_ecount_opt(BufferSize) PWSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG NameSize + ) PURE; + STDMETHOD(GetSymbolEntryInformation)( + THIS_ + __in ULONG Index, + __out PDEBUG_SYMBOL_ENTRY Entry + ) PURE; +}; + +//---------------------------------------------------------------------------- +// +// IDebugSymbols. +// +//---------------------------------------------------------------------------- + +// +// Information about a module. +// + +// Flags. +#define DEBUG_MODULE_LOADED 0x00000000 +#define DEBUG_MODULE_UNLOADED 0x00000001 +#define DEBUG_MODULE_USER_MODE 0x00000002 +#define DEBUG_MODULE_EXPLICIT 0x00000008 +#define DEBUG_MODULE_SECONDARY 0x00000010 +#define DEBUG_MODULE_SYNTHETIC 0x00000020 +#define DEBUG_MODULE_SYM_BAD_CHECKSUM 0x00010000 + +// Symbol types. +#define DEBUG_SYMTYPE_NONE 0 +#define DEBUG_SYMTYPE_COFF 1 +#define DEBUG_SYMTYPE_CODEVIEW 2 +#define DEBUG_SYMTYPE_PDB 3 +#define DEBUG_SYMTYPE_EXPORT 4 +#define DEBUG_SYMTYPE_DEFERRED 5 +#define DEBUG_SYMTYPE_SYM 6 +#define DEBUG_SYMTYPE_DIA 7 + +typedef struct _DEBUG_MODULE_PARAMETERS +{ + ULONG64 Base; + ULONG Size; + ULONG TimeDateStamp; + ULONG Checksum; + ULONG Flags; + ULONG SymbolType; + ULONG ImageNameSize; + ULONG ModuleNameSize; + ULONG LoadedImageNameSize; + ULONG SymbolFileNameSize; + ULONG MappedImageNameSize; + ULONG64 Reserved[2]; +} DEBUG_MODULE_PARAMETERS, *PDEBUG_MODULE_PARAMETERS; + +// Scope arguments are function arguments +// and thus only change when the scope +// crosses functions. +#define DEBUG_SCOPE_GROUP_ARGUMENTS 0x00000001 +// Scope locals are locals declared in a particular +// scope and are only defined within that scope. +#define DEBUG_SCOPE_GROUP_LOCALS 0x00000002 +// All symbols in the scope. +#define DEBUG_SCOPE_GROUP_ALL 0x00000003 + +// Typed data output control flags. +#define DEBUG_OUTTYPE_DEFAULT 0x00000000 +#define DEBUG_OUTTYPE_NO_INDENT 0x00000001 +#define DEBUG_OUTTYPE_NO_OFFSET 0x00000002 +#define DEBUG_OUTTYPE_VERBOSE 0x00000004 +#define DEBUG_OUTTYPE_COMPACT_OUTPUT 0x00000008 +#define DEBUG_OUTTYPE_RECURSION_LEVEL(Max) (((Max) & 0xf) << 4) +#define DEBUG_OUTTYPE_ADDRESS_OF_FIELD 0x00010000 +#define DEBUG_OUTTYPE_ADDRESS_AT_END 0x00020000 +#define DEBUG_OUTTYPE_BLOCK_RECURSE 0x00200000 + +// FindSourceFile flags. +#define DEBUG_FIND_SOURCE_DEFAULT 0x00000000 +// Returns fully-qualified paths only. If this +// is not set the path returned may be relative. +#define DEBUG_FIND_SOURCE_FULL_PATH 0x00000001 +// Scans all the path elements for a match and +// returns the one that has the most similarity +// between the given file and the matching element. +#define DEBUG_FIND_SOURCE_BEST_MATCH 0x00000002 +// Do not search source server paths. +#define DEBUG_FIND_SOURCE_NO_SRCSRV 0x00000004 +// Restrict FindSourceFileAndToken to token lookup only. +#define DEBUG_FIND_SOURCE_TOKEN_LOOKUP 0x00000008 + +// A special value marking an offset that should not +// be treated as a valid offset. This is only used +// in special situations where it is unlikely that +// this value would be a valid offset. +#define DEBUG_INVALID_OFFSET ((ULONG64)-1) + +#undef INTERFACE +#define INTERFACE IDebugSymbols +DECLARE_INTERFACE_(IDebugSymbols, IUnknown) +{ + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + __in REFIID InterfaceId, + __out PVOID* Interface + ) PURE; + STDMETHOD_(ULONG, AddRef)( + THIS + ) PURE; + STDMETHOD_(ULONG, Release)( + THIS + ) PURE; + + // IDebugSymbols. + + // Controls the symbol options used during + // symbol operations. + // Uses the same flags as dbghelps SymSetOptions. + STDMETHOD(GetSymbolOptions)( + THIS_ + __out PULONG Options + ) PURE; + STDMETHOD(AddSymbolOptions)( + THIS_ + __in ULONG Options + ) PURE; + STDMETHOD(RemoveSymbolOptions)( + THIS_ + __in ULONG Options + ) PURE; + STDMETHOD(SetSymbolOptions)( + THIS_ + __in ULONG Options + ) PURE; + + STDMETHOD(GetNameByOffset)( + THIS_ + __in ULONG64 Offset, + __out_ecount_opt(NameBufferSize) PSTR NameBuffer, + __in ULONG NameBufferSize, + __out_opt PULONG NameSize, + __out_opt PULONG64 Displacement + ) PURE; + // A symbol name may not be unique, particularly + // when overloaded functions exist which all + // have the same name. If GetOffsetByName + // finds multiple matches for the name it + // can return any one of them. In that + // case it will return S_FALSE to indicate + // that ambiguity was arbitrarily resolved. + // A caller can then use SearchSymbols to + // find all of the matches if it wishes to + // perform different disambiguation. + STDMETHOD(GetOffsetByName)( + THIS_ + __in PCSTR Symbol, + __out PULONG64 Offset + ) PURE; + // GetNearNameByOffset returns symbols + // located near the symbol closest to + // to the offset, such as the previous + // or next symbol. If Delta is zero it + // operates identically to GetNameByOffset. + // If Delta is nonzero and such a symbol + // does not exist an error is returned. + // The next symbol, if one exists, will + // always have a higher offset than the + // input offset so the displacement is + // always negative. The situation is + // reversed for the previous symbol. + STDMETHOD(GetNearNameByOffset)( + THIS_ + __in ULONG64 Offset, + __in LONG Delta, + __out_ecount_opt(NameBufferSize) PSTR NameBuffer, + __in ULONG NameBufferSize, + __out_opt PULONG NameSize, + __out_opt PULONG64 Displacement + ) PURE; + + STDMETHOD(GetLineByOffset)( + THIS_ + __in ULONG64 Offset, + __out_opt PULONG Line, + __out_ecount_opt(FileBufferSize) PSTR FileBuffer, + __in ULONG FileBufferSize, + __out_opt PULONG FileSize, + __out_opt PULONG64 Displacement + ) PURE; + STDMETHOD(GetOffsetByLine)( + THIS_ + __in ULONG Line, + __in PCSTR File, + __out PULONG64 Offset + ) PURE; + + // Enumerates the engines list of modules + // loaded for the current process. This may + // or may not match the system module list + // for the process. Reload can be used to + // synchronize the engines list with the system + // if necessary. + // Some sessions also track recently unloaded + // code modules for help in analyzing failures + // where an attempt is made to call unloaded code. + // These modules are indexed after the loaded + // modules. + STDMETHOD(GetNumberModules)( + THIS_ + __out PULONG Loaded, + __out PULONG Unloaded + ) PURE; + STDMETHOD(GetModuleByIndex)( + THIS_ + __in ULONG Index, + __out PULONG64 Base + ) PURE; + // The module name may not be unique. + // This method returns the first match. + STDMETHOD(GetModuleByModuleName)( + THIS_ + __in PCSTR Name, + __in ULONG StartIndex, + __out_opt PULONG Index, + __out_opt PULONG64 Base + ) PURE; + // Offset can be any offset within + // the module extent. Extents may + // not be unique when including unloaded + // drivers. This method returns the + // first match. + STDMETHOD(GetModuleByOffset)( + THIS_ + __in ULONG64 Offset, + __in ULONG StartIndex, + __out_opt PULONG Index, + __out_opt PULONG64 Base + ) PURE; + // If Index is DEBUG_ANY_ID the base address + // is used to look up the module instead. + STDMETHOD(GetModuleNames)( + THIS_ + __in ULONG Index, + __in ULONG64 Base, + __out_ecount_opt(ImageNameBufferSize) PSTR ImageNameBuffer, + __in ULONG ImageNameBufferSize, + __out_opt PULONG ImageNameSize, + __out_ecount_opt(ModuleNameBufferSize) PSTR ModuleNameBuffer, + __in ULONG ModuleNameBufferSize, + __out_opt PULONG ModuleNameSize, + __out_ecount_opt(LoadedImageNameBufferSize) PSTR LoadedImageNameBuffer, + __in ULONG LoadedImageNameBufferSize, + __out_opt PULONG LoadedImageNameSize + ) PURE; + STDMETHOD(GetModuleParameters)( + THIS_ + __in ULONG Count, + __in_ecount_opt(Count) PULONG64 Bases, + __in ULONG Start, + __out_ecount(Count) PDEBUG_MODULE_PARAMETERS Params + ) PURE; + // Looks up the module from a <Module>!<Symbol> + // string. + STDMETHOD(GetSymbolModule)( + THIS_ + __in PCSTR Symbol, + __out PULONG64 Base + ) PURE; + + // Returns the string name of a type. + STDMETHOD(GetTypeName)( + THIS_ + __in ULONG64 Module, + __in ULONG TypeId, + __out_ecount_opt(NameBufferSize) PSTR NameBuffer, + __in ULONG NameBufferSize, + __out_opt PULONG NameSize + ) PURE; + // Returns the ID for a type name. + STDMETHOD(GetTypeId)( + THIS_ + __in ULONG64 Module, + __in PCSTR Name, + __out PULONG TypeId + ) PURE; + STDMETHOD(GetTypeSize)( + THIS_ + __in ULONG64 Module, + __in ULONG TypeId, + __out PULONG Size + ) PURE; + // Given a type which can contain members + // this method returns the offset of a + // particular member within the type. + // TypeId should give the container type ID + // and Field gives the dot-separated path + // to the field of interest. + STDMETHOD(GetFieldOffset)( + THIS_ + __in ULONG64 Module, + __in ULONG TypeId, + __in PCSTR Field, + __out PULONG Offset + ) PURE; + + STDMETHOD(GetSymbolTypeId)( + THIS_ + __in PCSTR Symbol, + __out PULONG TypeId, + __out_opt PULONG64 Module + ) PURE; + // As with GetOffsetByName a symbol's + // name may be ambiguous. GetOffsetTypeId + // returns the type for the symbol closest + // to the given offset and can be used + // to avoid ambiguity. + STDMETHOD(GetOffsetTypeId)( + THIS_ + __in ULONG64 Offset, + __out PULONG TypeId, + __out_opt PULONG64 Module + ) PURE; + + // Helpers for virtual and physical data + // which combine creation of a location with + // the actual operation. + STDMETHOD(ReadTypedDataVirtual)( + THIS_ + __in ULONG64 Offset, + __in ULONG64 Module, + __in ULONG TypeId, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesRead + ) PURE; + STDMETHOD(WriteTypedDataVirtual)( + THIS_ + __in ULONG64 Offset, + __in ULONG64 Module, + __in ULONG TypeId, + __in_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesWritten + ) PURE; + STDMETHOD(OutputTypedDataVirtual)( + THIS_ + __in ULONG OutputControl, + __in ULONG64 Offset, + __in ULONG64 Module, + __in ULONG TypeId, + __in ULONG Flags + ) PURE; + STDMETHOD(ReadTypedDataPhysical)( + THIS_ + __in ULONG64 Offset, + __in ULONG64 Module, + __in ULONG TypeId, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesRead + ) PURE; + STDMETHOD(WriteTypedDataPhysical)( + THIS_ + __in ULONG64 Offset, + __in ULONG64 Module, + __in ULONG TypeId, + __in_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesWritten + ) PURE; + STDMETHOD(OutputTypedDataPhysical)( + THIS_ + __in ULONG OutputControl, + __in ULONG64 Offset, + __in ULONG64 Module, + __in ULONG TypeId, + __in ULONG Flags + ) PURE; + + // Function arguments and scope block symbols + // can be retrieved relative to currently + // executing code. A caller can provide just + // a code offset for scoping purposes and look + // up names or the caller can provide a full frame + // and look up actual values. The values for + // scoped symbols are best-guess and may or may not + // be accurate depending on program optimizations, + // the machine architecture, the current point + // in the programs execution and so on. + // A caller can also provide a complete register + // context for setting a scope to a previous + // machine state such as a context saved for + // an exception. Usually this isnt necessary + // and the current register context is used. + STDMETHOD(GetScope)( + THIS_ + __out_opt PULONG64 InstructionOffset, + __out_opt PDEBUG_STACK_FRAME ScopeFrame, + __out_bcount_opt(ScopeContextSize) PVOID ScopeContext, + __in ULONG ScopeContextSize + ) PURE; + // If ScopeFrame or ScopeContext is non-NULL then + // InstructionOffset is ignored. + // If ScopeContext is NULL the current + // register context is used. + // If the scope identified by the given + // information is the same as before + // SetScope returns S_OK. If the scope + // information changes, such as when the + // scope moves between functions or scope + // blocks, SetScope returns S_FALSE. + STDMETHOD(SetScope)( + THIS_ + __in ULONG64 InstructionOffset, + __in_opt PDEBUG_STACK_FRAME ScopeFrame, + __in_bcount_opt(ScopeContextSize) PVOID ScopeContext, + __in ULONG ScopeContextSize + ) PURE; + // ResetScope clears the scope information + // for situations where scoped symbols + // mask global symbols or when resetting + // from explicit information to the current + // information. + STDMETHOD(ResetScope)( + THIS + ) PURE; + // A scope symbol is tied to its particular + // scope and only is meaningful within the scope. + // The returned group can be updated by passing it back + // into the method for lower-cost + // incremental updates when stepping. + STDMETHOD(GetScopeSymbolGroup)( + THIS_ + __in ULONG Flags, + __in_opt PDEBUG_SYMBOL_GROUP Update, + __out PDEBUG_SYMBOL_GROUP* Symbols + ) PURE; + + // Create a new symbol group. + STDMETHOD(CreateSymbolGroup)( + THIS_ + __out PDEBUG_SYMBOL_GROUP* Group + ) PURE; + + // StartSymbolMatch matches symbol names + // against the given pattern using simple + // regular expressions. The search results + // are iterated through using GetNextSymbolMatch. + // When the caller is done examining results + // the match should be freed via EndSymbolMatch. + // If the match pattern contains a module name + // the search is restricted to a single module. + // Pattern matching is only done on symbol names, + // not module names. + // All active symbol match handles are invalidated + // when the set of loaded symbols changes. + STDMETHOD(StartSymbolMatch)( + THIS_ + __in PCSTR Pattern, + __out PULONG64 Handle + ) PURE; + // If Buffer is NULL the match does not + // advance. + STDMETHOD(GetNextSymbolMatch)( + THIS_ + __in ULONG64 Handle, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG MatchSize, + __out_opt PULONG64 Offset + ) PURE; + STDMETHOD(EndSymbolMatch)( + THIS_ + __in ULONG64 Handle + ) PURE; + + STDMETHOD(Reload)( + THIS_ + __in PCSTR Module + ) PURE; + + STDMETHOD(GetSymbolPath)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG PathSize + ) PURE; + STDMETHOD(SetSymbolPath)( + THIS_ + __in PCSTR Path + ) PURE; + STDMETHOD(AppendSymbolPath)( + THIS_ + __in PCSTR Addition + ) PURE; + + // Manipulate the path for executable images. + // Some dump files need to load executable images + // in order to resolve dump information. This + // path controls where the engine looks for + // images. + STDMETHOD(GetImagePath)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG PathSize + ) PURE; + STDMETHOD(SetImagePath)( + THIS_ + __in PCSTR Path + ) PURE; + STDMETHOD(AppendImagePath)( + THIS_ + __in PCSTR Addition + ) PURE; + + // Path routines for source file location + // methods. + STDMETHOD(GetSourcePath)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG PathSize + ) PURE; + // Gets the nth part of the source path. + STDMETHOD(GetSourcePathElement)( + THIS_ + __in ULONG Index, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG ElementSize + ) PURE; + STDMETHOD(SetSourcePath)( + THIS_ + __in PCSTR Path + ) PURE; + STDMETHOD(AppendSourcePath)( + THIS_ + __in PCSTR Addition + ) PURE; + // Uses the given file path and the source path + // information to try and locate an existing file. + // The given file path is merged with elements + // of the source path and checked for existence. + // If a match is found the element used is returned. + // A starting element can be specified to restrict + // the search to a subset of the path elements; + // this can be useful when checking for multiple + // matches along the source path. + // The returned element can be 1, indicating + // the file was found directly and not on the path. + STDMETHOD(FindSourceFile)( + THIS_ + __in ULONG StartElement, + __in PCSTR File, + __in ULONG Flags, + __out_opt PULONG FoundElement, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG FoundSize + ) PURE; + // Retrieves all the line offset information + // for a particular source file. Buffer is + // first intialized to DEBUG_INVALID_OFFSET for + // every entry. Then for each piece of line + // symbol information Buffer[Line] set to + // Lines offset. This produces a per-line + // map of the offsets for the lines of the + // given file. Line numbers are decremented + // for the map so Buffer[0] contains the offset + // for line number 1. + // If there is no line information at all for + // the given file the method fails rather + // than returning a map of invalid offsets. + STDMETHOD(GetSourceFileLineOffsets)( + THIS_ + __in PCSTR File, + __out_ecount_opt(BufferLines) PULONG64 Buffer, + __in ULONG BufferLines, + __out_opt PULONG FileLines + ) PURE; +}; + +// +// GetModuleNameString strings. +// + +#define DEBUG_MODNAME_IMAGE 0x00000000 +#define DEBUG_MODNAME_MODULE 0x00000001 +#define DEBUG_MODNAME_LOADED_IMAGE 0x00000002 +#define DEBUG_MODNAME_SYMBOL_FILE 0x00000003 +#define DEBUG_MODNAME_MAPPED_IMAGE 0x00000004 + +// +// Type options, used with Get/SetTypeOptions. +// + +// Display PUSHORT and USHORT arrays in Unicode. +#define DEBUG_TYPEOPTS_UNICODE_DISPLAY 0x00000001 +// Display LONG types in default base instead of decimal. +#define DEBUG_TYPEOPTS_LONGSTATUS_DISPLAY 0x00000002 +// Display integer types in default base instead of decimal. +#define DEBUG_TYPEOPTS_FORCERADIX_OUTPUT 0x00000004 +// Search for the type/symbol with largest size when +// multiple type/symbol match for a given name +#define DEBUG_TYPEOPTS_MATCH_MAXSIZE 0x00000008 + +#undef INTERFACE +#define INTERFACE IDebugSymbols2 +DECLARE_INTERFACE_(IDebugSymbols2, IUnknown) +{ + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + __in REFIID InterfaceId, + __out PVOID* Interface + ) PURE; + STDMETHOD_(ULONG, AddRef)( + THIS + ) PURE; + STDMETHOD_(ULONG, Release)( + THIS + ) PURE; + + // IDebugSymbols. + + // Controls the symbol options used during + // symbol operations. + // Uses the same flags as dbghelps SymSetOptions. + STDMETHOD(GetSymbolOptions)( + THIS_ + __out PULONG Options + ) PURE; + STDMETHOD(AddSymbolOptions)( + THIS_ + __in ULONG Options + ) PURE; + STDMETHOD(RemoveSymbolOptions)( + THIS_ + __in ULONG Options + ) PURE; + STDMETHOD(SetSymbolOptions)( + THIS_ + __in ULONG Options + ) PURE; + + STDMETHOD(GetNameByOffset)( + THIS_ + __in ULONG64 Offset, + __out_ecount_opt(NameBufferSize) PSTR NameBuffer, + __in ULONG NameBufferSize, + __out_opt PULONG NameSize, + __out_opt PULONG64 Displacement + ) PURE; + // A symbol name may not be unique, particularly + // when overloaded functions exist which all + // have the same name. If GetOffsetByName + // finds multiple matches for the name it + // can return any one of them. In that + // case it will return S_FALSE to indicate + // that ambiguity was arbitrarily resolved. + // A caller can then use SearchSymbols to + // find all of the matches if it wishes to + // perform different disambiguation. + STDMETHOD(GetOffsetByName)( + THIS_ + __in PCSTR Symbol, + __out PULONG64 Offset + ) PURE; + // GetNearNameByOffset returns symbols + // located near the symbol closest to + // to the offset, such as the previous + // or next symbol. If Delta is zero it + // operates identically to GetNameByOffset. + // If Delta is nonzero and such a symbol + // does not exist an error is returned. + // The next symbol, if one exists, will + // always have a higher offset than the + // input offset so the displacement is + // always negative. The situation is + // reversed for the previous symbol. + STDMETHOD(GetNearNameByOffset)( + THIS_ + __in ULONG64 Offset, + __in LONG Delta, + __out_ecount_opt(NameBufferSize) PSTR NameBuffer, + __in ULONG NameBufferSize, + __out_opt PULONG NameSize, + __out_opt PULONG64 Displacement + ) PURE; + + STDMETHOD(GetLineByOffset)( + THIS_ + __in ULONG64 Offset, + __out_opt PULONG Line, + __out_ecount_opt(FileBufferSize) PSTR FileBuffer, + __in ULONG FileBufferSize, + __out_opt PULONG FileSize, + __out_opt PULONG64 Displacement + ) PURE; + STDMETHOD(GetOffsetByLine)( + THIS_ + __in ULONG Line, + __in PCSTR File, + __out PULONG64 Offset + ) PURE; + + // Enumerates the engines list of modules + // loaded for the current process. This may + // or may not match the system module list + // for the process. Reload can be used to + // synchronize the engines list with the system + // if necessary. + // Some sessions also track recently unloaded + // code modules for help in analyzing failures + // where an attempt is made to call unloaded code. + // These modules are indexed after the loaded + // modules. + STDMETHOD(GetNumberModules)( + THIS_ + __out PULONG Loaded, + __out PULONG Unloaded + ) PURE; + STDMETHOD(GetModuleByIndex)( + THIS_ + __in ULONG Index, + __out PULONG64 Base + ) PURE; + // The module name may not be unique. + // This method returns the first match. + STDMETHOD(GetModuleByModuleName)( + THIS_ + __in PCSTR Name, + __in ULONG StartIndex, + __out_opt PULONG Index, + __out_opt PULONG64 Base + ) PURE; + // Offset can be any offset within + // the module extent. Extents may + // not be unique when including unloaded + // drivers. This method returns the + // first match. + STDMETHOD(GetModuleByOffset)( + THIS_ + __in ULONG64 Offset, + __in ULONG StartIndex, + __out_opt PULONG Index, + __out_opt PULONG64 Base + ) PURE; + // If Index is DEBUG_ANY_ID the base address + // is used to look up the module instead. + STDMETHOD(GetModuleNames)( + THIS_ + __in ULONG Index, + __in ULONG64 Base, + __out_ecount_opt(ImageNameBufferSize) PSTR ImageNameBuffer, + __in ULONG ImageNameBufferSize, + __out_opt PULONG ImageNameSize, + __out_ecount_opt(ModuleNameBufferSize) PSTR ModuleNameBuffer, + __in ULONG ModuleNameBufferSize, + __out_opt PULONG ModuleNameSize, + __out_ecount_opt(LoadedImageNameBufferSize) PSTR LoadedImageNameBuffer, + __in ULONG LoadedImageNameBufferSize, + __out_opt PULONG LoadedImageNameSize + ) PURE; + STDMETHOD(GetModuleParameters)( + THIS_ + __in ULONG Count, + __in_ecount_opt(Count) PULONG64 Bases, + __in ULONG Start, + __out_ecount(Count) PDEBUG_MODULE_PARAMETERS Params + ) PURE; + // Looks up the module from a <Module>!<Symbol> + // string. + STDMETHOD(GetSymbolModule)( + THIS_ + __in PCSTR Symbol, + __out PULONG64 Base + ) PURE; + + // Returns the string name of a type. + STDMETHOD(GetTypeName)( + THIS_ + __in ULONG64 Module, + __in ULONG TypeId, + __out_ecount_opt(NameBufferSize) PSTR NameBuffer, + __in ULONG NameBufferSize, + __out_opt PULONG NameSize + ) PURE; + // Returns the ID for a type name. + STDMETHOD(GetTypeId)( + THIS_ + __in ULONG64 Module, + __in PCSTR Name, + __out PULONG TypeId + ) PURE; + STDMETHOD(GetTypeSize)( + THIS_ + __in ULONG64 Module, + __in ULONG TypeId, + __out PULONG Size + ) PURE; + // Given a type which can contain members + // this method returns the offset of a + // particular member within the type. + // TypeId should give the container type ID + // and Field gives the dot-separated path + // to the field of interest. + STDMETHOD(GetFieldOffset)( + THIS_ + __in ULONG64 Module, + __in ULONG TypeId, + __in PCSTR Field, + __out PULONG Offset + ) PURE; + + STDMETHOD(GetSymbolTypeId)( + THIS_ + __in PCSTR Symbol, + __out PULONG TypeId, + __out_opt PULONG64 Module + ) PURE; + // As with GetOffsetByName a symbol's + // name may be ambiguous. GetOffsetTypeId + // returns the type for the symbol closest + // to the given offset and can be used + // to avoid ambiguity. + STDMETHOD(GetOffsetTypeId)( + THIS_ + __in ULONG64 Offset, + __out PULONG TypeId, + __out_opt PULONG64 Module + ) PURE; + + // Helpers for virtual and physical data + // which combine creation of a location with + // the actual operation. + STDMETHOD(ReadTypedDataVirtual)( + THIS_ + __in ULONG64 Offset, + __in ULONG64 Module, + __in ULONG TypeId, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesRead + ) PURE; + STDMETHOD(WriteTypedDataVirtual)( + THIS_ + __in ULONG64 Offset, + __in ULONG64 Module, + __in ULONG TypeId, + __in_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesWritten + ) PURE; + STDMETHOD(OutputTypedDataVirtual)( + THIS_ + __in ULONG OutputControl, + __in ULONG64 Offset, + __in ULONG64 Module, + __in ULONG TypeId, + __in ULONG Flags + ) PURE; + STDMETHOD(ReadTypedDataPhysical)( + THIS_ + __in ULONG64 Offset, + __in ULONG64 Module, + __in ULONG TypeId, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesRead + ) PURE; + STDMETHOD(WriteTypedDataPhysical)( + THIS_ + __in ULONG64 Offset, + __in ULONG64 Module, + __in ULONG TypeId, + __in_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesWritten + ) PURE; + STDMETHOD(OutputTypedDataPhysical)( + THIS_ + __in ULONG OutputControl, + __in ULONG64 Offset, + __in ULONG64 Module, + __in ULONG TypeId, + __in ULONG Flags + ) PURE; + + // Function arguments and scope block symbols + // can be retrieved relative to currently + // executing code. A caller can provide just + // a code offset for scoping purposes and look + // up names or the caller can provide a full frame + // and look up actual values. The values for + // scoped symbols are best-guess and may or may not + // be accurate depending on program optimizations, + // the machine architecture, the current point + // in the programs execution and so on. + // A caller can also provide a complete register + // context for setting a scope to a previous + // machine state such as a context saved for + // an exception. Usually this isnt necessary + // and the current register context is used. + STDMETHOD(GetScope)( + THIS_ + __out_opt PULONG64 InstructionOffset, + __out_opt PDEBUG_STACK_FRAME ScopeFrame, + __out_bcount_opt(ScopeContextSize) PVOID ScopeContext, + __in ULONG ScopeContextSize + ) PURE; + // If ScopeFrame or ScopeContext is non-NULL then + // InstructionOffset is ignored. + // If ScopeContext is NULL the current + // register context is used. + // If the scope identified by the given + // information is the same as before + // SetScope returns S_OK. If the scope + // information changes, such as when the + // scope moves between functions or scope + // blocks, SetScope returns S_FALSE. + STDMETHOD(SetScope)( + THIS_ + __in ULONG64 InstructionOffset, + __in_opt PDEBUG_STACK_FRAME ScopeFrame, + __in_bcount_opt(ScopeContextSize) PVOID ScopeContext, + __in ULONG ScopeContextSize + ) PURE; + // ResetScope clears the scope information + // for situations where scoped symbols + // mask global symbols or when resetting + // from explicit information to the current + // information. + STDMETHOD(ResetScope)( + THIS + ) PURE; + // A scope symbol is tied to its particular + // scope and only is meaningful within the scope. + // The returned group can be updated by passing it back + // into the method for lower-cost + // incremental updates when stepping. + STDMETHOD(GetScopeSymbolGroup)( + THIS_ + __in ULONG Flags, + __in_opt PDEBUG_SYMBOL_GROUP Update, + __out PDEBUG_SYMBOL_GROUP* Symbols + ) PURE; + + // Create a new symbol group. + STDMETHOD(CreateSymbolGroup)( + THIS_ + __out PDEBUG_SYMBOL_GROUP* Group + ) PURE; + + // StartSymbolMatch matches symbol names + // against the given pattern using simple + // regular expressions. The search results + // are iterated through using GetNextSymbolMatch. + // When the caller is done examining results + // the match should be freed via EndSymbolMatch. + // If the match pattern contains a module name + // the search is restricted to a single module. + // Pattern matching is only done on symbol names, + // not module names. + // All active symbol match handles are invalidated + // when the set of loaded symbols changes. + STDMETHOD(StartSymbolMatch)( + THIS_ + __in PCSTR Pattern, + __out PULONG64 Handle + ) PURE; + // If Buffer is NULL the match does not + // advance. + STDMETHOD(GetNextSymbolMatch)( + THIS_ + __in ULONG64 Handle, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG MatchSize, + __out_opt PULONG64 Offset + ) PURE; + STDMETHOD(EndSymbolMatch)( + THIS_ + __in ULONG64 Handle + ) PURE; + + STDMETHOD(Reload)( + THIS_ + __in PCSTR Module + ) PURE; + + STDMETHOD(GetSymbolPath)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG PathSize + ) PURE; + STDMETHOD(SetSymbolPath)( + THIS_ + __in PCSTR Path + ) PURE; + STDMETHOD(AppendSymbolPath)( + THIS_ + __in PCSTR Addition + ) PURE; + + // Manipulate the path for executable images. + // Some dump files need to load executable images + // in order to resolve dump information. This + // path controls where the engine looks for + // images. + STDMETHOD(GetImagePath)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG PathSize + ) PURE; + STDMETHOD(SetImagePath)( + THIS_ + __in PCSTR Path + ) PURE; + STDMETHOD(AppendImagePath)( + THIS_ + __in PCSTR Addition + ) PURE; + + // Path routines for source file location + // methods. + STDMETHOD(GetSourcePath)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG PathSize + ) PURE; + // Gets the nth part of the source path. + STDMETHOD(GetSourcePathElement)( + THIS_ + __in ULONG Index, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG ElementSize + ) PURE; + STDMETHOD(SetSourcePath)( + THIS_ + __in PCSTR Path + ) PURE; + STDMETHOD(AppendSourcePath)( + THIS_ + __in PCSTR Addition + ) PURE; + // Uses the given file path and the source path + // information to try and locate an existing file. + // The given file path is merged with elements + // of the source path and checked for existence. + // If a match is found the element used is returned. + // A starting element can be specified to restrict + // the search to a subset of the path elements; + // this can be useful when checking for multiple + // matches along the source path. + // The returned element can be 1, indicating + // the file was found directly and not on the path. + STDMETHOD(FindSourceFile)( + THIS_ + __in ULONG StartElement, + __in PCSTR File, + __in ULONG Flags, + __out_opt PULONG FoundElement, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG FoundSize + ) PURE; + // Retrieves all the line offset information + // for a particular source file. Buffer is + // first intialized to DEBUG_INVALID_OFFSET for + // every entry. Then for each piece of line + // symbol information Buffer[Line] set to + // Lines offset. This produces a per-line + // map of the offsets for the lines of the + // given file. Line numbers are decremented + // for the map so Buffer[0] contains the offset + // for line number 1. + // If there is no line information at all for + // the given file the method fails rather + // than returning a map of invalid offsets. + STDMETHOD(GetSourceFileLineOffsets)( + THIS_ + __in PCSTR File, + __out_ecount_opt(BufferLines) PULONG64 Buffer, + __in ULONG BufferLines, + __out_opt PULONG FileLines + ) PURE; + + // IDebugSymbols2. + + // If Index is DEBUG_ANY_ID the base address + // is used to look up the module instead. + // Item is specified as in VerQueryValue. + // Module version information is only + // available for loaded modules and may + // not be available in all debug sessions. + STDMETHOD(GetModuleVersionInformation)( + THIS_ + __in ULONG Index, + __in ULONG64 Base, + __in PCSTR Item, + __out_bcount_opt(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG VerInfoSize + ) PURE; + // Retrieves any available module name string + // such as module name or symbol file name. + // If Index is DEBUG_ANY_ID the base address + // is used to look up the module instead. + // If symbols are deferred an error will + // be returned. + // E_NOINTERFACE may be returned, indicating + // no information exists. + STDMETHOD(GetModuleNameString)( + THIS_ + __in ULONG Which, + __in ULONG Index, + __in ULONG64 Base, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG NameSize + ) PURE; + + // Returns the string name of a constant type. + STDMETHOD(GetConstantName)( + THIS_ + __in ULONG64 Module, + __in ULONG TypeId, + __in ULONG64 Value, + __out_ecount_opt(NameBufferSize) PSTR NameBuffer, + __in ULONG NameBufferSize, + __out_opt PULONG NameSize + ) PURE; + + // Gets name of a field in a struct + // FieldNumber is 0 based index of field in a struct + STDMETHOD(GetFieldName)( + THIS_ + __in ULONG64 Module, + __in ULONG TypeId, + __in ULONG FieldIndex, + __out_ecount_opt(NameBufferSize) PSTR NameBuffer, + __in ULONG NameBufferSize, + __out_opt PULONG NameSize + ) PURE; + + // Control options for typed values. + STDMETHOD(GetTypeOptions)( + THIS_ + __out PULONG Options + ) PURE; + STDMETHOD(AddTypeOptions)( + THIS_ + __in ULONG Options + ) PURE; + STDMETHOD(RemoveTypeOptions)( + THIS_ + __in ULONG Options + ) PURE; + STDMETHOD(SetTypeOptions)( + THIS_ + __in ULONG Options + ) PURE; +}; + +// +// GetModuleBy* flags. +// + +// Scan all modules, loaded and unloaded. +#define DEBUG_GETMOD_DEFAULT 0x00000000 +// Do not scan loaded modules. +#define DEBUG_GETMOD_NO_LOADED_MODULES 0x00000001 +// Do not scan unloaded modules. +#define DEBUG_GETMOD_NO_UNLOADED_MODULES 0x00000002 + +// +// AddSyntheticModule flags. +// + +#define DEBUG_ADDSYNTHMOD_DEFAULT 0x00000000 + +// +// AddSyntheticSymbol flags. +// + +#define DEBUG_ADDSYNTHSYM_DEFAULT 0x00000000 + +// +// OutputSymbolByOffset flags. +// + +// Use the current debugger settings for symbol output. +#define DEBUG_OUTSYM_DEFAULT 0x00000000 +// Always display the offset in addition to any symbol hit. +#define DEBUG_OUTSYM_FORCE_OFFSET 0x00000001 +// Display source line information if found. +#define DEBUG_OUTSYM_SOURCE_LINE 0x00000002 +// Output symbol hits that don't exactly match. +#define DEBUG_OUTSYM_ALLOW_DISPLACEMENT 0x00000004 + +// +// GetFunctionEntryByOffset flags. +// + +#define DEBUG_GETFNENT_DEFAULT 0x00000000 +// The engine provides artificial entries for well-known +// cases. This flag limits the entry search to only +// the raw entries and disables artificial entry lookup. +#define DEBUG_GETFNENT_RAW_ENTRY_ONLY 0x00000001 + +typedef struct _DEBUG_MODULE_AND_ID +{ + ULONG64 ModuleBase; + ULONG64 Id; +} DEBUG_MODULE_AND_ID, *PDEBUG_MODULE_AND_ID; + +#define DEBUG_SOURCE_IS_STATEMENT 0x00000001 + +// +// GetSourceEntriesByLine flags. +// + +#define DEBUG_GSEL_DEFAULT 0x00000000 +// Do not allow any extra symbols to load during the search. +#define DEBUG_GSEL_NO_SYMBOL_LOADS 0x00000001 +// Allow source hits with lower line numbers. +#define DEBUG_GSEL_ALLOW_LOWER 0x00000002 +// Allow source hits with higher line numbers. +#define DEBUG_GSEL_ALLOW_HIGHER 0x00000004 +// Only return the nearest hits. +#define DEBUG_GSEL_NEAREST_ONLY 0x00000008 + +typedef struct _DEBUG_SYMBOL_SOURCE_ENTRY +{ + ULONG64 ModuleBase; + ULONG64 Offset; + ULONG64 FileNameId; + ULONG64 EngineInternal; + ULONG Size; + ULONG Flags; + ULONG FileNameSize; + // Line numbers are one-based. + // May be DEBUG_ANY_ID if unknown. + ULONG StartLine; + ULONG EndLine; + // Column numbers are one-based byte indices. + // May be DEBUG_ANY_ID if unknown. + ULONG StartColumn; + ULONG EndColumn; + ULONG Reserved; +} DEBUG_SYMBOL_SOURCE_ENTRY, *PDEBUG_SYMBOL_SOURCE_ENTRY; + +#undef INTERFACE +#define INTERFACE IDebugSymbols3 +DECLARE_INTERFACE_(IDebugSymbols3, IUnknown) +{ + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + __in REFIID InterfaceId, + __out PVOID* Interface + ) PURE; + STDMETHOD_(ULONG, AddRef)( + THIS + ) PURE; + STDMETHOD_(ULONG, Release)( + THIS + ) PURE; + + // IDebugSymbols. + + // Controls the symbol options used during + // symbol operations. + // Uses the same flags as dbghelps SymSetOptions. + STDMETHOD(GetSymbolOptions)( + THIS_ + __out PULONG Options + ) PURE; + STDMETHOD(AddSymbolOptions)( + THIS_ + __in ULONG Options + ) PURE; + STDMETHOD(RemoveSymbolOptions)( + THIS_ + __in ULONG Options + ) PURE; + STDMETHOD(SetSymbolOptions)( + THIS_ + __in ULONG Options + ) PURE; + + STDMETHOD(GetNameByOffset)( + THIS_ + __in ULONG64 Offset, + __out_ecount_opt(NameBufferSize) PSTR NameBuffer, + __in ULONG NameBufferSize, + __out_opt PULONG NameSize, + __out_opt PULONG64 Displacement + ) PURE; + // A symbol name may not be unique, particularly + // when overloaded functions exist which all + // have the same name. If GetOffsetByName + // finds multiple matches for the name it + // can return any one of them. In that + // case it will return S_FALSE to indicate + // that ambiguity was arbitrarily resolved. + // A caller can then use SearchSymbols to + // find all of the matches if it wishes to + // perform different disambiguation. + STDMETHOD(GetOffsetByName)( + THIS_ + __in PCSTR Symbol, + __out PULONG64 Offset + ) PURE; + // GetNearNameByOffset returns symbols + // located near the symbol closest to + // to the offset, such as the previous + // or next symbol. If Delta is zero it + // operates identically to GetNameByOffset. + // If Delta is nonzero and such a symbol + // does not exist an error is returned. + // The next symbol, if one exists, will + // always have a higher offset than the + // input offset so the displacement is + // always negative. The situation is + // reversed for the previous symbol. + STDMETHOD(GetNearNameByOffset)( + THIS_ + __in ULONG64 Offset, + __in LONG Delta, + __out_ecount_opt(NameBufferSize) PSTR NameBuffer, + __in ULONG NameBufferSize, + __out_opt PULONG NameSize, + __out_opt PULONG64 Displacement + ) PURE; + + STDMETHOD(GetLineByOffset)( + THIS_ + __in ULONG64 Offset, + __out_opt PULONG Line, + __out_ecount_opt(FileBufferSize) PSTR FileBuffer, + __in ULONG FileBufferSize, + __out_opt PULONG FileSize, + __out_opt PULONG64 Displacement + ) PURE; + STDMETHOD(GetOffsetByLine)( + THIS_ + __in ULONG Line, + __in PCSTR File, + __out PULONG64 Offset + ) PURE; + + // Enumerates the engines list of modules + // loaded for the current process. This may + // or may not match the system module list + // for the process. Reload can be used to + // synchronize the engines list with the system + // if necessary. + // Some sessions also track recently unloaded + // code modules for help in analyzing failures + // where an attempt is made to call unloaded code. + // These modules are indexed after the loaded + // modules. + STDMETHOD(GetNumberModules)( + THIS_ + __out PULONG Loaded, + __out PULONG Unloaded + ) PURE; + STDMETHOD(GetModuleByIndex)( + THIS_ + __in ULONG Index, + __out PULONG64 Base + ) PURE; + // The module name may not be unique. + // This method returns the first match. + STDMETHOD(GetModuleByModuleName)( + THIS_ + __in PCSTR Name, + __in ULONG StartIndex, + __out_opt PULONG Index, + __out_opt PULONG64 Base + ) PURE; + // Offset can be any offset within + // the module extent. Extents may + // not be unique when including unloaded + // drivers. This method returns the + // first match. + STDMETHOD(GetModuleByOffset)( + THIS_ + __in ULONG64 Offset, + __in ULONG StartIndex, + __out_opt PULONG Index, + __out_opt PULONG64 Base + ) PURE; + // If Index is DEBUG_ANY_ID the base address + // is used to look up the module instead. + STDMETHOD(GetModuleNames)( + THIS_ + __in ULONG Index, + __in ULONG64 Base, + __out_ecount_opt(ImageNameBufferSize) PSTR ImageNameBuffer, + __in ULONG ImageNameBufferSize, + __out_opt PULONG ImageNameSize, + __out_ecount_opt(ModuleNameBufferSize) PSTR ModuleNameBuffer, + __in ULONG ModuleNameBufferSize, + __out_opt PULONG ModuleNameSize, + __out_ecount_opt(LoadedImageNameBufferSize) PSTR LoadedImageNameBuffer, + __in ULONG LoadedImageNameBufferSize, + __out_opt PULONG LoadedImageNameSize + ) PURE; + STDMETHOD(GetModuleParameters)( + THIS_ + __in ULONG Count, + __in_ecount_opt(Count) PULONG64 Bases, + __in ULONG Start, + __out_ecount(Count) PDEBUG_MODULE_PARAMETERS Params + ) PURE; + // Looks up the module from a <Module>!<Symbol> + // string. + STDMETHOD(GetSymbolModule)( + THIS_ + __in PCSTR Symbol, + __out PULONG64 Base + ) PURE; + + // Returns the string name of a type. + STDMETHOD(GetTypeName)( + THIS_ + __in ULONG64 Module, + __in ULONG TypeId, + __out_ecount_opt(NameBufferSize) PSTR NameBuffer, + __in ULONG NameBufferSize, + __out_opt PULONG NameSize + ) PURE; + // Returns the ID for a type name. + STDMETHOD(GetTypeId)( + THIS_ + __in ULONG64 Module, + __in PCSTR Name, + __out PULONG TypeId + ) PURE; + STDMETHOD(GetTypeSize)( + THIS_ + __in ULONG64 Module, + __in ULONG TypeId, + __out PULONG Size + ) PURE; + // Given a type which can contain members + // this method returns the offset of a + // particular member within the type. + // TypeId should give the container type ID + // and Field gives the dot-separated path + // to the field of interest. + STDMETHOD(GetFieldOffset)( + THIS_ + __in ULONG64 Module, + __in ULONG TypeId, + __in PCSTR Field, + __out PULONG Offset + ) PURE; + + STDMETHOD(GetSymbolTypeId)( + THIS_ + __in PCSTR Symbol, + __out PULONG TypeId, + __out_opt PULONG64 Module + ) PURE; + // As with GetOffsetByName a symbol's + // name may be ambiguous. GetOffsetTypeId + // returns the type for the symbol closest + // to the given offset and can be used + // to avoid ambiguity. + STDMETHOD(GetOffsetTypeId)( + THIS_ + __in ULONG64 Offset, + __out PULONG TypeId, + __out_opt PULONG64 Module + ) PURE; + + // Helpers for virtual and physical data + // which combine creation of a location with + // the actual operation. + STDMETHOD(ReadTypedDataVirtual)( + THIS_ + __in ULONG64 Offset, + __in ULONG64 Module, + __in ULONG TypeId, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesRead + ) PURE; + STDMETHOD(WriteTypedDataVirtual)( + THIS_ + __in ULONG64 Offset, + __in ULONG64 Module, + __in ULONG TypeId, + __in_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesWritten + ) PURE; + STDMETHOD(OutputTypedDataVirtual)( + THIS_ + __in ULONG OutputControl, + __in ULONG64 Offset, + __in ULONG64 Module, + __in ULONG TypeId, + __in ULONG Flags + ) PURE; + STDMETHOD(ReadTypedDataPhysical)( + THIS_ + __in ULONG64 Offset, + __in ULONG64 Module, + __in ULONG TypeId, + __out_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesRead + ) PURE; + STDMETHOD(WriteTypedDataPhysical)( + THIS_ + __in ULONG64 Offset, + __in ULONG64 Module, + __in ULONG TypeId, + __in_bcount(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BytesWritten + ) PURE; + STDMETHOD(OutputTypedDataPhysical)( + THIS_ + __in ULONG OutputControl, + __in ULONG64 Offset, + __in ULONG64 Module, + __in ULONG TypeId, + __in ULONG Flags + ) PURE; + + // Function arguments and scope block symbols + // can be retrieved relative to currently + // executing code. A caller can provide just + // a code offset for scoping purposes and look + // up names or the caller can provide a full frame + // and look up actual values. The values for + // scoped symbols are best-guess and may or may not + // be accurate depending on program optimizations, + // the machine architecture, the current point + // in the programs execution and so on. + // A caller can also provide a complete register + // context for setting a scope to a previous + // machine state such as a context saved for + // an exception. Usually this isnt necessary + // and the current register context is used. + STDMETHOD(GetScope)( + THIS_ + __out_opt PULONG64 InstructionOffset, + __out_opt PDEBUG_STACK_FRAME ScopeFrame, + __out_bcount_opt(ScopeContextSize) PVOID ScopeContext, + __in ULONG ScopeContextSize + ) PURE; + // If ScopeFrame or ScopeContext is non-NULL then + // InstructionOffset is ignored. + // If ScopeContext is NULL the current + // register context is used. + // If the scope identified by the given + // information is the same as before + // SetScope returns S_OK. If the scope + // information changes, such as when the + // scope moves between functions or scope + // blocks, SetScope returns S_FALSE. + STDMETHOD(SetScope)( + THIS_ + __in ULONG64 InstructionOffset, + __in_opt PDEBUG_STACK_FRAME ScopeFrame, + __in_bcount_opt(ScopeContextSize) PVOID ScopeContext, + __in ULONG ScopeContextSize + ) PURE; + // ResetScope clears the scope information + // for situations where scoped symbols + // mask global symbols or when resetting + // from explicit information to the current + // information. + STDMETHOD(ResetScope)( + THIS + ) PURE; + // A scope symbol is tied to its particular + // scope and only is meaningful within the scope. + // The returned group can be updated by passing it back + // into the method for lower-cost + // incremental updates when stepping. + STDMETHOD(GetScopeSymbolGroup)( + THIS_ + __in ULONG Flags, + __in_opt PDEBUG_SYMBOL_GROUP Update, + __out PDEBUG_SYMBOL_GROUP* Symbols + ) PURE; + + // Create a new symbol group. + STDMETHOD(CreateSymbolGroup)( + THIS_ + __out PDEBUG_SYMBOL_GROUP* Group + ) PURE; + + // StartSymbolMatch matches symbol names + // against the given pattern using simple + // regular expressions. The search results + // are iterated through using GetNextSymbolMatch. + // When the caller is done examining results + // the match should be freed via EndSymbolMatch. + // If the match pattern contains a module name + // the search is restricted to a single module. + // Pattern matching is only done on symbol names, + // not module names. + // All active symbol match handles are invalidated + // when the set of loaded symbols changes. + STDMETHOD(StartSymbolMatch)( + THIS_ + __in PCSTR Pattern, + __out PULONG64 Handle + ) PURE; + // If Buffer is NULL the match does not + // advance. + STDMETHOD(GetNextSymbolMatch)( + THIS_ + __in ULONG64 Handle, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG MatchSize, + __out_opt PULONG64 Offset + ) PURE; + STDMETHOD(EndSymbolMatch)( + THIS_ + __in ULONG64 Handle + ) PURE; + + STDMETHOD(Reload)( + THIS_ + __in PCSTR Module + ) PURE; + + STDMETHOD(GetSymbolPath)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG PathSize + ) PURE; + STDMETHOD(SetSymbolPath)( + THIS_ + __in PCSTR Path + ) PURE; + STDMETHOD(AppendSymbolPath)( + THIS_ + __in PCSTR Addition + ) PURE; + + // Manipulate the path for executable images. + // Some dump files need to load executable images + // in order to resolve dump information. This + // path controls where the engine looks for + // images. + STDMETHOD(GetImagePath)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG PathSize + ) PURE; + STDMETHOD(SetImagePath)( + THIS_ + __in PCSTR Path + ) PURE; + STDMETHOD(AppendImagePath)( + THIS_ + __in PCSTR Addition + ) PURE; + + // Path routines for source file location + // methods. + STDMETHOD(GetSourcePath)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG PathSize + ) PURE; + // Gets the nth part of the source path. + STDMETHOD(GetSourcePathElement)( + THIS_ + __in ULONG Index, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG ElementSize + ) PURE; + STDMETHOD(SetSourcePath)( + THIS_ + __in PCSTR Path + ) PURE; + STDMETHOD(AppendSourcePath)( + THIS_ + __in PCSTR Addition + ) PURE; + // Uses the given file path and the source path + // information to try and locate an existing file. + // The given file path is merged with elements + // of the source path and checked for existence. + // If a match is found the element used is returned. + // A starting element can be specified to restrict + // the search to a subset of the path elements; + // this can be useful when checking for multiple + // matches along the source path. + // The returned element can be 1, indicating + // the file was found directly and not on the path. + STDMETHOD(FindSourceFile)( + THIS_ + __in ULONG StartElement, + __in PCSTR File, + __in ULONG Flags, + __out_opt PULONG FoundElement, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG FoundSize + ) PURE; + // Retrieves all the line offset information + // for a particular source file. Buffer is + // first intialized to DEBUG_INVALID_OFFSET for + // every entry. Then for each piece of line + // symbol information Buffer[Line] set to + // Lines offset. This produces a per-line + // map of the offsets for the lines of the + // given file. Line numbers are decremented + // for the map so Buffer[0] contains the offset + // for line number 1. + // If there is no line information at all for + // the given file the method fails rather + // than returning a map of invalid offsets. + STDMETHOD(GetSourceFileLineOffsets)( + THIS_ + __in PCSTR File, + __out_ecount_opt(BufferLines) PULONG64 Buffer, + __in ULONG BufferLines, + __out_opt PULONG FileLines + ) PURE; + + // IDebugSymbols2. + + // If Index is DEBUG_ANY_ID the base address + // is used to look up the module instead. + // Item is specified as in VerQueryValue. + // Module version information is only + // available for loaded modules and may + // not be available in all debug sessions. + STDMETHOD(GetModuleVersionInformation)( + THIS_ + __in ULONG Index, + __in ULONG64 Base, + __in PCSTR Item, + __out_bcount_opt(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG VerInfoSize + ) PURE; + // Retrieves any available module name string + // such as module name or symbol file name. + // If Index is DEBUG_ANY_ID the base address + // is used to look up the module instead. + // If symbols are deferred an error will + // be returned. + // E_NOINTERFACE may be returned, indicating + // no information exists. + STDMETHOD(GetModuleNameString)( + THIS_ + __in ULONG Which, + __in ULONG Index, + __in ULONG64 Base, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG NameSize + ) PURE; + + // Returns the string name of a constant type. + STDMETHOD(GetConstantName)( + THIS_ + __in ULONG64 Module, + __in ULONG TypeId, + __in ULONG64 Value, + __out_ecount_opt(NameBufferSize) PSTR NameBuffer, + __in ULONG NameBufferSize, + __out_opt PULONG NameSize + ) PURE; + + // Gets name of a field in a struct + // FieldNumber is 0 based index of field in a struct + STDMETHOD(GetFieldName)( + THIS_ + __in ULONG64 Module, + __in ULONG TypeId, + __in ULONG FieldIndex, + __out_ecount_opt(NameBufferSize) PSTR NameBuffer, + __in ULONG NameBufferSize, + __out_opt PULONG NameSize + ) PURE; + + // Control options for typed values. + STDMETHOD(GetTypeOptions)( + THIS_ + __out PULONG Options + ) PURE; + STDMETHOD(AddTypeOptions)( + THIS_ + __in ULONG Options + ) PURE; + STDMETHOD(RemoveTypeOptions)( + THIS_ + __in ULONG Options + ) PURE; + STDMETHOD(SetTypeOptions)( + THIS_ + __in ULONG Options + ) PURE; + + // IDebugSymbols3. + + STDMETHOD(GetNameByOffsetWide)( + THIS_ + __in ULONG64 Offset, + __out_ecount_opt(NameBufferSize) PWSTR NameBuffer, + __in ULONG NameBufferSize, + __out_opt PULONG NameSize, + __out_opt PULONG64 Displacement + ) PURE; + STDMETHOD(GetOffsetByNameWide)( + THIS_ + __in PCWSTR Symbol, + __out PULONG64 Offset + ) PURE; + STDMETHOD(GetNearNameByOffsetWide)( + THIS_ + __in ULONG64 Offset, + __in LONG Delta, + __out_ecount_opt(NameBufferSize) PWSTR NameBuffer, + __in ULONG NameBufferSize, + __out_opt PULONG NameSize, + __out_opt PULONG64 Displacement + ) PURE; + + STDMETHOD(GetLineByOffsetWide)( + THIS_ + __in ULONG64 Offset, + __out_opt PULONG Line, + __out_ecount_opt(FileBufferSize) PWSTR FileBuffer, + __in ULONG FileBufferSize, + __out_opt PULONG FileSize, + __out_opt PULONG64 Displacement + ) PURE; + STDMETHOD(GetOffsetByLineWide)( + THIS_ + __in ULONG Line, + __in PCWSTR File, + __out PULONG64 Offset + ) PURE; + + STDMETHOD(GetModuleByModuleNameWide)( + THIS_ + __in PCWSTR Name, + __in ULONG StartIndex, + __out_opt PULONG Index, + __out_opt PULONG64 Base + ) PURE; + STDMETHOD(GetSymbolModuleWide)( + THIS_ + __in PCWSTR Symbol, + __out PULONG64 Base + ) PURE; + + STDMETHOD(GetTypeNameWide)( + THIS_ + __in ULONG64 Module, + __in ULONG TypeId, + __out_ecount_opt(NameBufferSize) PWSTR NameBuffer, + __in ULONG NameBufferSize, + __out_opt PULONG NameSize + ) PURE; + // Returns the ID for a type name. + STDMETHOD(GetTypeIdWide)( + THIS_ + __in ULONG64 Module, + __in PCWSTR Name, + __out PULONG TypeId + ) PURE; + STDMETHOD(GetFieldOffsetWide)( + THIS_ + __in ULONG64 Module, + __in ULONG TypeId, + __in PCWSTR Field, + __out PULONG Offset + ) PURE; + + STDMETHOD(GetSymbolTypeIdWide)( + THIS_ + __in PCWSTR Symbol, + __out PULONG TypeId, + __out_opt PULONG64 Module + ) PURE; + + STDMETHOD(GetScopeSymbolGroup2)( + THIS_ + __in ULONG Flags, + __in_opt PDEBUG_SYMBOL_GROUP2 Update, + __out PDEBUG_SYMBOL_GROUP2* Symbols + ) PURE; + + STDMETHOD(CreateSymbolGroup2)( + THIS_ + __out PDEBUG_SYMBOL_GROUP2* Group + ) PURE; + + STDMETHOD(StartSymbolMatchWide)( + THIS_ + __in PCWSTR Pattern, + __out PULONG64 Handle + ) PURE; + STDMETHOD(GetNextSymbolMatchWide)( + THIS_ + __in ULONG64 Handle, + __out_ecount_opt(BufferSize) PWSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG MatchSize, + __out_opt PULONG64 Offset + ) PURE; + + STDMETHOD(ReloadWide)( + THIS_ + __in PCWSTR Module + ) PURE; + + STDMETHOD(GetSymbolPathWide)( + THIS_ + __out_ecount_opt(BufferSize) PWSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG PathSize + ) PURE; + STDMETHOD(SetSymbolPathWide)( + THIS_ + __in PCWSTR Path + ) PURE; + STDMETHOD(AppendSymbolPathWide)( + THIS_ + __in PCWSTR Addition + ) PURE; + + STDMETHOD(GetImagePathWide)( + THIS_ + __out_ecount_opt(BufferSize) PWSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG PathSize + ) PURE; + STDMETHOD(SetImagePathWide)( + THIS_ + __in PCWSTR Path + ) PURE; + STDMETHOD(AppendImagePathWide)( + THIS_ + __in PCWSTR Addition + ) PURE; + + STDMETHOD(GetSourcePathWide)( + THIS_ + __out_ecount_opt(BufferSize) PWSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG PathSize + ) PURE; + STDMETHOD(GetSourcePathElementWide)( + THIS_ + __in ULONG Index, + __out_ecount_opt(BufferSize) PWSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG ElementSize + ) PURE; + STDMETHOD(SetSourcePathWide)( + THIS_ + __in PCWSTR Path + ) PURE; + STDMETHOD(AppendSourcePathWide)( + THIS_ + __in PCWSTR Addition + ) PURE; + STDMETHOD(FindSourceFileWide)( + THIS_ + __in ULONG StartElement, + __in PCWSTR File, + __in ULONG Flags, + __out_opt PULONG FoundElement, + __out_ecount_opt(BufferSize) PWSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG FoundSize + ) PURE; + STDMETHOD(GetSourceFileLineOffsetsWide)( + THIS_ + __in PCWSTR File, + __out_ecount_opt(BufferLines) PULONG64 Buffer, + __in ULONG BufferLines, + __out_opt PULONG FileLines + ) PURE; + + STDMETHOD(GetModuleVersionInformationWide)( + THIS_ + __in ULONG Index, + __in ULONG64 Base, + __in PCWSTR Item, + __out_bcount_opt(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG VerInfoSize + ) PURE; + STDMETHOD(GetModuleNameStringWide)( + THIS_ + __in ULONG Which, + __in ULONG Index, + __in ULONG64 Base, + __out_ecount_opt(BufferSize) PWSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG NameSize + ) PURE; + + STDMETHOD(GetConstantNameWide)( + THIS_ + __in ULONG64 Module, + __in ULONG TypeId, + __in ULONG64 Value, + __out_ecount_opt(NameBufferSize) PWSTR NameBuffer, + __in ULONG NameBufferSize, + __out_opt PULONG NameSize + ) PURE; + + STDMETHOD(GetFieldNameWide)( + THIS_ + __in ULONG64 Module, + __in ULONG TypeId, + __in ULONG FieldIndex, + __out_ecount_opt(NameBufferSize) PWSTR NameBuffer, + __in ULONG NameBufferSize, + __out_opt PULONG NameSize + ) PURE; + + // Returns S_OK if the engine is using managed + // debugging support when retriving information + // for the given module. This can be expensive + // to check. + STDMETHOD(IsManagedModule)( + THIS_ + __in ULONG Index, + __in ULONG64 Base + ) PURE; + + // The module name may not be unique. + // This method returns the first match. + STDMETHOD(GetModuleByModuleName2)( + THIS_ + __in PCSTR Name, + __in ULONG StartIndex, + __in ULONG Flags, + __out_opt PULONG Index, + __out_opt PULONG64 Base + ) PURE; + STDMETHOD(GetModuleByModuleName2Wide)( + THIS_ + __in PCWSTR Name, + __in ULONG StartIndex, + __in ULONG Flags, + __out_opt PULONG Index, + __out_opt PULONG64 Base + ) PURE; + // Offset can be any offset within + // the module extent. Extents may + // not be unique when including unloaded + // drivers. This method returns the + // first match. + STDMETHOD(GetModuleByOffset2)( + THIS_ + __in ULONG64 Offset, + __in ULONG StartIndex, + __in ULONG Flags, + __out_opt PULONG Index, + __out_opt PULONG64 Base + ) PURE; + + // A caller can create artificial loaded modules in + // the engine's module list if desired. + // These modules only serve as names for + // a region of addresses. They cannot have + // real symbols loaded for them; if that + // is desired Reload can be used with explicit + // parameters to create a true module entry. + // The region must not be in use by any other + // module. + // A general reload will discard any synthetic modules. + STDMETHOD(AddSyntheticModule)( + THIS_ + __in ULONG64 Base, + __in ULONG Size, + __in PCSTR ImagePath, + __in PCSTR ModuleName, + __in ULONG Flags + ) PURE; + STDMETHOD(AddSyntheticModuleWide)( + THIS_ + __in ULONG64 Base, + __in ULONG Size, + __in PCWSTR ImagePath, + __in PCWSTR ModuleName, + __in ULONG Flags + ) PURE; + STDMETHOD(RemoveSyntheticModule)( + THIS_ + __in ULONG64 Base + ) PURE; + + // Modify the current frame used for scoping. + // This is equivalent to the '.frame' command. + STDMETHOD(GetCurrentScopeFrameIndex)( + THIS_ + __out PULONG Index + ) PURE; + STDMETHOD(SetScopeFrameByIndex)( + THIS_ + __in ULONG Index + ) PURE; + + // Recovers JIT_DEBUG_INFO information at the given + // address from the debuggee and sets current + // debugger scope context from it. + // Equivalent to '.jdinfo' command. + STDMETHOD(SetScopeFromJitDebugInfo)( + THIS_ + __in ULONG OutputControl, + __in ULONG64 InfoOffset + ) PURE; + + // Switches the current debugger scope to + // the stored event information. + // Equivalent to the '.ecxr' command. + STDMETHOD(SetScopeFromStoredEvent)( + THIS + ) PURE; + + // Takes the first symbol hit and outputs it. + // Controlled with DEBUG_OUTSYM_* flags. + STDMETHOD(OutputSymbolByOffset)( + THIS_ + __in ULONG OutputControl, + __in ULONG Flags, + __in ULONG64 Offset + ) PURE; + + // Function entry information for a particular + // piece of code can be retrieved by this method. + // The actual data returned is system-dependent. + STDMETHOD(GetFunctionEntryByOffset)( + THIS_ + __in ULONG64 Offset, + __in ULONG Flags, + __out_bcount_opt(BufferSize) PVOID Buffer, + __in ULONG BufferSize, + __out_opt PULONG BufferNeeded + ) PURE; + + // Given a type which can contain members + // this method returns the type ID and offset of a + // particular member within the type. + // Field gives the dot-separated path + // to the field of interest. + STDMETHOD(GetFieldTypeAndOffset)( + THIS_ + __in ULONG64 Module, + __in ULONG ContainerTypeId, + __in PCSTR Field, + __out_opt PULONG FieldTypeId, + __out_opt PULONG Offset + ) PURE; + STDMETHOD(GetFieldTypeAndOffsetWide)( + THIS_ + __in ULONG64 Module, + __in ULONG ContainerTypeId, + __in PCWSTR Field, + __out_opt PULONG FieldTypeId, + __out_opt PULONG Offset + ) PURE; + + // Artificial symbols can be created in any + // existing module as a way to name an address. + // The address must not already have symbol + // information. + // A reload will discard synthetic symbols + // for all address regions reloaded. + STDMETHOD(AddSyntheticSymbol)( + THIS_ + __in ULONG64 Offset, + __in ULONG Size, + __in PCSTR Name, + __in ULONG Flags, + __out_opt PDEBUG_MODULE_AND_ID Id + ) PURE; + STDMETHOD(AddSyntheticSymbolWide)( + THIS_ + __in ULONG64 Offset, + __in ULONG Size, + __in PCWSTR Name, + __in ULONG Flags, + __out_opt PDEBUG_MODULE_AND_ID Id + ) PURE; + STDMETHOD(RemoveSyntheticSymbol)( + THIS_ + __in PDEBUG_MODULE_AND_ID Id + ) PURE; + + // The following methods can return multiple + // hits for symbol lookups to allow for all + // possible hits to be returned. + STDMETHOD(GetSymbolEntriesByOffset)( + THIS_ + __in ULONG64 Offset, + __in ULONG Flags, + __out_ecount_opt(IdsCount) PDEBUG_MODULE_AND_ID Ids, + __out_ecount_opt(IdsCount) PULONG64 Displacements, + __in ULONG IdsCount, + __out_opt PULONG Entries + ) PURE; + STDMETHOD(GetSymbolEntriesByName)( + THIS_ + __in PCSTR Symbol, + __in ULONG Flags, + __out_ecount_opt(IdsCount) PDEBUG_MODULE_AND_ID Ids, + __in ULONG IdsCount, + __out_opt PULONG Entries + ) PURE; + STDMETHOD(GetSymbolEntriesByNameWide)( + THIS_ + __in PCWSTR Symbol, + __in ULONG Flags, + __out_ecount_opt(IdsCount) PDEBUG_MODULE_AND_ID Ids, + __in ULONG IdsCount, + __out_opt PULONG Entries + ) PURE; + // Symbol lookup by managed metadata token. + STDMETHOD(GetSymbolEntryByToken)( + THIS_ + __in ULONG64 ModuleBase, + __in ULONG Token, + __out PDEBUG_MODULE_AND_ID Id + ) PURE; + + // Retrieves full symbol entry information from an ID. + STDMETHOD(GetSymbolEntryInformation)( + THIS_ + __in PDEBUG_MODULE_AND_ID Id, + __out PDEBUG_SYMBOL_ENTRY Info + ) PURE; + STDMETHOD(GetSymbolEntryString)( + THIS_ + __in PDEBUG_MODULE_AND_ID Id, + __in ULONG Which, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG StringSize + ) PURE; + STDMETHOD(GetSymbolEntryStringWide)( + THIS_ + __in PDEBUG_MODULE_AND_ID Id, + __in ULONG Which, + __out_ecount_opt(BufferSize) PWSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG StringSize + ) PURE; + // Returns all known memory regions associated + // with the given symbol. Simple symbols will + // have a single region starting from their base. + // More complicated regions, such as functions + // with multiple code areas, can have an arbitrarily + // large number of regions. + // The quality of information returned is highly + // dependent on the symbolic information availble. + STDMETHOD(GetSymbolEntryOffsetRegions)( + THIS_ + __in PDEBUG_MODULE_AND_ID Id, + __in ULONG Flags, + __out_ecount_opt(RegionsCount) PDEBUG_OFFSET_REGION Regions, + __in ULONG RegionsCount, + __out_opt PULONG RegionsAvail + ) PURE; + + // This method allows navigating within the + // symbol entry hierarchy. + STDMETHOD(GetSymbolEntryBySymbolEntry)( + THIS_ + __in PDEBUG_MODULE_AND_ID FromId, + __in ULONG Flags, + __out PDEBUG_MODULE_AND_ID ToId + ) PURE; + + // The following methods can return multiple + // hits for source lookups to allow for all + // possible hits to be returned. + STDMETHOD(GetSourceEntriesByOffset)( + THIS_ + __in ULONG64 Offset, + __in ULONG Flags, + __out_ecount_opt(EntriesCount) PDEBUG_SYMBOL_SOURCE_ENTRY Entries, + __in ULONG EntriesCount, + __out_opt PULONG EntriesAvail + ) PURE; + STDMETHOD(GetSourceEntriesByLine)( + THIS_ + __in ULONG Line, + __in PCSTR File, + __in ULONG Flags, + __out_ecount_opt(EntriesCount) PDEBUG_SYMBOL_SOURCE_ENTRY Entries, + __in ULONG EntriesCount, + __out_opt PULONG EntriesAvail + ) PURE; + STDMETHOD(GetSourceEntriesByLineWide)( + THIS_ + __in ULONG Line, + __in PCWSTR File, + __in ULONG Flags, + __out_ecount_opt(EntriesCount) PDEBUG_SYMBOL_SOURCE_ENTRY Entries, + __in ULONG EntriesCount, + __out_opt PULONG EntriesAvail + ) PURE; + + STDMETHOD(GetSourceEntryString)( + THIS_ + __in PDEBUG_SYMBOL_SOURCE_ENTRY Entry, + __in ULONG Which, + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG StringSize + ) PURE; + STDMETHOD(GetSourceEntryStringWide)( + THIS_ + __in PDEBUG_SYMBOL_SOURCE_ENTRY Entry, + __in ULONG Which, + __out_ecount_opt(BufferSize) PWSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG StringSize + ) PURE; + // Returns all known memory regions associated + // with the given source entry. As with + // GetSymbolEntryOffsetRegions the regions available + // are variable. + STDMETHOD(GetSourceEntryOffsetRegions)( + THIS_ + __in PDEBUG_SYMBOL_SOURCE_ENTRY Entry, + __in ULONG Flags, + __out_ecount_opt(RegionsCount) PDEBUG_OFFSET_REGION Regions, + __in ULONG RegionsCount, + __out_opt PULONG RegionsAvail + ) PURE; + + // This method allows navigating within the + // source entries. + STDMETHOD(GetSourceEntryBySourceEntry)( + THIS_ + __in PDEBUG_SYMBOL_SOURCE_ENTRY FromEntry, + __in ULONG Flags, + __out PDEBUG_SYMBOL_SOURCE_ENTRY ToEntry + ) PURE; +}; + +//---------------------------------------------------------------------------- +// +// IDebugSystemObjects +// +//---------------------------------------------------------------------------- + +#undef INTERFACE +#define INTERFACE IDebugSystemObjects +DECLARE_INTERFACE_(IDebugSystemObjects, IUnknown) +{ + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + __in REFIID InterfaceId, + __out PVOID* Interface + ) PURE; + STDMETHOD_(ULONG, AddRef)( + THIS + ) PURE; + STDMETHOD_(ULONG, Release)( + THIS + ) PURE; + + // IDebugSystemObjects. + + // In user mode debugging the debugger + // tracks all threads and processes and + // enumerates them through the following + // methods. When enumerating threads + // the threads are enumerated for the current + // process. + // Kernel mode debugging currently is + // limited to enumerating only the threads + // assigned to processors, not all of + // the threads in the system. Process + // enumeration is limited to a single + // virtual process representing kernel space. + + // Returns the ID of the thread on which + // the last event occurred. + STDMETHOD(GetEventThread)( + THIS_ + __out PULONG Id + ) PURE; + STDMETHOD(GetEventProcess)( + THIS_ + __out PULONG Id + ) PURE; + + // Controls implicit thread used by the + // debug engine. The debuggers current + // thread is just a piece of data held + // by the debugger for calls which use + // thread-specific information. In those + // calls the debuggers current thread is used. + // The debuggers current thread is not related + // to any system thread attribute. + // IDs for threads are small integer IDs + // maintained by the engine. They are not + // related to system thread IDs. + STDMETHOD(GetCurrentThreadId)( + THIS_ + __out PULONG Id + ) PURE; + STDMETHOD(SetCurrentThreadId)( + THIS_ + __in ULONG Id + ) PURE; + // The current process is the process + // that owns the current thread. + STDMETHOD(GetCurrentProcessId)( + THIS_ + __out PULONG Id + ) PURE; + // Setting the current process automatically + // sets the current thread to the thread that + // was last current in that process. + STDMETHOD(SetCurrentProcessId)( + THIS_ + __in ULONG Id + ) PURE; + + // Gets the number of threads in the current process. + STDMETHOD(GetNumberThreads)( + THIS_ + __out PULONG Number + ) PURE; + // Gets thread count information for all processes + // and the largest number of threads in a single process. + STDMETHOD(GetTotalNumberThreads)( + THIS_ + __out PULONG Total, + __out PULONG LargestProcess + ) PURE; + STDMETHOD(GetThreadIdsByIndex)( + THIS_ + __in ULONG Start, + __in ULONG Count, + __out_ecount_opt(Count) PULONG Ids, + __out_ecount_opt(Count) PULONG SysIds + ) PURE; + // Gets the debugger ID for the thread + // currently running on the given + // processor. Only works in kernel + // debugging. + STDMETHOD(GetThreadIdByProcessor)( + THIS_ + __in ULONG Processor, + __out PULONG Id + ) PURE; + // Returns the offset of the current threads + // system data structure. When kernel debugging + // this is the offset of the KTHREAD. + // When user debugging it is the offset + // of the current TEB. + STDMETHOD(GetCurrentThreadDataOffset)( + THIS_ + __out PULONG64 Offset + ) PURE; + // Looks up a debugger thread ID for the given + // system thread data structure. + // Currently when kernel debugging this will fail + // if the thread is not executing on a processor. + STDMETHOD(GetThreadIdByDataOffset)( + THIS_ + __in ULONG64 Offset, + __out PULONG Id + ) PURE; + // Returns the offset of the current threads + // TEB. In user mode this is equivalent to + // the threads data offset. + STDMETHOD(GetCurrentThreadTeb)( + THIS_ + __out PULONG64 Offset + ) PURE; + // Looks up a debugger thread ID for the given TEB. + // Currently when kernel debugging this will fail + // if the thread is not executing on a processor. + STDMETHOD(GetThreadIdByTeb)( + THIS_ + __in ULONG64 Offset, + __out PULONG Id + ) PURE; + // Returns the system unique ID for the current thread. + // Not currently supported when kernel debugging. + STDMETHOD(GetCurrentThreadSystemId)( + THIS_ + __out PULONG SysId + ) PURE; + // Looks up a debugger thread ID for the given + // system thread ID. + // Currently when kernel debugging this will fail + // if the thread is not executing on a processor. + STDMETHOD(GetThreadIdBySystemId)( + THIS_ + __in ULONG SysId, + __out PULONG Id + ) PURE; + // Returns the handle of the current thread. + // In kernel mode the value returned is the + // index of the processor the thread is + // executing on plus one. + STDMETHOD(GetCurrentThreadHandle)( + THIS_ + __out PULONG64 Handle + ) PURE; + // Looks up a debugger thread ID for the given handle. + // Currently when kernel debugging this will fail + // if the thread is not executing on a processor. + STDMETHOD(GetThreadIdByHandle)( + THIS_ + __in ULONG64 Handle, + __out PULONG Id + ) PURE; + + // Currently kernel mode sessions will only have + // a single process representing kernel space. + STDMETHOD(GetNumberProcesses)( + THIS_ + __out PULONG Number + ) PURE; + STDMETHOD(GetProcessIdsByIndex)( + THIS_ + __in ULONG Start, + __in ULONG Count, + __out_ecount_opt(Count) PULONG Ids, + __out_ecount_opt(Count) PULONG SysIds + ) PURE; + // Returns the offset of the current processs + // system data structure. When kernel debugging + // this is the offset of the KPROCESS of + // the process that owns the current thread. + // When user debugging it is the offset + // of the current PEB. + STDMETHOD(GetCurrentProcessDataOffset)( + THIS_ + __out PULONG64 Offset + ) PURE; + // Looks up a debugger process ID for the given + // system process data structure. + // Not currently supported when kernel debugging. + STDMETHOD(GetProcessIdByDataOffset)( + THIS_ + __in ULONG64 Offset, + __out PULONG Id + ) PURE; + // Returns the offset of the current processs + // PEB. In user mode this is equivalent to + // the processs data offset. + STDMETHOD(GetCurrentProcessPeb)( + THIS_ + __out PULONG64 Offset + ) PURE; + // Looks up a debugger process ID for the given PEB. + // Not currently supported when kernel debugging. + STDMETHOD(GetProcessIdByPeb)( + THIS_ + __in ULONG64 Offset, + __out PULONG Id + ) PURE; + // Returns the system unique ID for the current process. + // Not currently supported when kernel debugging. + STDMETHOD(GetCurrentProcessSystemId)( + THIS_ + __out PULONG SysId + ) PURE; + // Looks up a debugger process ID for the given + // system process ID. + // Not currently supported when kernel debugging. + STDMETHOD(GetProcessIdBySystemId)( + THIS_ + __in ULONG SysId, + __out PULONG Id + ) PURE; + // Returns the handle of the current process. + // In kernel mode this is the kernel processs + // artificial handle used for symbol operations + // and so can only be used with dbghelp APIs. + STDMETHOD(GetCurrentProcessHandle)( + THIS_ + __out PULONG64 Handle + ) PURE; + // Looks up a debugger process ID for the given handle. + STDMETHOD(GetProcessIdByHandle)( + THIS_ + __in ULONG64 Handle, + __out PULONG Id + ) PURE; + // Retrieve the name of the executable loaded + // in the process. This may fail if no executable + // was identified. + STDMETHOD(GetCurrentProcessExecutableName)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG ExeSize + ) PURE; +}; + +#undef INTERFACE +#define INTERFACE IDebugSystemObjects2 +DECLARE_INTERFACE_(IDebugSystemObjects2, IUnknown) +{ + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + __in REFIID InterfaceId, + __out PVOID* Interface + ) PURE; + STDMETHOD_(ULONG, AddRef)( + THIS + ) PURE; + STDMETHOD_(ULONG, Release)( + THIS + ) PURE; + + // IDebugSystemObjects. + + // In user mode debugging the debugger + // tracks all threads and processes and + // enumerates them through the following + // methods. When enumerating threads + // the threads are enumerated for the current + // process. + // Kernel mode debugging currently is + // limited to enumerating only the threads + // assigned to processors, not all of + // the threads in the system. Process + // enumeration is limited to a single + // virtual process representing kernel space. + + // Returns the ID of the thread on which + // the last event occurred. + STDMETHOD(GetEventThread)( + THIS_ + __out PULONG Id + ) PURE; + STDMETHOD(GetEventProcess)( + THIS_ + __out PULONG Id + ) PURE; + + // Controls implicit thread used by the + // debug engine. The debuggers current + // thread is just a piece of data held + // by the debugger for calls which use + // thread-specific information. In those + // calls the debuggers current thread is used. + // The debuggers current thread is not related + // to any system thread attribute. + // IDs for threads are small integer IDs + // maintained by the engine. They are not + // related to system thread IDs. + STDMETHOD(GetCurrentThreadId)( + THIS_ + __out PULONG Id + ) PURE; + STDMETHOD(SetCurrentThreadId)( + THIS_ + __in ULONG Id + ) PURE; + // The current process is the process + // that owns the current thread. + STDMETHOD(GetCurrentProcessId)( + THIS_ + __out PULONG Id + ) PURE; + // Setting the current process automatically + // sets the current thread to the thread that + // was last current in that process. + STDMETHOD(SetCurrentProcessId)( + THIS_ + __in ULONG Id + ) PURE; + + // Gets the number of threads in the current process. + STDMETHOD(GetNumberThreads)( + THIS_ + __out PULONG Number + ) PURE; + // Gets thread count information for all processes + // and the largest number of threads in a single process. + STDMETHOD(GetTotalNumberThreads)( + THIS_ + __out PULONG Total, + __out PULONG LargestProcess + ) PURE; + STDMETHOD(GetThreadIdsByIndex)( + THIS_ + __in ULONG Start, + __in ULONG Count, + __out_ecount_opt(Count) PULONG Ids, + __out_ecount_opt(Count) PULONG SysIds + ) PURE; + // Gets the debugger ID for the thread + // currently running on the given + // processor. Only works in kernel + // debugging. + STDMETHOD(GetThreadIdByProcessor)( + THIS_ + __in ULONG Processor, + __out PULONG Id + ) PURE; + // Returns the offset of the current threads + // system data structure. When kernel debugging + // this is the offset of the KTHREAD. + // When user debugging it is the offset + // of the current TEB. + STDMETHOD(GetCurrentThreadDataOffset)( + THIS_ + __out PULONG64 Offset + ) PURE; + // Looks up a debugger thread ID for the given + // system thread data structure. + // Currently when kernel debugging this will fail + // if the thread is not executing on a processor. + STDMETHOD(GetThreadIdByDataOffset)( + THIS_ + __in ULONG64 Offset, + __out PULONG Id + ) PURE; + // Returns the offset of the current threads + // TEB. In user mode this is equivalent to + // the threads data offset. + STDMETHOD(GetCurrentThreadTeb)( + THIS_ + __out PULONG64 Offset + ) PURE; + // Looks up a debugger thread ID for the given TEB. + // Currently when kernel debugging this will fail + // if the thread is not executing on a processor. + STDMETHOD(GetThreadIdByTeb)( + THIS_ + __in ULONG64 Offset, + __out PULONG Id + ) PURE; + // Returns the system unique ID for the current thread. + // Not currently supported when kernel debugging. + STDMETHOD(GetCurrentThreadSystemId)( + THIS_ + __out PULONG SysId + ) PURE; + // Looks up a debugger thread ID for the given + // system thread ID. + // Currently when kernel debugging this will fail + // if the thread is not executing on a processor. + STDMETHOD(GetThreadIdBySystemId)( + THIS_ + __in ULONG SysId, + __out PULONG Id + ) PURE; + // Returns the handle of the current thread. + // In kernel mode the value returned is the + // index of the processor the thread is + // executing on plus one. + STDMETHOD(GetCurrentThreadHandle)( + THIS_ + __out PULONG64 Handle + ) PURE; + // Looks up a debugger thread ID for the given handle. + // Currently when kernel debugging this will fail + // if the thread is not executing on a processor. + STDMETHOD(GetThreadIdByHandle)( + THIS_ + __in ULONG64 Handle, + __out PULONG Id + ) PURE; + + // Currently kernel mode sessions will only have + // a single process representing kernel space. + STDMETHOD(GetNumberProcesses)( + THIS_ + __out PULONG Number + ) PURE; + STDMETHOD(GetProcessIdsByIndex)( + THIS_ + __in ULONG Start, + __in ULONG Count, + __out_ecount_opt(Count) PULONG Ids, + __out_ecount_opt(Count) PULONG SysIds + ) PURE; + // Returns the offset of the current processs + // system data structure. When kernel debugging + // this is the offset of the KPROCESS of + // the process that owns the current thread. + // When user debugging it is the offset + // of the current PEB. + STDMETHOD(GetCurrentProcessDataOffset)( + THIS_ + __out PULONG64 Offset + ) PURE; + // Looks up a debugger process ID for the given + // system process data structure. + // Not currently supported when kernel debugging. + STDMETHOD(GetProcessIdByDataOffset)( + THIS_ + __in ULONG64 Offset, + __out PULONG Id + ) PURE; + // Returns the offset of the current processs + // PEB. In user mode this is equivalent to + // the processs data offset. + STDMETHOD(GetCurrentProcessPeb)( + THIS_ + __out PULONG64 Offset + ) PURE; + // Looks up a debugger process ID for the given PEB. + // Not currently supported when kernel debugging. + STDMETHOD(GetProcessIdByPeb)( + THIS_ + __in ULONG64 Offset, + __out PULONG Id + ) PURE; + // Returns the system unique ID for the current process. + // Not currently supported when kernel debugging. + STDMETHOD(GetCurrentProcessSystemId)( + THIS_ + __out PULONG SysId + ) PURE; + // Looks up a debugger process ID for the given + // system process ID. + // Not currently supported when kernel debugging. + STDMETHOD(GetProcessIdBySystemId)( + THIS_ + __in ULONG SysId, + __out PULONG Id + ) PURE; + // Returns the handle of the current process. + // In kernel mode this is the kernel processs + // artificial handle used for symbol operations + // and so can only be used with dbghelp APIs. + STDMETHOD(GetCurrentProcessHandle)( + THIS_ + __out PULONG64 Handle + ) PURE; + // Looks up a debugger process ID for the given handle. + STDMETHOD(GetProcessIdByHandle)( + THIS_ + __in ULONG64 Handle, + __out PULONG Id + ) PURE; + // Retrieve the name of the executable loaded + // in the process. This may fail if no executable + // was identified. + STDMETHOD(GetCurrentProcessExecutableName)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG ExeSize + ) PURE; + + // IDebugSystemObjects2. + + // Return the number of seconds that the current + // process has been running. + STDMETHOD(GetCurrentProcessUpTime)( + THIS_ + __out PULONG UpTime + ) PURE; + + // During kernel sessions the debugger retrieves + // some information from the system thread and process + // running on the current processor. For example, + // the debugger will retrieve virtual memory translation + // information for when the debugger needs to + // carry out its own virtual to physical translations. + // Occasionally it can be interesting to perform + // similar operations but on a process which isnt + // currently running. The follow methods allow a caller + // to override the data offsets used by the debugger + // so that other system threads and processes can + // be used instead. These values are defaulted to + // the thread and process running on the current + // processor each time the debuggee executes or + // the current processor changes. + // The thread and process settings are independent so + // it is possible to refer to a thread in a process + // other than the current process and vice versa. + // Setting an offset of zero will reload the + // default value. + STDMETHOD(GetImplicitThreadDataOffset)( + THIS_ + __out PULONG64 Offset + ) PURE; + STDMETHOD(SetImplicitThreadDataOffset)( + THIS_ + __in ULONG64 Offset + ) PURE; + STDMETHOD(GetImplicitProcessDataOffset)( + THIS_ + __out PULONG64 Offset + ) PURE; + STDMETHOD(SetImplicitProcessDataOffset)( + THIS_ + __in ULONG64 Offset + ) PURE; +}; + +#undef INTERFACE +#define INTERFACE IDebugSystemObjects3 +DECLARE_INTERFACE_(IDebugSystemObjects3, IUnknown) +{ + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + __in REFIID InterfaceId, + __out PVOID* Interface + ) PURE; + STDMETHOD_(ULONG, AddRef)( + THIS + ) PURE; + STDMETHOD_(ULONG, Release)( + THIS + ) PURE; + + // IDebugSystemObjects. + + // In user mode debugging the debugger + // tracks all threads and processes and + // enumerates them through the following + // methods. When enumerating threads + // the threads are enumerated for the current + // process. + // Kernel mode debugging currently is + // limited to enumerating only the threads + // assigned to processors, not all of + // the threads in the system. Process + // enumeration is limited to a single + // virtual process representing kernel space. + + // Returns the ID of the thread on which + // the last event occurred. + STDMETHOD(GetEventThread)( + THIS_ + __out PULONG Id + ) PURE; + STDMETHOD(GetEventProcess)( + THIS_ + __out PULONG Id + ) PURE; + + // Controls implicit thread used by the + // debug engine. The debuggers current + // thread is just a piece of data held + // by the debugger for calls which use + // thread-specific information. In those + // calls the debuggers current thread is used. + // The debuggers current thread is not related + // to any system thread attribute. + // IDs for threads are small integer IDs + // maintained by the engine. They are not + // related to system thread IDs. + STDMETHOD(GetCurrentThreadId)( + THIS_ + __out PULONG Id + ) PURE; + STDMETHOD(SetCurrentThreadId)( + THIS_ + __in ULONG Id + ) PURE; + // The current process is the process + // that owns the current thread. + STDMETHOD(GetCurrentProcessId)( + THIS_ + __out PULONG Id + ) PURE; + // Setting the current process automatically + // sets the current thread to the thread that + // was last current in that process. + STDMETHOD(SetCurrentProcessId)( + THIS_ + __in ULONG Id + ) PURE; + + // Gets the number of threads in the current process. + STDMETHOD(GetNumberThreads)( + THIS_ + __out PULONG Number + ) PURE; + // Gets thread count information for all processes + // and the largest number of threads in a single process. + STDMETHOD(GetTotalNumberThreads)( + THIS_ + __out PULONG Total, + __out PULONG LargestProcess + ) PURE; + STDMETHOD(GetThreadIdsByIndex)( + THIS_ + __in ULONG Start, + __in ULONG Count, + __out_ecount_opt(Count) PULONG Ids, + __out_ecount_opt(Count) PULONG SysIds + ) PURE; + // Gets the debugger ID for the thread + // currently running on the given + // processor. Only works in kernel + // debugging. + STDMETHOD(GetThreadIdByProcessor)( + THIS_ + __in ULONG Processor, + __out PULONG Id + ) PURE; + // Returns the offset of the current threads + // system data structure. When kernel debugging + // this is the offset of the KTHREAD. + // When user debugging it is the offset + // of the current TEB. + STDMETHOD(GetCurrentThreadDataOffset)( + THIS_ + __out PULONG64 Offset + ) PURE; + // Looks up a debugger thread ID for the given + // system thread data structure. + // Currently when kernel debugging this will fail + // if the thread is not executing on a processor. + STDMETHOD(GetThreadIdByDataOffset)( + THIS_ + __in ULONG64 Offset, + __out PULONG Id + ) PURE; + // Returns the offset of the current threads + // TEB. In user mode this is equivalent to + // the threads data offset. + STDMETHOD(GetCurrentThreadTeb)( + THIS_ + __out PULONG64 Offset + ) PURE; + // Looks up a debugger thread ID for the given TEB. + // Currently when kernel debugging this will fail + // if the thread is not executing on a processor. + STDMETHOD(GetThreadIdByTeb)( + THIS_ + __in ULONG64 Offset, + __out PULONG Id + ) PURE; + // Returns the system unique ID for the current thread. + // Not currently supported when kernel debugging. + STDMETHOD(GetCurrentThreadSystemId)( + THIS_ + __out PULONG SysId + ) PURE; + // Looks up a debugger thread ID for the given + // system thread ID. + // Currently when kernel debugging this will fail + // if the thread is not executing on a processor. + STDMETHOD(GetThreadIdBySystemId)( + THIS_ + __in ULONG SysId, + __out PULONG Id + ) PURE; + // Returns the handle of the current thread. + // In kernel mode the value returned is the + // index of the processor the thread is + // executing on plus one. + STDMETHOD(GetCurrentThreadHandle)( + THIS_ + __out PULONG64 Handle + ) PURE; + // Looks up a debugger thread ID for the given handle. + // Currently when kernel debugging this will fail + // if the thread is not executing on a processor. + STDMETHOD(GetThreadIdByHandle)( + THIS_ + __in ULONG64 Handle, + __out PULONG Id + ) PURE; + + // Currently kernel mode sessions will only have + // a single process representing kernel space. + STDMETHOD(GetNumberProcesses)( + THIS_ + __out PULONG Number + ) PURE; + STDMETHOD(GetProcessIdsByIndex)( + THIS_ + __in ULONG Start, + __in ULONG Count, + __out_ecount_opt(Count) PULONG Ids, + __out_ecount_opt(Count) PULONG SysIds + ) PURE; + // Returns the offset of the current processs + // system data structure. When kernel debugging + // this is the offset of the KPROCESS of + // the process that owns the current thread. + // When user debugging it is the offset + // of the current PEB. + STDMETHOD(GetCurrentProcessDataOffset)( + THIS_ + __out PULONG64 Offset + ) PURE; + // Looks up a debugger process ID for the given + // system process data structure. + // Not currently supported when kernel debugging. + STDMETHOD(GetProcessIdByDataOffset)( + THIS_ + __in ULONG64 Offset, + __out PULONG Id + ) PURE; + // Returns the offset of the current processs + // PEB. In user mode this is equivalent to + // the processs data offset. + STDMETHOD(GetCurrentProcessPeb)( + THIS_ + __out PULONG64 Offset + ) PURE; + // Looks up a debugger process ID for the given PEB. + // Not currently supported when kernel debugging. + STDMETHOD(GetProcessIdByPeb)( + THIS_ + __in ULONG64 Offset, + __out PULONG Id + ) PURE; + // Returns the system unique ID for the current process. + // Not currently supported when kernel debugging. + STDMETHOD(GetCurrentProcessSystemId)( + THIS_ + __out PULONG SysId + ) PURE; + // Looks up a debugger process ID for the given + // system process ID. + // Not currently supported when kernel debugging. + STDMETHOD(GetProcessIdBySystemId)( + THIS_ + __in ULONG SysId, + __out PULONG Id + ) PURE; + // Returns the handle of the current process. + // In kernel mode this is the kernel processs + // artificial handle used for symbol operations + // and so can only be used with dbghelp APIs. + STDMETHOD(GetCurrentProcessHandle)( + THIS_ + __out PULONG64 Handle + ) PURE; + // Looks up a debugger process ID for the given handle. + STDMETHOD(GetProcessIdByHandle)( + THIS_ + __in ULONG64 Handle, + __out PULONG Id + ) PURE; + // Retrieve the name of the executable loaded + // in the process. This may fail if no executable + // was identified. + STDMETHOD(GetCurrentProcessExecutableName)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG ExeSize + ) PURE; + + // IDebugSystemObjects2. + + // Return the number of seconds that the current + // process has been running. + STDMETHOD(GetCurrentProcessUpTime)( + THIS_ + __out PULONG UpTime + ) PURE; + + // During kernel sessions the debugger retrieves + // some information from the system thread and process + // running on the current processor. For example, + // the debugger will retrieve virtual memory translation + // information for when the debugger needs to + // carry out its own virtual to physical translations. + // Occasionally it can be interesting to perform + // similar operations but on a process which isnt + // currently running. The follow methods allow a caller + // to override the data offsets used by the debugger + // so that other system threads and processes can + // be used instead. These values are defaulted to + // the thread and process running on the current + // processor each time the debuggee executes or + // the current processor changes. + // The thread and process settings are independent so + // it is possible to refer to a thread in a process + // other than the current process and vice versa. + // Setting an offset of zero will reload the + // default value. + STDMETHOD(GetImplicitThreadDataOffset)( + THIS_ + __out PULONG64 Offset + ) PURE; + STDMETHOD(SetImplicitThreadDataOffset)( + THIS_ + __in ULONG64 Offset + ) PURE; + STDMETHOD(GetImplicitProcessDataOffset)( + THIS_ + __out PULONG64 Offset + ) PURE; + STDMETHOD(SetImplicitProcessDataOffset)( + THIS_ + __in ULONG64 Offset + ) PURE; + + // IDebugSystemObjects3. + + STDMETHOD(GetEventSystem)( + THIS_ + __out PULONG Id + ) PURE; + + STDMETHOD(GetCurrentSystemId)( + THIS_ + __out PULONG Id + ) PURE; + STDMETHOD(SetCurrentSystemId)( + THIS_ + __in ULONG Id + ) PURE; + + STDMETHOD(GetNumberSystems)( + THIS_ + __out PULONG Number + ) PURE; + STDMETHOD(GetSystemIdsByIndex)( + THIS_ + __in ULONG Start, + __in ULONG Count, + __out_ecount(Count) PULONG Ids + ) PURE; + STDMETHOD(GetTotalNumberThreadsAndProcesses)( + THIS_ + __out PULONG TotalThreads, + __out PULONG TotalProcesses, + __out PULONG LargestProcessThreads, + __out PULONG LargestSystemThreads, + __out PULONG LargestSystemProcesses + ) PURE; + STDMETHOD(GetCurrentSystemServer)( + THIS_ + __out PULONG64 Server + ) PURE; + STDMETHOD(GetSystemByServer)( + THIS_ + __in ULONG64 Server, + __out PULONG Id + ) PURE; + STDMETHOD(GetCurrentSystemServerName)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG NameSize + ) PURE; +}; + +#undef INTERFACE +#define INTERFACE IDebugSystemObjects4 +DECLARE_INTERFACE_(IDebugSystemObjects4, IUnknown) +{ + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + __in REFIID InterfaceId, + __out PVOID* Interface + ) PURE; + STDMETHOD_(ULONG, AddRef)( + THIS + ) PURE; + STDMETHOD_(ULONG, Release)( + THIS + ) PURE; + + // IDebugSystemObjects. + + // In user mode debugging the debugger + // tracks all threads and processes and + // enumerates them through the following + // methods. When enumerating threads + // the threads are enumerated for the current + // process. + // Kernel mode debugging currently is + // limited to enumerating only the threads + // assigned to processors, not all of + // the threads in the system. Process + // enumeration is limited to a single + // virtual process representing kernel space. + + // Returns the ID of the thread on which + // the last event occurred. + STDMETHOD(GetEventThread)( + THIS_ + __out PULONG Id + ) PURE; + STDMETHOD(GetEventProcess)( + THIS_ + __out PULONG Id + ) PURE; + + // Controls implicit thread used by the + // debug engine. The debuggers current + // thread is just a piece of data held + // by the debugger for calls which use + // thread-specific information. In those + // calls the debuggers current thread is used. + // The debuggers current thread is not related + // to any system thread attribute. + // IDs for threads are small integer IDs + // maintained by the engine. They are not + // related to system thread IDs. + STDMETHOD(GetCurrentThreadId)( + THIS_ + __out PULONG Id + ) PURE; + STDMETHOD(SetCurrentThreadId)( + THIS_ + __in ULONG Id + ) PURE; + // The current process is the process + // that owns the current thread. + STDMETHOD(GetCurrentProcessId)( + THIS_ + __out PULONG Id + ) PURE; + // Setting the current process automatically + // sets the current thread to the thread that + // was last current in that process. + STDMETHOD(SetCurrentProcessId)( + THIS_ + __in ULONG Id + ) PURE; + + // Gets the number of threads in the current process. + STDMETHOD(GetNumberThreads)( + THIS_ + __out PULONG Number + ) PURE; + // Gets thread count information for all processes + // and the largest number of threads in a single process. + STDMETHOD(GetTotalNumberThreads)( + THIS_ + __out PULONG Total, + __out PULONG LargestProcess + ) PURE; + STDMETHOD(GetThreadIdsByIndex)( + THIS_ + __in ULONG Start, + __in ULONG Count, + __out_ecount_opt(Count) PULONG Ids, + __out_ecount_opt(Count) PULONG SysIds + ) PURE; + // Gets the debugger ID for the thread + // currently running on the given + // processor. Only works in kernel + // debugging. + STDMETHOD(GetThreadIdByProcessor)( + THIS_ + __in ULONG Processor, + __out PULONG Id + ) PURE; + // Returns the offset of the current threads + // system data structure. When kernel debugging + // this is the offset of the KTHREAD. + // When user debugging it is the offset + // of the current TEB. + STDMETHOD(GetCurrentThreadDataOffset)( + THIS_ + __out PULONG64 Offset + ) PURE; + // Looks up a debugger thread ID for the given + // system thread data structure. + // Currently when kernel debugging this will fail + // if the thread is not executing on a processor. + STDMETHOD(GetThreadIdByDataOffset)( + THIS_ + __in ULONG64 Offset, + __out PULONG Id + ) PURE; + // Returns the offset of the current threads + // TEB. In user mode this is equivalent to + // the threads data offset. + STDMETHOD(GetCurrentThreadTeb)( + THIS_ + __out PULONG64 Offset + ) PURE; + // Looks up a debugger thread ID for the given TEB. + // Currently when kernel debugging this will fail + // if the thread is not executing on a processor. + STDMETHOD(GetThreadIdByTeb)( + THIS_ + __in ULONG64 Offset, + __out PULONG Id + ) PURE; + // Returns the system unique ID for the current thread. + // Not currently supported when kernel debugging. + STDMETHOD(GetCurrentThreadSystemId)( + THIS_ + __out PULONG SysId + ) PURE; + // Looks up a debugger thread ID for the given + // system thread ID. + // Currently when kernel debugging this will fail + // if the thread is not executing on a processor. + STDMETHOD(GetThreadIdBySystemId)( + THIS_ + __in ULONG SysId, + __out PULONG Id + ) PURE; + // Returns the handle of the current thread. + // In kernel mode the value returned is the + // index of the processor the thread is + // executing on plus one. + STDMETHOD(GetCurrentThreadHandle)( + THIS_ + __out PULONG64 Handle + ) PURE; + // Looks up a debugger thread ID for the given handle. + // Currently when kernel debugging this will fail + // if the thread is not executing on a processor. + STDMETHOD(GetThreadIdByHandle)( + THIS_ + __in ULONG64 Handle, + __out PULONG Id + ) PURE; + + // Currently kernel mode sessions will only have + // a single process representing kernel space. + STDMETHOD(GetNumberProcesses)( + THIS_ + __out PULONG Number + ) PURE; + STDMETHOD(GetProcessIdsByIndex)( + THIS_ + __in ULONG Start, + __in ULONG Count, + __out_ecount_opt(Count) PULONG Ids, + __out_ecount_opt(Count) PULONG SysIds + ) PURE; + // Returns the offset of the current processs + // system data structure. When kernel debugging + // this is the offset of the KPROCESS of + // the process that owns the current thread. + // When user debugging it is the offset + // of the current PEB. + STDMETHOD(GetCurrentProcessDataOffset)( + THIS_ + __out PULONG64 Offset + ) PURE; + // Looks up a debugger process ID for the given + // system process data structure. + // Not currently supported when kernel debugging. + STDMETHOD(GetProcessIdByDataOffset)( + THIS_ + __in ULONG64 Offset, + __out PULONG Id + ) PURE; + // Returns the offset of the current processs + // PEB. In user mode this is equivalent to + // the processs data offset. + STDMETHOD(GetCurrentProcessPeb)( + THIS_ + __out PULONG64 Offset + ) PURE; + // Looks up a debugger process ID for the given PEB. + // Not currently supported when kernel debugging. + STDMETHOD(GetProcessIdByPeb)( + THIS_ + __in ULONG64 Offset, + __out PULONG Id + ) PURE; + // Returns the system unique ID for the current process. + // Not currently supported when kernel debugging. + STDMETHOD(GetCurrentProcessSystemId)( + THIS_ + __out PULONG SysId + ) PURE; + // Looks up a debugger process ID for the given + // system process ID. + // Not currently supported when kernel debugging. + STDMETHOD(GetProcessIdBySystemId)( + THIS_ + __in ULONG SysId, + __out PULONG Id + ) PURE; + // Returns the handle of the current process. + // In kernel mode this is the kernel processs + // artificial handle used for symbol operations + // and so can only be used with dbghelp APIs. + STDMETHOD(GetCurrentProcessHandle)( + THIS_ + __out PULONG64 Handle + ) PURE; + // Looks up a debugger process ID for the given handle. + STDMETHOD(GetProcessIdByHandle)( + THIS_ + __in ULONG64 Handle, + __out PULONG Id + ) PURE; + // Retrieve the name of the executable loaded + // in the process. This may fail if no executable + // was identified. + STDMETHOD(GetCurrentProcessExecutableName)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG ExeSize + ) PURE; + + // IDebugSystemObjects2. + + // Return the number of seconds that the current + // process has been running. + STDMETHOD(GetCurrentProcessUpTime)( + THIS_ + __out PULONG UpTime + ) PURE; + + // During kernel sessions the debugger retrieves + // some information from the system thread and process + // running on the current processor. For example, + // the debugger will retrieve virtual memory translation + // information for when the debugger needs to + // carry out its own virtual to physical translations. + // Occasionally it can be interesting to perform + // similar operations but on a process which isnt + // currently running. The follow methods allow a caller + // to override the data offsets used by the debugger + // so that other system threads and processes can + // be used instead. These values are defaulted to + // the thread and process running on the current + // processor each time the debuggee executes or + // the current processor changes. + // The thread and process settings are independent so + // it is possible to refer to a thread in a process + // other than the current process and vice versa. + // Setting an offset of zero will reload the + // default value. + STDMETHOD(GetImplicitThreadDataOffset)( + THIS_ + __out PULONG64 Offset + ) PURE; + STDMETHOD(SetImplicitThreadDataOffset)( + THIS_ + __in ULONG64 Offset + ) PURE; + STDMETHOD(GetImplicitProcessDataOffset)( + THIS_ + __out PULONG64 Offset + ) PURE; + STDMETHOD(SetImplicitProcessDataOffset)( + THIS_ + __in ULONG64 Offset + ) PURE; + + // IDebugSystemObjects3. + + STDMETHOD(GetEventSystem)( + THIS_ + __out PULONG Id + ) PURE; + + STDMETHOD(GetCurrentSystemId)( + THIS_ + __out PULONG Id + ) PURE; + STDMETHOD(SetCurrentSystemId)( + THIS_ + __in ULONG Id + ) PURE; + + STDMETHOD(GetNumberSystems)( + THIS_ + __out PULONG Number + ) PURE; + STDMETHOD(GetSystemIdsByIndex)( + THIS_ + __in ULONG Start, + __in ULONG Count, + __out_ecount(Count) PULONG Ids + ) PURE; + STDMETHOD(GetTotalNumberThreadsAndProcesses)( + THIS_ + __out PULONG TotalThreads, + __out PULONG TotalProcesses, + __out PULONG LargestProcessThreads, + __out PULONG LargestSystemThreads, + __out PULONG LargestSystemProcesses + ) PURE; + STDMETHOD(GetCurrentSystemServer)( + THIS_ + __out PULONG64 Server + ) PURE; + STDMETHOD(GetSystemByServer)( + THIS_ + __in ULONG64 Server, + __out PULONG Id + ) PURE; + STDMETHOD(GetCurrentSystemServerName)( + THIS_ + __out_ecount_opt(BufferSize) PSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG NameSize + ) PURE; + + // IDebugSystemObjects4. + + STDMETHOD(GetCurrentProcessExecutableNameWide)( + THIS_ + __out_ecount_opt(BufferSize) PWSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG ExeSize + ) PURE; + + STDMETHOD(GetCurrentSystemServerNameWide)( + THIS_ + __out_ecount_opt(BufferSize) PWSTR Buffer, + __in ULONG BufferSize, + __out_opt PULONG NameSize + ) PURE; +}; + +//---------------------------------------------------------------------------- +// +// Debugger/debuggee communication. +// +// A distinguished exception, DBG_COMMAND_EXCEPTION (0x40010009), +// can be used by a debuggee to communicate with the debugger. +// The arguments of the exception must be: +// 1. Exception ID. +// 2. Command code. +// 3. Size of argument. +// 4. Pointer to argument. +// +// The arguments depend on the command code. +// +//---------------------------------------------------------------------------- + +#define DEBUG_COMMAND_EXCEPTION_ID 0xdbe00dbe + +// Invalid command code. +#define DEBUG_CMDEX_INVALID 0x00000000 + +// +// The debugger can collect strings for display at the +// next event. A debuggee can use this to register information +// about a program situation before places where an event +// may occur, such as a risky operation or assertion. +// The strings are automatically flushed on the next +// event continuation. Strings are kept on a per-thread basis. +// +// When adding, the argument is the string to add. +// Reset has no arguments and clears all strings. +// +#define DEBUG_CMDEX_ADD_EVENT_STRING 0x00000001 +#define DEBUG_CMDEX_RESET_EVENT_STRINGS 0x00000002 + +#ifndef DEBUG_NO_IMPLEMENTATION + +FORCEINLINE void +DebugCommandException(ULONG Command, ULONG ArgSize, PVOID Arg) +{ + ULONG_PTR ExArgs[4]; + + ExArgs[0] = DEBUG_COMMAND_EXCEPTION_ID; + ExArgs[1] = Command; + ExArgs[2] = ArgSize; + ExArgs[3] = (ULONG_PTR)Arg; + RaiseException(DBG_COMMAND_EXCEPTION, 0, 4, ExArgs); +} + +#endif // #ifndef DEBUG_NO_IMPLEMENTATION + +//---------------------------------------------------------------------------- +// +// Extension callbacks. +// +//---------------------------------------------------------------------------- + +// Returns a version with the major version in +// the high word and the minor version in the low word. +#define DEBUG_EXTENSION_VERSION(Major, Minor) \ + ((((Major) & 0xffff) << 16) | ((Minor) & 0xffff)) + +// +// Descriptive flags returned from extension initialization. +// + +// Extension has a !help command which can give +// per-command help. +#define DEBUG_EXTINIT_HAS_COMMAND_HELP 0x00000001 + +// Initialization routine. Called once when the extension DLL +// is loaded. Returns a version and returns flags detailing +// overall qualities of the extension DLL. +// A session may or may not be active at the time the DLL +// is loaded so initialization routines should not expect +// to be able to query session information. +typedef HRESULT (CALLBACK* PDEBUG_EXTENSION_INITIALIZE) + (__out PULONG Version, __out PULONG Flags); +// Exit routine. Called once just before the extension DLL is +// unloaded. As with initialization, a session may or +// may not be active at the time of the call. +typedef void (CALLBACK* PDEBUG_EXTENSION_UNINITIALIZE) + (void); + +// A debuggee has been discovered for the session. It +// is not necessarily halted. +#define DEBUG_NOTIFY_SESSION_ACTIVE 0x00000000 +// The session no longer has a debuggee. +#define DEBUG_NOTIFY_SESSION_INACTIVE 0x00000001 +// The debuggee is halted and accessible. +#define DEBUG_NOTIFY_SESSION_ACCESSIBLE 0x00000002 +// The debuggee is running or inaccessible. +#define DEBUG_NOTIFY_SESSION_INACCESSIBLE 0x00000003 + +typedef void (CALLBACK* PDEBUG_EXTENSION_NOTIFY) + (__in ULONG Notify, __in ULONG64 Argument); + +// A PDEBUG_EXTENSION_CALL function can return this code +// to indicate that it was unable to handle the request +// and that the search for an extension function should +// continue down the extension DLL chain. +// Taken from STATUS_VALIDATE_CONTINUE. +#define DEBUG_EXTENSION_CONTINUE_SEARCH \ + HRESULT_FROM_NT(0xC0000271L) + +// A PDEBUG_EXTENSION_CALL function can return this code +// to indicate that the engine should unload and reload +// the extension binary. This allows extensions to implement +// auto-update functionality. +#define DEBUG_EXTENSION_RELOAD_EXTENSION \ + HRESULT_FROM_NT(0xC00000EEL) + +// Every routine in an extension DLL has the following prototype. +// The extension may be called from multiple clients so it +// should not cache the client value between calls. +typedef HRESULT (CALLBACK* PDEBUG_EXTENSION_CALL) + (__in PDEBUG_CLIENT Client, __in_opt PCSTR Args); + +// +// KnownStructOutput[Ex] flags +// + +// Return names of supported structs. +#define DEBUG_KNOWN_STRUCT_GET_NAMES 1 +// Return value output for type. +#define DEBUG_KNOWN_STRUCT_GET_SINGLE_LINE_OUTPUT 2 +// Return S_OK if suppressing type name. +#define DEBUG_KNOWN_STRUCT_SUPPRESS_TYPE_NAME 3 + +// Extensions may export this callback in order to dump structs that +// are well known to them. The engine calls this to inject extension +// output into dt's struct dump. +typedef HRESULT (CALLBACK* PDEBUG_EXTENSION_KNOWN_STRUCT) + (__in ULONG Flags, + __in ULONG64 Offset, + __in_opt PSTR TypeName, + __out_ecount_opt(*BufferChars) PSTR Buffer, + __inout_opt PULONG BufferChars); +typedef HRESULT (CALLBACK* PDEBUG_EXTENSION_KNOWN_STRUCT_EX) + (__in PDEBUG_CLIENT Client, + __in ULONG Flags, + __in ULONG64 Offset, + __in_opt PCSTR TypeName, + __out_ecount_opt(*BufferChars) PSTR Buffer, + __inout_opt PULONG BufferChars); + +// Backwards compatibility with old, incorrect name. +typedef PDEBUG_EXTENSION_KNOWN_STRUCT PDEBUG_ENTENSION_KNOWNSTRUCT; + +// +// Extensions can provide pseudo-register values that +// operate similiarly to the debugger's built-in $teb, etc. +// + +#define DEBUG_EXT_QVALUE_DEFAULT 0x00000000 + +typedef HRESULT (CALLBACK* PDEBUG_EXTENSION_QUERY_VALUE_NAMES) + (__in PDEBUG_CLIENT Client, + __in ULONG Flags, + __out_ecount(BufferChars) PWSTR Buffer, + __in ULONG BufferChars, + __out PULONG BufferNeeded); + +#define DEBUG_EXT_PVALUE_DEFAULT 0x00000000 + +#define DEBUG_EXT_PVTYPE_IS_VALUE 0x00000000 +#define DEBUG_EXT_PVTYPE_IS_POINTER 0x00000001 + +typedef HRESULT (CALLBACK* PDEBUG_EXTENSION_PROVIDE_VALUE) + (__in PDEBUG_CLIENT Client, + __in ULONG Flags, + __in PCWSTR Name, + __out PULONG64 Value, + __out PULONG64 TypeModBase, + __out PULONG TypeId, + __out PULONG TypeFlags); + +//---------------------------------------------------------------------------- +// +// Extension functions. +// +// Extension functions differ from extension callbacks in that +// they are arbitrary functions exported from an extension DLL +// for other code callers instead of for human invocation from +// debugger commands. Extension function pointers are retrieved +// for an extension DLL with IDebugControl::GetExtensionFunction. +// +// Extension function names must begin with _EFN_. Other than that +// they can have any name and prototype. Extension functions +// must be public exports of their extension DLL. They should +// have a typedef for their function pointer prototype in an +// extension header so that callers have a header file to include +// with a type that allows a correctly-formed invocation of the +// extension function. +// +// The engine does not perform any validation of calls to +// extension functions. Once the extension function pointer +// is retrieved with GetExtensionFunction all calls go +// directly between the caller and the extension function and +// are not mediated by the engine. +// +//---------------------------------------------------------------------------- + +#ifdef __cplusplus +}; + +//---------------------------------------------------------------------------- +// +// C++ implementation helper classes. +// +//---------------------------------------------------------------------------- + +#if !defined(DEBUG_NO_IMPLEMENTATION) && !defined(_M_CEE_PURE) + +// +// DebugBaseEventCallbacks provides a do-nothing base implementation +// of IDebugEventCallbacks. A program can derive their own +// event callbacks class from DebugBaseEventCallbacks and implement +// only the methods they are interested in. Programs must be +// careful to implement GetInterestMask appropriately. +// +class DebugBaseEventCallbacks : public IDebugEventCallbacks +{ +public: + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + __in REFIID InterfaceId, + __out PVOID* Interface + ) + { + *Interface = NULL; + +#if _MSC_VER >= 1100 + if (IsEqualIID(InterfaceId, __uuidof(IUnknown)) || + IsEqualIID(InterfaceId, __uuidof(IDebugEventCallbacks))) +#else + if (IsEqualIID(InterfaceId, IID_IUnknown) || + IsEqualIID(InterfaceId, IID_IDebugEventCallbacks)) +#endif + { + *Interface = (IDebugEventCallbacks *)this; + AddRef(); + return S_OK; + } + else + { + return E_NOINTERFACE; + } + } + + // IDebugEventCallbacks. + + STDMETHOD(Breakpoint)( + THIS_ + __in PDEBUG_BREAKPOINT Bp + ) + { + UNREFERENCED_PARAMETER(Bp); + return DEBUG_STATUS_NO_CHANGE; + } + STDMETHOD(Exception)( + THIS_ + __in PEXCEPTION_RECORD64 Exception, + __in ULONG FirstChance + ) + { + UNREFERENCED_PARAMETER(Exception); + UNREFERENCED_PARAMETER(FirstChance); + return DEBUG_STATUS_NO_CHANGE; + } + STDMETHOD(CreateThread)( + THIS_ + __in ULONG64 Handle, + __in ULONG64 DataOffset, + __in ULONG64 StartOffset + ) + { + UNREFERENCED_PARAMETER(Handle); + UNREFERENCED_PARAMETER(DataOffset); + UNREFERENCED_PARAMETER(StartOffset); + return DEBUG_STATUS_NO_CHANGE; + } + STDMETHOD(ExitThread)( + THIS_ + __in ULONG ExitCode + ) + { + UNREFERENCED_PARAMETER(ExitCode); + return DEBUG_STATUS_NO_CHANGE; + } + STDMETHOD(CreateProcess)( + THIS_ + __in ULONG64 ImageFileHandle, + __in ULONG64 Handle, + __in ULONG64 BaseOffset, + __in ULONG ModuleSize, + __in PCSTR ModuleName, + __in PCSTR ImageName, + __in ULONG CheckSum, + __in ULONG TimeDateStamp, + __in ULONG64 InitialThreadHandle, + __in ULONG64 ThreadDataOffset, + __in ULONG64 StartOffset + ) + { + UNREFERENCED_PARAMETER(ImageFileHandle); + UNREFERENCED_PARAMETER(Handle); + UNREFERENCED_PARAMETER(BaseOffset); + UNREFERENCED_PARAMETER(ModuleSize); + UNREFERENCED_PARAMETER(ModuleName); + UNREFERENCED_PARAMETER(ImageName); + UNREFERENCED_PARAMETER(CheckSum); + UNREFERENCED_PARAMETER(TimeDateStamp); + UNREFERENCED_PARAMETER(InitialThreadHandle); + UNREFERENCED_PARAMETER(ThreadDataOffset); + UNREFERENCED_PARAMETER(StartOffset); + return DEBUG_STATUS_NO_CHANGE; + } + STDMETHOD(ExitProcess)( + THIS_ + __in ULONG ExitCode + ) + { + UNREFERENCED_PARAMETER(ExitCode); + return DEBUG_STATUS_NO_CHANGE; + } + STDMETHOD(LoadModule)( + THIS_ + __in ULONG64 ImageFileHandle, + __in ULONG64 BaseOffset, + __in ULONG ModuleSize, + __in PCSTR ModuleName, + __in PCSTR ImageName, + __in ULONG CheckSum, + __in ULONG TimeDateStamp + ) + { + UNREFERENCED_PARAMETER(ImageFileHandle); + UNREFERENCED_PARAMETER(BaseOffset); + UNREFERENCED_PARAMETER(ModuleSize); + UNREFERENCED_PARAMETER(ModuleName); + UNREFERENCED_PARAMETER(ImageName); + UNREFERENCED_PARAMETER(CheckSum); + UNREFERENCED_PARAMETER(TimeDateStamp); + return DEBUG_STATUS_NO_CHANGE; + } + STDMETHOD(UnloadModule)( + THIS_ + __in PCSTR ImageBaseName, + __in ULONG64 BaseOffset + ) + { + UNREFERENCED_PARAMETER(ImageBaseName); + UNREFERENCED_PARAMETER(BaseOffset); + return DEBUG_STATUS_NO_CHANGE; + } + STDMETHOD(SystemError)( + THIS_ + __in ULONG Error, + __in ULONG Level + ) + { + UNREFERENCED_PARAMETER(Error); + UNREFERENCED_PARAMETER(Level); + return DEBUG_STATUS_NO_CHANGE; + } + STDMETHOD(SessionStatus)( + THIS_ + __in ULONG Status + ) + { + UNREFERENCED_PARAMETER(Status); + return DEBUG_STATUS_NO_CHANGE; + } + STDMETHOD(ChangeDebuggeeState)( + THIS_ + __in ULONG Flags, + __in ULONG64 Argument + ) + { + UNREFERENCED_PARAMETER(Flags); + UNREFERENCED_PARAMETER(Argument); + return S_OK; + } + STDMETHOD(ChangeEngineState)( + THIS_ + __in ULONG Flags, + __in ULONG64 Argument + ) + { + UNREFERENCED_PARAMETER(Flags); + UNREFERENCED_PARAMETER(Argument); + return S_OK; + } + STDMETHOD(ChangeSymbolState)( + THIS_ + __in ULONG Flags, + __in ULONG64 Argument + ) + { + UNREFERENCED_PARAMETER(Flags); + UNREFERENCED_PARAMETER(Argument); + return S_OK; + } +}; + +class DebugBaseEventCallbacksWide : public IDebugEventCallbacksWide +{ +public: + // IUnknown. + STDMETHOD(QueryInterface)( + THIS_ + __in REFIID InterfaceId, + __out PVOID* Interface + ) + { + *Interface = NULL; + +#if _MSC_VER >= 1100 + if (IsEqualIID(InterfaceId, __uuidof(IUnknown)) || + IsEqualIID(InterfaceId, __uuidof(IDebugEventCallbacksWide))) +#else + if (IsEqualIID(InterfaceId, IID_IUnknown) || + IsEqualIID(InterfaceId, IID_IDebugEventCallbacksWide)) +#endif + { + *Interface = (IDebugEventCallbacksWide *)this; + AddRef(); + return S_OK; + } + else + { + return E_NOINTERFACE; + } + } + + // IDebugEventCallbacksWide. + + STDMETHOD(Breakpoint)( + THIS_ + __in PDEBUG_BREAKPOINT2 Bp + ) + { + UNREFERENCED_PARAMETER(Bp); + return DEBUG_STATUS_NO_CHANGE; + } + STDMETHOD(Exception)( + THIS_ + __in PEXCEPTION_RECORD64 Exception, + __in ULONG FirstChance + ) + { + UNREFERENCED_PARAMETER(Exception); + UNREFERENCED_PARAMETER(FirstChance); + return DEBUG_STATUS_NO_CHANGE; + } + STDMETHOD(CreateThread)( + THIS_ + __in ULONG64 Handle, + __in ULONG64 DataOffset, + __in ULONG64 StartOffset + ) + { + UNREFERENCED_PARAMETER(Handle); + UNREFERENCED_PARAMETER(DataOffset); + UNREFERENCED_PARAMETER(StartOffset); + return DEBUG_STATUS_NO_CHANGE; + } + STDMETHOD(ExitThread)( + THIS_ + __in ULONG ExitCode + ) + { + UNREFERENCED_PARAMETER(ExitCode); + return DEBUG_STATUS_NO_CHANGE; + } + STDMETHOD(CreateProcess)( + THIS_ + __in ULONG64 ImageFileHandle, + __in ULONG64 Handle, + __in ULONG64 BaseOffset, + __in ULONG ModuleSize, + __in PCWSTR ModuleName, + __in PCWSTR ImageName, + __in ULONG CheckSum, + __in ULONG TimeDateStamp, + __in ULONG64 InitialThreadHandle, + __in ULONG64 ThreadDataOffset, + __in ULONG64 StartOffset + ) + { + UNREFERENCED_PARAMETER(ImageFileHandle); + UNREFERENCED_PARAMETER(Handle); + UNREFERENCED_PARAMETER(BaseOffset); + UNREFERENCED_PARAMETER(ModuleSize); + UNREFERENCED_PARAMETER(ModuleName); + UNREFERENCED_PARAMETER(ImageName); + UNREFERENCED_PARAMETER(CheckSum); + UNREFERENCED_PARAMETER(TimeDateStamp); + UNREFERENCED_PARAMETER(InitialThreadHandle); + UNREFERENCED_PARAMETER(ThreadDataOffset); + UNREFERENCED_PARAMETER(StartOffset); + return DEBUG_STATUS_NO_CHANGE; + } + STDMETHOD(ExitProcess)( + THIS_ + __in ULONG ExitCode + ) + { + UNREFERENCED_PARAMETER(ExitCode); + return DEBUG_STATUS_NO_CHANGE; + } + STDMETHOD(LoadModule)( + THIS_ + __in ULONG64 ImageFileHandle, + __in ULONG64 BaseOffset, + __in ULONG ModuleSize, + __in PCWSTR ModuleName, + __in PCWSTR ImageName, + __in ULONG CheckSum, + __in ULONG TimeDateStamp + ) + { + UNREFERENCED_PARAMETER(ImageFileHandle); + UNREFERENCED_PARAMETER(BaseOffset); + UNREFERENCED_PARAMETER(ModuleSize); + UNREFERENCED_PARAMETER(ModuleName); + UNREFERENCED_PARAMETER(ImageName); + UNREFERENCED_PARAMETER(CheckSum); + UNREFERENCED_PARAMETER(TimeDateStamp); + return DEBUG_STATUS_NO_CHANGE; + } + STDMETHOD(UnloadModule)( + THIS_ + __in PCWSTR ImageBaseName, + __in ULONG64 BaseOffset + ) + { + UNREFERENCED_PARAMETER(ImageBaseName); + UNREFERENCED_PARAMETER(BaseOffset); + return DEBUG_STATUS_NO_CHANGE; + } + STDMETHOD(SystemError)( + THIS_ + __in ULONG Error, + __in ULONG Level + ) + { + UNREFERENCED_PARAMETER(Error); + UNREFERENCED_PARAMETER(Level); + return DEBUG_STATUS_NO_CHANGE; + } + STDMETHOD(SessionStatus)( + THIS_ + __in ULONG Status + ) + { + UNREFERENCED_PARAMETER(Status); + return DEBUG_STATUS_NO_CHANGE; + } + STDMETHOD(ChangeDebuggeeState)( + THIS_ + __in ULONG Flags, + __in ULONG64 Argument + ) + { + UNREFERENCED_PARAMETER(Flags); + UNREFERENCED_PARAMETER(Argument); + return S_OK; + } + STDMETHOD(ChangeEngineState)( + THIS_ + __in ULONG Flags, + __in ULONG64 Argument + ) + { + UNREFERENCED_PARAMETER(Flags); + UNREFERENCED_PARAMETER(Argument); + return S_OK; + } + STDMETHOD(ChangeSymbolState)( + THIS_ + __in ULONG Flags, + __in ULONG64 Argument + ) + { + UNREFERENCED_PARAMETER(Flags); + UNREFERENCED_PARAMETER(Argument); + return S_OK; + } +}; + +#endif // #ifndef DEBUG_NO_IMPLEMENTATION + +#ifdef DEBUG_UNICODE_MACROS + +#ifdef UNICODE + +#define IDebugEventCallbacksT IDebugEventCallbacksWide +#define IID_IDebugEventCallbacksT IID_IDebugEventCallbacksWide +#define IDebugOutputCallbacksT IDebugOutputCallbacksWide +#define IID_IDebugOutputCallbacksT IID_IDebugOutputCallbacksWide +#define DebugBaseEventCallbacksT DebugBaseEventCallbacksWide + +#define DebugConnectT DebugConnectWide +#define GetSourceFileInformationT GetSourceFileInformationWide +#define FindSourceFileAndTokenT FindSourceFileAndTokenWide +#define GetSymbolInformationT GetSymbolInformationWide +#define GetCommandT GetCommandWide +#define SetCommandT SetCommandWide +#define GetOffsetExpressionT GetOffsetExpressionWide +#define SetOffsetExpressionT SetOffsetExpressionWide +#define GetRunningProcessSystemIdByExecutableNameT GetRunningProcessSystemIdByExecutableNameWide +#define GetRunningProcessDescriptionT GetRunningProcessDescriptionWide +#define CreateProcessT CreateProcessWide +#define CreateProcessAndAttachT CreateProcessAndAttachWide +#define AddDumpInformationFileT AddDumpInformationFileWide +#define GetDumpFileT GetDumpFileWide +#define AttachKernelT AttachKernelWide +#define GetKernelConnectionOptionsT GetKernelConnectionOptionsWide +#define SetKernelConnectionOptionsT SetKernelConnectionOptionsWide +#define StartProcessServerT StartProcessServerWide +#define ConnectProcessServerT ConnectProcessServerWide +#define StartServerT StartServerWide +#define OutputServersT OutputServersWide +#define GetOutputCallbacksT GetOutputCallbacksWide +#define SetOutputCallbacksT SetOutputCallbacksWide +#define GetOutputLinePrefixT GetOutputLinePrefixWide +#define SetOutputLinePrefixT SetOutputLinePrefixWide +#define GetIdentityT GetIdentityWide +#define OutputIdentityT OutputIdentityWide +#define GetEventCallbacksT GetEventCallbacksWide +#define SetEventCallbacksT SetEventCallbacksWide +#define CreateProcess2T CreateProcess2Wide +#define CreateProcessAndAttach2T CreateProcessAndAttach2Wide +#define PushOutputLinePrefixT PushOutputLinePrefixWide +#define GetQuitLockStringT GetQuitLockStringWide +#define SetQuitLockStringT SetQuitLockStringWide +#define GetLogFileT GetLogFileWide +#define OpenLogFileT OpenLogFileWide +#define InputT InputWide +#define ReturnInputT ReturnInputWide +#define OutputT OutputWide +#define OutputVaListT OutputVaListWide +#define ControlledOutputT ControlledOutputWide +#define ControlledOutputVaListT ControlledOutputVaListWide +#define OutputPromptT OutputPromptWide +#define OutputPromptVaListT OutputPromptVaListWide +#define GetPromptTextT GetPromptTextWide +#define AssembleT AssembleWide +#define DisassembleT DisassembleWide +#define GetProcessorTypeNamesT GetProcessorTypeNamesWide +#define GetTextMacroT GetTextMacroWide +#define SetTextMacroT SetTextMacroWide +#define EvaluateT EvaluateWide +#define ExecuteT ExecuteWide +#define ExecuteCommandFileT ExecuteCommandFileWide +#define AddExtensionT AddExtensionWide +#define GetExtensionByPathT GetExtensionByPathWide +#define CallExtensionT CallExtensionWide +#define GetExtensionFunctionT GetExtensionFunctionWide +#define GetEventFilterTextT GetEventFilterTextWide +#define GetEventFilterCommandT GetEventFilterCommandWide +#define SetEventFilterCommandT SetEventFilterCommandWide +#define GetSpecificFilterArgumentT GetSpecificFilterArgumentWide +#define SetSpecificFilterArgumentT SetSpecificFilterArgumentWide +#define GetExceptionFilterSecondCommandT GetExceptionFilterSecondCommandWide +#define SetExceptionFilterSecondCommandT SetExceptionFilterSecondCommandWide +#define GetLastEventInformationT GetLastEventInformationWide +#define GetTextReplacementT GetTextReplacementWide +#define SetTextReplacementT SetTextReplacementWide +#define SetExpressionSyntaxByNameT SetExpressionSyntaxByNameWide +#define GetExpressionSyntaxNamesT GetExpressionSyntaxNamesWide +#define GetEventIndexDescriptionT GetEventIndexDescriptionWide +#define GetLogFile2T GetLogFile2Wide +#define OpenLogFile2T OpenLogFile2Wide +#define GetSystemVersionStringT GetSystemVersionStringWide +#define ReadMultiByteStringVirtualT ReadMultiByteStringVirtualWide +#define ReadUnicodeStringVirtualT ReadUnicodeStringVirtualWide +#define GetDescriptionT GetDescriptionWide +#define GetIndexByNameT GetIndexByNameWide +#define GetPseudoDescriptionT GetPseudoDescriptionWide +#define GetPseudoIndexByNameT GetPseudoIndexByNameWide +#define AddSymbolT AddSymbolWide +#define RemoveSymbolByNameT RemoveSymbolByNameWide +#define GetSymbolNameT GetSymbolNameWide +#define WriteSymbolT WriteSymbolWide +#define OutputAsTypeT OutputAsTypeWide +#define GetSymbolTypeNameT GetSymbolTypeNameWide +#define GetSymbolValueTextT GetSymbolValueTextWide +#define GetNameByOffsetT GetNameByOffsetWide +#define GetOffsetByNameT GetOffsetByNameWide +#define GetNearNameByOffsetT GetNearNameByOffsetWide +#define GetLineByOffsetT GetLineByOffsetWide +#define GetOffsetByLineT GetOffsetByLineWide +#define GetModuleByModuleNameT GetModuleByModuleNameWide +#define GetModuleByModuleName2T GetModuleByModuleName2Wide +#define GetSymbolModuleT GetSymbolModuleWide +#define GetTypeNameT GetTypeNameWide +#define GetTypeIdT GetTypeIdWide +#define GetFieldOffsetT GetFieldOffsetWide +#define GetSymbolTypeIdT GetSymbolTypeIdWide +#define StartSymbolMatchT StartSymbolMatchWide +#define GetNextSymbolMatchT GetNextSymbolMatchWide +#define ReloadT ReloadWide +#define GetSymbolPathT GetSymbolPathWide +#define SetSymbolPathT SetSymbolPathWide +#define AppendSymbolPathT AppendSymbolPathWide +#define GetImagePathT GetImagePathWide +#define SetImagePathT SetImagePathWide +#define AppendImagePathT AppendImagePathWide +#define GetSourcePathT GetSourcePathWide +#define GetSourcePathElementT GetSourcePathElementWide +#define SetSourcePathT SetSourcePathWide +#define AppendSourcePathT AppendSourcePathWide +#define FindSourceFileT FindSourceFileWide +#define GetSourceFileLineOffsetsT GetSourceFileLineOffsetsWide +#define GetModuleVersionInformationT GetModuleVersionInformationWide +#define GetModuleNameStringT GetModuleNameStringWide +#define GetConstantNameT GetConstantNameWide +#define GetFieldNameT GetFieldNameWide +#define GetFieldTypeAndOffsetT GetFieldTypeAndOffsetWide +#define GetSymbolEntriesByNameT GetSymbolEntriesByNameWide +#define GetSymbolEntryStringT GetSymbolEntryStringWide +#define GetSourceEntriesByLineT GetSourceEntriesByLineWide +#define GetSourceEntryStringT GetSourceEntryStringWide +#define GetCurrentProcessExecutableNameT GetCurrentProcessExecutableNameWide +#define GetCurrentSystemServerNameT GetCurrentSystemServerNameWide + +#else // #ifdef UNICODE + +#define IDebugEventCallbacksT IDebugEventCallbacks +#define IID_IDebugEventCallbacksT IID_IDebugEventCallbacks +#define IDebugOutputCallbacksT IDebugOutputCallbacks +#define IID_IDebugOutputCallbacksT IID_IDebugOutputCallbacks +#define DebugBaseEventCallbacksT DebugBaseEventCallbacks + +#define DebugConnectT DebugConnect +#define GetSourceFileInformationT GetSourceFileInformation +#define FindSourceFileAndTokenT FindSourceFileAndToken +#define GetSymbolInformationT GetSymbolInformation +#define GetCommandT GetCommand +#define SetCommandT SetCommand +#define GetOffsetExpressionT GetOffsetExpression +#define SetOffsetExpressionT SetOffsetExpression +#define GetRunningProcessSystemIdByExecutableNameT GetRunningProcessSystemIdByExecutableName +#define GetRunningProcessDescriptionT GetRunningProcessDescription +#define CreateProcessT CreateProcess +#define CreateProcessAndAttachT CreateProcessAndAttach +#define AddDumpInformationFileT AddDumpInformationFile +#define GetDumpFileT GetDumpFile +#define AttachKernelT AttachKernel +#define GetKernelConnectionOptionsT GetKernelConnectionOptions +#define SetKernelConnectionOptionsT SetKernelConnectionOptions +#define StartProcessServerT StartProcessServer +#define ConnectProcessServerT ConnectProcessServer +#define StartServerT StartServer +#define OutputServersT OutputServers +#define GetOutputCallbacksT GetOutputCallbacks +#define SetOutputCallbacksT SetOutputCallbacks +#define GetOutputLinePrefixT GetOutputLinePrefix +#define SetOutputLinePrefixT SetOutputLinePrefix +#define GetIdentityT GetIdentity +#define OutputIdentityT OutputIdentity +#define GetEventCallbacksT GetEventCallbacks +#define SetEventCallbacksT SetEventCallbacks +#define CreateProcess2T CreateProcess2 +#define CreateProcessAndAttach2T CreateProcessAndAttach2 +#define PushOutputLinePrefixT PushOutputLinePrefix +#define GetQuitLockStringT GetQuitLockString +#define SetQuitLockStringT SetQuitLockString +#define GetLogFileT GetLogFile +#define OpenLogFileT OpenLogFile +#define InputT Input +#define ReturnInputT ReturnInput +#define OutputT Output +#define OutputVaListT OutputVaList +#define ControlledOutputT ControlledOutput +#define ControlledOutputVaListT ControlledOutputVaList +#define OutputPromptT OutputPrompt +#define OutputPromptVaListT OutputPromptVaList +#define GetPromptTextT GetPromptText +#define AssembleT Assemble +#define DisassembleT Disassemble +#define GetProcessorTypeNamesT GetProcessorTypeNames +#define GetTextMacroT GetTextMacro +#define SetTextMacroT SetTextMacro +#define EvaluateT Evaluate +#define ExecuteT Execute +#define ExecuteCommandFileT ExecuteCommandFile +#define AddExtensionT AddExtension +#define GetExtensionByPathT GetExtensionByPath +#define CallExtensionT CallExtension +#define GetExtensionFunctionT GetExtensionFunction +#define GetEventFilterTextT GetEventFilterText +#define GetEventFilterCommandT GetEventFilterCommand +#define SetEventFilterCommandT SetEventFilterCommand +#define GetSpecificFilterArgumentT GetSpecificFilterArgument +#define SetSpecificFilterArgumentT SetSpecificFilterArgument +#define GetExceptionFilterSecondCommandT GetExceptionFilterSecondCommand +#define SetExceptionFilterSecondCommandT SetExceptionFilterSecondCommand +#define GetLastEventInformationT GetLastEventInformation +#define GetTextReplacementT GetTextReplacement +#define SetTextReplacementT SetTextReplacement +#define SetExpressionSyntaxByNameT SetExpressionSyntaxByName +#define GetExpressionSyntaxNamesT GetExpressionSyntaxNames +#define GetEventIndexDescriptionT GetEventIndexDescription +#define GetLogFile2T GetLogFile2 +#define OpenLogFile2T OpenLogFile2 +#define GetSystemVersionStringT GetSystemVersionString +#define ReadMultiByteStringVirtualT ReadMultiByteStringVirtual +#define ReadUnicodeStringVirtualT ReadUnicodeStringVirtual +#define GetDescriptionT GetDescription +#define GetIndexByNameT GetIndexByName +#define GetPseudoDescriptionT GetPseudoDescription +#define GetPseudoIndexByNameT GetPseudoIndexByName +#define AddSymbolT AddSymbol +#define RemoveSymbolByNameT RemoveSymbolByName +#define GetSymbolNameT GetSymbolName +#define WriteSymbolT WriteSymbol +#define OutputAsTypeT OutputAsType +#define GetSymbolTypeNameT GetSymbolTypeName +#define GetSymbolValueTextT GetSymbolValueText +#define GetNameByOffsetT GetNameByOffset +#define GetOffsetByNameT GetOffsetByName +#define GetNearNameByOffsetT GetNearNameByOffset +#define GetLineByOffsetT GetLineByOffset +#define GetOffsetByLineT GetOffsetByLine +#define GetModuleByModuleNameT GetModuleByModuleName +#define GetModuleByModuleName2T GetModuleByModuleName2 +#define GetSymbolModuleT GetSymbolModule +#define GetTypeNameT GetTypeName +#define GetTypeIdT GetTypeId +#define GetFieldOffsetT GetFieldOffset +#define GetSymbolTypeIdT GetSymbolTypeId +#define StartSymbolMatchT StartSymbolMatch +#define GetNextSymbolMatchT GetNextSymbolMatch +#define ReloadT Reload +#define GetSymbolPathT GetSymbolPath +#define SetSymbolPathT SetSymbolPath +#define AppendSymbolPathT AppendSymbolPath +#define GetImagePathT GetImagePath +#define SetImagePathT SetImagePath +#define AppendImagePathT AppendImagePath +#define GetSourcePathT GetSourcePath +#define GetSourcePathElementT GetSourcePathElement +#define SetSourcePathT SetSourcePath +#define AppendSourcePathT AppendSourcePath +#define FindSourceFileT FindSourceFile +#define GetSourceFileLineOffsetsT GetSourceFileLineOffsets +#define GetModuleVersionInformationT GetModuleVersionInformation +#define GetModuleNameStringT GetModuleNameString +#define GetConstantNameT GetConstantName +#define GetFieldNameT GetFieldName +#define GetFieldTypeAndOffsetT GetFieldTypeAndOffset +#define GetSymbolEntriesByNameT GetSymbolEntriesByName +#define GetSymbolEntryStringT GetSymbolEntryString +#define GetSourceEntriesByLineT GetSourceEntriesByLine +#define GetSourceEntryStringT GetSourceEntryString +#define GetCurrentProcessExecutableNameT GetCurrentProcessExecutableName +#define GetCurrentSystemServerNameT GetCurrentSystemServerName + +#endif // #ifdef UNICODE + +#endif // #ifdef DEBUG_UNICODE_MACROS + +#endif // #ifdef __cplusplus + +#endif // #ifndef __DBGENG_H__ diff --git a/src/ToolBox/SOS/Strike/inc/dbghelp.h b/src/ToolBox/SOS/Strike/inc/dbghelp.h new file mode 100644 index 0000000000..8075929bf0 --- /dev/null +++ b/src/ToolBox/SOS/Strike/inc/dbghelp.h @@ -0,0 +1,4540 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*++ BUILD Version: 0000 Increment this if a change has global effects + + + +Module Name: + + dbghelp.h + +Abstract: + + This module defines the prototypes and constants required for the image + help routines. + + Contains debugging support routines that are redistributable. + +Revision History: + +--*/ + +#ifndef _DBGHELP_ +#define _DBGHELP_ + +#if _MSC_VER > 1020 +#pragma once +#endif + + +// As a general principal always call the 64 bit version +// of every API, if a choice exists. The 64 bit version +// works great on 32 bit platforms, and is forward +// compatible to 64 bit platforms. + +#ifdef _WIN64 +#ifndef _IMAGEHLP64 +#define _IMAGEHLP64 +#endif +#endif + +// For those without specstrings.h +// Since there are different versions of this header, I need to +// individually test each item and define it if it is not around. + +#ifndef __in + #define __in +#endif +#ifndef __out + #define __out +#endif +#ifndef __inout + #define __inout +#endif +#ifndef __in_opt + #define __in_opt +#endif +#ifndef __out_opt + #define __out_opt +#endif +#ifndef __inout_opt + #define __inout_opt +#endif +#ifndef __in_ecount + #define __in_ecount(x) +#endif +#ifndef __out_ecount + #define __out_ecount(x) +#endif +#ifndef __inout_ecount + #define __inout_ecount(x) +#endif +#ifndef __in_bcount + #define __in_bcount(x) +#endif +#ifndef __out_bcount + #define __out_bcount(x) +#endif +#ifndef __inout_bcount + #define __inout_bcount(x) +#endif +#ifndef __out_xcount + #define __out_xcount(x) +#endif +#ifndef __deref_opt_out + #define __deref_opt_out +#endif +#ifndef __deref_out + #define __deref_out +#endif +#ifndef __out_ecount_opt + #define __out_ecount_opt(x) +#endif + + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef _IMAGEHLP_SOURCE_ + #define IMAGEAPI __stdcall + #define DBHLP_DEPRECIATED +#else + #define IMAGEAPI DECLSPEC_IMPORT __stdcall + #if (_MSC_VER >= 1300) && !defined(MIDL_PASS) + #define DBHLP_DEPRECIATED __declspec(deprecated) + #else + #define DBHLP_DEPRECIATED + #endif +#endif + +#define DBHLPAPI IMAGEAPI + +#define IMAGE_SEPARATION (64*1024) + +// Observant readers may notice that 2 new fields, +// 'fReadOnly' and 'Version' have been added to +// the LOADED_IMAGE structure after 'fDOSImage'. +// This does not change the size of the structure +// from previous headers. That is because while +// 'fDOSImage' is a byte, it is padded by the +// compiler to 4 bytes. So the 2 new fields are +// slipped into the extra space. + +typedef struct _LOADED_IMAGE { + PSTR ModuleName; + HANDLE hFile; + PUCHAR MappedAddress; +#ifdef _IMAGEHLP64 + PIMAGE_NT_HEADERS64 FileHeader; +#else + PIMAGE_NT_HEADERS32 FileHeader; +#endif + PIMAGE_SECTION_HEADER LastRvaSection; + ULONG NumberOfSections; + PIMAGE_SECTION_HEADER Sections; + ULONG Characteristics; + BOOLEAN fSystemImage; + BOOLEAN fDOSImage; + BOOLEAN fReadOnly; + UCHAR Version; + LIST_ENTRY Links; + ULONG SizeOfImage; +} LOADED_IMAGE, *PLOADED_IMAGE; + +#define MAX_SYM_NAME 2000 + + +// Error codes set by dbghelp functions. Call GetLastError +// to see them. +// Dbghelp also sets error codes found in winerror.h + +#define ERROR_IMAGE_NOT_STRIPPED 0x8800 // the image is not stripped. No dbg file available. +#define ERROR_NO_DBG_POINTER 0x8801 // image is stripped but there is no pointer to a dbg file +#define ERROR_NO_PDB_POINTER 0x8802 // image does not point to a pdb file + +typedef BOOL +(CALLBACK *PFIND_DEBUG_FILE_CALLBACK)( + __in HANDLE FileHandle, + __in PCSTR FileName, + __in PVOID CallerData + ); + +HANDLE +IMAGEAPI +SymFindDebugInfoFile( + __in HANDLE hProcess, + __in PCSTR FileName, + __out_ecount(MAX_PATH + 1) PSTR DebugFilePath, + __in_opt PFIND_DEBUG_FILE_CALLBACK Callback, + __in_opt PVOID CallerData + ); + +typedef BOOL +(CALLBACK *PFIND_DEBUG_FILE_CALLBACKW)( + __in HANDLE FileHandle, + __in PCWSTR FileName, + __in PVOID CallerData + ); + +HANDLE +IMAGEAPI +SymFindDebugInfoFileW( + __in HANDLE hProcess, + __in PCWSTR FileName, + __out_ecount(MAX_PATH + 1) PWSTR DebugFilePath, + __in_opt PFIND_DEBUG_FILE_CALLBACKW Callback, + __in_opt PVOID CallerData + ); + +HANDLE +IMAGEAPI +FindDebugInfoFile ( + __in PCSTR FileName, + __in PCSTR SymbolPath, + __out_ecount(MAX_PATH + 1) PSTR DebugFilePath + ); + +HANDLE +IMAGEAPI +FindDebugInfoFileEx ( + __in PCSTR FileName, + __in PCSTR SymbolPath, + __out_ecount(MAX_PATH + 1) PSTR DebugFilePath, + __in_opt PFIND_DEBUG_FILE_CALLBACK Callback, + __in_opt PVOID CallerData + ); + +HANDLE +IMAGEAPI +FindDebugInfoFileExW ( + __in PCWSTR FileName, + __in PCWSTR SymbolPath, + __out_ecount(MAX_PATH + 1) PWSTR DebugFilePath, + __in_opt PFIND_DEBUG_FILE_CALLBACKW Callback, + __in_opt PVOID CallerData + ); + +typedef BOOL +(CALLBACK *PFINDFILEINPATHCALLBACK)( + PCSTR filename, + PVOID context + ); + +BOOL +IMAGEAPI +SymFindFileInPath( + __in HANDLE hprocess, + __in_opt PCSTR SearchPath, + __in PCSTR FileName, + __in_opt PVOID id, + __in DWORD two, + __in DWORD three, + __in DWORD flags, + __out_ecount(MAX_PATH + 1) PSTR FoundFile, + __in_opt PFINDFILEINPATHCALLBACK callback, + __in_opt PVOID context + ); + +typedef BOOL +(CALLBACK *PFINDFILEINPATHCALLBACKW)( + __in PCWSTR filename, + __in PVOID context + ); + +BOOL +IMAGEAPI +SymFindFileInPathW( + __in HANDLE hprocess, + __in_opt PCWSTR SearchPath, + __in PCWSTR FileName, + __in_opt PVOID id, + __in DWORD two, + __in DWORD three, + __in DWORD flags, + __out_ecount(MAX_PATH + 1) PWSTR FoundFile, + __in_opt PFINDFILEINPATHCALLBACKW callback, + __in_opt PVOID context + ); + +typedef BOOL +(CALLBACK *PFIND_EXE_FILE_CALLBACK)( + __in HANDLE FileHandle, + __in PCSTR FileName, + __in_opt PVOID CallerData + ); + +HANDLE +IMAGEAPI +SymFindExecutableImage( + __in HANDLE hProcess, + __in PCSTR FileName, + __out_ecount(MAX_PATH + 1) PSTR ImageFilePath, + __in PFIND_EXE_FILE_CALLBACK Callback, + __in PVOID CallerData + ); + +typedef BOOL +(CALLBACK *PFIND_EXE_FILE_CALLBACKW)( + __in HANDLE FileHandle, + __in PCWSTR FileName, + __in_opt PVOID CallerData + ); + +HANDLE +IMAGEAPI +SymFindExecutableImageW( + __in HANDLE hProcess, + __in PCWSTR FileName, + __out_ecount(MAX_PATH + 1) PWSTR ImageFilePath, + __in PFIND_EXE_FILE_CALLBACKW Callback, + __in PVOID CallerData + ); + +HANDLE +IMAGEAPI +FindExecutableImage( + __in PCSTR FileName, + __in PCSTR SymbolPath, + __out_ecount(MAX_PATH + 1) PSTR ImageFilePath + ); + +HANDLE +IMAGEAPI +FindExecutableImageEx( + __in PCSTR FileName, + __in PCSTR SymbolPath, + __out_ecount(MAX_PATH + 1) PSTR ImageFilePath, + __in_opt PFIND_EXE_FILE_CALLBACK Callback, + __in_opt PVOID CallerData + ); + +HANDLE +IMAGEAPI +FindExecutableImageExW( + __in PCWSTR FileName, + __in PCWSTR SymbolPath, + __out_ecount(MAX_PATH + 1) PWSTR ImageFilePath, + __in_opt PFIND_EXE_FILE_CALLBACKW Callback, + __in PVOID CallerData + ); + +PIMAGE_NT_HEADERS +IMAGEAPI +ImageNtHeader ( + __in PVOID Base + ); + +PVOID +IMAGEAPI +ImageDirectoryEntryToDataEx ( + __in PVOID Base, + __in BOOLEAN MappedAsImage, + __in USHORT DirectoryEntry, + __out PULONG Size, + __out_opt PIMAGE_SECTION_HEADER *FoundHeader + ); + +PVOID +IMAGEAPI +ImageDirectoryEntryToData ( + __in PVOID Base, + __in BOOLEAN MappedAsImage, + __in USHORT DirectoryEntry, + __out PULONG Size + ); + +PIMAGE_SECTION_HEADER +IMAGEAPI +ImageRvaToSection( + __in PIMAGE_NT_HEADERS NtHeaders, + __in PVOID Base, + __in ULONG Rva + ); + +PVOID +IMAGEAPI +ImageRvaToVa( + __in PIMAGE_NT_HEADERS NtHeaders, + __in PVOID Base, + __in ULONG Rva, + __in_opt OUT PIMAGE_SECTION_HEADER *LastRvaSection + ); + +#ifndef _WIN64 +// This api won't be ported to Win64 - Fix your code. + +typedef struct _IMAGE_DEBUG_INFORMATION { + LIST_ENTRY List; + DWORD ReservedSize; + PVOID ReservedMappedBase; + USHORT ReservedMachine; + USHORT ReservedCharacteristics; + DWORD ReservedCheckSum; + DWORD ImageBase; + DWORD SizeOfImage; + + DWORD ReservedNumberOfSections; + PIMAGE_SECTION_HEADER ReservedSections; + + DWORD ReservedExportedNamesSize; + PSTR ReservedExportedNames; + + DWORD ReservedNumberOfFunctionTableEntries; + PIMAGE_FUNCTION_ENTRY ReservedFunctionTableEntries; + DWORD ReservedLowestFunctionStartingAddress; + DWORD ReservedHighestFunctionEndingAddress; + + DWORD ReservedNumberOfFpoTableEntries; + PFPO_DATA ReservedFpoTableEntries; + + DWORD SizeOfCoffSymbols; + PIMAGE_COFF_SYMBOLS_HEADER CoffSymbols; + + DWORD ReservedSizeOfCodeViewSymbols; + PVOID ReservedCodeViewSymbols; + + PSTR ImageFilePath; + PSTR ImageFileName; + PSTR ReservedDebugFilePath; + + DWORD ReservedTimeDateStamp; + + BOOL ReservedRomImage; + PIMAGE_DEBUG_DIRECTORY ReservedDebugDirectory; + DWORD ReservedNumberOfDebugDirectories; + + DWORD ReservedOriginalFunctionTableBaseAddress; + + DWORD Reserved[ 2 ]; + +} IMAGE_DEBUG_INFORMATION, *PIMAGE_DEBUG_INFORMATION; + + +PIMAGE_DEBUG_INFORMATION +IMAGEAPI +MapDebugInformation( + __in_opt HANDLE FileHandle, + __in PCSTR FileName, + __in_opt PCSTR SymbolPath, + __in ULONG ImageBase + ); + +BOOL +IMAGEAPI +UnmapDebugInformation( + __out_xcount(unknown) PIMAGE_DEBUG_INFORMATION DebugInfo + ); + +#endif + +BOOL +IMAGEAPI +SearchTreeForFile( + __in PCSTR RootPath, + __in PCSTR InputPathName, + __out_ecount(MAX_PATH + 1) PSTR OutputPathBuffer + ); + +BOOL +IMAGEAPI +SearchTreeForFileW( + __in PCWSTR RootPath, + __in PCWSTR InputPathName, + __out_ecount(MAX_PATH + 1) PWSTR OutputPathBuffer + ); + +typedef BOOL +(CALLBACK *PENUMDIRTREE_CALLBACK)( + __in PCSTR FilePath, + __in_opt PVOID CallerData + ); + +BOOL +IMAGEAPI +EnumDirTree( + __in_opt HANDLE hProcess, + __in PCSTR RootPath, + __in PCSTR InputPathName, + __out_ecount_opt(MAX_PATH + 1) PSTR OutputPathBuffer, + __in_opt PENUMDIRTREE_CALLBACK cb, + __in_opt PVOID data + ); + +typedef BOOL +(CALLBACK *PENUMDIRTREE_CALLBACKW)( + __in PCWSTR FilePath, + __in_opt PVOID CallerData + ); + +BOOL +IMAGEAPI +EnumDirTreeW( + __in_opt HANDLE hProcess, + __in PCWSTR RootPath, + __in PCWSTR InputPathName, + __out_ecount_opt(MAX_PATH + 1) PWSTR OutputPathBuffer, + __in_opt PENUMDIRTREE_CALLBACKW cb, + __in_opt PVOID data + ); + +BOOL +IMAGEAPI +MakeSureDirectoryPathExists( + __in PCSTR DirPath + ); + +// +// UnDecorateSymbolName Flags +// + +#define UNDNAME_COMPLETE (0x0000) // Enable full undecoration +#define UNDNAME_NO_LEADING_UNDERSCORES (0x0001) // Remove leading underscores from MS extended keywords +#define UNDNAME_NO_MS_KEYWORDS (0x0002) // Disable expansion of MS extended keywords +#define UNDNAME_NO_FUNCTION_RETURNS (0x0004) // Disable expansion of return type for primary declaration +#define UNDNAME_NO_ALLOCATION_MODEL (0x0008) // Disable expansion of the declaration model +#define UNDNAME_NO_ALLOCATION_LANGUAGE (0x0010) // Disable expansion of the declaration language specifier +#define UNDNAME_NO_MS_THISTYPE (0x0020) // NYI Disable expansion of MS keywords on the 'this' type for primary declaration +#define UNDNAME_NO_CV_THISTYPE (0x0040) // NYI Disable expansion of CV modifiers on the 'this' type for primary declaration +#define UNDNAME_NO_THISTYPE (0x0060) // Disable all modifiers on the 'this' type +#define UNDNAME_NO_ACCESS_SPECIFIERS (0x0080) // Disable expansion of access specifiers for members +#define UNDNAME_NO_THROW_SIGNATURES (0x0100) // Disable expansion of 'throw-signatures' for functions and pointers to functions +#define UNDNAME_NO_MEMBER_TYPE (0x0200) // Disable expansion of 'static' or 'virtual'ness of members +#define UNDNAME_NO_RETURN_UDT_MODEL (0x0400) // Disable expansion of MS model for UDT returns +#define UNDNAME_32_BIT_DECODE (0x0800) // Undecorate 32-bit decorated names +#define UNDNAME_NAME_ONLY (0x1000) // Crack only the name for primary declaration; + // return just [scope::]name. Does expand template params +#define UNDNAME_NO_ARGUMENTS (0x2000) // Don't undecorate arguments to function +#define UNDNAME_NO_SPECIAL_SYMS (0x4000) // Don't undecorate special names (v-table, vcall, vector xxx, metatype, etc) + +DWORD +IMAGEAPI +WINAPI +UnDecorateSymbolName( + __in PCSTR name, + __out_ecount(maxStringLength) PSTR outputString, + __in DWORD maxStringLength, + __in DWORD flags + ); + +DWORD +IMAGEAPI +WINAPI +UnDecorateSymbolNameW( + __in PCWSTR name, + __out_ecount(maxStringLength) PWSTR outputString, + __in DWORD maxStringLength, + __in DWORD flags + ); + +// +// these values are used for synthesized file types +// that can be passed in as image headers instead of +// the standard ones from ntimage.h +// + +#define DBHHEADER_DEBUGDIRS 0x1 +#define DBHHEADER_CVMISC 0x2 + +typedef struct _MODLOAD_DATA { + DWORD ssize; // size of this struct + DWORD ssig; // signature identifying the passed data + PVOID data; // pointer to passed data + DWORD size; // size of passed data + DWORD flags; // options +} MODLOAD_DATA, *PMODLOAD_DATA; + +typedef struct _MODLOAD_CVMISC { + DWORD oCV; // ofset to the codeview record + size_t cCV; // size of the codeview record + DWORD oMisc; // offset to the misc record + size_t cMisc; // size of the misc record + DWORD dtImage; // datetime stamp of the image + DWORD cImage; // size of the image +} MODLOAD_CVMISC, *PMODLOAD_CVMISC; + +// +// StackWalking API +// + +typedef enum { + AddrMode1616, + AddrMode1632, + AddrModeReal, + AddrModeFlat +} ADDRESS_MODE; + +typedef struct _tagADDRESS64 { + DWORD64 Offset; + WORD Segment; + ADDRESS_MODE Mode; +} ADDRESS64, *LPADDRESS64; + +#if !defined(_IMAGEHLP_SOURCE_) && defined(_IMAGEHLP64) +#define ADDRESS ADDRESS64 +#define LPADDRESS LPADDRESS64 +#else +typedef struct _tagADDRESS { + DWORD Offset; + WORD Segment; + ADDRESS_MODE Mode; +} ADDRESS, *LPADDRESS; + +__inline +void +Address32To64( + __in LPADDRESS a32, + __out LPADDRESS64 a64 + ) +{ + a64->Offset = (ULONG64)(LONG64)(LONG)a32->Offset; + a64->Segment = a32->Segment; + a64->Mode = a32->Mode; +} + +__inline +void +Address64To32( + __in LPADDRESS64 a64, + __out LPADDRESS a32 + ) +{ + a32->Offset = (ULONG)a64->Offset; + a32->Segment = a64->Segment; + a32->Mode = a64->Mode; +} +#endif + +// +// This structure is included in the STACKFRAME structure, +// and is used to trace through usermode callbacks in a thread's +// kernel stack. The values must be copied by the kernel debugger +// from the DBGKD_GET_VERSION and WAIT_STATE_CHANGE packets. +// + +// +// New KDHELP structure for 64 bit system support. +// This structure is preferred in new code. +// +typedef struct _KDHELP64 { + + // + // address of kernel thread object, as provided in the + // WAIT_STATE_CHANGE packet. + // + DWORD64 Thread; + + // + // offset in thread object to pointer to the current callback frame + // in kernel stack. + // + DWORD ThCallbackStack; + + // + // offset in thread object to pointer to the current callback backing + // store frame in kernel stack. + // + DWORD ThCallbackBStore; + + // + // offsets to values in frame: + // + // address of next callback frame + DWORD NextCallback; + + // address of saved frame pointer (if applicable) + DWORD FramePointer; + + + // + // Address of the kernel function that calls out to user mode + // + DWORD64 KiCallUserMode; + + // + // Address of the user mode dispatcher function + // + DWORD64 KeUserCallbackDispatcher; + + // + // Lowest kernel mode address + // + DWORD64 SystemRangeStart; + + // + // Address of the user mode exception dispatcher function. + // Added in API version 10. + // + DWORD64 KiUserExceptionDispatcher; + + // + // Stack bounds, added in API version 11. + // + DWORD64 StackBase; + DWORD64 StackLimit; + + DWORD64 Reserved[5]; + +} KDHELP64, *PKDHELP64; + +#if !defined(_IMAGEHLP_SOURCE_) && defined(_IMAGEHLP64) +#define KDHELP KDHELP64 +#define PKDHELP PKDHELP64 +#else +typedef struct _KDHELP { + + // + // address of kernel thread object, as provided in the + // WAIT_STATE_CHANGE packet. + // + DWORD Thread; + + // + // offset in thread object to pointer to the current callback frame + // in kernel stack. + // + DWORD ThCallbackStack; + + // + // offsets to values in frame: + // + // address of next callback frame + DWORD NextCallback; + + // address of saved frame pointer (if applicable) + DWORD FramePointer; + + // + // Address of the kernel function that calls out to user mode + // + DWORD KiCallUserMode; + + // + // Address of the user mode dispatcher function + // + DWORD KeUserCallbackDispatcher; + + // + // Lowest kernel mode address + // + DWORD SystemRangeStart; + + // + // offset in thread object to pointer to the current callback backing + // store frame in kernel stack. + // + DWORD ThCallbackBStore; + + // + // Address of the user mode exception dispatcher function. + // Added in API version 10. + // + DWORD KiUserExceptionDispatcher; + + // + // Stack bounds, added in API version 11. + // + DWORD StackBase; + DWORD StackLimit; + + DWORD Reserved[5]; + +} KDHELP, *PKDHELP; + +__inline +void +KdHelp32To64( + __in PKDHELP p32, + __out PKDHELP64 p64 + ) +{ + p64->Thread = p32->Thread; + p64->ThCallbackStack = p32->ThCallbackStack; + p64->NextCallback = p32->NextCallback; + p64->FramePointer = p32->FramePointer; + p64->KiCallUserMode = p32->KiCallUserMode; + p64->KeUserCallbackDispatcher = p32->KeUserCallbackDispatcher; + p64->SystemRangeStart = p32->SystemRangeStart; + p64->KiUserExceptionDispatcher = p32->KiUserExceptionDispatcher; + p64->StackBase = p32->StackBase; + p64->StackLimit = p32->StackLimit; +} +#endif + +typedef struct _tagSTACKFRAME64 { + ADDRESS64 AddrPC; // program counter + ADDRESS64 AddrReturn; // return address + ADDRESS64 AddrFrame; // frame pointer + ADDRESS64 AddrStack; // stack pointer + ADDRESS64 AddrBStore; // backing store pointer + PVOID FuncTableEntry; // pointer to pdata/fpo or NULL + DWORD64 Params[4]; // possible arguments to the function + BOOL Far; // WOW far call + BOOL Virtual; // is this a virtual frame? + DWORD64 Reserved[3]; + KDHELP64 KdHelp; +} STACKFRAME64, *LPSTACKFRAME64; + +#if !defined(_IMAGEHLP_SOURCE_) && defined(_IMAGEHLP64) +#define STACKFRAME STACKFRAME64 +#define LPSTACKFRAME LPSTACKFRAME64 +#else +typedef struct _tagSTACKFRAME { + ADDRESS AddrPC; // program counter + ADDRESS AddrReturn; // return address + ADDRESS AddrFrame; // frame pointer + ADDRESS AddrStack; // stack pointer + PVOID FuncTableEntry; // pointer to pdata/fpo or NULL + DWORD Params[4]; // possible arguments to the function + BOOL Far; // WOW far call + BOOL Virtual; // is this a virtual frame? + DWORD Reserved[3]; + KDHELP KdHelp; + ADDRESS AddrBStore; // backing store pointer +} STACKFRAME, *LPSTACKFRAME; +#endif + + +typedef +BOOL +(__stdcall *PREAD_PROCESS_MEMORY_ROUTINE64)( + __in HANDLE hProcess, + __in DWORD64 qwBaseAddress, + __out_bcount(nSize) PVOID lpBuffer, + __in DWORD nSize, + __out LPDWORD lpNumberOfBytesRead + ); + +typedef +PVOID +(__stdcall *PFUNCTION_TABLE_ACCESS_ROUTINE64)( + __in HANDLE ahProcess, + __in DWORD64 AddrBase + ); + +typedef +DWORD64 +(__stdcall *PGET_MODULE_BASE_ROUTINE64)( + __in HANDLE hProcess, + __in DWORD64 Address + ); + +typedef +DWORD64 +(__stdcall *PTRANSLATE_ADDRESS_ROUTINE64)( + __in HANDLE hProcess, + __in HANDLE hThread, + __in LPADDRESS64 lpaddr + ); + +BOOL +IMAGEAPI +StackWalk64( + __in DWORD MachineType, + __in HANDLE hProcess, + __in HANDLE hThread, + __inout LPSTACKFRAME64 StackFrame, + __inout PVOID ContextRecord, + __in_opt PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, + __in_opt PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, + __in_opt PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, + __in_opt PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress + ); + +#if !defined(_IMAGEHLP_SOURCE_) && defined(_IMAGEHLP64) + +#define PREAD_PROCESS_MEMORY_ROUTINE PREAD_PROCESS_MEMORY_ROUTINE64 +#define PFUNCTION_TABLE_ACCESS_ROUTINE PFUNCTION_TABLE_ACCESS_ROUTINE64 +#define PGET_MODULE_BASE_ROUTINE PGET_MODULE_BASE_ROUTINE64 +#define PTRANSLATE_ADDRESS_ROUTINE PTRANSLATE_ADDRESS_ROUTINE64 + +#define StackWalk StackWalk64 + +#else + +typedef +BOOL +(__stdcall *PREAD_PROCESS_MEMORY_ROUTINE)( + __in HANDLE hProcess, + __in DWORD lpBaseAddress, + __out_bcount(nSize) PVOID lpBuffer, + __in DWORD nSize, + __out PDWORD lpNumberOfBytesRead + ); + +typedef +PVOID +(__stdcall *PFUNCTION_TABLE_ACCESS_ROUTINE)( + __in HANDLE hProcess, + __in DWORD AddrBase + ); + +typedef +DWORD +(__stdcall *PGET_MODULE_BASE_ROUTINE)( + __in HANDLE hProcess, + __in DWORD Address + ); + +typedef +DWORD +(__stdcall *PTRANSLATE_ADDRESS_ROUTINE)( + __in HANDLE hProcess, + __in HANDLE hThread, + __out LPADDRESS lpaddr + ); + +BOOL +IMAGEAPI +StackWalk( + DWORD MachineType, + __in HANDLE hProcess, + __in HANDLE hThread, + __inout LPSTACKFRAME StackFrame, + __inout PVOID ContextRecord, + __in_opt PREAD_PROCESS_MEMORY_ROUTINE ReadMemoryRoutine, + __in_opt PFUNCTION_TABLE_ACCESS_ROUTINE FunctionTableAccessRoutine, + __in_opt PGET_MODULE_BASE_ROUTINE GetModuleBaseRoutine, + __in_opt PTRANSLATE_ADDRESS_ROUTINE TranslateAddress + ); + +#endif + + +#define API_VERSION_NUMBER 11 + +typedef struct API_VERSION { + USHORT MajorVersion; + USHORT MinorVersion; + USHORT Revision; + USHORT Reserved; +} API_VERSION, *LPAPI_VERSION; + +LPAPI_VERSION +IMAGEAPI +ImagehlpApiVersion( + VOID + ); + +LPAPI_VERSION +IMAGEAPI +ImagehlpApiVersionEx( + __in LPAPI_VERSION AppVersion + ); + +DWORD +IMAGEAPI +GetTimestampForLoadedLibrary( + __in HMODULE Module + ); + +// +// typedefs for function pointers +// +typedef BOOL +(CALLBACK *PSYM_ENUMMODULES_CALLBACK64)( + __in PCSTR ModuleName, + __in DWORD64 BaseOfDll, + __in_opt PVOID UserContext + ); + +typedef BOOL +(CALLBACK *PSYM_ENUMMODULES_CALLBACKW64)( + __in PCWSTR ModuleName, + __in DWORD64 BaseOfDll, + __in_opt PVOID UserContext + ); + +typedef BOOL +(CALLBACK *PENUMLOADED_MODULES_CALLBACK64)( + __in PCSTR ModuleName, + __in DWORD64 ModuleBase, + __in ULONG ModuleSize, + __in_opt PVOID UserContext + ); + +typedef BOOL +(CALLBACK *PENUMLOADED_MODULES_CALLBACKW64)( + __in PCWSTR ModuleName, + __in DWORD64 ModuleBase, + __in ULONG ModuleSize, + __in_opt PVOID UserContext + ); + +typedef BOOL +(CALLBACK *PSYM_ENUMSYMBOLS_CALLBACK64)( + __in PCSTR SymbolName, + __in DWORD64 SymbolAddress, + __in ULONG SymbolSize, + __in_opt PVOID UserContext + ); + +typedef BOOL +(CALLBACK *PSYM_ENUMSYMBOLS_CALLBACK64W)( + __in PCWSTR SymbolName, + __in DWORD64 SymbolAddress, + __in ULONG SymbolSize, + __in_opt PVOID UserContext + ); + +typedef BOOL +(CALLBACK *PSYMBOL_REGISTERED_CALLBACK64)( + __in HANDLE hProcess, + __in ULONG ActionCode, + __in_opt ULONG64 CallbackData, + __in_opt ULONG64 UserContext + ); + +typedef +PVOID +(CALLBACK *PSYMBOL_FUNCENTRY_CALLBACK)( + __in HANDLE hProcess, + __in DWORD AddrBase, + __in_opt PVOID UserContext + ); + +typedef +PVOID +(CALLBACK *PSYMBOL_FUNCENTRY_CALLBACK64)( + __in HANDLE hProcess, + __in ULONG64 AddrBase, + __in ULONG64 UserContext + ); + +#if !defined(_IMAGEHLP_SOURCE_) && defined(_IMAGEHLP64) + +#define PSYM_ENUMMODULES_CALLBACK PSYM_ENUMMODULES_CALLBACK64 +#define PSYM_ENUMSYMBOLS_CALLBACK PSYM_ENUMSYMBOLS_CALLBACK64 +#define PSYM_ENUMSYMBOLS_CALLBACKW PSYM_ENUMSYMBOLS_CALLBACK64W +#define PENUMLOADED_MODULES_CALLBACK PENUMLOADED_MODULES_CALLBACK64 +#define PSYMBOL_REGISTERED_CALLBACK PSYMBOL_REGISTERED_CALLBACK64 +#define PSYMBOL_FUNCENTRY_CALLBACK PSYMBOL_FUNCENTRY_CALLBACK64 + +#else + +typedef BOOL +(CALLBACK *PSYM_ENUMMODULES_CALLBACK)( + __in PCSTR ModuleName, + __in ULONG BaseOfDll, + __in_opt PVOID UserContext + ); + +typedef BOOL +(CALLBACK *PSYM_ENUMSYMBOLS_CALLBACK)( + __in PCSTR SymbolName, + __in ULONG SymbolAddress, + __in ULONG SymbolSize, + __in_opt PVOID UserContext + ); + +typedef BOOL +(CALLBACK *PSYM_ENUMSYMBOLS_CALLBACKW)( + __in PCWSTR SymbolName, + __in ULONG SymbolAddress, + __in ULONG SymbolSize, + __in_opt PVOID UserContext + ); + +typedef BOOL +(CALLBACK *PENUMLOADED_MODULES_CALLBACK)( + __in PCSTR ModuleName, + __in ULONG ModuleBase, + __in ULONG ModuleSize, + __in_opt PVOID UserContext + ); + +typedef BOOL +(CALLBACK *PSYMBOL_REGISTERED_CALLBACK)( + __in HANDLE hProcess, + __in ULONG ActionCode, + __in_opt PVOID CallbackData, + __in_opt PVOID UserContext + ); + +#endif + + +// values found in SYMBOL_INFO.Tag +// +// This was taken from cvconst.h and should +// not override any values found there. +// +// #define _NO_CVCONST_H_ if you don't +// have access to that file... + +#ifdef _NO_CVCONST_H + +// DIA enums + +enum SymTagEnum +{ + SymTagNull, + SymTagExe, + SymTagCompiland, + SymTagCompilandDetails, + SymTagCompilandEnv, + SymTagFunction, + SymTagBlock, + SymTagData, + SymTagAnnotation, + SymTagLabel, + SymTagPublicSymbol, + SymTagUDT, + SymTagEnum, + SymTagFunctionType, + SymTagPointerType, + SymTagArrayType, + SymTagBaseType, + SymTagTypedef, + SymTagBaseClass, + SymTagFriend, + SymTagFunctionArgType, + SymTagFuncDebugStart, + SymTagFuncDebugEnd, + SymTagUsingNamespace, + SymTagVTableShape, + SymTagVTable, + SymTagCustom, + SymTagThunk, + SymTagCustomType, + SymTagManagedType, + SymTagDimension, + SymTagMax +}; + +#endif + +// +// flags found in SYMBOL_INFO.Flags +// + +#define SYMFLAG_VALUEPRESENT 0x00000001 +#define SYMFLAG_REGISTER 0x00000008 +#define SYMFLAG_REGREL 0x00000010 +#define SYMFLAG_FRAMEREL 0x00000020 +#define SYMFLAG_PARAMETER 0x00000040 +#define SYMFLAG_LOCAL 0x00000080 +#define SYMFLAG_CONSTANT 0x00000100 +#define SYMFLAG_EXPORT 0x00000200 +#define SYMFLAG_FORWARDER 0x00000400 +#define SYMFLAG_FUNCTION 0x00000800 +#define SYMFLAG_VIRTUAL 0x00001000 +#define SYMFLAG_THUNK 0x00002000 +#define SYMFLAG_TLSREL 0x00004000 +#define SYMFLAG_SLOT 0x00008000 +#define SYMFLAG_ILREL 0x00010000 +#define SYMFLAG_METADATA 0x00020000 +#define SYMFLAG_CLR_TOKEN 0x00040000 + +// this resets SymNext/Prev to the beginning +// of the module passed in the address field + +#define SYMFLAG_RESET 0x80000000 + +// +// symbol type enumeration +// +typedef enum { + SymNone = 0, + SymCoff, + SymCv, + SymPdb, + SymExport, + SymDeferred, + SymSym, // .sym file + SymDia, + SymVirtual, + NumSymTypes +} SYM_TYPE; + +// +// symbol data structure +// + +typedef struct _IMAGEHLP_SYMBOL64 { + DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_SYMBOL64) + DWORD64 Address; // virtual address including dll base address + DWORD Size; // estimated size of symbol, can be zero + DWORD Flags; // info about the symbols, see the SYMF defines + DWORD MaxNameLength; // maximum size of symbol name in 'Name' + CHAR Name[1]; // symbol name (null terminated string) +} IMAGEHLP_SYMBOL64, *PIMAGEHLP_SYMBOL64; + +typedef struct _IMAGEHLP_SYMBOL64_PACKAGE { + IMAGEHLP_SYMBOL64 sym; + CHAR name[MAX_SYM_NAME + 1]; +} IMAGEHLP_SYMBOL64_PACKAGE, *PIMAGEHLP_SYMBOL64_PACKAGE; + +typedef struct _IMAGEHLP_SYMBOLW64 { + DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_SYMBOLW64) + DWORD64 Address; // virtual address including dll base address + DWORD Size; // estimated size of symbol, can be zero + DWORD Flags; // info about the symbols, see the SYMF defines + DWORD MaxNameLength; // maximum size of symbol name in 'Name' + WCHAR Name[1]; // symbol name (null terminated string) +} IMAGEHLP_SYMBOLW64, *PIMAGEHLP_SYMBOLW64; + +typedef struct _IMAGEHLP_SYMBOLW64_PACKAGE { + IMAGEHLP_SYMBOLW64 sym; + WCHAR name[MAX_SYM_NAME + 1]; +} IMAGEHLP_SYMBOLW64_PACKAGE, *PIMAGEHLP_SYMBOLW64_PACKAGE; + +#if !defined(_IMAGEHLP_SOURCE_) && defined(_IMAGEHLP64) + + #define IMAGEHLP_SYMBOL IMAGEHLP_SYMBOL64 + #define PIMAGEHLP_SYMBOL PIMAGEHLP_SYMBOL64 + #define IMAGEHLP_SYMBOL_PACKAGE IMAGEHLP_SYMBOL64_PACKAGE + #define PIMAGEHLP_SYMBOL_PACKAGE PIMAGEHLP_SYMBOL64_PACKAGE + #define IMAGEHLP_SYMBOLW IMAGEHLP_SYMBOLW64 + #define PIMAGEHLP_SYMBOLW PIMAGEHLP_SYMBOLW64 + #define IMAGEHLP_SYMBOLW_PACKAGE IMAGEHLP_SYMBOLW64_PACKAGE + #define PIMAGEHLP_SYMBOLW_PACKAGE PIMAGEHLP_SYMBOLW64_PACKAGE + +#else + + typedef struct _IMAGEHLP_SYMBOL { + DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_SYMBOL) + DWORD Address; // virtual address including dll base address + DWORD Size; // estimated size of symbol, can be zero + DWORD Flags; // info about the symbols, see the SYMF defines + DWORD MaxNameLength; // maximum size of symbol name in 'Name' + CHAR Name[1]; // symbol name (null terminated string) + } IMAGEHLP_SYMBOL, *PIMAGEHLP_SYMBOL; + + typedef struct _IMAGEHLP_SYMBOL_PACKAGE { + IMAGEHLP_SYMBOL sym; + CHAR name[MAX_SYM_NAME + 1]; + } IMAGEHLP_SYMBOL_PACKAGE, *PIMAGEHLP_SYMBOL_PACKAGE; + + typedef struct _IMAGEHLP_SYMBOLW { + DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_SYMBOLW) + DWORD Address; // virtual address including dll base address + DWORD Size; // estimated size of symbol, can be zero + DWORD Flags; // info about the symbols, see the SYMF defines + DWORD MaxNameLength; // maximum size of symbol name in 'Name' + WCHAR Name[1]; // symbol name (null terminated string) + } IMAGEHLP_SYMBOLW, *PIMAGEHLP_SYMBOLW; + + typedef struct _IMAGEHLP_SYMBOLW_PACKAGE { + IMAGEHLP_SYMBOLW sym; + WCHAR name[MAX_SYM_NAME + 1]; + } IMAGEHLP_SYMBOLW_PACKAGE, *PIMAGEHLP_SYMBOLW_PACKAGE; + +#endif + +// +// module data structure +// + +typedef struct _IMAGEHLP_MODULE64 { + DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_MODULE64) + DWORD64 BaseOfImage; // base load address of module + DWORD ImageSize; // virtual size of the loaded module + DWORD TimeDateStamp; // date/time stamp from pe header + DWORD CheckSum; // checksum from the pe header + DWORD NumSyms; // number of symbols in the symbol table + SYM_TYPE SymType; // type of symbols loaded + CHAR ModuleName[32]; // module name + CHAR ImageName[256]; // image name + CHAR LoadedImageName[256]; // symbol file name + // new elements: 07-Jun-2002 + CHAR LoadedPdbName[256]; // pdb file name + DWORD CVSig; // Signature of the CV record in the debug directories + CHAR CVData[MAX_PATH * 3]; // Contents of the CV record + DWORD PdbSig; // Signature of PDB + GUID PdbSig70; // Signature of PDB (VC 7 and up) + DWORD PdbAge; // DBI age of pdb + BOOL PdbUnmatched; // loaded an unmatched pdb + BOOL DbgUnmatched; // loaded an unmatched dbg + BOOL LineNumbers; // we have line number information + BOOL GlobalSymbols; // we have internal symbol information + BOOL TypeInfo; // we have type information + // new elements: 17-Dec-2003 + BOOL SourceIndexed; // pdb supports source server + BOOL Publics; // contains public symbols +} IMAGEHLP_MODULE64, *PIMAGEHLP_MODULE64; + +typedef struct _IMAGEHLP_MODULEW64 { + DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_MODULE64) + DWORD64 BaseOfImage; // base load address of module + DWORD ImageSize; // virtual size of the loaded module + DWORD TimeDateStamp; // date/time stamp from pe header + DWORD CheckSum; // checksum from the pe header + DWORD NumSyms; // number of symbols in the symbol table + SYM_TYPE SymType; // type of symbols loaded + WCHAR ModuleName[32]; // module name + WCHAR ImageName[256]; // image name + // new elements: 07-Jun-2002 + WCHAR LoadedImageName[256]; // symbol file name + WCHAR LoadedPdbName[256]; // pdb file name + DWORD CVSig; // Signature of the CV record in the debug directories + WCHAR CVData[MAX_PATH * 3]; // Contents of the CV record + DWORD PdbSig; // Signature of PDB + GUID PdbSig70; // Signature of PDB (VC 7 and up) + DWORD PdbAge; // DBI age of pdb + BOOL PdbUnmatched; // loaded an unmatched pdb + BOOL DbgUnmatched; // loaded an unmatched dbg + BOOL LineNumbers; // we have line number information + BOOL GlobalSymbols; // we have internal symbol information + BOOL TypeInfo; // we have type information + // new elements: 17-Dec-2003 + BOOL SourceIndexed; // pdb supports source server + BOOL Publics; // contains public symbols +} IMAGEHLP_MODULEW64, *PIMAGEHLP_MODULEW64; + +#if !defined(_IMAGEHLP_SOURCE_) && defined(_IMAGEHLP64) +#define IMAGEHLP_MODULE IMAGEHLP_MODULE64 +#define PIMAGEHLP_MODULE PIMAGEHLP_MODULE64 +#define IMAGEHLP_MODULEW IMAGEHLP_MODULEW64 +#define PIMAGEHLP_MODULEW PIMAGEHLP_MODULEW64 +#else +typedef struct _IMAGEHLP_MODULE { + DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_MODULE) + DWORD BaseOfImage; // base load address of module + DWORD ImageSize; // virtual size of the loaded module + DWORD TimeDateStamp; // date/time stamp from pe header + DWORD CheckSum; // checksum from the pe header + DWORD NumSyms; // number of symbols in the symbol table + SYM_TYPE SymType; // type of symbols loaded + CHAR ModuleName[32]; // module name + CHAR ImageName[256]; // image name + CHAR LoadedImageName[256]; // symbol file name +} IMAGEHLP_MODULE, *PIMAGEHLP_MODULE; + +typedef struct _IMAGEHLP_MODULEW { + DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_MODULE) + DWORD BaseOfImage; // base load address of module + DWORD ImageSize; // virtual size of the loaded module + DWORD TimeDateStamp; // date/time stamp from pe header + DWORD CheckSum; // checksum from the pe header + DWORD NumSyms; // number of symbols in the symbol table + SYM_TYPE SymType; // type of symbols loaded + WCHAR ModuleName[32]; // module name + WCHAR ImageName[256]; // image name + WCHAR LoadedImageName[256]; // symbol file name +} IMAGEHLP_MODULEW, *PIMAGEHLP_MODULEW; +#endif + +// +// source file line data structure +// + +typedef struct _IMAGEHLP_LINE64 { + DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_LINE64) + PVOID Key; // internal + DWORD LineNumber; // line number in file + PCHAR FileName; // full filename + DWORD64 Address; // first instruction of line +} IMAGEHLP_LINE64, *PIMAGEHLP_LINE64; + +typedef struct _IMAGEHLP_LINEW64 { + DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_LINE64) + PVOID Key; // internal + DWORD LineNumber; // line number in file + PWSTR FileName; // full filename + DWORD64 Address; // first instruction of line +} IMAGEHLP_LINEW64, *PIMAGEHLP_LINEW64; + +#if !defined(_IMAGEHLP_SOURCE_) && defined(_IMAGEHLP64) +#define IMAGEHLP_LINE IMAGEHLP_LINE64 +#define PIMAGEHLP_LINE PIMAGEHLP_LINE64 +#else +typedef struct _IMAGEHLP_LINE { + DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_LINE) + PVOID Key; // internal + DWORD LineNumber; // line number in file + PCHAR FileName; // full filename + DWORD Address; // first instruction of line +} IMAGEHLP_LINE, *PIMAGEHLP_LINE; + +typedef struct _IMAGEHLP_LINEW { + DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_LINE64) + PVOID Key; // internal + DWORD LineNumber; // line number in file + PCHAR FileName; // full filename + DWORD64 Address; // first instruction of line +} IMAGEHLP_LINEW, *PIMAGEHLP_LINEW; +#endif + +// +// source file structure +// + +typedef struct _SOURCEFILE { + DWORD64 ModBase; // base address of loaded module + PCHAR FileName; // full filename of source +} SOURCEFILE, *PSOURCEFILE; + +typedef struct _SOURCEFILEW { + DWORD64 ModBase; // base address of loaded module + PWSTR FileName; // full filename of source +} SOURCEFILEW, *PSOURCEFILEW; + +// +// data structures used for registered symbol callbacks +// + +#define CBA_DEFERRED_SYMBOL_LOAD_START 0x00000001 +#define CBA_DEFERRED_SYMBOL_LOAD_COMPLETE 0x00000002 +#define CBA_DEFERRED_SYMBOL_LOAD_FAILURE 0x00000003 +#define CBA_SYMBOLS_UNLOADED 0x00000004 +#define CBA_DUPLICATE_SYMBOL 0x00000005 +#define CBA_READ_MEMORY 0x00000006 +#define CBA_DEFERRED_SYMBOL_LOAD_CANCEL 0x00000007 +#define CBA_SET_OPTIONS 0x00000008 +#define CBA_EVENT 0x00000010 +#define CBA_DEFERRED_SYMBOL_LOAD_PARTIAL 0x00000020 +#define CBA_DEBUG_INFO 0x10000000 +#define CBA_SRCSRV_INFO 0x20000000 +#define CBA_SRCSRV_EVENT 0x40000000 + +typedef struct _IMAGEHLP_CBA_READ_MEMORY { + DWORD64 addr; // address to read from + PVOID buf; // buffer to read to + DWORD bytes; // amount of bytes to read + DWORD *bytesread; // pointer to store amount of bytes read +} IMAGEHLP_CBA_READ_MEMORY, *PIMAGEHLP_CBA_READ_MEMORY; + +enum { + sevInfo = 0, + sevProblem, + sevAttn, + sevFatal, + sevMax // unused +}; + +#define EVENT_SRCSPEW_START 100 +#define EVENT_SRCSPEW 100 +#define EVENT_SRCSPEW_END 199 + +typedef struct _IMAGEHLP_CBA_EVENT { + DWORD severity; // values from sevInfo to sevFatal + DWORD code; // numerical code IDs the error + PCHAR desc; // may contain a text description of the error + PVOID object; // value dependant upon the error code +} IMAGEHLP_CBA_EVENT, *PIMAGEHLP_CBA_EVENT; + +typedef struct _IMAGEHLP_CBA_EVENTW { + DWORD severity; // values from sevInfo to sevFatal + DWORD code; // numerical code IDs the error + PCWSTR desc; // may contain a text description of the error + PVOID object; // value dependant upon the error code +} IMAGEHLP_CBA_EVENTW, *PIMAGEHLP_CBA_EVENTW; + +typedef struct _IMAGEHLP_DEFERRED_SYMBOL_LOAD64 { + DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_DEFERRED_SYMBOL_LOAD64) + DWORD64 BaseOfImage; // base load address of module + DWORD CheckSum; // checksum from the pe header + DWORD TimeDateStamp; // date/time stamp from pe header + CHAR FileName[MAX_PATH]; // symbols file or image name + BOOLEAN Reparse; // load failure reparse + HANDLE hFile; // file handle, if passed + DWORD Flags; // +} IMAGEHLP_DEFERRED_SYMBOL_LOAD64, *PIMAGEHLP_DEFERRED_SYMBOL_LOAD64; + +typedef struct _IMAGEHLP_DEFERRED_SYMBOL_LOADW64 { + DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_DEFERRED_SYMBOL_LOADW64) + DWORD64 BaseOfImage; // base load address of module + DWORD CheckSum; // checksum from the pe header + DWORD TimeDateStamp; // date/time stamp from pe header + WCHAR FileName[MAX_PATH + 1]; // symbols file or image name + BOOLEAN Reparse; // load failure reparse + HANDLE hFile; // file handle, if passed + DWORD Flags; // +} IMAGEHLP_DEFERRED_SYMBOL_LOADW64, *PIMAGEHLP_DEFERRED_SYMBOL_LOADW64; + +#define DSLFLAG_MISMATCHED_PDB 0x1 +#define DSLFLAG_MISMATCHED_DBG 0x2 + +#if !defined(_IMAGEHLP_SOURCE_) && defined(_IMAGEHLP64) +#define IMAGEHLP_DEFERRED_SYMBOL_LOAD IMAGEHLP_DEFERRED_SYMBOL_LOAD64 +#define PIMAGEHLP_DEFERRED_SYMBOL_LOAD PIMAGEHLP_DEFERRED_SYMBOL_LOAD64 +#else +typedef struct _IMAGEHLP_DEFERRED_SYMBOL_LOAD { + DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_DEFERRED_SYMBOL_LOAD) + DWORD BaseOfImage; // base load address of module + DWORD CheckSum; // checksum from the pe header + DWORD TimeDateStamp; // date/time stamp from pe header + CHAR FileName[MAX_PATH]; // symbols file or image name + BOOLEAN Reparse; // load failure reparse + HANDLE hFile; // file handle, if passed +} IMAGEHLP_DEFERRED_SYMBOL_LOAD, *PIMAGEHLP_DEFERRED_SYMBOL_LOAD; +#endif + +typedef struct _IMAGEHLP_DUPLICATE_SYMBOL64 { + DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_DUPLICATE_SYMBOL64) + DWORD NumberOfDups; // number of duplicates in the Symbol array + PIMAGEHLP_SYMBOL64 Symbol; // array of duplicate symbols + DWORD SelectedSymbol; // symbol selected (-1 to start) +} IMAGEHLP_DUPLICATE_SYMBOL64, *PIMAGEHLP_DUPLICATE_SYMBOL64; + +#if !defined(_IMAGEHLP_SOURCE_) && defined(_IMAGEHLP64) +#define IMAGEHLP_DUPLICATE_SYMBOL IMAGEHLP_DUPLICATE_SYMBOL64 +#define PIMAGEHLP_DUPLICATE_SYMBOL PIMAGEHLP_DUPLICATE_SYMBOL64 +#else +typedef struct _IMAGEHLP_DUPLICATE_SYMBOL { + DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_DUPLICATE_SYMBOL) + DWORD NumberOfDups; // number of duplicates in the Symbol array + PIMAGEHLP_SYMBOL Symbol; // array of duplicate symbols + DWORD SelectedSymbol; // symbol selected (-1 to start) +} IMAGEHLP_DUPLICATE_SYMBOL, *PIMAGEHLP_DUPLICATE_SYMBOL; +#endif + +// If dbghelp ever needs to display graphical UI, it will use this as the parent window. + +BOOL +IMAGEAPI +SymSetParentWindow( + __in HWND hwnd + ); + +PCHAR +IMAGEAPI +SymSetHomeDirectory( + __in_opt HANDLE hProcess, + __in_opt PCSTR dir + ); + +PWSTR +IMAGEAPI +SymSetHomeDirectoryW( + __in_opt HANDLE hProcess, + __in_opt PCWSTR dir + ); + +PCHAR +IMAGEAPI +SymGetHomeDirectory( + __in DWORD type, + __out_ecount(size) PSTR dir, + __in size_t size + ); + +PWSTR +IMAGEAPI +SymGetHomeDirectoryW( + __in DWORD type, + __out_ecount(size) PWSTR dir, + __in size_t size + ); + +typedef enum { + hdBase = 0, // root directory for dbghelp + hdSym, // where symbols are stored + hdSrc, // where source is stored + hdMax // end marker +}; + +typedef struct _OMAP { + ULONG rva; + ULONG rvaTo; +} OMAP, *POMAP; + +BOOL +IMAGEAPI +SymGetOmaps( + __in HANDLE hProcess, + __in DWORD64 BaseOfDll, + __out POMAP *OmapTo, + __out PDWORD64 cOmapTo, + __out POMAP *OmapFrom, + __out PDWORD64 cOmapFrom + ); + +// +// options that are set/returned by SymSetOptions() & SymGetOptions() +// these are used as a mask +// +#define SYMOPT_CASE_INSENSITIVE 0x00000001 +#define SYMOPT_UNDNAME 0x00000002 +#define SYMOPT_DEFERRED_LOADS 0x00000004 +#define SYMOPT_NO_CPP 0x00000008 +#define SYMOPT_LOAD_LINES 0x00000010 +#define SYMOPT_OMAP_FIND_NEAREST 0x00000020 +#define SYMOPT_LOAD_ANYTHING 0x00000040 +#define SYMOPT_IGNORE_CVREC 0x00000080 +#define SYMOPT_NO_UNQUALIFIED_LOADS 0x00000100 +#define SYMOPT_FAIL_CRITICAL_ERRORS 0x00000200 +#define SYMOPT_EXACT_SYMBOLS 0x00000400 +#define SYMOPT_ALLOW_ABSOLUTE_SYMBOLS 0x00000800 +#define SYMOPT_IGNORE_NT_SYMPATH 0x00001000 +#define SYMOPT_INCLUDE_32BIT_MODULES 0x00002000 +#define SYMOPT_PUBLICS_ONLY 0x00004000 +#define SYMOPT_NO_PUBLICS 0x00008000 +#define SYMOPT_AUTO_PUBLICS 0x00010000 +#define SYMOPT_NO_IMAGE_SEARCH 0x00020000 +#define SYMOPT_SECURE 0x00040000 +#define SYMOPT_NO_PROMPTS 0x00080000 +#define SYMOPT_OVERWRITE 0x00100000 +#define SYMOPT_IGNORE_IMAGEDIR 0x00200000 +#define SYMOPT_FLAT_DIRECTORY 0x00400000 +#define SYMOPT_FAVOR_COMPRESSED 0x00800000 +#define SYMOPT_ALLOW_ZERO_ADDRESS 0x01000000 +#define SYMOPT_DISABLE_SYMSRV_AUTODETECT 0x02000000 + +#define SYMOPT_DEBUG 0x80000000 + +DWORD +IMAGEAPI +SymSetOptions( + __in DWORD SymOptions + ); + +DWORD +IMAGEAPI +SymGetOptions( + VOID + ); + +BOOL +IMAGEAPI +SymCleanup( + __in HANDLE hProcess + ); + +BOOL +IMAGEAPI +SymMatchString( + __in PCSTR string, + __in PCSTR expression, + __in BOOL fCase + ); + +BOOL +IMAGEAPI +SymMatchStringA( + __in PCSTR string, + __in PCSTR expression, + __in BOOL fCase + ); + +BOOL +IMAGEAPI +SymMatchStringW( + __in PCWSTR string, + __in PCWSTR expression, + __in BOOL fCase + ); + +typedef BOOL +(CALLBACK *PSYM_ENUMSOURCEFILES_CALLBACK)( + __in PSOURCEFILE pSourceFile, + __in_opt PVOID UserContext + ); + +// for backwards compatibility - don't use this +#define PSYM_ENUMSOURCFILES_CALLBACK PSYM_ENUMSOURCEFILES_CALLBACK + +BOOL +IMAGEAPI +SymEnumSourceFiles( + __in HANDLE hProcess, + __in ULONG64 ModBase, + __in_opt PCSTR Mask, + __in PSYM_ENUMSOURCEFILES_CALLBACK cbSrcFiles, + __in_opt PVOID UserContext + ); + +typedef BOOL +(CALLBACK *PSYM_ENUMSOURCEFILES_CALLBACKW)( + __in PSOURCEFILEW pSourceFile, + __in_opt PVOID UserContext + ); + +BOOL +IMAGEAPI +SymEnumSourceFilesW( + __in HANDLE hProcess, + __in ULONG64 ModBase, + __in_opt PCWSTR Mask, + __in PSYM_ENUMSOURCEFILES_CALLBACKW cbSrcFiles, + __in_opt PVOID UserContext + ); + +BOOL +IMAGEAPI +SymEnumerateModules64( + __in HANDLE hProcess, + __in PSYM_ENUMMODULES_CALLBACK64 EnumModulesCallback, + __in_opt PVOID UserContext + ); + +BOOL +IMAGEAPI +SymEnumerateModulesW64( + __in HANDLE hProcess, + __in PSYM_ENUMMODULES_CALLBACKW64 EnumModulesCallback, + __in_opt PVOID UserContext + ); + +#if !defined(_IMAGEHLP_SOURCE_) && defined(_IMAGEHLP64) +#define SymEnumerateModules SymEnumerateModules64 +#else +BOOL +IMAGEAPI +SymEnumerateModules( + __in HANDLE hProcess, + __in PSYM_ENUMMODULES_CALLBACK EnumModulesCallback, + __in_opt PVOID UserContext + ); +#endif + +BOOL +IMAGEAPI +EnumerateLoadedModulesEx( + __in HANDLE hProcess, + __in PENUMLOADED_MODULES_CALLBACK64 EnumLoadedModulesCallback, + __in_opt PVOID UserContext + ); + +BOOL +IMAGEAPI +EnumerateLoadedModulesExW( + __in HANDLE hProcess, + __in PENUMLOADED_MODULES_CALLBACKW64 EnumLoadedModulesCallback, + __in_opt PVOID UserContext + ); + +BOOL +IMAGEAPI +EnumerateLoadedModules64( + __in HANDLE hProcess, + __in PENUMLOADED_MODULES_CALLBACK64 EnumLoadedModulesCallback, + __in_opt PVOID UserContext + ); + +BOOL +IMAGEAPI +EnumerateLoadedModulesW64( + __in HANDLE hProcess, + __in PENUMLOADED_MODULES_CALLBACKW64 EnumLoadedModulesCallback, + __in_opt PVOID UserContext + ); + +#if !defined(_IMAGEHLP_SOURCE_) && defined(_IMAGEHLP64) +#define EnumerateLoadedModules EnumerateLoadedModules64 +#else +BOOL +IMAGEAPI +EnumerateLoadedModules( + __in HANDLE hProcess, + __in PENUMLOADED_MODULES_CALLBACK EnumLoadedModulesCallback, + __in_opt PVOID UserContext + ); +#endif + +PVOID +IMAGEAPI +SymFunctionTableAccess64( + __in HANDLE hProcess, + __in DWORD64 AddrBase + ); + +#if !defined(_IMAGEHLP_SOURCE_) && defined(_IMAGEHLP64) +#define SymFunctionTableAccess SymFunctionTableAccess64 +#else +PVOID +IMAGEAPI +SymFunctionTableAccess( + __in HANDLE hProcess, + __in DWORD AddrBase + ); +#endif + +BOOL +IMAGEAPI +SymGetUnwindInfo( + __in HANDLE hProcess, + __in DWORD64 Address, + __out_bcount_opt(*Size) PVOID Buffer, + __inout PULONG Size + ); + +BOOL +IMAGEAPI +SymGetModuleInfo64( + __in HANDLE hProcess, + __in DWORD64 qwAddr, + __out PIMAGEHLP_MODULE64 ModuleInfo + ); + +BOOL +IMAGEAPI +SymGetModuleInfoW64( + __in HANDLE hProcess, + __in DWORD64 qwAddr, + __out PIMAGEHLP_MODULEW64 ModuleInfo + ); + +#if !defined(_IMAGEHLP_SOURCE_) && defined(_IMAGEHLP64) +#define SymGetModuleInfo SymGetModuleInfo64 +#define SymGetModuleInfoW SymGetModuleInfoW64 +#else +BOOL +IMAGEAPI +SymGetModuleInfo( + __in HANDLE hProcess, + __in DWORD dwAddr, + __out PIMAGEHLP_MODULE ModuleInfo + ); + +BOOL +IMAGEAPI +SymGetModuleInfoW( + __in HANDLE hProcess, + __in DWORD dwAddr, + __out PIMAGEHLP_MODULEW ModuleInfo + ); +#endif + +DWORD64 +IMAGEAPI +SymGetModuleBase64( + __in HANDLE hProcess, + __in DWORD64 qwAddr + ); + +#if !defined(_IMAGEHLP_SOURCE_) && defined(_IMAGEHLP64) +#define SymGetModuleBase SymGetModuleBase64 +#else +DWORD +IMAGEAPI +SymGetModuleBase( + __in HANDLE hProcess, + __in DWORD dwAddr + ); +#endif + +typedef struct _SRCCODEINFO { + DWORD SizeOfStruct; // set to sizeof(SRCCODEINFO) + PVOID Key; // not used + DWORD64 ModBase; // base address of module this applies to + CHAR Obj[MAX_PATH + 1]; // the object file within the module + CHAR FileName[MAX_PATH + 1]; // full filename + DWORD LineNumber; // line number in file + DWORD64 Address; // first instruction of line +} SRCCODEINFO, *PSRCCODEINFO; + +typedef struct _SRCCODEINFOW { + DWORD SizeOfStruct; // set to sizeof(SRCCODEINFO) + PVOID Key; // not used + DWORD64 ModBase; // base address of module this applies to + WCHAR Obj[MAX_PATH + 1]; // the object file within the module + WCHAR FileName[MAX_PATH + 1]; // full filename + DWORD LineNumber; // line number in file + DWORD64 Address; // first instruction of line +} SRCCODEINFOW, *PSRCCODEINFOW; + +typedef BOOL +(CALLBACK *PSYM_ENUMLINES_CALLBACK)( + __in PSRCCODEINFO LineInfo, + __in_opt PVOID UserContext + ); + +BOOL +IMAGEAPI +SymEnumLines( + __in HANDLE hProcess, + __in ULONG64 Base, + __in_opt PCSTR Obj, + __in_opt PCSTR File, + __in PSYM_ENUMLINES_CALLBACK EnumLinesCallback, + __in_opt PVOID UserContext + ); + +typedef BOOL +(CALLBACK *PSYM_ENUMLINES_CALLBACKW)( + __in PSRCCODEINFOW LineInfo, + __in_opt PVOID UserContext + ); + +BOOL +IMAGEAPI +SymEnumLinesW( + __in HANDLE hProcess, + __in ULONG64 Base, + __in_opt PCWSTR Obj, + __in_opt PCWSTR File, + __in PSYM_ENUMLINES_CALLBACKW EnumLinesCallback, + __in_opt PVOID UserContext + ); + +BOOL +IMAGEAPI +SymGetLineFromAddr64( + __in HANDLE hProcess, + __in DWORD64 qwAddr, + __out PDWORD pdwDisplacement, + __out PIMAGEHLP_LINE64 Line64 + ); + +BOOL +IMAGEAPI +SymGetLineFromAddrW64( + __in HANDLE hProcess, + __in DWORD64 dwAddr, + __out PDWORD pdwDisplacement, + __out PIMAGEHLP_LINEW64 Line + ); + +BOOL +IMAGEAPI +SymEnumSourceLines( + __in HANDLE hProcess, + __in ULONG64 Base, + __in_opt PCSTR Obj, + __in_opt PCSTR File, + __in_opt DWORD Line, + __in DWORD Flags, + __in PSYM_ENUMLINES_CALLBACK EnumLinesCallback, + __in_opt PVOID UserContext + ); + +BOOL +IMAGEAPI +SymEnumSourceLinesW( + __in HANDLE hProcess, + __in ULONG64 Base, + __in_opt PCWSTR Obj, + __in_opt PCWSTR File, + __in_opt DWORD Line, + __in DWORD Flags, + __in PSYM_ENUMLINES_CALLBACKW EnumLinesCallback, + __in_opt PVOID UserContext + ); + +// flags for SymEnumSourceLines + +#define ESLFLAG_FULLPATH 0x1 +#define ESLFLAG_NEAREST 0x2 +#define ESLFLAG_PREV 0x4 +#define ESLFLAG_NEXT 0x8 + +#if !defined(_IMAGEHLP_SOURCE_) && defined(_IMAGEHLP64) +#define SymGetLineFromAddr SymGetLineFromAddr64 +#define SymGetLineFromAddrW SymGetLineFromAddrW64 +#else +BOOL +IMAGEAPI +SymGetLineFromAddr( + __in HANDLE hProcess, + __in DWORD dwAddr, + __out PDWORD pdwDisplacement, + __out PIMAGEHLP_LINE Line + ); + +BOOL +IMAGEAPI +SymGetLineFromAddrW( + __in HANDLE hProcess, + __in DWORD dwAddr, + __out PDWORD pdwDisplacement, + __out PIMAGEHLP_LINEW Line + ); +#endif + +BOOL +IMAGEAPI +SymGetLineFromName64( + __in HANDLE hProcess, + __in_opt PCSTR ModuleName, + __in_opt PCSTR FileName, + __in DWORD dwLineNumber, + __out PLONG plDisplacement, + __inout PIMAGEHLP_LINE64 Line + ); + +BOOL +IMAGEAPI +SymGetLineFromNameW64( + __in HANDLE hProcess, + __in_opt PCWSTR ModuleName, + __in_opt PCWSTR FileName, + __in DWORD dwLineNumber, + __out PLONG plDisplacement, + __inout PIMAGEHLP_LINEW64 Line + ); + +#if !defined(_IMAGEHLP_SOURCE_) && defined(_IMAGEHLP64) +#define SymGetLineFromName SymGetLineFromName64 +#else +BOOL +IMAGEAPI +SymGetLineFromName( + __in HANDLE hProcess, + __in_opt PCSTR ModuleName, + __in_opt PCSTR FileName, + __in DWORD dwLineNumber, + __out PLONG plDisplacement, + __inout PIMAGEHLP_LINE Line + ); +#endif + +BOOL +IMAGEAPI +SymGetLineNext64( + __in HANDLE hProcess, + __inout PIMAGEHLP_LINE64 Line + ); + +BOOL +IMAGEAPI +SymGetLineNextW64( + __in HANDLE hProcess, + __inout PIMAGEHLP_LINEW64 Line + ); + +#if !defined(_IMAGEHLP_SOURCE_) && defined(_IMAGEHLP64) +#define SymGetLineNext SymGetLineNext64 +#else +BOOL +IMAGEAPI +SymGetLineNext( + __in HANDLE hProcess, + __inout PIMAGEHLP_LINE Line + ); + +BOOL +IMAGEAPI +SymGetLineNextW( + __in HANDLE hProcess, + __inout PIMAGEHLP_LINEW Line + ); +#endif + +BOOL +IMAGEAPI +SymGetLinePrev64( + __in HANDLE hProcess, + __inout PIMAGEHLP_LINE64 Line + ); + +BOOL +IMAGEAPI +SymGetLinePrevW64( + __in HANDLE hProcess, + __inout PIMAGEHLP_LINEW64 Line + ); + +#if !defined(_IMAGEHLP_SOURCE_) && defined(_IMAGEHLP64) +#define SymGetLinePrev SymGetLinePrev64 +#else +BOOL +IMAGEAPI +SymGetLinePrev( + __in HANDLE hProcess, + __inout PIMAGEHLP_LINE Line + ); + +BOOL +IMAGEAPI +SymGetLinePrevW( + __in HANDLE hProcess, + __inout PIMAGEHLP_LINEW Line + ); +#endif + +ULONG +IMAGEAPI +SymGetFileLineOffsets64( + __in HANDLE hProcess, + __in_opt PCSTR ModuleName, + __in PCSTR FileName, + __out_ecount(BufferLines) PDWORD64 Buffer, + __in ULONG BufferLines + ); + +BOOL +IMAGEAPI +SymMatchFileName( + __in PCSTR FileName, + __in PCSTR Match, + __deref_opt_out PSTR *FileNameStop, + __deref_opt_out PSTR *MatchStop + ); + +BOOL +IMAGEAPI +SymMatchFileNameW( + __in PCWSTR FileName, + __in PCWSTR Match, + __deref_opt_out PWSTR *FileNameStop, + __deref_opt_out PWSTR *MatchStop + ); + +BOOL +IMAGEAPI +SymGetSourceFile( + __in HANDLE hProcess, + __in ULONG64 Base, + __in_opt PCSTR Params, + __in PCSTR FileSpec, + __out_ecount(Size) PSTR FilePath, + __in DWORD Size + ); + +BOOL +IMAGEAPI +SymGetSourceFileW( + __in HANDLE hProcess, + __in ULONG64 Base, + __in_opt PCWSTR Params, + __in PCWSTR FileSpec, + __out_ecount(Size) PWSTR FilePath, + __in DWORD Size + ); + +BOOL +IMAGEAPI +SymGetSourceFileToken( + __in HANDLE hProcess, + __in ULONG64 Base, + __in PCSTR FileSpec, + __deref_out PVOID *Token, + __out DWORD *Size + ); + +BOOL +IMAGEAPI +SymGetSourceFileTokenW( + __in HANDLE hProcess, + __in ULONG64 Base, + __in PCWSTR FileSpec, + __deref_out PVOID *Token, + __out DWORD *Size + ); + +BOOL +IMAGEAPI +SymGetSourceFileFromToken( + __in HANDLE hProcess, + __in PVOID Token, + __in_opt PCSTR Params, + __out_ecount(Size) PSTR FilePath, + __in DWORD Size + ); + +BOOL +IMAGEAPI +SymGetSourceFileFromTokenW( + __in HANDLE hProcess, + __in PVOID Token, + __in_opt PCWSTR Params, + __out_ecount(Size) PWSTR FilePath, + __in DWORD Size + ); + +BOOL +IMAGEAPI +SymGetSourceVarFromToken( + __in HANDLE hProcess, + __in PVOID Token, + __in_opt PCSTR Params, + __in PCSTR VarName, + __out_ecount(Size) PSTR Value, + __in DWORD Size + ); + +BOOL +IMAGEAPI +SymGetSourceVarFromTokenW( + __in HANDLE hProcess, + __in PVOID Token, + __in_opt PCWSTR Params, + __in PCWSTR VarName, + __out_ecount(Size) PWSTR Value, + __in DWORD Size + ); + +typedef BOOL (CALLBACK *PENUMSOURCEFILETOKENSCALLBACK)(__in PVOID token, __in size_t size); + +BOOL +IMAGEAPI +SymEnumSourceFileTokens( + __in HANDLE hProcess, + __in ULONG64 Base, + __in PENUMSOURCEFILETOKENSCALLBACK Callback + ); + +BOOL +IMAGEAPI +SymInitialize( + __in HANDLE hProcess, + __in_opt PCSTR UserSearchPath, + __in BOOL fInvadeProcess + ); + +BOOL +IMAGEAPI +SymInitializeW( + __in HANDLE hProcess, + __in_opt PCWSTR UserSearchPath, + __in BOOL fInvadeProcess + ); + +BOOL +IMAGEAPI +SymGetSearchPath( + __in HANDLE hProcess, + __out_ecount(SearchPathLength) PSTR SearchPath, + __in DWORD SearchPathLength + ); + +BOOL +IMAGEAPI +SymGetSearchPathW( + __in HANDLE hProcess, + __out_ecount(SearchPathLength) PWSTR SearchPath, + __in DWORD SearchPathLength + ); + +BOOL +IMAGEAPI +SymSetSearchPath( + __in HANDLE hProcess, + __in_opt PCSTR SearchPath + ); + +BOOL +IMAGEAPI +SymSetSearchPathW( + __in HANDLE hProcess, + __in_opt PCWSTR SearchPath + ); + +#define SLMFLAG_VIRTUAL 0x1 +#define SLMFLAG_ALT_INDEX 0x2 +#define SLMFLAG_NO_SYMBOLS 0x4 + +DWORD64 +IMAGEAPI +SymLoadModuleEx( + __in HANDLE hProcess, + __in_opt HANDLE hFile, + __in_opt PCSTR ImageName, + __in_opt PCSTR ModuleName, + __in DWORD64 BaseOfDll, + __in DWORD DllSize, + __in_opt PMODLOAD_DATA Data, + __in_opt DWORD Flags + ); + +DWORD64 +IMAGEAPI +SymLoadModuleExW( + __in HANDLE hProcess, + __in_opt HANDLE hFile, + __in_opt PCWSTR ImageName, + __in_opt PCWSTR ModuleName, + __in DWORD64 BaseOfDll, + __in DWORD DllSize, + __in_opt PMODLOAD_DATA Data, + __in_opt DWORD Flags + ); + +BOOL +IMAGEAPI +SymUnloadModule64( + __in HANDLE hProcess, + __in DWORD64 BaseOfDll + ); + +#if !defined(_IMAGEHLP_SOURCE_) && defined(_IMAGEHLP64) +#define SymUnloadModule SymUnloadModule64 +#else +BOOL +IMAGEAPI +SymUnloadModule( + __in HANDLE hProcess, + __in DWORD BaseOfDll + ); +#endif + +BOOL +IMAGEAPI +SymUnDName64( + __in PIMAGEHLP_SYMBOL64 sym, // Symbol to undecorate + __out_ecount(UnDecNameLength) PSTR UnDecName, // Buffer to store undecorated name in + __in DWORD UnDecNameLength // Size of the buffer + ); + +#if !defined(_IMAGEHLP_SOURCE_) && defined(_IMAGEHLP64) +#define SymUnDName SymUnDName64 +#else +BOOL +IMAGEAPI +SymUnDName( + __in PIMAGEHLP_SYMBOL sym, // Symbol to undecorate + __out_ecount(UnDecNameLength) PSTR UnDecName, // Buffer to store undecorated name in + __in DWORD UnDecNameLength // Size of the buffer + ); +#endif + +BOOL +IMAGEAPI +SymRegisterCallback64( + __in HANDLE hProcess, + __in PSYMBOL_REGISTERED_CALLBACK64 CallbackFunction, + __in ULONG64 UserContext + ); + +BOOL +IMAGEAPI +SymRegisterCallbackW64( + __in HANDLE hProcess, + __in PSYMBOL_REGISTERED_CALLBACK64 CallbackFunction, + __in ULONG64 UserContext + ); + +BOOL +IMAGEAPI +SymRegisterFunctionEntryCallback64( + __in HANDLE hProcess, + __in PSYMBOL_FUNCENTRY_CALLBACK64 CallbackFunction, + __in ULONG64 UserContext + ); + +#if !defined(_IMAGEHLP_SOURCE_) && defined(_IMAGEHLP64) +#define SymRegisterCallback SymRegisterCallback64 +#define SymRegisterFunctionEntryCallback SymRegisterFunctionEntryCallback64 +#else +BOOL +IMAGEAPI +SymRegisterCallback( + __in HANDLE hProcess, + __in PSYMBOL_REGISTERED_CALLBACK CallbackFunction, + __in_opt PVOID UserContext + ); + +BOOL +IMAGEAPI +SymRegisterFunctionEntryCallback( + __in HANDLE hProcess, + __in PSYMBOL_FUNCENTRY_CALLBACK CallbackFunction, + __in_opt PVOID UserContext + ); +#endif + + +typedef struct _IMAGEHLP_SYMBOL_SRC { + DWORD sizeofstruct; + DWORD type; + char file[MAX_PATH]; +} IMAGEHLP_SYMBOL_SRC, *PIMAGEHLP_SYMBOL_SRC; + +typedef struct _MODULE_TYPE_INFO { // AKA TYPTYP + USHORT dataLength; + USHORT leaf; + BYTE data[1]; +} MODULE_TYPE_INFO, *PMODULE_TYPE_INFO; + +typedef struct _SYMBOL_INFO { + ULONG SizeOfStruct; + ULONG TypeIndex; // Type Index of symbol + ULONG64 Reserved[2]; + ULONG Index; + ULONG Size; + ULONG64 ModBase; // Base Address of module comtaining this symbol + ULONG Flags; + ULONG64 Value; // Value of symbol, ValuePresent should be 1 + ULONG64 Address; // Address of symbol including base address of module + ULONG Register; // register holding value or pointer to value + ULONG Scope; // scope of the symbol + ULONG Tag; // pdb classification + ULONG NameLen; // Actual length of name + ULONG MaxNameLen; + CHAR Name[1]; // Name of symbol +} SYMBOL_INFO, *PSYMBOL_INFO; + +typedef struct _SYMBOL_INFO_PACKAGE { + SYMBOL_INFO si; + CHAR name[MAX_SYM_NAME + 1]; +} SYMBOL_INFO_PACKAGE, *PSYMBOL_INFO_PACKAGE; + +typedef struct _SYMBOL_INFOW { + ULONG SizeOfStruct; + ULONG TypeIndex; // Type Index of symbol + ULONG64 Reserved[2]; + ULONG Index; + ULONG Size; + ULONG64 ModBase; // Base Address of module comtaining this symbol + ULONG Flags; + ULONG64 Value; // Value of symbol, ValuePresent should be 1 + ULONG64 Address; // Address of symbol including base address of module + ULONG Register; // register holding value or pointer to value + ULONG Scope; // scope of the symbol + ULONG Tag; // pdb classification + ULONG NameLen; // Actual length of name + ULONG MaxNameLen; + WCHAR Name[1]; // Name of symbol +} SYMBOL_INFOW, *PSYMBOL_INFOW; + +typedef struct _SYMBOL_INFO_PACKAGEW { + SYMBOL_INFOW si; + WCHAR name[MAX_SYM_NAME + 1]; +} SYMBOL_INFO_PACKAGEW, *PSYMBOL_INFO_PACKAGEW; + +typedef struct _IMAGEHLP_STACK_FRAME +{ + ULONG64 InstructionOffset; + ULONG64 ReturnOffset; + ULONG64 FrameOffset; + ULONG64 StackOffset; + ULONG64 BackingStoreOffset; + ULONG64 FuncTableEntry; + ULONG64 Params[4]; + ULONG64 Reserved[5]; + BOOL Virtual; + ULONG Reserved2; +} IMAGEHLP_STACK_FRAME, *PIMAGEHLP_STACK_FRAME; + +typedef VOID IMAGEHLP_CONTEXT, *PIMAGEHLP_CONTEXT; + + +BOOL +IMAGEAPI +SymSetContext( + __in HANDLE hProcess, + __in PIMAGEHLP_STACK_FRAME StackFrame, + __in_opt PIMAGEHLP_CONTEXT Context + ); + +BOOL +IMAGEAPI +SymSetScopeFromAddr( + __in HANDLE hProcess, + __in ULONG64 Address + ); + +BOOL +IMAGEAPI +SymSetScopeFromIndex( + __in HANDLE hProcess, + __in ULONG64 BaseOfDll, + __in DWORD Index + ); + +typedef BOOL +(CALLBACK *PSYM_ENUMPROCESSES_CALLBACK)( + __in HANDLE hProcess, + __in PVOID UserContext + ); + +BOOL +IMAGEAPI +SymEnumProcesses( + __in PSYM_ENUMPROCESSES_CALLBACK EnumProcessesCallback, + __in PVOID UserContext + ); + +BOOL +IMAGEAPI +SymFromAddr( + __in HANDLE hProcess, + __in DWORD64 Address, + __out_opt PDWORD64 Displacement, + __inout PSYMBOL_INFO Symbol + ); + +BOOL +IMAGEAPI +SymFromAddrW( + __in HANDLE hProcess, + __in DWORD64 Address, + __out_opt PDWORD64 Displacement, + __inout PSYMBOL_INFOW Symbol + ); + +BOOL +IMAGEAPI +SymFromToken( + __in HANDLE hProcess, + __in DWORD64 Base, + __in DWORD Token, + __inout PSYMBOL_INFO Symbol + ); + +BOOL +IMAGEAPI +SymFromTokenW( + __in HANDLE hProcess, + __in DWORD64 Base, + __in DWORD Token, + __inout PSYMBOL_INFOW Symbol + ); + +BOOL +IMAGEAPI +SymNext( + __in HANDLE hProcess, + __inout PSYMBOL_INFO si + ); + +BOOL +IMAGEAPI +SymNextW( + __in HANDLE hProcess, + __inout PSYMBOL_INFOW siw + ); + +BOOL +IMAGEAPI +SymPrev( + __in HANDLE hProcess, + __inout PSYMBOL_INFO si + ); + +BOOL +IMAGEAPI +SymPrevW( + __in HANDLE hProcess, + __inout PSYMBOL_INFOW siw + ); + +// While SymFromName will provide a symbol from a name, +// SymEnumSymbols can provide the same matching information +// for ALL symbols with a matching name, even regular +// expressions. That way you can search across modules +// and differentiate between identically named symbols. + +BOOL +IMAGEAPI +SymFromName( + __in HANDLE hProcess, + __in PCSTR Name, + __inout PSYMBOL_INFO Symbol + ); + +BOOL +IMAGEAPI +SymFromNameW( + __in HANDLE hProcess, + __in PCWSTR Name, + __inout PSYMBOL_INFOW Symbol + ); + +typedef BOOL +(CALLBACK *PSYM_ENUMERATESYMBOLS_CALLBACK)( + __in PSYMBOL_INFO pSymInfo, + __in ULONG SymbolSize, + __in_opt PVOID UserContext + ); + +BOOL +IMAGEAPI +SymEnumSymbols( + __in HANDLE hProcess, + __in ULONG64 BaseOfDll, + __in_opt PCSTR Mask, + __in PSYM_ENUMERATESYMBOLS_CALLBACK EnumSymbolsCallback, + __in_opt PVOID UserContext + ); + +typedef BOOL +(CALLBACK *PSYM_ENUMERATESYMBOLS_CALLBACKW)( + __in PSYMBOL_INFOW pSymInfo, + __in ULONG SymbolSize, + __in_opt PVOID UserContext + ); + +BOOL +IMAGEAPI +SymEnumSymbolsW( + __in HANDLE hProcess, + __in ULONG64 BaseOfDll, + __in_opt PCWSTR Mask, + __in PSYM_ENUMERATESYMBOLS_CALLBACKW EnumSymbolsCallback, + __in_opt PVOID UserContext + ); + +BOOL +IMAGEAPI +SymEnumSymbolsForAddr( + __in HANDLE hProcess, + __in DWORD64 Address, + __in PSYM_ENUMERATESYMBOLS_CALLBACK EnumSymbolsCallback, + __in_opt PVOID UserContext + ); + +BOOL +IMAGEAPI +SymEnumSymbolsForAddrW( + __in HANDLE hProcess, + __in DWORD64 Address, + __in PSYM_ENUMERATESYMBOLS_CALLBACKW EnumSymbolsCallback, + __in_opt PVOID UserContext + ); + +#define SYMSEARCH_MASKOBJS 0x01 // used internally to implement other APIs +#define SYMSEARCH_RECURSE 0X02 // recurse scopes +#define SYMSEARCH_GLOBALSONLY 0X04 // search only for global symbols +#define SYMSEARCH_ALLITEMS 0X08 // search for everything in the pdb, not just normal scoped symbols + +BOOL +IMAGEAPI +SymSearch( + __in HANDLE hProcess, + __in ULONG64 BaseOfDll, + __in_opt DWORD Index, + __in_opt DWORD SymTag, + __in_opt PCSTR Mask, + __in_opt DWORD64 Address, + __in PSYM_ENUMERATESYMBOLS_CALLBACK EnumSymbolsCallback, + __in_opt PVOID UserContext, + __in DWORD Options + ); + +BOOL +IMAGEAPI +SymSearchW( + __in HANDLE hProcess, + __in ULONG64 BaseOfDll, + __in_opt DWORD Index, + __in_opt DWORD SymTag, + __in_opt PCWSTR Mask, + __in_opt DWORD64 Address, + __in PSYM_ENUMERATESYMBOLS_CALLBACKW EnumSymbolsCallback, + __in_opt PVOID UserContext, + __in DWORD Options + ); + +BOOL +IMAGEAPI +SymGetScope( + __in HANDLE hProcess, + __in ULONG64 BaseOfDll, + __in DWORD Index, + __inout PSYMBOL_INFO Symbol + ); + +BOOL +IMAGEAPI +SymGetScopeW( + __in HANDLE hProcess, + __in ULONG64 BaseOfDll, + __in DWORD Index, + __inout PSYMBOL_INFOW Symbol + ); + +BOOL +IMAGEAPI +SymFromIndex( + __in HANDLE hProcess, + __in ULONG64 BaseOfDll, + __in DWORD Index, + __inout PSYMBOL_INFO Symbol + ); + +BOOL +IMAGEAPI +SymFromIndexW( + __in HANDLE hProcess, + __in ULONG64 BaseOfDll, + __in DWORD Index, + __inout PSYMBOL_INFOW Symbol + ); + +typedef enum _IMAGEHLP_SYMBOL_TYPE_INFO { + TI_GET_SYMTAG, + TI_GET_SYMNAME, + TI_GET_LENGTH, + TI_GET_TYPE, + TI_GET_TYPEID, + TI_GET_BASETYPE, + TI_GET_ARRAYINDEXTYPEID, + TI_FINDCHILDREN, + TI_GET_DATAKIND, + TI_GET_ADDRESSOFFSET, + TI_GET_OFFSET, + TI_GET_VALUE, + TI_GET_COUNT, + TI_GET_CHILDRENCOUNT, + TI_GET_BITPOSITION, + TI_GET_VIRTUALBASECLASS, + TI_GET_VIRTUALTABLESHAPEID, + TI_GET_VIRTUALBASEPOINTEROFFSET, + TI_GET_CLASSPARENTID, + TI_GET_NESTED, + TI_GET_SYMINDEX, + TI_GET_LEXICALPARENT, + TI_GET_ADDRESS, + TI_GET_THISADJUST, + TI_GET_UDTKIND, + TI_IS_EQUIV_TO, + TI_GET_CALLING_CONVENTION, + TI_IS_CLOSE_EQUIV_TO, + TI_GTIEX_REQS_VALID, + TI_GET_VIRTUALBASEOFFSET, + TI_GET_VIRTUALBASEDISPINDEX, + TI_GET_IS_REFERENCE, + TI_GET_INDIRECTVIRTUALBASECLASS, + IMAGEHLP_SYMBOL_TYPE_INFO_MAX, +} IMAGEHLP_SYMBOL_TYPE_INFO; + +typedef struct _TI_FINDCHILDREN_PARAMS { + ULONG Count; + ULONG Start; + ULONG ChildId[1]; +} TI_FINDCHILDREN_PARAMS; + +BOOL +IMAGEAPI +SymGetTypeInfo( + __in HANDLE hProcess, + __in DWORD64 ModBase, + __in ULONG TypeId, + __in IMAGEHLP_SYMBOL_TYPE_INFO GetType, + __out PVOID pInfo + ); + +#define IMAGEHLP_GET_TYPE_INFO_UNCACHED 0x00000001 +#define IMAGEHLP_GET_TYPE_INFO_CHILDREN 0x00000002 + +typedef struct _IMAGEHLP_GET_TYPE_INFO_PARAMS { + IN ULONG SizeOfStruct; + IN ULONG Flags; + IN ULONG NumIds; + IN PULONG TypeIds; + IN ULONG64 TagFilter; + IN ULONG NumReqs; + IN IMAGEHLP_SYMBOL_TYPE_INFO* ReqKinds; + IN PULONG_PTR ReqOffsets; + IN PULONG ReqSizes; + IN ULONG_PTR ReqStride; + IN ULONG_PTR BufferSize; + OUT PVOID Buffer; + OUT ULONG EntriesMatched; + OUT ULONG EntriesFilled; + OUT ULONG64 TagsFound; + OUT ULONG64 AllReqsValid; + IN ULONG NumReqsValid; + OUT PULONG64 ReqsValid OPTIONAL; +} IMAGEHLP_GET_TYPE_INFO_PARAMS, *PIMAGEHLP_GET_TYPE_INFO_PARAMS; + +BOOL +IMAGEAPI +SymGetTypeInfoEx( + __in HANDLE hProcess, + __in DWORD64 ModBase, + __inout PIMAGEHLP_GET_TYPE_INFO_PARAMS Params + ); + +BOOL +IMAGEAPI +SymEnumTypes( + __in HANDLE hProcess, + __in ULONG64 BaseOfDll, + __in PSYM_ENUMERATESYMBOLS_CALLBACK EnumSymbolsCallback, + __in_opt PVOID UserContext + ); + +BOOL +IMAGEAPI +SymEnumTypesW( + __in HANDLE hProcess, + __in ULONG64 BaseOfDll, + __in PSYM_ENUMERATESYMBOLS_CALLBACKW EnumSymbolsCallback, + __in_opt PVOID UserContext + ); + +BOOL +IMAGEAPI +SymEnumTypesByName( + __in HANDLE hProcess, + __in ULONG64 BaseOfDll, + __in_opt PCSTR mask, + __in PSYM_ENUMERATESYMBOLS_CALLBACK EnumSymbolsCallback, + __in_opt PVOID UserContext + ); + +BOOL +IMAGEAPI +SymEnumTypesByNameW( + __in HANDLE hProcess, + __in ULONG64 BaseOfDll, + __in_opt PCWSTR mask, + __in PSYM_ENUMERATESYMBOLS_CALLBACKW EnumSymbolsCallback, + __in_opt PVOID UserContext + ); + +BOOL +IMAGEAPI +SymGetTypeFromName( + __in HANDLE hProcess, + __in ULONG64 BaseOfDll, + __in PCSTR Name, + __inout PSYMBOL_INFO Symbol + ); + +BOOL +IMAGEAPI +SymGetTypeFromNameW( + __in HANDLE hProcess, + __in ULONG64 BaseOfDll, + __in PCWSTR Name, + __inout PSYMBOL_INFOW Symbol + ); + +BOOL +IMAGEAPI +SymAddSymbol( + __in HANDLE hProcess, + __in ULONG64 BaseOfDll, + __in PCSTR Name, + __in DWORD64 Address, + __in DWORD Size, + __in DWORD Flags + ); + +BOOL +IMAGEAPI +SymAddSymbolW( + __in HANDLE hProcess, + __in ULONG64 BaseOfDll, + __in PCWSTR Name, + __in DWORD64 Address, + __in DWORD Size, + __in DWORD Flags + ); + +BOOL +IMAGEAPI +SymDeleteSymbol( + __in HANDLE hProcess, + __in ULONG64 BaseOfDll, + __in_opt PCSTR Name, + __in DWORD64 Address, + __in DWORD Flags + ); + +BOOL +IMAGEAPI +SymDeleteSymbolW( + __in HANDLE hProcess, + __in ULONG64 BaseOfDll, + __in_opt PCWSTR Name, + __in DWORD64 Address, + __in DWORD Flags + ); + +BOOL +IMAGEAPI +SymRefreshModuleList( + __in HANDLE hProcess + ); + +BOOL +IMAGEAPI +SymAddSourceStream( + __in HANDLE hProcess, + __in ULONG64 Base, + __in_opt PCSTR StreamFile, + __in_bcount_opt(Size) PBYTE Buffer, + __in size_t Size + ); + +typedef BOOL (WINAPI *SYMADDSOURCESTREAM)(HANDLE, ULONG64, PCSTR, PBYTE, size_t); + +BOOL +IMAGEAPI +SymAddSourceStreamA( + __in HANDLE hProcess, + __in ULONG64 Base, + __in_opt PCSTR StreamFile, + __in_bcount_opt(Size) PBYTE Buffer, + __in size_t Size + ); + +typedef BOOL (WINAPI *SYMADDSOURCESTREAMA)(HANDLE, ULONG64, PCSTR, PBYTE, size_t); + +BOOL +IMAGEAPI +SymAddSourceStreamW( + __in HANDLE hProcess, + __in ULONG64 Base, + __in_opt PCWSTR FileSpec, + __in_bcount_opt(Size) PBYTE Buffer, + __in size_t Size + ); + +BOOL +IMAGEAPI +SymSrvIsStoreW( + __in_opt HANDLE hProcess, + __in PCWSTR path + ); + +BOOL +IMAGEAPI +SymSrvIsStore( + __in_opt HANDLE hProcess, + __in PCSTR path + ); + +PCSTR +IMAGEAPI +SymSrvDeltaName( + __in HANDLE hProcess, + __in_opt PCSTR SymPath, + __in PCSTR Type, + __in PCSTR File1, + __in PCSTR File2 + ); + +PCWSTR +IMAGEAPI +SymSrvDeltaNameW( + __in HANDLE hProcess, + __in_opt PCWSTR SymPath, + __in PCWSTR Type, + __in PCWSTR File1, + __in PCWSTR File2 + ); + +PCSTR +IMAGEAPI +SymSrvGetSupplement( + __in HANDLE hProcess, + __in_opt PCSTR SymPath, + __in PCSTR Node, + __in PCSTR File + ); + +PCWSTR +IMAGEAPI +SymSrvGetSupplementW( + __in HANDLE hProcess, + __in_opt PCWSTR SymPath, + __in PCWSTR Node, + __in PCWSTR File + ); + +BOOL +IMAGEAPI +SymSrvGetFileIndexes( + __in PCSTR File, + __out GUID *Id, + __out PDWORD Val1, + __out_opt PDWORD Val2, + __in DWORD Flags + ); + +BOOL +IMAGEAPI +SymSrvGetFileIndexesW( + __in PCWSTR File, + __out GUID *Id, + __out PDWORD Val1, + __out_opt PDWORD Val2, + __in DWORD Flags + ); + +BOOL +IMAGEAPI +SymSrvGetFileIndexStringW( + __in HANDLE hProcess, + __in_opt PCWSTR SrvPath, + __in PCWSTR File, + __out_ecount(Size) PWSTR Index, + __in size_t Size, + __in DWORD Flags + ); + +BOOL +IMAGEAPI +SymSrvGetFileIndexString( + __in HANDLE hProcess, + __in_opt PCSTR SrvPath, + __in PCSTR File, + __out_ecount(Size) PSTR Index, + __in size_t Size, + __in DWORD Flags + ); + +typedef struct { + DWORD sizeofstruct; + char file[MAX_PATH +1]; + BOOL stripped; + DWORD timestamp; + DWORD size; + char dbgfile[MAX_PATH +1]; + char pdbfile[MAX_PATH + 1]; + GUID guid; + DWORD sig; + DWORD age; +} SYMSRV_INDEX_INFO, *PSYMSRV_INDEX_INFO; + +typedef struct { + DWORD sizeofstruct; + WCHAR file[MAX_PATH +1]; + BOOL stripped; + DWORD timestamp; + DWORD size; + WCHAR dbgfile[MAX_PATH +1]; + WCHAR pdbfile[MAX_PATH + 1]; + GUID guid; + DWORD sig; + DWORD age; +} SYMSRV_INDEX_INFOW, *PSYMSRV_INDEX_INFOW; + +BOOL +IMAGEAPI +SymSrvGetFileIndexInfo( + __in PCSTR File, + __out PSYMSRV_INDEX_INFO Info, + __in DWORD Flags + ); + +BOOL +IMAGEAPI +SymSrvGetFileIndexInfoW( + __in PCWSTR File, + __out PSYMSRV_INDEX_INFOW Info, + __in DWORD Flags + ); + +PCSTR +IMAGEAPI +SymSrvStoreSupplement( + __in HANDLE hProcess, + __in_opt PCSTR SrvPath, + __in PCSTR Node, + __in PCSTR File, + __in DWORD Flags + ); + +PCWSTR +IMAGEAPI +SymSrvStoreSupplementW( + __in HANDLE hProcess, + __in_opt PCWSTR SymPath, + __in PCWSTR Node, + __in PCWSTR File, + __in DWORD Flags + ); + +PCSTR +IMAGEAPI +SymSrvStoreFile( + __in HANDLE hProcess, + __in_opt PCSTR SrvPath, + __in PCSTR File, + __in DWORD Flags + ); + +PCWSTR +IMAGEAPI +SymSrvStoreFileW( + __in HANDLE hProcess, + __in_opt PCWSTR SrvPath, + __in PCWSTR File, + __in DWORD Flags + ); + +// used by SymGetSymbolFile's "Type" parameter + +typedef enum { + sfImage = 0, + sfDbg, + sfPdb, + sfMpd, + sfMax +}; + +BOOL +IMAGEAPI +SymGetSymbolFile( + __in_opt HANDLE hProcess, + __in_opt PCSTR SymPath, + __in PCSTR ImageFile, + __in DWORD Type, + __out_ecount(cSymbolFile) PSTR SymbolFile, + __in size_t cSymbolFile, + __out_ecount(cDbgFile) PSTR DbgFile, + __in size_t cDbgFile + ); + +BOOL +IMAGEAPI +SymGetSymbolFileW( + __in_opt HANDLE hProcess, + __in_opt PCWSTR SymPath, + __in PCWSTR ImageFile, + __in DWORD Type, + __out_ecount(cSymbolFile) PWSTR SymbolFile, + __in size_t cSymbolFile, + __out_ecount(cDbgFile) PWSTR DbgFile, + __in size_t cDbgFile + ); + +// +// Full user-mode dump creation. +// + +typedef BOOL (WINAPI *PDBGHELP_CREATE_USER_DUMP_CALLBACK)( + __in DWORD DataType, + __in PVOID* Data, + __out LPDWORD DataLength, + __in_opt PVOID UserData + ); + +BOOL +WINAPI +DbgHelpCreateUserDump( + __in_opt LPCSTR FileName, + __in PDBGHELP_CREATE_USER_DUMP_CALLBACK Callback, + __in_opt PVOID UserData + ); + +BOOL +WINAPI +DbgHelpCreateUserDumpW( + __in_opt LPCWSTR FileName, + __in PDBGHELP_CREATE_USER_DUMP_CALLBACK Callback, + __in_opt PVOID UserData + ); + +// ----------------------------------------------------------------- +// The following 4 legacy APIs are fully supported, but newer +// ones are recommended. SymFromName and SymFromAddr provide +// much more detailed info on the returned symbol. + +BOOL +IMAGEAPI +SymGetSymFromAddr64( + __in HANDLE hProcess, + __in DWORD64 qwAddr, + __out_opt PDWORD64 pdwDisplacement, + __inout PIMAGEHLP_SYMBOL64 Symbol + ); + + +#if !defined(_IMAGEHLP_SOURCE_) && defined(_IMAGEHLP64) +#define SymGetSymFromAddr SymGetSymFromAddr64 +#else +BOOL +IMAGEAPI +SymGetSymFromAddr( + __in HANDLE hProcess, + __in DWORD dwAddr, + __out_opt PDWORD pdwDisplacement, + __inout PIMAGEHLP_SYMBOL Symbol + ); +#endif + +// While following two APIs will provide a symbol from a name, +// SymEnumSymbols can provide the same matching information +// for ALL symbols with a matching name, even regular +// expressions. That way you can search across modules +// and differentiate between identically named symbols. + +BOOL +IMAGEAPI +SymGetSymFromName64( + __in HANDLE hProcess, + __in PCSTR Name, + __inout PIMAGEHLP_SYMBOL64 Symbol + ); + +#if !defined(_IMAGEHLP_SOURCE_) && defined(_IMAGEHLP64) +#define SymGetSymFromName SymGetSymFromName64 +#else +BOOL +IMAGEAPI +SymGetSymFromName( + __in HANDLE hProcess, + __in PCSTR Name, + __inout PIMAGEHLP_SYMBOL Symbol + ); +#endif + + +// Symbol server exports + +typedef BOOL (WINAPI *PSYMBOLSERVERPROC)(PCSTR, PCSTR, PVOID, DWORD, DWORD, PSTR); +typedef BOOL (WINAPI *PSYMBOLSERVERPROCA)(PCSTR, PCSTR, PVOID, DWORD, DWORD, PSTR); +typedef BOOL (WINAPI *PSYMBOLSERVERPROCW)(PCWSTR, PCWSTR, PVOID, DWORD, DWORD, PWSTR); +typedef BOOL (WINAPI *PSYMBOLSERVERBYINDEXPROC)(PCSTR, PCSTR, PCSTR, PSTR); +typedef BOOL (WINAPI *PSYMBOLSERVERBYINDEXPROCA)(PCSTR, PCSTR, PCSTR, PSTR); +typedef BOOL (WINAPI *PSYMBOLSERVERBYINDEXPROCW)(PCWSTR, PCWSTR, PCWSTR, PWSTR); +typedef BOOL (WINAPI *PSYMBOLSERVEROPENPROC)(VOID); +typedef BOOL (WINAPI *PSYMBOLSERVERCLOSEPROC)(VOID); +typedef BOOL (WINAPI *PSYMBOLSERVERSETOPTIONSPROC)(UINT_PTR, ULONG64); +typedef BOOL (WINAPI *PSYMBOLSERVERSETOPTIONSWPROC)(UINT_PTR, ULONG64); +typedef BOOL (CALLBACK WINAPI *PSYMBOLSERVERCALLBACKPROC)(UINT_PTR action, ULONG64 data, ULONG64 context); +typedef UINT_PTR (WINAPI *PSYMBOLSERVERGETOPTIONSPROC)(); +typedef BOOL (WINAPI *PSYMBOLSERVERPINGPROC)(PCSTR); +typedef BOOL (WINAPI *PSYMBOLSERVERPINGPROCA)(PCSTR); +typedef BOOL (WINAPI *PSYMBOLSERVERPINGPROCW)(PCWSTR); +typedef BOOL (WINAPI *PSYMBOLSERVERGETVERSION)(LPAPI_VERSION); +typedef BOOL (WINAPI *PSYMBOLSERVERDELTANAME)(PCSTR, PVOID, DWORD, DWORD, PVOID, DWORD, DWORD, PSTR, size_t); +typedef BOOL (WINAPI *PSYMBOLSERVERDELTANAMEW)(PCWSTR, PVOID, DWORD, DWORD, PVOID, DWORD, DWORD, PWSTR, size_t); +typedef BOOL (WINAPI *PSYMBOLSERVERGETSUPPLEMENT)(PCSTR, PCSTR, PCSTR, PSTR, size_t); +typedef BOOL (WINAPI *PSYMBOLSERVERGETSUPPLEMENTW)(PCWSTR, PCWSTR, PCWSTR, PWSTR, size_t); +typedef BOOL (WINAPI *PSYMBOLSERVERSTORESUPPLEMENT)(PCSTR, PCSTR, PCSTR, PSTR, size_t, DWORD); +typedef BOOL (WINAPI *PSYMBOLSERVERSTORESUPPLEMENTW)(PCWSTR, PCWSTR, PCWSTR, PWSTR, size_t, DWORD); +typedef BOOL (WINAPI *PSYMBOLSERVERGETINDEXSTRING)(PVOID, DWORD, DWORD, PSTR, size_t); +typedef BOOL (WINAPI *PSYMBOLSERVERGETINDEXSTRINGW)(PVOID, DWORD, DWORD, PWSTR, size_t); +typedef BOOL (WINAPI *PSYMBOLSERVERSTOREFILE)(PCSTR, PCSTR, PVOID, DWORD, DWORD, PSTR, size_t, DWORD); +typedef BOOL (WINAPI *PSYMBOLSERVERSTOREFILEW)(PCWSTR, PCWSTR, PVOID, DWORD, DWORD, PWSTR, size_t, DWORD); +typedef BOOL (WINAPI *PSYMBOLSERVERISSTORE)(PCSTR); +typedef BOOL (WINAPI *PSYMBOLSERVERISSTOREW)(PCWSTR); +typedef DWORD (WINAPI *PSYMBOLSERVERVERSION)(); +typedef BOOL (CALLBACK WINAPI *PSYMBOLSERVERMESSAGEPROC)(UINT_PTR action, ULONG64 data, ULONG64 context); + +#define SYMSRV_VERSION 2 + +#define SSRVOPT_CALLBACK 0x00000001 +#define SSRVOPT_DWORD 0x00000002 +#define SSRVOPT_DWORDPTR 0x00000004 +#define SSRVOPT_GUIDPTR 0x00000008 +#define SSRVOPT_OLDGUIDPTR 0x00000010 +#define SSRVOPT_UNATTENDED 0x00000020 +#define SSRVOPT_NOCOPY 0x00000040 +#define SSRVOPT_GETPATH 0x00000040 +#define SSRVOPT_PARENTWIN 0x00000080 +#define SSRVOPT_PARAMTYPE 0x00000100 +#define SSRVOPT_SECURE 0x00000200 +#define SSRVOPT_TRACE 0x00000400 +#define SSRVOPT_SETCONTEXT 0x00000800 +#define SSRVOPT_PROXY 0x00001000 +#define SSRVOPT_DOWNSTREAM_STORE 0x00002000 +#define SSRVOPT_OVERWRITE 0x00004000 +#define SSRVOPT_RESETTOU 0x00008000 +#define SSRVOPT_CALLBACKW 0x00010000 +#define SSRVOPT_FLAT_DEFAULT_STORE 0x00020000 +#define SSRVOPT_PROXYW 0x00040000 +#define SSRVOPT_MESSAGE 0x00080000 +#define SSRVOPT_SERVICE 0x00100000 // deprecated +#define SSRVOPT_FAVOR_COMPRESSED 0x00200000 +#define SSRVOPT_STRING 0x00400000 +#define SSRVOPT_WINHTTP 0x00800000 +#define SSRVOPT_WININET 0x01000000 + +#define SSRVOPT_MAX 0x0100000 + +#define SSRVOPT_RESET ((ULONG_PTR)-1) + + +#define NUM_SSRVOPTS 30 + +#define SSRVACTION_TRACE 1 +#define SSRVACTION_QUERYCANCEL 2 +#define SSRVACTION_EVENT 3 +#define SSRVACTION_EVENTW 4 +#define SSRVACTION_SIZE 5 + +#define SYMSTOREOPT_COMPRESS 0x01 +#define SYMSTOREOPT_OVERWRITE 0x02 +#define SYMSTOREOPT_RETURNINDEX 0x04 +#define SYMSTOREOPT_POINTER 0x08 +#define SYMSTOREOPT_ALT_INDEX 0x10 +#define SYMSTOREOPT_UNICODE 0x20 +#define SYMSTOREOPT_PASS_IF_EXISTS 0x40 + +#ifdef DBGHELP_TRANSLATE_TCHAR + #define SymInitialize SymInitializeW + #define SymAddSymbol SymAddSymbolW + #define SymDeleteSymbol SymDeleteSymbolW + #define SearchTreeForFile SearchTreeForFileW + #define UnDecorateSymbolName UnDecorateSymbolNameW + #define SymGetLineFromName64 SymGetLineFromNameW64 + #define SymGetLineFromAddr64 SymGetLineFromAddrW64 + #define SymGetLineNext64 SymGetLineNextW64 + #define SymGetLinePrev64 SymGetLinePrevW64 + #define SymFromName SymFromNameW + #define SymFindExecutableImage SymFindExecutableImageW + #define FindExecutableImageEx FindExecutableImageExW + #define SymSearch SymSearchW + #define SymEnumLines SymEnumLinesW + #define SymEnumSourceLines SymEnumSourceLinesW + #define SymGetTypeFromName SymGetTypeFromNameW + #define SymEnumSymbolsForAddr SymEnumSymbolsForAddrW + #define SymFromAddr SymFromAddrW + #define SymMatchString SymMatchStringW + #define SymEnumSourceFiles SymEnumSourceFilesW + #define SymEnumSymbols SymEnumSymbolsW + #define SymLoadModuleEx SymLoadModuleExW + #define SymSetSearchPath SymSetSearchPathW + #define SymGetSearchPath SymGetSearchPathW + #define EnumDirTree EnumDirTreeW + #define SymFromToken SymFromTokenW + #define SymFromIndex SymFromIndexW + #define SymGetScope SymGetScopeW + #define SymNext SymNextW + #define SymPrev SymPrevW + #define SymEnumTypes SymEnumTypesW + #define SymEnumTypesByName SymEnumTypesByNameW + #define SymRegisterCallback64 SymRegisterCallbackW64 + #define SymFindDebugInfoFile SymFindDebugInfoFileW + #define FindDebugInfoFileEx FindDebugInfoFileExW + #define SymFindFileInPath SymFindFileInPathW + #define SymEnumerateModules64 SymEnumerateModulesW64 + #define SymSetHomeDirectory SymSetHomeDirectoryW + #define SymGetHomeDirectory SymGetHomeDirectoryW + #define SymGetSourceFile SymGetSourceFileW + #define SymGetSourceFileToken SymGetSourceFileTokenW + #define SymGetSourceFileFromToken SymGetSourceFileFromTokenW + #define SymGetSourceVarFromToken SymGetSourceVarFromTokenW + #define SymGetSourceFileToken SymGetSourceFileTokenW + #define SymGetFileLineOffsets64 SymGetFileLineOffsetsW64 + #define SymFindFileInPath SymFindFileInPathW + #define SymMatchFileName SymMatchFileNameW + #define SymGetSourceFileFromToken SymGetSourceFileFromTokenW + #define SymGetSourceVarFromToken SymGetSourceVarFromTokenW + #define SymGetModuleInfo64 SymGetModuleInfoW64 + #define SymSrvIsStore SymSrvIsStoreW + #define SymSrvDeltaName SymSrvDeltaNameW + #define SymSrvGetSupplement SymSrvGetSupplementW + #define SymSrvStoreSupplement SymSrvStoreSupplementW + #define SymSrvGetFileIndexes SymSrvGetFileIndexes + #define SymSrvGetFileIndexString SymSrvGetFileIndexStringW + #define SymSrvStoreFile SymSrvStoreFileW + #define SymGetSymbolFile SymGetSymbolFileW + #define EnumerateLoadedModules64 EnumerateLoadedModulesW64 + #define EnumerateLoadedModulesEx EnumerateLoadedModulesExW + #define SymSrvGetFileIndexInfo SymSrvGetFileIndexInfoW + + #define IMAGEHLP_LINE64 IMAGEHLP_LINEW64 + #define PIMAGEHLP_LINE64 PIMAGEHLP_LINEW64 + #define SYMBOL_INFO SYMBOL_INFOW + #define PSYMBOL_INFO PSYMBOL_INFOW + #define SYMBOL_INFO_PACKAGE SYMBOL_INFO_PACKAGEW + #define PSYMBOL_INFO_PACKAGE PSYMBOL_INFO_PACKAGEW + #define FIND_EXE_FILE_CALLBACK FIND_EXE_FILE_CALLBACKW + #define PFIND_EXE_FILE_CALLBACK PFIND_EXE_FILE_CALLBACKW + #define SYM_ENUMERATESYMBOLS_CALLBACK SYM_ENUMERATESYMBOLS_CALLBACKW + #define PSYM_ENUMERATESYMBOLS_CALLBACK PSYM_ENUMERATESYMBOLS_CALLBACKW + #define SRCCODEINFO SRCCODEINFOW + #define PSRCCODEINFO PSRCCODEINFOW + #define SOURCEFILE SOURCEFILEW + #define PSOURCEFILE PSOURCEFILEW + #define SYM_ENUMSOURECFILES_CALLBACK SYM_ENUMSOURCEFILES_CALLBACKW + #define PSYM_ENUMSOURCEFILES_CALLBACK PSYM_ENUMSOURECFILES_CALLBACKW + #define IMAGEHLP_CBA_EVENT IMAGEHLP_CBA_EVENTW + #define PIMAGEHLP_CBA_EVENT PIMAGEHLP_CBA_EVENTW + #define PENUMDIRTREE_CALLBACK PENUMDIRTREE_CALLBACKW + #define IMAGEHLP_DEFERRED_SYMBOL_LOAD64 IMAGEHLP_DEFERRED_SYMBOL_LOADW64 + #define PIMAGEHLP_DEFERRED_SYMBOL_LOAD64 PIMAGEHLP_DEFERRED_SYMBOL_LOADW64 + #define PFIND_DEBUG_FILE_CALLBACK PFIND_DEBUG_FILE_CALLBACKW + #define PFINDFILEINPATHCALLBACK PFINDFILEINPATHCALLBACKW + #define IMAGEHLP_MODULE64 IMAGEHLP_MODULEW64 + #define PIMAGEHLP_MODULE64 PIMAGEHLP_MODULEW64 + #define SYMSRV_INDEX_INFO SYMSRV_INDEX_INFOW + #define PSYMSRV_INDEX_INFO PSYMSRV_INDEX_INFOW + + #define PSYMBOLSERVERPROC PSYMBOLSERVERPROCW + #define PSYMBOLSERVERPINGPROC PSYMBOLSERVERPINGPROCW +#endif + +// ----------------------------------------------------------------- +// The following APIs exist only for backwards compatibility +// with a pre-release version documented in an MSDN release. + +// You should use SymFindFileInPath if you want to maintain +// future compatibility. + +DBHLP_DEPRECIATED +BOOL +IMAGEAPI +FindFileInPath( + __in HANDLE hprocess, + __in PCSTR SearchPath, + __in PCSTR FileName, + __in PVOID id, + __in DWORD two, + __in DWORD three, + __in DWORD flags, + __out_ecount(MAX_PATH + 1) PSTR FilePath + ); + +// You should use SymFindFileInPath if you want to maintain +// future compatibility. + +DBHLP_DEPRECIATED +BOOL +IMAGEAPI +FindFileInSearchPath( + __in HANDLE hprocess, + __in PCSTR SearchPath, + __in PCSTR FileName, + __in DWORD one, + __in DWORD two, + __in DWORD three, + __out_ecount(MAX_PATH + 1) PSTR FilePath + ); + +DBHLP_DEPRECIATED +BOOL +IMAGEAPI +SymEnumSym( + __in HANDLE hProcess, + __in ULONG64 BaseOfDll, + __in PSYM_ENUMERATESYMBOLS_CALLBACK EnumSymbolsCallback, + __in_opt PVOID UserContext + ); + +DBHLP_DEPRECIATED +BOOL +IMAGEAPI +SymEnumerateSymbols64( + __in HANDLE hProcess, + __in ULONG64 BaseOfDll, + __in PSYM_ENUMSYMBOLS_CALLBACK64 EnumSymbolsCallback, + __in_opt PVOID UserContext + ); + +DBHLP_DEPRECIATED +BOOL +IMAGEAPI +SymEnumerateSymbolsW64( + __in HANDLE hProcess, + __in ULONG64 BaseOfDll, + __in PSYM_ENUMSYMBOLS_CALLBACK64W EnumSymbolsCallback, + __in_opt PVOID UserContext + ); + + +#if !defined(_IMAGEHLP_SOURCE_) && defined(_IMAGEHLP64) +#define SymEnumerateSymbols SymEnumerateSymbols64 +#define SymEnumerateSymbolsW SymEnumerateSymbolsW64 +#else +DBHLP_DEPRECIATED +BOOL +IMAGEAPI +SymEnumerateSymbols( + __in HANDLE hProcess, + __in ULONG BaseOfDll, + __in PSYM_ENUMSYMBOLS_CALLBACK EnumSymbolsCallback, + __in_opt PVOID UserContext + ); + +DBHLP_DEPRECIATED +BOOL +IMAGEAPI +SymEnumerateSymbolsW( + __in HANDLE hProcess, + __in ULONG BaseOfDll, + __in PSYM_ENUMSYMBOLS_CALLBACKW EnumSymbolsCallback, + __in_opt PVOID UserContext + ); +#endif + +// use SymLoadModuleEx + +DWORD64 +IMAGEAPI +SymLoadModule64( + __in HANDLE hProcess, + __in_opt HANDLE hFile, + __in_opt PCSTR ImageName, + __in_opt PCSTR ModuleName, + __in DWORD64 BaseOfDll, + __in DWORD SizeOfDll + ); + +#if !defined(_IMAGEHLP_SOURCE_) && defined(_IMAGEHLP64) +#define SymLoadModule SymLoadModule64 +#else +DWORD +IMAGEAPI +SymLoadModule( + __in HANDLE hProcess, + __in_opt HANDLE hFile, + __in_opt PCSTR ImageName, + __in_opt PCSTR ModuleName, + __in DWORD BaseOfDll, + __in DWORD SizeOfDll + ); +#endif + +BOOL +IMAGEAPI +SymGetSymNext64( + __in HANDLE hProcess, + __inout PIMAGEHLP_SYMBOL64 Symbol + ); + +BOOL +IMAGEAPI +SymGetSymNextW64( + __in HANDLE hProcess, + __inout PIMAGEHLP_SYMBOLW64 Symbol + ); + +#if !defined(_IMAGEHLP_SOURCE_) && defined(_IMAGEHLP64) +#define SymGetSymNext SymGetSymNext64 +#define SymGetSymNextW SymGetSymNextW64 +#else +BOOL +IMAGEAPI +SymGetSymNext( + __in HANDLE hProcess, + __inout PIMAGEHLP_SYMBOL Symbol + ); + +BOOL +IMAGEAPI +SymGetSymNextW( + __in HANDLE hProcess, + __inout PIMAGEHLP_SYMBOLW Symbol + ); +#endif + +BOOL +IMAGEAPI +SymGetSymPrev64( + __in HANDLE hProcess, + __inout PIMAGEHLP_SYMBOL64 Symbol + ); + +BOOL +IMAGEAPI +SymGetSymPrevW64( + __in HANDLE hProcess, + __inout PIMAGEHLP_SYMBOLW64 Symbol + ); + +#if !defined(_IMAGEHLP_SOURCE_) && defined(_IMAGEHLP64) +#define SymGetSymPrev SymGetSymPrev64 +#define SymGetSymPrevW SymGetSymPrevW64 +#else +BOOL +IMAGEAPI +SymGetSymPrev( + __in HANDLE hProcess, + __inout PIMAGEHLP_SYMBOL Symbol + ); + +BOOL +IMAGEAPI +SymGetSymPrevW( + __in HANDLE hProcess, + __inout PIMAGEHLP_SYMBOLW Symbol + ); +#endif + + +// These values should not be used. +// They have been replaced by SYMFLAG_ values. + +#define SYMF_OMAP_GENERATED 0x00000001 +#define SYMF_OMAP_MODIFIED 0x00000002 +#define SYMF_REGISTER 0x00000008 +#define SYMF_REGREL 0x00000010 +#define SYMF_FRAMEREL 0x00000020 +#define SYMF_PARAMETER 0x00000040 +#define SYMF_LOCAL 0x00000080 +#define SYMF_CONSTANT 0x00000100 +#define SYMF_EXPORT 0x00000200 +#define SYMF_FORWARDER 0x00000400 +#define SYMF_FUNCTION 0x00000800 +#define SYMF_VIRTUAL 0x00001000 +#define SYMF_THUNK 0x00002000 +#define SYMF_TLSREL 0x00004000 + +// These values should also not be used. +// They have been replaced by SYMFLAG_ values. + +#define IMAGEHLP_SYMBOL_INFO_VALUEPRESENT 1 +#define IMAGEHLP_SYMBOL_INFO_REGISTER SYMF_REGISTER // 0x0008 +#define IMAGEHLP_SYMBOL_INFO_REGRELATIVE SYMF_REGREL // 0x0010 +#define IMAGEHLP_SYMBOL_INFO_FRAMERELATIVE SYMF_FRAMEREL // 0x0020 +#define IMAGEHLP_SYMBOL_INFO_PARAMETER SYMF_PARAMETER // 0x0040 +#define IMAGEHLP_SYMBOL_INFO_LOCAL SYMF_LOCAL // 0x0080 +#define IMAGEHLP_SYMBOL_INFO_CONSTANT SYMF_CONSTANT // 0x0100 +#define IMAGEHLP_SYMBOL_FUNCTION SYMF_FUNCTION // 0x0800 +#define IMAGEHLP_SYMBOL_VIRTUAL SYMF_VIRTUAL // 0x1000 +#define IMAGEHLP_SYMBOL_THUNK SYMF_THUNK // 0x2000 +#define IMAGEHLP_SYMBOL_INFO_TLSRELATIVE SYMF_TLSREL // 0x4000 + + +#include <pshpack4.h> + +#if defined(_MSC_VER) +#if _MSC_VER >= 800 +#if _MSC_VER >= 1200 +#pragma warning(push) +#endif +#pragma warning(disable:4200) /* Zero length array */ +#pragma warning(disable:4201) /* Nameless struct/union */ +#endif +#endif + +#define MINIDUMP_SIGNATURE ('PMDM') +#define MINIDUMP_VERSION (42899) +typedef DWORD RVA; +typedef ULONG64 RVA64; + +typedef struct _MINIDUMP_LOCATION_DESCRIPTOR { + ULONG32 DataSize; + RVA Rva; +} MINIDUMP_LOCATION_DESCRIPTOR; + +typedef struct _MINIDUMP_LOCATION_DESCRIPTOR64 { + ULONG64 DataSize; + RVA64 Rva; +} MINIDUMP_LOCATION_DESCRIPTOR64; + + +typedef struct _MINIDUMP_MEMORY_DESCRIPTOR { + ULONG64 StartOfMemoryRange; + MINIDUMP_LOCATION_DESCRIPTOR Memory; +} MINIDUMP_MEMORY_DESCRIPTOR, *PMINIDUMP_MEMORY_DESCRIPTOR; + +// DESCRIPTOR64 is used for full-memory minidumps where +// all of the raw memory is laid out sequentially at the +// end of the dump. There is no need for individual RVAs +// as the RVA is the base RVA plus the sum of the preceeding +// data blocks. +typedef struct _MINIDUMP_MEMORY_DESCRIPTOR64 { + ULONG64 StartOfMemoryRange; + ULONG64 DataSize; +} MINIDUMP_MEMORY_DESCRIPTOR64, *PMINIDUMP_MEMORY_DESCRIPTOR64; + + +typedef struct _MINIDUMP_HEADER { + ULONG32 Signature; + ULONG32 Version; + ULONG32 NumberOfStreams; + RVA StreamDirectoryRva; + ULONG32 CheckSum; + union { + ULONG32 Reserved; + ULONG32 TimeDateStamp; + }; + ULONG64 Flags; +} MINIDUMP_HEADER, *PMINIDUMP_HEADER; + +// +// The MINIDUMP_HEADER field StreamDirectoryRva points to +// an array of MINIDUMP_DIRECTORY structures. +// + +typedef struct _MINIDUMP_DIRECTORY { + ULONG32 StreamType; + MINIDUMP_LOCATION_DESCRIPTOR Location; +} MINIDUMP_DIRECTORY, *PMINIDUMP_DIRECTORY; + + +typedef struct _MINIDUMP_STRING { + ULONG32 Length; // Length in bytes of the string + WCHAR Buffer [0]; // Variable size buffer +} MINIDUMP_STRING, *PMINIDUMP_STRING; + + + +// +// The MINIDUMP_DIRECTORY field StreamType may be one of the following types. +// Types will be added in the future, so if a program reading the minidump +// header encounters a stream type it does not understand it should ignore +// the data altogether. Any tag above LastReservedStream will not be used by +// the system and is reserved for program-specific information. +// + +typedef enum _MINIDUMP_STREAM_TYPE { + + UnusedStream = 0, + ReservedStream0 = 1, + ReservedStream1 = 2, + ThreadListStream = 3, + ModuleListStream = 4, + MemoryListStream = 5, + ExceptionStream = 6, + SystemInfoStream = 7, + ThreadExListStream = 8, + Memory64ListStream = 9, + CommentStreamA = 10, + CommentStreamW = 11, + HandleDataStream = 12, + FunctionTableStream = 13, + UnloadedModuleListStream = 14, + MiscInfoStream = 15, + MemoryInfoListStream = 16, + ThreadInfoListStream = 17, + HandleOperationListStream = 18, + + ceStreamNull = 0x8000, + ceStreamSystemInfo = 0x8001, + ceStreamException = 0x8002, + ceStreamModuleList = 0x8003, + ceStreamProcessList = 0x8004, + ceStreamThreadList = 0x8005, + ceStreamThreadContextList = 0x8006, + ceStreamThreadCallStackList = 0x8007, + ceStreamMemoryVirtualList = 0x8008, + ceStreamMemoryPhysicalList = 0x8009, + ceStreamBucketParameters = 0x800A, + + LastReservedStream = 0xffff + +} MINIDUMP_STREAM_TYPE; + + +// +// The minidump system information contains processor and +// Operating System specific information. +// + +// +// CPU information is obtained from one of two places. +// +// 1) On x86 computers, CPU_INFORMATION is obtained from the CPUID +// instruction. You must use the X86 portion of the union for X86 +// computers. +// +// 2) On non-x86 architectures, CPU_INFORMATION is obtained by calling +// IsProcessorFeatureSupported(). +// + +typedef union _CPU_INFORMATION { + + // + // X86 platforms use CPUID function to obtain processor information. + // + + struct { + + // + // CPUID Subfunction 0, register EAX (VendorId [0]), + // EBX (VendorId [1]) and ECX (VendorId [2]). + // + + ULONG32 VendorId [ 3 ]; + + // + // CPUID Subfunction 1, register EAX + // + + ULONG32 VersionInformation; + + // + // CPUID Subfunction 1, register EDX + // + + ULONG32 FeatureInformation; + + + // + // CPUID, Subfunction 80000001, register EBX. This will only + // be obtained if the vendor id is "AuthenticAMD". + // + + ULONG32 AMDExtendedCpuFeatures; + + } X86CpuInfo; + + // + // Non-x86 platforms use processor feature flags. + // + + struct { + + ULONG64 ProcessorFeatures [ 2 ]; + + } OtherCpuInfo; + +} CPU_INFORMATION, *PCPU_INFORMATION; + +typedef struct _MINIDUMP_SYSTEM_INFO { + + // + // ProcessorArchitecture, ProcessorLevel and ProcessorRevision are all + // taken from the SYSTEM_INFO structure obtained by GetSystemInfo( ). + // + + USHORT ProcessorArchitecture; + USHORT ProcessorLevel; + USHORT ProcessorRevision; + + union { + USHORT Reserved0; + struct { + UCHAR NumberOfProcessors; + UCHAR ProductType; + }; + }; + + // + // MajorVersion, MinorVersion, BuildNumber, PlatformId and + // CSDVersion are all taken from the OSVERSIONINFO structure + // returned by GetVersionEx( ). + // + + ULONG32 MajorVersion; + ULONG32 MinorVersion; + ULONG32 BuildNumber; + ULONG32 PlatformId; + + // + // RVA to a CSDVersion string in the string table. + // + + RVA CSDVersionRva; + + union { + ULONG32 Reserved1; + struct { + USHORT SuiteMask; + USHORT Reserved2; + }; + }; + + CPU_INFORMATION Cpu; + +} MINIDUMP_SYSTEM_INFO, *PMINIDUMP_SYSTEM_INFO; + + +// +// The minidump thread contains standard thread +// information plus an RVA to the memory for this +// thread and an RVA to the CONTEXT structure for +// this thread. +// + + +// +// ThreadId must be 4 bytes on all architectures. +// +#ifndef FEATURE_PAL +static_assert (sizeof ( ((PPROCESS_INFORMATION)0)->dwThreadId ) == 4, "ThreadId must be 4 bytes on all architectures."); +#else +typedef int VS_FIXEDFILEINFO; +#endif + + +typedef struct _MINIDUMP_THREAD { + ULONG32 ThreadId; + ULONG32 SuspendCount; + ULONG32 PriorityClass; + ULONG32 Priority; + ULONG64 Teb; + MINIDUMP_MEMORY_DESCRIPTOR Stack; + MINIDUMP_LOCATION_DESCRIPTOR ThreadContext; +} MINIDUMP_THREAD, *PMINIDUMP_THREAD; + +// +// The thread list is a container of threads. +// + +typedef struct _MINIDUMP_THREAD_LIST { + ULONG32 NumberOfThreads; + MINIDUMP_THREAD Threads [0]; +} MINIDUMP_THREAD_LIST, *PMINIDUMP_THREAD_LIST; + + +typedef struct _MINIDUMP_THREAD_EX { + ULONG32 ThreadId; + ULONG32 SuspendCount; + ULONG32 PriorityClass; + ULONG32 Priority; + ULONG64 Teb; + MINIDUMP_MEMORY_DESCRIPTOR Stack; + MINIDUMP_LOCATION_DESCRIPTOR ThreadContext; + MINIDUMP_MEMORY_DESCRIPTOR BackingStore; +} MINIDUMP_THREAD_EX, *PMINIDUMP_THREAD_EX; + +// +// The thread list is a container of threads. +// + +typedef struct _MINIDUMP_THREAD_EX_LIST { + ULONG32 NumberOfThreads; + MINIDUMP_THREAD_EX Threads [0]; +} MINIDUMP_THREAD_EX_LIST, *PMINIDUMP_THREAD_EX_LIST; + + +// +// The MINIDUMP_EXCEPTION is the same as EXCEPTION on Win64. +// + +typedef struct _MINIDUMP_EXCEPTION { + ULONG32 ExceptionCode; + ULONG32 ExceptionFlags; + ULONG64 ExceptionRecord; + ULONG64 ExceptionAddress; + ULONG32 NumberParameters; + ULONG32 __unusedAlignment; + ULONG64 ExceptionInformation [ EXCEPTION_MAXIMUM_PARAMETERS ]; +} MINIDUMP_EXCEPTION, *PMINIDUMP_EXCEPTION; + + +// +// The exception information stream contains the id of the thread that caused +// the exception (ThreadId), the exception record for the exception +// (ExceptionRecord) and an RVA to the thread context where the exception +// occured. +// + +typedef struct MINIDUMP_EXCEPTION_STREAM { + ULONG32 ThreadId; + ULONG32 __alignment; + MINIDUMP_EXCEPTION ExceptionRecord; + MINIDUMP_LOCATION_DESCRIPTOR ThreadContext; +} MINIDUMP_EXCEPTION_STREAM, *PMINIDUMP_EXCEPTION_STREAM; + + +// +// The MINIDUMP_MODULE contains information about a +// a specific module. It includes the CheckSum and +// the TimeDateStamp for the module so the module +// can be reloaded during the analysis phase. +// + +typedef struct _MINIDUMP_MODULE { + ULONG64 BaseOfImage; + ULONG32 SizeOfImage; + ULONG32 CheckSum; + ULONG32 TimeDateStamp; + RVA ModuleNameRva; + VS_FIXEDFILEINFO VersionInfo; + MINIDUMP_LOCATION_DESCRIPTOR CvRecord; + MINIDUMP_LOCATION_DESCRIPTOR MiscRecord; + ULONG64 Reserved0; // Reserved for future use. + ULONG64 Reserved1; // Reserved for future use. +} MINIDUMP_MODULE, *PMINIDUMP_MODULE; + + +// +// The minidump module list is a container for modules. +// + +typedef struct _MINIDUMP_MODULE_LIST { + ULONG32 NumberOfModules; + MINIDUMP_MODULE Modules [ 0 ]; +} MINIDUMP_MODULE_LIST, *PMINIDUMP_MODULE_LIST; + + +// +// Memory Ranges +// + +typedef struct _MINIDUMP_MEMORY_LIST { + ULONG32 NumberOfMemoryRanges; + MINIDUMP_MEMORY_DESCRIPTOR MemoryRanges [0]; +} MINIDUMP_MEMORY_LIST, *PMINIDUMP_MEMORY_LIST; + +typedef struct _MINIDUMP_MEMORY64_LIST { + ULONG64 NumberOfMemoryRanges; + RVA64 BaseRva; + MINIDUMP_MEMORY_DESCRIPTOR64 MemoryRanges [0]; +} MINIDUMP_MEMORY64_LIST, *PMINIDUMP_MEMORY64_LIST; + + +// +// Support for user supplied exception information. +// + +typedef struct _MINIDUMP_EXCEPTION_INFORMATION { + DWORD ThreadId; + PEXCEPTION_POINTERS ExceptionPointers; + BOOL ClientPointers; +} MINIDUMP_EXCEPTION_INFORMATION, *PMINIDUMP_EXCEPTION_INFORMATION; + +typedef struct _MINIDUMP_EXCEPTION_INFORMATION64 { + DWORD ThreadId; + ULONG64 ExceptionRecord; + ULONG64 ContextRecord; + BOOL ClientPointers; +} MINIDUMP_EXCEPTION_INFORMATION64, *PMINIDUMP_EXCEPTION_INFORMATION64; + + +// +// Support for capturing system handle state at the time of the dump. +// + +// Per-handle object information varies according to +// the OS, the OS version, the processor type and +// so on. The minidump gives a minidump identifier +// to each possible data format for identification +// purposes but does not control nor describe the actual data. +typedef enum _MINIDUMP_HANDLE_OBJECT_INFORMATION_TYPE { + MiniHandleObjectInformationNone, + MiniThreadInformation1, + MiniMutantInformation1, + MiniMutantInformation2, + MiniProcessInformation1, + MiniProcessInformation2, + MiniHandleObjectInformationTypeMax +} MINIDUMP_HANDLE_OBJECT_INFORMATION_TYPE; + +typedef struct _MINIDUMP_HANDLE_OBJECT_INFORMATION { + RVA NextInfoRva; + ULONG32 InfoType; + ULONG32 SizeOfInfo; + // Raw information follows. +} MINIDUMP_HANDLE_OBJECT_INFORMATION; + +typedef struct _MINIDUMP_HANDLE_DESCRIPTOR { + ULONG64 Handle; + RVA TypeNameRva; + RVA ObjectNameRva; + ULONG32 Attributes; + ULONG32 GrantedAccess; + ULONG32 HandleCount; + ULONG32 PointerCount; +} MINIDUMP_HANDLE_DESCRIPTOR, *PMINIDUMP_HANDLE_DESCRIPTOR; + +typedef struct _MINIDUMP_HANDLE_DESCRIPTOR_2 { + ULONG64 Handle; + RVA TypeNameRva; + RVA ObjectNameRva; + ULONG32 Attributes; + ULONG32 GrantedAccess; + ULONG32 HandleCount; + ULONG32 PointerCount; + RVA ObjectInfoRva; + ULONG32 Reserved0; +} MINIDUMP_HANDLE_DESCRIPTOR_2, *PMINIDUMP_HANDLE_DESCRIPTOR_2; + +// The latest MINIDUMP_HANDLE_DESCRIPTOR definition. +typedef MINIDUMP_HANDLE_DESCRIPTOR_2 MINIDUMP_HANDLE_DESCRIPTOR_N; +typedef MINIDUMP_HANDLE_DESCRIPTOR_N *PMINIDUMP_HANDLE_DESCRIPTOR_N; + +typedef struct _MINIDUMP_HANDLE_DATA_STREAM { + ULONG32 SizeOfHeader; + ULONG32 SizeOfDescriptor; + ULONG32 NumberOfDescriptors; + ULONG32 Reserved; +} MINIDUMP_HANDLE_DATA_STREAM, *PMINIDUMP_HANDLE_DATA_STREAM; + +// Some operating systems can track the last operations +// performed on a handle. For example, Application Verifier +// can enable this for some versions of Windows. The +// handle operation list collects handle operations +// known for the dump target. +// Each entry is an AVRF_HANDLE_OPERATION. +typedef struct _MINIDUMP_HANDLE_OPERATION_LIST { + ULONG32 SizeOfHeader; + ULONG32 SizeOfEntry; + ULONG32 NumberOfEntries; + ULONG32 Reserved; +} MINIDUMP_HANDLE_OPERATION_LIST, *PMINIDUMP_HANDLE_OPERATION_LIST; + + +// +// Support for capturing dynamic function table state at the time of the dump. +// + +typedef struct _MINIDUMP_FUNCTION_TABLE_DESCRIPTOR { + ULONG64 MinimumAddress; + ULONG64 MaximumAddress; + ULONG64 BaseAddress; + ULONG32 EntryCount; + ULONG32 SizeOfAlignPad; +} MINIDUMP_FUNCTION_TABLE_DESCRIPTOR, *PMINIDUMP_FUNCTION_TABLE_DESCRIPTOR; + +typedef struct _MINIDUMP_FUNCTION_TABLE_STREAM { + ULONG32 SizeOfHeader; + ULONG32 SizeOfDescriptor; + ULONG32 SizeOfNativeDescriptor; + ULONG32 SizeOfFunctionEntry; + ULONG32 NumberOfDescriptors; + ULONG32 SizeOfAlignPad; +} MINIDUMP_FUNCTION_TABLE_STREAM, *PMINIDUMP_FUNCTION_TABLE_STREAM; + + +// +// The MINIDUMP_UNLOADED_MODULE contains information about a +// a specific module that was previously loaded but no +// longer is. This can help with diagnosing problems where +// callers attempt to call code that is no longer loaded. +// + +typedef struct _MINIDUMP_UNLOADED_MODULE { + ULONG64 BaseOfImage; + ULONG32 SizeOfImage; + ULONG32 CheckSum; + ULONG32 TimeDateStamp; + RVA ModuleNameRva; +} MINIDUMP_UNLOADED_MODULE, *PMINIDUMP_UNLOADED_MODULE; + + +// +// The minidump unloaded module list is a container for unloaded modules. +// + +typedef struct _MINIDUMP_UNLOADED_MODULE_LIST { + ULONG32 SizeOfHeader; + ULONG32 SizeOfEntry; + ULONG32 NumberOfEntries; +} MINIDUMP_UNLOADED_MODULE_LIST, *PMINIDUMP_UNLOADED_MODULE_LIST; + + +// +// The miscellaneous information stream contains a variety +// of small pieces of information. A member is valid if +// it's within the available size and its corresponding +// bit is set. +// + +#define MINIDUMP_MISC1_PROCESS_ID 0x00000001 +#define MINIDUMP_MISC1_PROCESS_TIMES 0x00000002 +#define MINIDUMP_MISC1_PROCESSOR_POWER_INFO 0x00000004 + +typedef struct _MINIDUMP_MISC_INFO { + ULONG32 SizeOfInfo; + ULONG32 Flags1; + ULONG32 ProcessId; + ULONG32 ProcessCreateTime; + ULONG32 ProcessUserTime; + ULONG32 ProcessKernelTime; +} MINIDUMP_MISC_INFO, *PMINIDUMP_MISC_INFO; + +typedef struct _MINIDUMP_MISC_INFO_2 { + ULONG32 SizeOfInfo; + ULONG32 Flags1; + ULONG32 ProcessId; + ULONG32 ProcessCreateTime; + ULONG32 ProcessUserTime; + ULONG32 ProcessKernelTime; + ULONG32 ProcessorMaxMhz; + ULONG32 ProcessorCurrentMhz; + ULONG32 ProcessorMhzLimit; + ULONG32 ProcessorMaxIdleState; + ULONG32 ProcessorCurrentIdleState; +} MINIDUMP_MISC_INFO_2, *PMINIDUMP_MISC_INFO_2; + +// The latest MINIDUMP_MISC_INFO definition. +typedef MINIDUMP_MISC_INFO_2 MINIDUMP_MISC_INFO_N; +typedef MINIDUMP_MISC_INFO_N* PMINIDUMP_MISC_INFO_N; + + +// +// The memory information stream contains memory region +// description information. This stream corresponds to +// what VirtualQuery would return for the process the +// dump was created for. +// + +typedef struct _MINIDUMP_MEMORY_INFO { + ULONG64 BaseAddress; + ULONG64 AllocationBase; + ULONG32 AllocationProtect; + ULONG32 __alignment1; + ULONG64 RegionSize; + ULONG32 State; + ULONG32 Protect; + ULONG32 Type; + ULONG32 __alignment2; +} MINIDUMP_MEMORY_INFO, *PMINIDUMP_MEMORY_INFO; + +typedef struct _MINIDUMP_MEMORY_INFO_LIST { + ULONG SizeOfHeader; + ULONG SizeOfEntry; + ULONG64 NumberOfEntries; +} MINIDUMP_MEMORY_INFO_LIST, *PMINIDUMP_MEMORY_INFO_LIST; + + +// +// The memory information stream contains memory region +// description information. This stream corresponds to +// what VirtualQuery would return for the process the +// dump was created for. +// + +// Thread dump writer status flags. +#define MINIDUMP_THREAD_INFO_ERROR_THREAD 0x00000001 +#define MINIDUMP_THREAD_INFO_WRITING_THREAD 0x00000002 +#define MINIDUMP_THREAD_INFO_EXITED_THREAD 0x00000004 +#define MINIDUMP_THREAD_INFO_INVALID_INFO 0x00000008 +#define MINIDUMP_THREAD_INFO_INVALID_CONTEXT 0x00000010 +#define MINIDUMP_THREAD_INFO_INVALID_TEB 0x00000020 + +typedef struct _MINIDUMP_THREAD_INFO { + ULONG32 ThreadId; + ULONG32 DumpFlags; + ULONG32 DumpError; + ULONG32 ExitStatus; + ULONG64 CreateTime; + ULONG64 ExitTime; + ULONG64 KernelTime; + ULONG64 UserTime; + ULONG64 StartAddress; + ULONG64 Affinity; +} MINIDUMP_THREAD_INFO, *PMINIDUMP_THREAD_INFO; + +typedef struct _MINIDUMP_THREAD_INFO_LIST { + ULONG SizeOfHeader; + ULONG SizeOfEntry; + ULONG NumberOfEntries; +} MINIDUMP_THREAD_INFO_LIST, *PMINIDUMP_THREAD_INFO_LIST; + + +// +// Support for arbitrary user-defined information. +// + +typedef struct _MINIDUMP_USER_RECORD { + ULONG32 Type; + MINIDUMP_LOCATION_DESCRIPTOR Memory; +} MINIDUMP_USER_RECORD, *PMINIDUMP_USER_RECORD; + + +typedef struct _MINIDUMP_USER_STREAM { + ULONG32 Type; + ULONG BufferSize; + PVOID Buffer; + +} MINIDUMP_USER_STREAM, *PMINIDUMP_USER_STREAM; + + +typedef struct _MINIDUMP_USER_STREAM_INFORMATION { + ULONG UserStreamCount; + PMINIDUMP_USER_STREAM UserStreamArray; +} MINIDUMP_USER_STREAM_INFORMATION, *PMINIDUMP_USER_STREAM_INFORMATION; + +// +// Callback support. +// + +typedef enum _MINIDUMP_CALLBACK_TYPE { + ModuleCallback, + ThreadCallback, + ThreadExCallback, + IncludeThreadCallback, + IncludeModuleCallback, + MemoryCallback, + CancelCallback, + WriteKernelMinidumpCallback, + KernelMinidumpStatusCallback, + RemoveMemoryCallback, + IncludeVmRegionCallback, + IoStartCallback, + IoWriteAllCallback, + IoFinishCallback, + ReadMemoryFailureCallback, + SecondaryFlagsCallback, +} MINIDUMP_CALLBACK_TYPE; + + +typedef struct _MINIDUMP_THREAD_CALLBACK { + ULONG ThreadId; + HANDLE ThreadHandle; + CONTEXT Context; + ULONG SizeOfContext; + ULONG64 StackBase; + ULONG64 StackEnd; +} MINIDUMP_THREAD_CALLBACK, *PMINIDUMP_THREAD_CALLBACK; + + +typedef struct _MINIDUMP_THREAD_EX_CALLBACK { + ULONG ThreadId; + HANDLE ThreadHandle; + CONTEXT Context; + ULONG SizeOfContext; + ULONG64 StackBase; + ULONG64 StackEnd; + ULONG64 BackingStoreBase; + ULONG64 BackingStoreEnd; +} MINIDUMP_THREAD_EX_CALLBACK, *PMINIDUMP_THREAD_EX_CALLBACK; + + +typedef struct _MINIDUMP_INCLUDE_THREAD_CALLBACK { + ULONG ThreadId; +} MINIDUMP_INCLUDE_THREAD_CALLBACK, *PMINIDUMP_INCLUDE_THREAD_CALLBACK; + + +typedef enum _THREAD_WRITE_FLAGS { + ThreadWriteThread = 0x0001, + ThreadWriteStack = 0x0002, + ThreadWriteContext = 0x0004, + ThreadWriteBackingStore = 0x0008, + ThreadWriteInstructionWindow = 0x0010, + ThreadWriteThreadData = 0x0020, + ThreadWriteThreadInfo = 0x0040, +} THREAD_WRITE_FLAGS; + +typedef struct _MINIDUMP_MODULE_CALLBACK { + PWCHAR FullPath; + ULONG64 BaseOfImage; + ULONG SizeOfImage; + ULONG CheckSum; + ULONG TimeDateStamp; + VS_FIXEDFILEINFO VersionInfo; + PVOID CvRecord; + ULONG SizeOfCvRecord; + PVOID MiscRecord; + ULONG SizeOfMiscRecord; +} MINIDUMP_MODULE_CALLBACK, *PMINIDUMP_MODULE_CALLBACK; + + +typedef struct _MINIDUMP_INCLUDE_MODULE_CALLBACK { + ULONG64 BaseOfImage; +} MINIDUMP_INCLUDE_MODULE_CALLBACK, *PMINIDUMP_INCLUDE_MODULE_CALLBACK; + + +typedef enum _MODULE_WRITE_FLAGS { + ModuleWriteModule = 0x0001, + ModuleWriteDataSeg = 0x0002, + ModuleWriteMiscRecord = 0x0004, + ModuleWriteCvRecord = 0x0008, + ModuleReferencedByMemory = 0x0010, + ModuleWriteTlsData = 0x0020, + ModuleWriteCodeSegs = 0x0040, +} MODULE_WRITE_FLAGS; + + +typedef struct _MINIDUMP_IO_CALLBACK { + HANDLE Handle; + ULONG64 Offset; + PVOID Buffer; + ULONG BufferBytes; +} MINIDUMP_IO_CALLBACK, *PMINIDUMP_IO_CALLBACK; + + +typedef struct _MINIDUMP_READ_MEMORY_FAILURE_CALLBACK +{ + ULONG64 Offset; + ULONG Bytes; + HRESULT FailureStatus; +} MINIDUMP_READ_MEMORY_FAILURE_CALLBACK, + *PMINIDUMP_READ_MEMORY_FAILURE_CALLBACK; + + +typedef struct _MINIDUMP_CALLBACK_INPUT { + ULONG ProcessId; + HANDLE ProcessHandle; + ULONG CallbackType; + union { + HRESULT Status; + MINIDUMP_THREAD_CALLBACK Thread; + MINIDUMP_THREAD_EX_CALLBACK ThreadEx; + MINIDUMP_MODULE_CALLBACK Module; + MINIDUMP_INCLUDE_THREAD_CALLBACK IncludeThread; + MINIDUMP_INCLUDE_MODULE_CALLBACK IncludeModule; + MINIDUMP_IO_CALLBACK Io; + MINIDUMP_READ_MEMORY_FAILURE_CALLBACK ReadMemoryFailure; + ULONG SecondaryFlags; + }; +} MINIDUMP_CALLBACK_INPUT, *PMINIDUMP_CALLBACK_INPUT; + +typedef struct _MINIDUMP_CALLBACK_OUTPUT { + union { + ULONG ModuleWriteFlags; + ULONG ThreadWriteFlags; + ULONG SecondaryFlags; + struct { + ULONG64 MemoryBase; + ULONG MemorySize; + }; + struct { + BOOL CheckCancel; + BOOL Cancel; + }; + HANDLE Handle; + struct { + MINIDUMP_MEMORY_INFO VmRegion; + BOOL Continue; + }; + HRESULT Status; + }; +} MINIDUMP_CALLBACK_OUTPUT, *PMINIDUMP_CALLBACK_OUTPUT; + + +// +// A normal minidump contains just the information +// necessary to capture stack traces for all of the +// existing threads in a process. +// +// A minidump with data segments includes all of the data +// sections from loaded modules in order to capture +// global variable contents. This can make the dump much +// larger if many modules have global data. +// +// A minidump with full memory includes all of the accessible +// memory in the process and can be very large. A minidump +// with full memory always has the raw memory data at the end +// of the dump so that the initial structures in the dump can +// be mapped directly without having to include the raw +// memory information. +// +// Stack and backing store memory can be filtered to remove +// data unnecessary for stack walking. This can improve +// compression of stacks and also deletes data that may +// be private and should not be stored in a dump. +// Memory can also be scanned to see what modules are +// referenced by stack and backing store memory to allow +// omission of other modules to reduce dump size. +// In either of these modes the ModuleReferencedByMemory flag +// is set for all modules referenced before the base +// module callbacks occur. +// +// On some operating systems a list of modules that were +// recently unloaded is kept in addition to the currently +// loaded module list. This information can be saved in +// the dump if desired. +// +// Stack and backing store memory can be scanned for referenced +// pages in order to pick up data referenced by locals or other +// stack memory. This can increase the size of a dump significantly. +// +// Module paths may contain undesired information such as user names +// or other important directory names so they can be stripped. This +// option reduces the ability to locate the proper image later +// and should only be used in certain situations. +// +// Complete operating system per-process and per-thread information can +// be gathered and stored in the dump. +// +// The virtual address space can be scanned for various types +// of memory to be included in the dump. +// +// Code which is concerned with potentially private information +// getting into the minidump can set a flag that automatically +// modifies all existing and future flags to avoid placing +// unnecessary data in the dump. Basic data, such as stack +// information, will still be included but optional data, such +// as indirect memory, will not. +// +// When doing a full memory dump it's possible to store all +// of the enumerated memory region descriptive information +// in a memory information stream. +// +// Additional thread information beyond the basic thread +// structure can be collected if desired. +// +// A minidump with code segments includes all of the code +// and code-related sections from loaded modules in order +// to capture executable content. +// +// MiniDumpWithoutAuxiliaryState turns off any secondary, +// auxiliary-supported memory gathering. +// +// MiniDumpWithFullAuxiliaryState asks any present auxiliary +// data providers to include all of their state in the dump. +// The exact set of what is provided depends on the auxiliary. +// This can be quite large. +// + +typedef enum _MINIDUMP_TYPE { + MiniDumpNormal = 0x00000000, + MiniDumpWithDataSegs = 0x00000001, + MiniDumpWithFullMemory = 0x00000002, + MiniDumpWithHandleData = 0x00000004, + MiniDumpFilterMemory = 0x00000008, + MiniDumpScanMemory = 0x00000010, + MiniDumpWithUnloadedModules = 0x00000020, + MiniDumpWithIndirectlyReferencedMemory = 0x00000040, + MiniDumpFilterModulePaths = 0x00000080, + MiniDumpWithProcessThreadData = 0x00000100, + MiniDumpWithPrivateReadWriteMemory = 0x00000200, + MiniDumpWithoutOptionalData = 0x00000400, + MiniDumpWithFullMemoryInfo = 0x00000800, + MiniDumpWithThreadInfo = 0x00001000, + MiniDumpWithCodeSegs = 0x00002000, + MiniDumpWithoutAuxiliaryState = 0x00004000, + MiniDumpWithFullAuxiliaryState = 0x00008000, + + MiniDumpValidTypeFlags = 0x0000ffff, +} MINIDUMP_TYPE; + +// +// In addition to the primary flags provided to +// MiniDumpWriteDump there are additional, less +// frequently used options queried via the secondary +// flags callback. +// +// MiniSecondaryWithoutPowerInfo suppresses the minidump +// query that retrieves processor power information for +// MINIDUMP_MISC_INFO. +// + +typedef enum _MINIDUMP_SECONDARY_FLAGS { + MiniSecondaryWithoutPowerInfo = 0x00000001, + + MiniSecondaryValidFlags = 0x00000001, +} MINIDUMP_SECONDARY_FLAGS; + + +// +// The minidump callback should modify the FieldsToWrite parameter to reflect +// what portions of the specified thread or module should be written to the +// file. +// + +typedef +BOOL +(WINAPI * MINIDUMP_CALLBACK_ROUTINE) ( + IN PVOID CallbackParam, + IN CONST PMINIDUMP_CALLBACK_INPUT CallbackInput, + IN OUT PMINIDUMP_CALLBACK_OUTPUT CallbackOutput + ); + +typedef struct _MINIDUMP_CALLBACK_INFORMATION { + MINIDUMP_CALLBACK_ROUTINE CallbackRoutine; + PVOID CallbackParam; +} MINIDUMP_CALLBACK_INFORMATION, *PMINIDUMP_CALLBACK_INFORMATION; + + + +//++ +// +// PVOID +// RVA_TO_ADDR( +// PVOID Mapping, +// ULONG Rva +// ) +// +// Routine Description: +// +// Map an RVA that is contained within a mapped file to it's associated +// flat address. +// +// Arguments: +// +// Mapping - Base address of mapped file containing the RVA. +// +// Rva - An Rva to fixup. +// +// Return Values: +// +// A pointer to the desired data. +// +//-- + +#define RVA_TO_ADDR(Mapping,Rva) ((PVOID)(((ULONG_PTR) (Mapping)) + (Rva))) + +BOOL +WINAPI +MiniDumpWriteDump( + IN HANDLE hProcess, + IN DWORD ProcessId, + IN HANDLE hFile, + IN MINIDUMP_TYPE DumpType, + IN CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, OPTIONAL + IN CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, OPTIONAL + IN CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam OPTIONAL + ); + +BOOL +WINAPI +MiniDumpReadDumpStream( + IN PVOID BaseOfDump, + IN ULONG StreamNumber, + OUT PMINIDUMP_DIRECTORY * Dir, OPTIONAL + OUT PVOID * StreamPointer, OPTIONAL + OUT ULONG * StreamSize OPTIONAL + ); + +#if defined(_MSC_VER) +#if _MSC_VER >= 800 +#if _MSC_VER >= 1200 +#pragma warning(pop) +#else +#pragma warning(default:4200) /* Zero length array */ +#pragma warning(default:4201) /* Nameless struct/union */ +#endif +#endif +#endif + +#include <poppack.h> + +#ifdef __cplusplus +} +#endif + + +#endif // _DBGHELP_ diff --git a/src/ToolBox/SOS/Strike/inc/wdbgexts.h b/src/ToolBox/SOS/Strike/inc/wdbgexts.h new file mode 100644 index 0000000000..5e0e8c3adf --- /dev/null +++ b/src/ToolBox/SOS/Strike/inc/wdbgexts.h @@ -0,0 +1,2807 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*++ + + + +Module Name: + + wdbgexts.h + +Abstract: + + This file contains the necessary prototypes and data types for a user + to write a debugger extension DLL. This header file is also included + by the NT debuggers (WINDBG & KD). + + This header file must be included after "windows.h" and "dbghelp.h". + + Please see the NT DDK documentation for specific information about + how to write your own debugger extension DLL. + +Environment: + + Win32 only. + +Revision History: + +--*/ + +#ifndef _WDBGEXTS_ +#define _WDBGEXTS_ + +#if _MSC_VER > 1000 +#pragma once +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if _MSC_VER >= 1200 +#pragma warning(push) +#endif +#ifndef FEATURE_PAL +#pragma warning(disable:4115 4201 4204 4214 4221) +#endif + +// Maximum value of MAXIMUM_PROCESSORS for all platforms. +#define CROSS_PLATFORM_MAXIMUM_PROCESSORS 256 + +#if !defined(WDBGAPI) +#define WDBGAPI __stdcall +#endif + +#if !defined(WDBGAPIV) +#define WDBGAPIV __cdecl +#endif + +#ifndef _WINDEF_ +typedef CONST void *LPCVOID; +#endif + +#ifndef _ULONGLONG_ +typedef unsigned __int64 ULONGLONG; +typedef ULONGLONG *PULONGLONG; +#endif + +#ifndef __field_ecount_opt +// Should include SpecStrings.h to get proper definitions. +#define __field_ecount_opt(x) +#endif + +#define WDBGEXTS_MAXSIZE_T ((SIZE_T)~((SIZE_T)0)) + +typedef +VOID +(WDBGAPIV*PWINDBG_OUTPUT_ROUTINE)( + PCSTR lpFormat, + ... + ); + +typedef +ULONG_PTR +(WDBGAPI*PWINDBG_GET_EXPRESSION)( + PCSTR lpExpression + ); + +typedef +ULONG +(WDBGAPI*PWINDBG_GET_EXPRESSION32)( + PCSTR lpExpression + ); + +typedef +ULONG64 +(WDBGAPI*PWINDBG_GET_EXPRESSION64)( + PCSTR lpExpression + ); + +typedef +VOID +(WDBGAPI*PWINDBG_GET_SYMBOL)( + PVOID offset, + PCHAR pchBuffer, + ULONG_PTR *pDisplacement + ); + +typedef +VOID +(WDBGAPI*PWINDBG_GET_SYMBOL32)( + ULONG offset, + PCHAR pchBuffer, + PULONG pDisplacement + ); + +typedef +VOID +(WDBGAPI*PWINDBG_GET_SYMBOL64)( + ULONG64 offset, + PCHAR pchBuffer, + PULONG64 pDisplacement + ); + +typedef +ULONG +(WDBGAPI*PWINDBG_DISASM)( + ULONG_PTR *lpOffset, + PCSTR lpBuffer, + ULONG fShowEffectiveAddress + ); + +typedef +ULONG +(WDBGAPI*PWINDBG_DISASM32)( + ULONG *lpOffset, + PCSTR lpBuffer, + ULONG fShowEffectiveAddress + ); + +typedef +ULONG +(WDBGAPI*PWINDBG_DISASM64)( + ULONG64 *lpOffset, + PCSTR lpBuffer, + ULONG fShowEffectiveAddress + ); + +typedef +ULONG +(WDBGAPI*PWINDBG_CHECK_CONTROL_C)( + VOID + ); + +typedef +ULONG +(WDBGAPI*PWINDBG_READ_PROCESS_MEMORY_ROUTINE)( + ULONG_PTR offset, + PVOID lpBuffer, + ULONG cb, + PULONG lpcbBytesRead + ); + +typedef +ULONG +(WDBGAPI*PWINDBG_READ_PROCESS_MEMORY_ROUTINE32)( + ULONG offset, + PVOID lpBuffer, + ULONG cb, + PULONG lpcbBytesRead + ); + +typedef +ULONG +(WDBGAPI*PWINDBG_READ_PROCESS_MEMORY_ROUTINE64)( + ULONG64 offset, + PVOID lpBuffer, + ULONG cb, + PULONG lpcbBytesRead + ); + +typedef +ULONG +(WDBGAPI*PWINDBG_WRITE_PROCESS_MEMORY_ROUTINE)( + ULONG_PTR offset, + LPCVOID lpBuffer, + ULONG cb, + PULONG lpcbBytesWritten + ); + +typedef +ULONG +(WDBGAPI*PWINDBG_WRITE_PROCESS_MEMORY_ROUTINE32)( + ULONG offset, + LPCVOID lpBuffer, + ULONG cb, + PULONG lpcbBytesWritten + ); + +typedef +ULONG +(WDBGAPI*PWINDBG_WRITE_PROCESS_MEMORY_ROUTINE64)( + ULONG64 offset, + LPCVOID lpBuffer, + ULONG cb, + PULONG lpcbBytesWritten + ); + +typedef +ULONG +(WDBGAPI*PWINDBG_GET_THREAD_CONTEXT_ROUTINE)( + ULONG Processor, + PCONTEXT lpContext, + ULONG cbSizeOfContext + ); + +typedef +ULONG +(WDBGAPI*PWINDBG_SET_THREAD_CONTEXT_ROUTINE)( + ULONG Processor, + PCONTEXT lpContext, + ULONG cbSizeOfContext + ); + +typedef +ULONG +(WDBGAPI*PWINDBG_IOCTL_ROUTINE)( + USHORT IoctlType, + PVOID lpvData, + ULONG cbSize + ); + +typedef +ULONG +(WDBGAPI*PWINDBG_OLDKD_READ_PHYSICAL_MEMORY)( + ULONGLONG address, + PVOID buffer, + ULONG count, + PULONG bytesread + ); + +typedef +ULONG +(WDBGAPI*PWINDBG_OLDKD_WRITE_PHYSICAL_MEMORY)( + ULONGLONG address, + PVOID buffer, + ULONG length, + PULONG byteswritten + ); + + +typedef struct _EXTSTACKTRACE { + ULONG FramePointer; + ULONG ProgramCounter; + ULONG ReturnAddress; + ULONG Args[4]; +} EXTSTACKTRACE, *PEXTSTACKTRACE; + +typedef struct _EXTSTACKTRACE32 { + ULONG FramePointer; + ULONG ProgramCounter; + ULONG ReturnAddress; + ULONG Args[4]; +} EXTSTACKTRACE32, *PEXTSTACKTRACE32; + +typedef struct _EXTSTACKTRACE64 { + ULONG64 FramePointer; + ULONG64 ProgramCounter; + ULONG64 ReturnAddress; + ULONG64 Args[4]; +} EXTSTACKTRACE64, *PEXTSTACKTRACE64; + + +typedef +ULONG +(WDBGAPI*PWINDBG_STACKTRACE_ROUTINE)( + ULONG FramePointer, + ULONG StackPointer, + ULONG ProgramCounter, + PEXTSTACKTRACE StackFrames, + ULONG Frames + ); + +typedef +ULONG +(WDBGAPI*PWINDBG_STACKTRACE_ROUTINE32)( + ULONG FramePointer, + ULONG StackPointer, + ULONG ProgramCounter, + PEXTSTACKTRACE32 StackFrames, + ULONG Frames + ); + +typedef +ULONG +(WDBGAPI*PWINDBG_STACKTRACE_ROUTINE64)( + ULONG64 FramePointer, + ULONG64 StackPointer, + ULONG64 ProgramCounter, + PEXTSTACKTRACE64 StackFrames, + ULONG Frames + ); + +typedef struct _WINDBG_EXTENSION_APIS { + ULONG nSize; + PWINDBG_OUTPUT_ROUTINE lpOutputRoutine; + PWINDBG_GET_EXPRESSION lpGetExpressionRoutine; + PWINDBG_GET_SYMBOL lpGetSymbolRoutine; + PWINDBG_DISASM lpDisasmRoutine; + PWINDBG_CHECK_CONTROL_C lpCheckControlCRoutine; + PWINDBG_READ_PROCESS_MEMORY_ROUTINE lpReadProcessMemoryRoutine; + PWINDBG_WRITE_PROCESS_MEMORY_ROUTINE lpWriteProcessMemoryRoutine; + PWINDBG_GET_THREAD_CONTEXT_ROUTINE lpGetThreadContextRoutine; + PWINDBG_SET_THREAD_CONTEXT_ROUTINE lpSetThreadContextRoutine; + PWINDBG_IOCTL_ROUTINE lpIoctlRoutine; + PWINDBG_STACKTRACE_ROUTINE lpStackTraceRoutine; +} WINDBG_EXTENSION_APIS, *PWINDBG_EXTENSION_APIS; + +typedef struct _WINDBG_EXTENSION_APIS32 { + ULONG nSize; + PWINDBG_OUTPUT_ROUTINE lpOutputRoutine; + PWINDBG_GET_EXPRESSION32 lpGetExpressionRoutine; + PWINDBG_GET_SYMBOL32 lpGetSymbolRoutine; + PWINDBG_DISASM32 lpDisasmRoutine; + PWINDBG_CHECK_CONTROL_C lpCheckControlCRoutine; + PWINDBG_READ_PROCESS_MEMORY_ROUTINE32 lpReadProcessMemoryRoutine; + PWINDBG_WRITE_PROCESS_MEMORY_ROUTINE32 lpWriteProcessMemoryRoutine; + PWINDBG_GET_THREAD_CONTEXT_ROUTINE lpGetThreadContextRoutine; + PWINDBG_SET_THREAD_CONTEXT_ROUTINE lpSetThreadContextRoutine; + PWINDBG_IOCTL_ROUTINE lpIoctlRoutine; + PWINDBG_STACKTRACE_ROUTINE32 lpStackTraceRoutine; +} WINDBG_EXTENSION_APIS32, *PWINDBG_EXTENSION_APIS32; + +typedef struct _WINDBG_EXTENSION_APIS64 { + ULONG nSize; + PWINDBG_OUTPUT_ROUTINE lpOutputRoutine; + PWINDBG_GET_EXPRESSION64 lpGetExpressionRoutine; + PWINDBG_GET_SYMBOL64 lpGetSymbolRoutine; + PWINDBG_DISASM64 lpDisasmRoutine; + PWINDBG_CHECK_CONTROL_C lpCheckControlCRoutine; + PWINDBG_READ_PROCESS_MEMORY_ROUTINE64 lpReadProcessMemoryRoutine; + PWINDBG_WRITE_PROCESS_MEMORY_ROUTINE64 lpWriteProcessMemoryRoutine; + PWINDBG_GET_THREAD_CONTEXT_ROUTINE lpGetThreadContextRoutine; + PWINDBG_SET_THREAD_CONTEXT_ROUTINE lpSetThreadContextRoutine; + PWINDBG_IOCTL_ROUTINE lpIoctlRoutine; + PWINDBG_STACKTRACE_ROUTINE64 lpStackTraceRoutine; +} WINDBG_EXTENSION_APIS64, *PWINDBG_EXTENSION_APIS64; + + +typedef struct _WINDBG_OLD_EXTENSION_APIS { + ULONG nSize; + PWINDBG_OUTPUT_ROUTINE lpOutputRoutine; + PWINDBG_GET_EXPRESSION lpGetExpressionRoutine; + PWINDBG_GET_SYMBOL lpGetSymbolRoutine; + PWINDBG_DISASM lpDisasmRoutine; + PWINDBG_CHECK_CONTROL_C lpCheckControlCRoutine; +} WINDBG_OLD_EXTENSION_APIS, *PWINDBG_OLD_EXTENSION_APIS; + +typedef struct _WINDBG_OLDKD_EXTENSION_APIS { + ULONG nSize; + PWINDBG_OUTPUT_ROUTINE lpOutputRoutine; + PWINDBG_GET_EXPRESSION32 lpGetExpressionRoutine; + PWINDBG_GET_SYMBOL32 lpGetSymbolRoutine; + PWINDBG_DISASM32 lpDisasmRoutine; + PWINDBG_CHECK_CONTROL_C lpCheckControlCRoutine; + PWINDBG_READ_PROCESS_MEMORY_ROUTINE32 lpReadVirtualMemRoutine; + PWINDBG_WRITE_PROCESS_MEMORY_ROUTINE32 lpWriteVirtualMemRoutine; + PWINDBG_OLDKD_READ_PHYSICAL_MEMORY lpReadPhysicalMemRoutine; + PWINDBG_OLDKD_WRITE_PHYSICAL_MEMORY lpWritePhysicalMemRoutine; +} WINDBG_OLDKD_EXTENSION_APIS, *PWINDBG_OLDKD_EXTENSION_APIS; + +typedef +VOID +(WDBGAPI*PWINDBG_OLD_EXTENSION_ROUTINE)( + ULONG dwCurrentPc, + PWINDBG_EXTENSION_APIS lpExtensionApis, + PCSTR lpArgumentString + ); + +typedef +VOID +(WDBGAPI*PWINDBG_EXTENSION_ROUTINE)( + HANDLE hCurrentProcess, + HANDLE hCurrentThread, + ULONG dwCurrentPc, + ULONG dwProcessor, + PCSTR lpArgumentString + ); + +typedef +VOID +(WDBGAPI*PWINDBG_EXTENSION_ROUTINE32)( + HANDLE hCurrentProcess, + HANDLE hCurrentThread, + ULONG dwCurrentPc, + ULONG dwProcessor, + PCSTR lpArgumentString + ); + +typedef +VOID +(WDBGAPI*PWINDBG_EXTENSION_ROUTINE64)( + HANDLE hCurrentProcess, + HANDLE hCurrentThread, + ULONG64 dwCurrentPc, + ULONG dwProcessor, + PCSTR lpArgumentString + ); + +typedef +VOID +(WDBGAPI*PWINDBG_OLDKD_EXTENSION_ROUTINE)( + ULONG dwCurrentPc, + PWINDBG_OLDKD_EXTENSION_APIS lpExtensionApis, + PCSTR lpArgumentString + ); + +typedef +VOID +(WDBGAPI*PWINDBG_EXTENSION_DLL_INIT)( + PWINDBG_EXTENSION_APIS lpExtensionApis, + USHORT MajorVersion, + USHORT MinorVersion + ); + +typedef +VOID +(WDBGAPI*PWINDBG_EXTENSION_DLL_INIT32)( + PWINDBG_EXTENSION_APIS32 lpExtensionApis, + USHORT MajorVersion, + USHORT MinorVersion + ); + +typedef +VOID +(WDBGAPI*PWINDBG_EXTENSION_DLL_INIT64)( + PWINDBG_EXTENSION_APIS64 lpExtensionApis, + USHORT MajorVersion, + USHORT MinorVersion + ); + +typedef +ULONG +(WDBGAPI*PWINDBG_CHECK_VERSION)( + VOID + ); + +#define EXT_API_VERSION_NUMBER 5 +#define EXT_API_VERSION_NUMBER32 5 +#define EXT_API_VERSION_NUMBER64 6 + +typedef struct EXT_API_VERSION { + USHORT MajorVersion; + USHORT MinorVersion; + USHORT Revision; + USHORT Reserved; +} EXT_API_VERSION, *LPEXT_API_VERSION; + +typedef +LPEXT_API_VERSION +(WDBGAPI*PWINDBG_EXTENSION_API_VERSION)( + VOID + ); + +#define IG_KD_CONTEXT 1 +#define IG_READ_CONTROL_SPACE 2 +#define IG_WRITE_CONTROL_SPACE 3 +#define IG_READ_IO_SPACE 4 +#define IG_WRITE_IO_SPACE 5 +#define IG_READ_PHYSICAL 6 +#define IG_WRITE_PHYSICAL 7 +#define IG_READ_IO_SPACE_EX 8 +#define IG_WRITE_IO_SPACE_EX 9 +#define IG_KSTACK_HELP 10 // obsolete +#define IG_SET_THREAD 11 +#define IG_READ_MSR 12 +#define IG_WRITE_MSR 13 +#define IG_GET_DEBUGGER_DATA 14 +#define IG_GET_KERNEL_VERSION 15 +#define IG_RELOAD_SYMBOLS 16 +#define IG_GET_SET_SYMPATH 17 +#define IG_GET_EXCEPTION_RECORD 18 +#define IG_IS_PTR64 19 +#define IG_GET_BUS_DATA 20 +#define IG_SET_BUS_DATA 21 +#define IG_DUMP_SYMBOL_INFO 22 +#define IG_LOWMEM_CHECK 23 +#define IG_SEARCH_MEMORY 24 +#define IG_GET_CURRENT_THREAD 25 +#define IG_GET_CURRENT_PROCESS 26 +#define IG_GET_TYPE_SIZE 27 +#define IG_GET_CURRENT_PROCESS_HANDLE 28 +#define IG_GET_INPUT_LINE 29 +#define IG_GET_EXPRESSION_EX 30 +#define IG_TRANSLATE_VIRTUAL_TO_PHYSICAL 31 +#define IG_GET_CACHE_SIZE 32 +#define IG_READ_PHYSICAL_WITH_FLAGS 33 +#define IG_WRITE_PHYSICAL_WITH_FLAGS 34 +#define IG_POINTER_SEARCH_PHYSICAL 35 +#define IG_OBSOLETE_PLACEHOLDER_36 36 +#define IG_GET_THREAD_OS_INFO 37 +#define IG_GET_CLR_DATA_INTERFACE 38 +#define IG_MATCH_PATTERN_A 39 +#define IG_FIND_FILE 40 +#define IG_TYPED_DATA_OBSOLETE 41 +#define IG_QUERY_TARGET_INTERFACE 42 +#define IG_TYPED_DATA 43 +#define IG_DISASSEMBLE_BUFFER 44 +#define IG_GET_ANY_MODULE_IN_RANGE 45 + +#define IG_GET_TEB_ADDRESS 128 +#define IG_GET_PEB_ADDRESS 129 + +typedef struct _PROCESSORINFO { + USHORT Processor; // current processor + USHORT NumberProcessors; // total number of processors +} PROCESSORINFO, *PPROCESSORINFO; + +typedef struct _READCONTROLSPACE { + USHORT Processor; + ULONG Address; + ULONG BufLen; + UCHAR Buf[1]; +} READCONTROLSPACE, *PREADCONTROLSPACE; + +typedef struct _READCONTROLSPACE32 { + USHORT Processor; + ULONG Address; + ULONG BufLen; + UCHAR Buf[1]; +} READCONTROLSPACE32, *PREADCONTROLSPACE32; + +typedef struct _READCONTROLSPACE64 { + USHORT Processor; + ULONG64 Address; + ULONG BufLen; + UCHAR Buf[1]; +} READCONTROLSPACE64, *PREADCONTROLSPACE64; + +typedef struct _IOSPACE { + ULONG Address; + ULONG Length; // 1, 2, or 4 bytes + ULONG Data; +} IOSPACE, *PIOSPACE; + +typedef struct _IOSPACE32 { + ULONG Address; + ULONG Length; // 1, 2, or 4 bytes + ULONG Data; +} IOSPACE32, *PIOSPACE32; + +typedef struct _IOSPACE64 { + ULONG64 Address; + ULONG Length; // 1, 2, or 4 bytes + ULONG Data; +} IOSPACE64, *PIOSPACE64; + +typedef struct _IOSPACE_EX { + ULONG Address; + ULONG Length; // 1, 2, or 4 bytes + ULONG Data; + ULONG InterfaceType; + ULONG BusNumber; + ULONG AddressSpace; +} IOSPACE_EX, *PIOSPACE_EX; + +typedef struct _IOSPACE_EX32 { + ULONG Address; + ULONG Length; // 1, 2, or 4 bytes + ULONG Data; + ULONG InterfaceType; + ULONG BusNumber; + ULONG AddressSpace; +} IOSPACE_EX32, *PIOSPACE_EX32; + +typedef struct _IOSPACE_EX64 { + ULONG64 Address; + ULONG Length; // 1, 2, or 4 bytes + ULONG Data; + ULONG InterfaceType; + ULONG BusNumber; + ULONG AddressSpace; +} IOSPACE_EX64, *PIOSPACE_EX64; + +typedef struct _GETSETBUSDATA { + ULONG BusDataType; + ULONG BusNumber; + ULONG SlotNumber; + PVOID Buffer; + ULONG Offset; + ULONG Length; +} BUSDATA, *PBUSDATA; + +typedef struct _SEARCHMEMORY { + ULONG64 SearchAddress; + ULONG64 SearchLength; + ULONG64 FoundAddress; + ULONG PatternLength; + PVOID Pattern; +} SEARCHMEMORY, *PSEARCHMEMORY; + +typedef struct _PHYSICAL { + ULONGLONG Address; + ULONG BufLen; + UCHAR Buf[1]; +} PHYSICAL, *PPHYSICAL; + +#define PHYS_FLAG_DEFAULT 0 +#define PHYS_FLAG_CACHED 1 +#define PHYS_FLAG_UNCACHED 2 +#define PHYS_FLAG_WRITE_COMBINED 3 + +typedef struct _PHYSICAL_WITH_FLAGS { + ULONGLONG Address; + ULONG BufLen; + ULONG Flags; + UCHAR Buf[1]; +} PHYSICAL_WITH_FLAGS, *PPHYSICAL_WITH_FLAGS; + +typedef struct _READ_WRITE_MSR { + ULONG Msr; + LONGLONG Value; +} READ_WRITE_MSR, *PREAD_WRITE_MSR; + +typedef struct _GET_SET_SYMPATH { + PCSTR Args; // args to !reload command + PSTR Result; // returns new path + int Length; // Length of result buffer +} GET_SET_SYMPATH, *PGET_SET_SYMPATH; + +typedef struct _GET_TEB_ADDRESS { + ULONGLONG Address; +} GET_TEB_ADDRESS, *PGET_TEB_ADDRESS; + +typedef struct _GET_PEB_ADDRESS { + ULONG64 CurrentThread; + ULONGLONG Address; +} GET_PEB_ADDRESS, *PGET_PEB_ADDRESS; + +typedef struct _GET_CURRENT_THREAD_ADDRESS { + ULONG Processor; + ULONG64 Address; +} GET_CURRENT_THREAD_ADDRESS, *PGET_CURRENT_THREAD_ADDRESS; + +typedef struct _GET_CURRENT_PROCESS_ADDRESS { + ULONG Processor; + ULONG64 CurrentThread; + ULONG64 Address; +} GET_CURRENT_PROCESS_ADDRESS, *PGET_CURRENT_PROCESS_ADDRESS; + +typedef struct _GET_INPUT_LINE { + PCSTR Prompt; + PSTR Buffer; + ULONG BufferSize; + ULONG InputSize; +} GET_INPUT_LINE, *PGET_INPUT_LINE; + +typedef struct _GET_EXPRESSION_EX { + PCSTR Expression; + PCSTR Remainder; + ULONG64 Value; +} GET_EXPRESSION_EX, *PGET_EXPRESSION_EX; + +typedef struct _TRANSLATE_VIRTUAL_TO_PHYSICAL { + ULONG64 Virtual; + ULONG64 Physical; +} TRANSLATE_VIRTUAL_TO_PHYSICAL, *PTRANSLATE_VIRTUAL_TO_PHYSICAL; + +#define PTR_SEARCH_PHYS_ALL_HITS 0x00000001 +#define PTR_SEARCH_PHYS_PTE 0x00000002 +#define PTR_SEARCH_PHYS_RANGE_CHECK_ONLY 0x00000004 + +#define PTR_SEARCH_PHYS_SIZE_SHIFT 3 +#define PTR_SEARCH_PHYS_SIZE_MASK (0xf << PTR_SEARCH_PHYS_SIZE_SHIFT) + +#define PTR_SEARCH_NO_SYMBOL_CHECK 0x80000000 + +typedef struct _POINTER_SEARCH_PHYSICAL { + IN ULONG64 Offset; + IN ULONG64 Length; + IN ULONG64 PointerMin; + IN ULONG64 PointerMax; + IN ULONG Flags; + OUT PULONG64 MatchOffsets; + IN ULONG MatchOffsetsSize; + OUT ULONG MatchOffsetsCount; +} POINTER_SEARCH_PHYSICAL, *PPOINTER_SEARCH_PHYSICAL; + +typedef struct _WDBGEXTS_THREAD_OS_INFO { + // System thread ID input. + ULONG ThreadId; + + // + // Output information. + // + + // Exit status is STILL_ACTIVE by default. + ULONG ExitStatus; + // Priority class is zero if not known. + ULONG PriorityClass; + // Priority defaults to normal. + ULONG Priority; + // Times can be zero if not known. + ULONG64 CreateTime; + ULONG64 ExitTime; + ULONG64 KernelTime; + ULONG64 UserTime; + // Start offset is zero if not known. + ULONG64 StartOffset; + // Affinity is zero if not known. + ULONG64 Affinity; +} WDBGEXTS_THREAD_OS_INFO, *PWDBGEXTS_THREAD_OS_INFO; + +typedef struct _WDBGEXTS_CLR_DATA_INTERFACE { + // Interface requested. + const IID* Iid; + // Interface pointer return. + PVOID Iface; +} WDBGEXTS_CLR_DATA_INTERFACE, *PWDBGEXTS_CLR_DATA_INTERFACE; + +typedef struct _EXT_MATCH_PATTERN_A { + IN PCSTR Str; + IN PCSTR Pattern; + IN ULONG CaseSensitive; +} EXT_MATCH_PATTERN_A, *PEXT_MATCH_PATTERN_A; + +#define EXT_FIND_FILE_ALLOW_GIVEN_PATH 0x00000001 + +typedef struct _EXT_FIND_FILE { + IN PCWSTR FileName; + IN ULONG64 IndexedSize; + IN ULONG ImageTimeDateStamp; + // Pass zero to ignore. + IN ULONG ImageCheckSum; + IN OPTIONAL PVOID ExtraInfo; + IN ULONG ExtraInfoSize; + IN ULONG Flags; + // Free with UnmapViewOfFile. + OUT PVOID FileMapping; + OUT ULONG64 FileMappingSize; + // Free with CloseHandle. + OUT HANDLE FileHandle; + // Must be at least MAX_PATH characters if set. + OUT OPTIONAL PWSTR FoundFileName; + OUT ULONG FoundFileNameChars; +} EXT_FIND_FILE, *PEXT_FIND_FILE; + +#define DEBUG_TYPED_DATA_IS_IN_MEMORY 0x00000001 +#define DEBUG_TYPED_DATA_PHYSICAL_DEFAULT 0x00000002 +#define DEBUG_TYPED_DATA_PHYSICAL_CACHED 0x00000004 +#define DEBUG_TYPED_DATA_PHYSICAL_UNCACHED 0x00000006 +#define DEBUG_TYPED_DATA_PHYSICAL_WRITE_COMBINED 0x00000008 + +// Mask for all physical flags. +#define DEBUG_TYPED_DATA_PHYSICAL_MEMORY 0x0000000e + +typedef struct _DEBUG_TYPED_DATA +{ + ULONG64 ModBase; + ULONG64 Offset; + ULONG64 EngineHandle; + ULONG64 Data; + ULONG Size; + ULONG Flags; + ULONG TypeId; + ULONG BaseTypeId; + ULONG Tag; + ULONG Register; + ULONG64 Internal[9]; +} DEBUG_TYPED_DATA, *PDEBUG_TYPED_DATA; + +typedef enum _EXT_TDOP { + EXT_TDOP_COPY, + EXT_TDOP_RELEASE, + EXT_TDOP_SET_FROM_EXPR, + EXT_TDOP_SET_FROM_U64_EXPR, + EXT_TDOP_GET_FIELD, + EXT_TDOP_EVALUATE, + EXT_TDOP_GET_TYPE_NAME, + EXT_TDOP_OUTPUT_TYPE_NAME, + EXT_TDOP_OUTPUT_SIMPLE_VALUE, + EXT_TDOP_OUTPUT_FULL_VALUE, + EXT_TDOP_HAS_FIELD, + EXT_TDOP_GET_FIELD_OFFSET, + EXT_TDOP_GET_ARRAY_ELEMENT, + EXT_TDOP_GET_DEREFERENCE, + EXT_TDOP_GET_TYPE_SIZE, + EXT_TDOP_OUTPUT_TYPE_DEFINITION, + EXT_TDOP_GET_POINTER_TO, + EXT_TDOP_SET_FROM_TYPE_ID_AND_U64, + EXT_TDOP_SET_PTR_FROM_TYPE_ID_AND_U64, + + EXT_TDOP_COUNT +} EXT_TDOP; + +// EXT_TDF physical flags must match DEBUG_TYPED. +#define EXT_TDF_PHYSICAL_DEFAULT 0x00000002 +#define EXT_TDF_PHYSICAL_CACHED 0x00000004 +#define EXT_TDF_PHYSICAL_UNCACHED 0x00000006 +#define EXT_TDF_PHYSICAL_WRITE_COMBINED 0x00000008 +#define EXT_TDF_PHYSICAL_MEMORY 0x0000000e + +// NOTE: Every DEBUG_TYPED_DATA should be released +// via EXT_TDOP_RELEASE when it is no longer needed. +typedef struct _EXT_TYPED_DATA { + IN EXT_TDOP Operation; + IN ULONG Flags; + IN DEBUG_TYPED_DATA InData; + OUT DEBUG_TYPED_DATA OutData; + IN ULONG InStrIndex; + IN ULONG In32; + OUT ULONG Out32; + IN ULONG64 In64; + OUT ULONG64 Out64; + OUT ULONG StrBufferIndex; + IN ULONG StrBufferChars; + OUT ULONG StrCharsNeeded; + IN OUT ULONG DataBufferIndex; + IN ULONG DataBufferBytes; + OUT ULONG DataBytesNeeded; + OUT HRESULT Status; + // Must be zeroed. + ULONG64 Reserved[8]; +} EXT_TYPED_DATA, *PEXT_TYPED_DATA; + +typedef struct _WDBGEXTS_QUERY_INTERFACE { + // Interface requested. + const IID* Iid; + // Interface pointer return. + PVOID Iface; +} WDBGEXTS_QUERY_INTERFACE, *PWDBGEXTS_QUERY_INTERFACE; + +#define WDBGEXTS_ADDRESS_DEFAULT 0x00000000 +#define WDBGEXTS_ADDRESS_SEG16 0x00000001 +#define WDBGEXTS_ADDRESS_SEG32 0x00000002 +#define WDBGEXTS_ADDRESS_RESERVED0 0x80000000 + +typedef struct _WDBGEXTS_DISASSEMBLE_BUFFER { + IN ULONG64 InOffset; + OUT ULONG64 OutOffset; + // AddrFlags are from above. + IN ULONG AddrFlags; + // FormatFlags are from dbgeng's DEBUG_DISASM_*. + IN ULONG FormatFlags; + IN ULONG DataBufferBytes; + IN ULONG DisasmBufferChars; + IN OPTIONAL PVOID DataBuffer; + OUT PWSTR DisasmBuffer; + IN ULONG64 Reserved0[3]; +} WDBGEXTS_DISASSEMBLE_BUFFER, *PWDBGEXTS_DISASSEMBLE_BUFFER; + +typedef struct _WDBGEXTS_MODULE_IN_RANGE { + IN ULONG64 Start; + // Inclusive ending offset. + IN ULONG64 End; + OUT ULONG64 FoundModBase; + OUT ULONG FoundModSize; +} WDBGEXTS_MODULE_IN_RANGE, *PWDBGEXTS_MODULE_IN_RANGE; + +// +// If DBGKD_VERS_FLAG_DATA is set in Flags, info should be retrieved from +// the KDDEBUGGER_DATA block rather than from the DBGKD_GET_VERSION +// packet. The data will remain in the version packet for a while to +// reduce compatibility problems. +// + +#define DBGKD_VERS_FLAG_MP 0x0001 // kernel is MP built +#define DBGKD_VERS_FLAG_DATA 0x0002 // DebuggerDataList is valid +#define DBGKD_VERS_FLAG_PTR64 0x0004 // native pointers are 64 bits +#define DBGKD_VERS_FLAG_NOMM 0x0008 // No MM - don't decode PTEs +#define DBGKD_VERS_FLAG_HSS 0x0010 // hardware stepping support +#define DBGKD_VERS_FLAG_PARTITIONS 0x0020 // multiple OS partitions exist + +#define KDBG_TAG 'GBDK' + +// +// KD version MajorVersion high-byte identifiers. +// + +typedef enum _DBGKD_MAJOR_TYPES +{ + DBGKD_MAJOR_NT, + DBGKD_MAJOR_XBOX, + DBGKD_MAJOR_BIG, + DBGKD_MAJOR_EXDI, + DBGKD_MAJOR_NTBD, + DBGKD_MAJOR_EFI, + DBGKD_MAJOR_TNT, + DBGKD_MAJOR_SINGULARITY, + DBGKD_MAJOR_HYPERVISOR, + DBGKD_MAJOR_COUNT +} DBGKD_MAJOR_TYPES; + +#define DBGKD_MAJOR_TYPE(MajorVersion) \ + ((DBGKD_MAJOR_TYPES)((MajorVersion) >> 8)) + + +// ********************************************************************** +// DO NOT CHANGE THESE 32 BIT STRUCTURES! +// ONLY MAKE CHAGES TO THE 64 BIT VERSION BELOW!! +// ********************************************************************** + +// +// The following structure has changed in more than pointer size. +// +// This is the version packet for pre-NT5 Beta 2 systems. +// For now, it is also still used on x86 +// +typedef struct _DBGKD_GET_VERSION32 { + USHORT MajorVersion; + USHORT MinorVersion; + USHORT ProtocolVersion; + USHORT Flags; + ULONG KernBase; + ULONG PsLoadedModuleList; + + USHORT MachineType; + + // + // help for walking stacks with user callbacks: + // + + // + // The address of the thread structure is provided in the + // WAIT_STATE_CHANGE packet. This is the offset from the base of + // the thread structure to the pointer to the kernel stack frame + // for the currently active usermode callback. + // + + USHORT ThCallbackStack; // offset in thread data + + // + // these values are offsets into that frame: + // + + USHORT NextCallback; // saved pointer to next callback frame + USHORT FramePointer; // saved frame pointer + + // + // Address of the kernel callout routine. + // + + ULONG KiCallUserMode; // kernel routine + + // + // Address of the usermode entry point for callbacks. + // + + ULONG KeUserCallbackDispatcher; // address in ntdll + + // + // DbgBreakPointWithStatus is a function which takes a ULONG argument + // and hits a breakpoint. This field contains the address of the + // breakpoint instruction. When the debugger sees a breakpoint + // at this address, it may retrieve the argument from the first + // argument register, or on x86 the eax register. + // + + ULONG BreakpointWithStatus; // address of breakpoint + + // + // Components may register a debug data block for use by + // debugger extensions. This is the address of the list head. + // + + ULONG DebuggerDataList; + +} DBGKD_GET_VERSION32, *PDBGKD_GET_VERSION32; + + +// +// This is the debugger data packet for pre NT5 Beta 2 systems. +// For now, it is still used on x86 +// + +typedef struct _DBGKD_DEBUG_DATA_HEADER32 { + + LIST_ENTRY32 List; + ULONG OwnerTag; + ULONG Size; + +} DBGKD_DEBUG_DATA_HEADER32, *PDBGKD_DEBUG_DATA_HEADER32; + +typedef struct _KDDEBUGGER_DATA32 { + + DBGKD_DEBUG_DATA_HEADER32 Header; + ULONG KernBase; + ULONG BreakpointWithStatus; // address of breakpoint + ULONG SavedContext; + USHORT ThCallbackStack; // offset in thread data + USHORT NextCallback; // saved pointer to next callback frame + USHORT FramePointer; // saved frame pointer + USHORT PaeEnabled:1; + ULONG KiCallUserMode; // kernel routine + ULONG KeUserCallbackDispatcher; // address in ntdll + + ULONG PsLoadedModuleList; + ULONG PsActiveProcessHead; + ULONG PspCidTable; + + ULONG ExpSystemResourcesList; + ULONG ExpPagedPoolDescriptor; + ULONG ExpNumberOfPagedPools; + + ULONG KeTimeIncrement; + ULONG KeBugCheckCallbackListHead; + ULONG KiBugcheckData; + + ULONG IopErrorLogListHead; + + ULONG ObpRootDirectoryObject; + ULONG ObpTypeObjectType; + + ULONG MmSystemCacheStart; + ULONG MmSystemCacheEnd; + ULONG MmSystemCacheWs; + + ULONG MmPfnDatabase; + ULONG MmSystemPtesStart; + ULONG MmSystemPtesEnd; + ULONG MmSubsectionBase; + ULONG MmNumberOfPagingFiles; + + ULONG MmLowestPhysicalPage; + ULONG MmHighestPhysicalPage; + ULONG MmNumberOfPhysicalPages; + + ULONG MmMaximumNonPagedPoolInBytes; + ULONG MmNonPagedSystemStart; + ULONG MmNonPagedPoolStart; + ULONG MmNonPagedPoolEnd; + + ULONG MmPagedPoolStart; + ULONG MmPagedPoolEnd; + ULONG MmPagedPoolInformation; + ULONG MmPageSize; + + ULONG MmSizeOfPagedPoolInBytes; + + ULONG MmTotalCommitLimit; + ULONG MmTotalCommittedPages; + ULONG MmSharedCommit; + ULONG MmDriverCommit; + ULONG MmProcessCommit; + ULONG MmPagedPoolCommit; + ULONG MmExtendedCommit; + + ULONG MmZeroedPageListHead; + ULONG MmFreePageListHead; + ULONG MmStandbyPageListHead; + ULONG MmModifiedPageListHead; + ULONG MmModifiedNoWritePageListHead; + ULONG MmAvailablePages; + ULONG MmResidentAvailablePages; + + ULONG PoolTrackTable; + ULONG NonPagedPoolDescriptor; + + ULONG MmHighestUserAddress; + ULONG MmSystemRangeStart; + ULONG MmUserProbeAddress; + + ULONG KdPrintCircularBuffer; + ULONG KdPrintCircularBufferEnd; + ULONG KdPrintWritePointer; + ULONG KdPrintRolloverCount; + + ULONG MmLoadedUserImageList; + +} KDDEBUGGER_DATA32, *PKDDEBUGGER_DATA32; + +// ********************************************************************** +// +// DO NOT CHANGE KDDEBUGGER_DATA32!! +// ONLY MAKE CHANGES TO KDDEBUGGER_DATA64!!! +// +// ********************************************************************** + + +enum +{ + DBGKD_SIMULATION_NONE, + DBGKD_SIMULATION_EXDI +}; + +#define KD_SECONDARY_VERSION_DEFAULT 0 + +#define KD_SECONDARY_VERSION_AMD64_OBSOLETE_CONTEXT_1 0 +#define KD_SECONDARY_VERSION_AMD64_OBSOLETE_CONTEXT_2 1 +#define KD_SECONDARY_VERSION_AMD64_CONTEXT 2 + +#ifdef _AMD64_ +#define CURRENT_KD_SECONDARY_VERSION \ + KD_SECONDARY_VERSION_AMD64_CONTEXT +#else +#define CURRENT_KD_SECONDARY_VERSION KD_SECONDARY_VERSION_DEFAULT +#endif + +typedef struct _DBGKD_GET_VERSION64 { + USHORT MajorVersion; + USHORT MinorVersion; + UCHAR ProtocolVersion; + UCHAR KdSecondaryVersion; // Cannot be 'A' for compat with dump header + USHORT Flags; + USHORT MachineType; + + // + // Protocol command support descriptions. + // These allow the debugger to automatically + // adapt to different levels of command support + // in different kernels. + // + + // One beyond highest packet type understood, zero based. + UCHAR MaxPacketType; + // One beyond highest state change understood, zero based. + UCHAR MaxStateChange; + // One beyond highest state manipulate message understood, zero based. + UCHAR MaxManipulate; + + // Kind of execution environment the kernel is running in, + // such as a real machine or a simulator. Written back + // by the simulation if one exists. + UCHAR Simulation; + + USHORT Unused[1]; + + ULONG64 KernBase; + ULONG64 PsLoadedModuleList; + + // + // Components may register a debug data block for use by + // debugger extensions. This is the address of the list head. + // + // There will always be an entry for the debugger. + // + + ULONG64 DebuggerDataList; + +} DBGKD_GET_VERSION64, *PDBGKD_GET_VERSION64; + + +// +// This structure is used by the debugger for all targets +// It is the same size as DBGKD_DATA_HEADER on all systems +// +typedef struct _DBGKD_DEBUG_DATA_HEADER64 { + + // + // Link to other blocks + // + + LIST_ENTRY64 List; + + // + // This is a unique tag to identify the owner of the block. + // If your component only uses one pool tag, use it for this, too. + // + + ULONG OwnerTag; + + // + // This must be initialized to the size of the data block, + // including this structure. + // + + ULONG Size; + +} DBGKD_DEBUG_DATA_HEADER64, *PDBGKD_DEBUG_DATA_HEADER64; + + +// +// This structure is the same size on all systems. The only field +// which must be translated by the debugger is Header.List. +// + +// +// DO NOT ADD OR REMOVE FIELDS FROM THE MIDDLE OF THIS STRUCTURE!!! +// +// If you remove a field, replace it with an "unused" placeholder. +// Do not reuse fields until there has been enough time for old debuggers +// and extensions to age out. +// +typedef struct _KDDEBUGGER_DATA64 { + + DBGKD_DEBUG_DATA_HEADER64 Header; + + // + // Base address of kernel image + // + + ULONG64 KernBase; + + // + // DbgBreakPointWithStatus is a function which takes an argument + // and hits a breakpoint. This field contains the address of the + // breakpoint instruction. When the debugger sees a breakpoint + // at this address, it may retrieve the argument from the first + // argument register, or on x86 the eax register. + // + + ULONG64 BreakpointWithStatus; // address of breakpoint + + // + // Address of the saved context record during a bugcheck + // + // N.B. This is an automatic in KeBugcheckEx's frame, and + // is only valid after a bugcheck. + // + + ULONG64 SavedContext; + + // + // help for walking stacks with user callbacks: + // + + // + // The address of the thread structure is provided in the + // WAIT_STATE_CHANGE packet. This is the offset from the base of + // the thread structure to the pointer to the kernel stack frame + // for the currently active usermode callback. + // + + USHORT ThCallbackStack; // offset in thread data + + // + // these values are offsets into that frame: + // + + USHORT NextCallback; // saved pointer to next callback frame + USHORT FramePointer; // saved frame pointer + + // + // pad to a quad boundary + // + USHORT PaeEnabled:1; + + // + // Address of the kernel callout routine. + // + + ULONG64 KiCallUserMode; // kernel routine + + // + // Address of the usermode entry point for callbacks. + // + + ULONG64 KeUserCallbackDispatcher; // address in ntdll + + + // + // Addresses of various kernel data structures and lists + // that are of interest to the kernel debugger. + // + + ULONG64 PsLoadedModuleList; + ULONG64 PsActiveProcessHead; + ULONG64 PspCidTable; + + ULONG64 ExpSystemResourcesList; + ULONG64 ExpPagedPoolDescriptor; + ULONG64 ExpNumberOfPagedPools; + + ULONG64 KeTimeIncrement; + ULONG64 KeBugCheckCallbackListHead; + ULONG64 KiBugcheckData; + + ULONG64 IopErrorLogListHead; + + ULONG64 ObpRootDirectoryObject; + ULONG64 ObpTypeObjectType; + + ULONG64 MmSystemCacheStart; + ULONG64 MmSystemCacheEnd; + ULONG64 MmSystemCacheWs; + + ULONG64 MmPfnDatabase; + ULONG64 MmSystemPtesStart; + ULONG64 MmSystemPtesEnd; + ULONG64 MmSubsectionBase; + ULONG64 MmNumberOfPagingFiles; + + ULONG64 MmLowestPhysicalPage; + ULONG64 MmHighestPhysicalPage; + ULONG64 MmNumberOfPhysicalPages; + + ULONG64 MmMaximumNonPagedPoolInBytes; + ULONG64 MmNonPagedSystemStart; + ULONG64 MmNonPagedPoolStart; + ULONG64 MmNonPagedPoolEnd; + + ULONG64 MmPagedPoolStart; + ULONG64 MmPagedPoolEnd; + ULONG64 MmPagedPoolInformation; + ULONG64 MmPageSize; + + ULONG64 MmSizeOfPagedPoolInBytes; + + ULONG64 MmTotalCommitLimit; + ULONG64 MmTotalCommittedPages; + ULONG64 MmSharedCommit; + ULONG64 MmDriverCommit; + ULONG64 MmProcessCommit; + ULONG64 MmPagedPoolCommit; + ULONG64 MmExtendedCommit; + + ULONG64 MmZeroedPageListHead; + ULONG64 MmFreePageListHead; + ULONG64 MmStandbyPageListHead; + ULONG64 MmModifiedPageListHead; + ULONG64 MmModifiedNoWritePageListHead; + ULONG64 MmAvailablePages; + ULONG64 MmResidentAvailablePages; + + ULONG64 PoolTrackTable; + ULONG64 NonPagedPoolDescriptor; + + ULONG64 MmHighestUserAddress; + ULONG64 MmSystemRangeStart; + ULONG64 MmUserProbeAddress; + + ULONG64 KdPrintCircularBuffer; + ULONG64 KdPrintCircularBufferEnd; + ULONG64 KdPrintWritePointer; + ULONG64 KdPrintRolloverCount; + + ULONG64 MmLoadedUserImageList; + + // NT 5.1 Addition + + ULONG64 NtBuildLab; + ULONG64 KiNormalSystemCall; + + // NT 5.0 hotfix addition + + ULONG64 KiProcessorBlock; + ULONG64 MmUnloadedDrivers; + ULONG64 MmLastUnloadedDriver; + ULONG64 MmTriageActionTaken; + ULONG64 MmSpecialPoolTag; + ULONG64 KernelVerifier; + ULONG64 MmVerifierData; + ULONG64 MmAllocatedNonPagedPool; + ULONG64 MmPeakCommitment; + ULONG64 MmTotalCommitLimitMaximum; + ULONG64 CmNtCSDVersion; + + // NT 5.1 Addition + + ULONG64 MmPhysicalMemoryBlock; + ULONG64 MmSessionBase; + ULONG64 MmSessionSize; + ULONG64 MmSystemParentTablePage; + + // Server 2003 addition + + ULONG64 MmVirtualTranslationBase; + + USHORT OffsetKThreadNextProcessor; + USHORT OffsetKThreadTeb; + USHORT OffsetKThreadKernelStack; + USHORT OffsetKThreadInitialStack; + + USHORT OffsetKThreadApcProcess; + USHORT OffsetKThreadState; + USHORT OffsetKThreadBStore; + USHORT OffsetKThreadBStoreLimit; + + USHORT SizeEProcess; + USHORT OffsetEprocessPeb; + USHORT OffsetEprocessParentCID; + USHORT OffsetEprocessDirectoryTableBase; + + USHORT SizePrcb; + USHORT OffsetPrcbDpcRoutine; + USHORT OffsetPrcbCurrentThread; + USHORT OffsetPrcbMhz; + + USHORT OffsetPrcbCpuType; + USHORT OffsetPrcbVendorString; + USHORT OffsetPrcbProcStateContext; + USHORT OffsetPrcbNumber; + + USHORT SizeEThread; + + ULONG64 KdPrintCircularBufferPtr; + ULONG64 KdPrintBufferSize; + + ULONG64 KeLoaderBlock; + + USHORT SizePcr; + USHORT OffsetPcrSelfPcr; + USHORT OffsetPcrCurrentPrcb; + USHORT OffsetPcrContainedPrcb; + + USHORT OffsetPcrInitialBStore; + USHORT OffsetPcrBStoreLimit; + USHORT OffsetPcrInitialStack; + USHORT OffsetPcrStackLimit; + + USHORT OffsetPrcbPcrPage; + USHORT OffsetPrcbProcStateSpecialReg; + USHORT GdtR0Code; + USHORT GdtR0Data; + + USHORT GdtR0Pcr; + USHORT GdtR3Code; + USHORT GdtR3Data; + USHORT GdtR3Teb; + + USHORT GdtLdt; + USHORT GdtTss; + USHORT Gdt64R3CmCode; + USHORT Gdt64R3CmTeb; + + ULONG64 IopNumTriageDumpDataBlocks; + ULONG64 IopTriageDumpDataBlocks; + + // Longhorn addition + + ULONG64 VfCrashDataBlock; + ULONG64 MmBadPagesDetected; + ULONG64 MmZeroedPageSingleBitErrorsDetected; + + +} KDDEBUGGER_DATA64, *PKDDEBUGGER_DATA64; + + + +/************************************ + + Type Dump Ioctl + +*************************************/ + + +// +// Fields are not indented if this is set +// +#define DBG_DUMP_NO_INDENT 0x00000001 +// +// Offsets are not printed if this is set +// +#define DBG_DUMP_NO_OFFSET 0x00000002 +// +// Verbose output +// +#define DBG_DUMP_VERBOSE 0x00000004 +// +// Callback is done for each of fields +// +#define DBG_DUMP_CALL_FOR_EACH 0x00000008 +// +// A list of type is dumped, listLink should have info about next element pointer +// +#define DBG_DUMP_LIST 0x00000020 +// +// Nothing is printed if this is set (only callbacks and data copies done) +// +#define DBG_DUMP_NO_PRINT 0x00000040 +// +// Ioctl returns the size as usual, but will not do field prints/callbacks if this is set +// +#define DBG_DUMP_GET_SIZE_ONLY 0x00000080 +// +// Specifies how much deep into structs we can go +// +#define DBG_DUMP_RECUR_LEVEL(l) ((l & 0xf) << 8) +// +// No newlines are printed after each field +// +#define DBG_DUMP_COMPACT_OUT 0x00002000 +// +// An array of type is dumped, number of elements can be specified in listLink->size +// +#define DBG_DUMP_ARRAY 0x00008000 +// +// The specified addr value is actually the address of field listLink->fName +// +#define DBG_DUMP_ADDRESS_OF_FIELD 0x00010000 + +// +// The specified addr value is actually the adress at the end of type +// +#define DBG_DUMP_ADDRESS_AT_END 0x00020000 + +// +// This could be used to copy only the primitive types like ULONG, PVOID etc. +// - will not work with structures/unions +// +#define DBG_DUMP_COPY_TYPE_DATA 0x00040000 +// +// Flag to allow read directly from physical memory +// +#define DBG_DUMP_READ_PHYSICAL 0x00080000 +// +// This causes a function type to be dumped in format function(arg1, arg2, ...) +// +#define DBG_DUMP_FUNCTION_FORMAT 0x00100000 +// +// This recurses on a struct but doesn't expand pointers +// +#define DBG_DUMP_BLOCK_RECURSE 0x00200000 +// +// Match the type size to resolve ambiguity in case multiple matches with same name are available +// +#define DBG_DUMP_MATCH_SIZE 0x00400000 + +// +// Obsolete defs +// +#define DBG_RETURN_TYPE 0 +#define DBG_RETURN_SUBTYPES 0 +#define DBG_RETURN_TYPE_VALUES 0 + +// +// Dump and callback optons for fields - Options used in FIELD_INFO.fOptions +// + +// +// Callback is done before printing the field if this is set +// +#define DBG_DUMP_FIELD_CALL_BEFORE_PRINT 0x00000001 +// +// No callback is done +// +#define DBG_DUMP_FIELD_NO_CALLBACK_REQ 0x00000002 +// +// Subfields of the fields are processesed +// +#define DBG_DUMP_FIELD_RECUR_ON_THIS 0x00000004 +// +// fName must match completely for the field to be dumped instead just a prefix +// match by default +// +#define DBG_DUMP_FIELD_FULL_NAME 0x00000008 +// +// This causes array elements of an array field to be printed +// +#define DBG_DUMP_FIELD_ARRAY 0x00000010 +// +// The data of the field is copied into fieldCallBack +// +#define DBG_DUMP_FIELD_COPY_FIELD_DATA 0x00000020 +// +// In callback or when Ioctl returns, the FIELD_INFO.address has the address of field. +// If no address is supplied for the type, it contains total offset of the field. +// +#define DBG_DUMP_FIELD_RETURN_ADDRESS 0x00001000 +// +// Return the offset and size in bits instead of bytes is case of Bitfield +// +#define DBG_DUMP_FIELD_SIZE_IN_BITS 0x00002000 +// +// Nothing is printed for field if this is set (only callbacks and data copies done) +// +#define DBG_DUMP_FIELD_NO_PRINT 0x00004000 +// +// If the field is a pointer, it is dumped as a string, ANSI, WCHAR, MULTI or GUID +// depending on following options +// +#define DBG_DUMP_FIELD_DEFAULT_STRING 0x00010000 +#define DBG_DUMP_FIELD_WCHAR_STRING 0x00020000 +#define DBG_DUMP_FIELD_MULTI_STRING 0x00040000 +#define DBG_DUMP_FIELD_GUID_STRING 0x00080000 + + +// +// Error status returned on TYPE DUMP Ioctl failure +// +#define MEMORY_READ_ERROR 0x01 +#define SYMBOL_TYPE_INDEX_NOT_FOUND 0x02 +#define SYMBOL_TYPE_INFO_NOT_FOUND 0x03 +#define FIELDS_DID_NOT_MATCH 0x04 +#define NULL_SYM_DUMP_PARAM 0x05 +#define NULL_FIELD_NAME 0x06 +#define INCORRECT_VERSION_INFO 0x07 +#define EXIT_ON_CONTROLC 0x08 +#define CANNOT_ALLOCATE_MEMORY 0x09 +#define INSUFFICIENT_SPACE_TO_COPY 0x0a +#define ADDRESS_TYPE_INDEX_NOT_FOUND 0x0b + + +//////////////////////////////////////////////////////////////////////////*/ + + +typedef +ULONG +(WDBGAPI*PSYM_DUMP_FIELD_CALLBACK)( + struct _FIELD_INFO *pField, + PVOID UserContext + ); + +typedef struct _FIELD_INFO { + PUCHAR fName; // Name of the field + PUCHAR printName; // Name to be printed at dump + ULONG size; // Size of the field + ULONG fOptions; // Dump Options for the field + ULONG64 address; // address of the field + union { + PVOID fieldCallBack; // Return info or callBack routine for the field + PVOID pBuffer; // the type data is copied into this + }; + ULONG TypeId; // OUT Type index of the field + ULONG FieldOffset; // OUT Offset of field inside struct + ULONG BufferSize; // size of buffer used with DBG_DUMP_FIELD_COPY_FIELD_DATA + struct _BitField { + USHORT Position; // OUT set to start position for bitfield + USHORT Size; // OUT set to size for bitfields + } BitField; + ULONG fPointer:2; // OUT set to 1 for pointers, 3 for 64bit pointers + ULONG fArray:1; // OUT set to 1 for array types + ULONG fStruct:1; // OUT set to 1 for struct/class tyoes + ULONG fConstant:1; // OUT set to 1 for constants (enumerate as fields) + ULONG fStatic:1; // OUT set to 1 for statics (class/struct static members) + ULONG Reserved:26; // unused +} FIELD_INFO, *PFIELD_INFO; + +typedef struct _SYM_DUMP_PARAM { + ULONG size; // size of this struct + PUCHAR sName; // type name + ULONG Options; // Dump options + ULONG64 addr; // Address to take data for type + PFIELD_INFO listLink; // fName here would be used to do list dump + union { + PVOID Context; // Usercontext passed to CallbackRoutine + PVOID pBuffer; // the type data is copied into this + }; + PSYM_DUMP_FIELD_CALLBACK CallbackRoutine; + // Routine called back + ULONG nFields; // # elements in Fields + __field_ecount_opt(nFields) PFIELD_INFO Fields; // Used to return information about field + ULONG64 ModBase; // OUT Module base address containing type + ULONG TypeId; // OUT Type index of the symbol + ULONG TypeSize; // OUT Size of type + ULONG BufferSize; // IN size of buffer (used with DBG_DUMP_COPY_TYPE_DATA) + ULONG fPointer:2; // OUT set to 1 for pointers, 3 for 64bit pointers + ULONG fArray:1; // OUT set to 1 for array types + ULONG fStruct:1; // OUT set to 1 for struct/class tyoes + ULONG fConstant:1; // OUT set to 1 for constant types (unused) + ULONG Reserved:27; // unused +} SYM_DUMP_PARAM, *PSYM_DUMP_PARAM; + +#ifdef __cplusplus +#define CPPMOD extern "C" +#else +#define CPPMOD +#endif + + +#ifndef NOEXTAPI + +#if defined(KDEXT_64BIT) +#define WINDBG_EXTENSION_APIS WINDBG_EXTENSION_APIS64 +#define PWINDBG_EXTENSION_APIS PWINDBG_EXTENSION_APIS64 +#define PWINDBG_EXTENSION_ROUTINE PWINDBG_EXTENSION_ROUTINE64 +#define DECLARE_API(s) DECLARE_API64(s) +#elif defined(KDEXT_32BIT) +#define WINDBG_EXTENSION_APIS WINDBG_EXTENSION_APIS32 +#define PWINDBG_EXTENSION_APIS PWINDBG_EXTENSION_APIS32 +#define PWINDBG_EXTENSION_ROUTINE PWINDBG_EXTENSION_ROUTINE32 +#define DECLARE_API(s) DECLARE_API32(s) +#else +#define DECLARE_API(s) \ + CPPMOD VOID \ + s( \ + HANDLE hCurrentProcess, \ + HANDLE hCurrentThread, \ + ULONG dwCurrentPc, \ + ULONG dwProcessor, \ + PCSTR args \ + ) +#endif + +#define DECLARE_API32(s) \ + CPPMOD VOID \ + s( \ + HANDLE hCurrentProcess, \ + HANDLE hCurrentThread, \ + ULONG dwCurrentPc, \ + ULONG dwProcessor, \ + PCSTR args \ + ) + +#define DECLARE_API64(s) \ + CPPMOD VOID \ + s( \ + HANDLE hCurrentProcess, \ + HANDLE hCurrentThread, \ + ULONG64 dwCurrentPc, \ + ULONG dwProcessor, \ + PCSTR args \ + ) + + +extern WINDBG_EXTENSION_APIS ExtensionApis; + + +#define dprintf (ExtensionApis.lpOutputRoutine) +#define GetExpression (ExtensionApis.lpGetExpressionRoutine) +#define CheckControlC (ExtensionApis.lpCheckControlCRoutine) +#define GetContext (ExtensionApis.lpGetThreadContextRoutine) +#define SetContext (ExtensionApis.lpSetThreadContextRoutine) +#define Ioctl (ExtensionApis.lpIoctlRoutine) +#define Disasm (ExtensionApis.lpDisasmRoutine) +#define GetSymbol (ExtensionApis.lpGetSymbolRoutine) +#define ReadMemory (ExtensionApis.lpReadProcessMemoryRoutine) +#define WriteMemory (ExtensionApis.lpWriteProcessMemoryRoutine) +#define StackTrace (ExtensionApis.lpStackTraceRoutine) + + +#define GetKdContext(ppi) \ + Ioctl( IG_KD_CONTEXT, (PVOID)ppi, sizeof(*ppi) ) + + +// +// BOOL +// GetDebuggerData( +// ULONG Tag, +// PVOID Buf, +// ULONG Size +// ) +// + +#define GetDebuggerData(TAG, BUF, SIZE) \ + ( (((PDBGKD_DEBUG_DATA_HEADER64)(BUF))->OwnerTag = (TAG)), \ + (((PDBGKD_DEBUG_DATA_HEADER64)(BUF))->Size = (SIZE)), \ + Ioctl( IG_GET_DEBUGGER_DATA, (PVOID)(BUF), (SIZE) ) ) + +// Check if LocalAlloc is prototyped +//#ifdef _WINBASE_ + +#ifndef FEATURE_PAL +__inline VOID +ReadPhysical( + ULONG64 address, + PVOID buf, + ULONG size, + PULONG sizer + ) +{ + PPHYSICAL phy = NULL; + *sizer = 0; + if (size <= WDBGEXTS_MAXSIZE_T - sizeof(*phy)) { + phy = (PPHYSICAL)LocalAlloc(LPTR, sizeof(*phy) + size ); + } + if (phy) { + ZeroMemory( phy->Buf, size ); + phy->Address = address; + phy->BufLen = size; + Ioctl( IG_READ_PHYSICAL, (PVOID)phy, sizeof(*phy) + size ); + *sizer = phy->BufLen; + CopyMemory( buf, phy->Buf, *sizer ); + LocalFree( phy ); + } +} + +__inline VOID +WritePhysical( + ULONG64 address, + PVOID buf, + ULONG size, + PULONG sizew + ) +{ + PPHYSICAL phy = NULL; + *sizew = 0; + if (size <= WDBGEXTS_MAXSIZE_T - sizeof(*phy)) { + phy = (PPHYSICAL)LocalAlloc(LPTR, sizeof(*phy) + size ); + } + if (phy) { + ZeroMemory( phy->Buf, size ); + phy->Address = address; + phy->BufLen = size; + CopyMemory( phy->Buf, buf, size ); + Ioctl( IG_WRITE_PHYSICAL, (PVOID)phy, sizeof(*phy) + size ); + *sizew = phy->BufLen; + LocalFree( phy ); + } +} + +__inline VOID +ReadPhysicalWithFlags( + ULONG64 address, + PVOID buf, + ULONG size, + ULONG flags, + PULONG sizer + ) +{ + PPHYSICAL_WITH_FLAGS phy = NULL; + *sizer = 0; + if (size <= WDBGEXTS_MAXSIZE_T - sizeof(*phy)) { + phy = (PPHYSICAL_WITH_FLAGS)LocalAlloc(LPTR, sizeof(*phy) + size ); + } + if (phy) { + ZeroMemory( phy->Buf, size ); + phy->Address = address; + phy->BufLen = size; + phy->Flags = flags; + Ioctl( IG_READ_PHYSICAL_WITH_FLAGS, (PVOID)phy, sizeof(*phy) + size ); + *sizer = phy->BufLen; + CopyMemory( buf, phy->Buf, *sizer ); + LocalFree( phy ); + } +} + +__inline VOID +WritePhysicalWithFlags( + ULONG64 address, + PVOID buf, + ULONG size, + ULONG flags, + PULONG sizew + ) +{ + PPHYSICAL_WITH_FLAGS phy = NULL; + *sizew = 0; + if (size <= WDBGEXTS_MAXSIZE_T - sizeof(*phy)) { + phy = (PPHYSICAL_WITH_FLAGS)LocalAlloc(LPTR, sizeof(*phy) + size ); + } + if (phy) { + ZeroMemory( phy->Buf, size ); + phy->Address = address; + phy->BufLen = size; + phy->Flags = flags; + CopyMemory( phy->Buf, buf, size ); + Ioctl( IG_WRITE_PHYSICAL_WITH_FLAGS, (PVOID)phy, sizeof(*phy) + size ); + *sizew = phy->BufLen; + LocalFree( phy ); + } +} + +__inline VOID +ReadMsr( + ULONG MsrReg, + ULONGLONG *MsrValue + ) +{ + READ_WRITE_MSR msr; + + msr.Msr = MsrReg; + Ioctl( IG_READ_MSR, (PVOID)&msr, sizeof(msr) ); + + *MsrValue = msr.Value; +} + +__inline VOID +WriteMsr( + ULONG MsrReg, + ULONGLONG MsrValue + ) +{ + READ_WRITE_MSR msr; + + msr.Msr = MsrReg; + msr.Value = MsrValue; + Ioctl( IG_WRITE_MSR, (PVOID)&msr, sizeof(msr) ); +} + +__inline VOID +SetThreadForOperation( + ULONG_PTR * Thread + ) +{ + Ioctl(IG_SET_THREAD, (PVOID)Thread, sizeof(PULONG)); +} + +__inline VOID +SetThreadForOperation32( + ULONG Thread + ) +{ + Ioctl(IG_SET_THREAD, (PVOID)LongToPtr(Thread), sizeof(ULONG)); +} + +__inline VOID +SetThreadForOperation64( + PULONG64 Thread + ) +{ + Ioctl(IG_SET_THREAD, (PVOID)Thread, sizeof(ULONG64)); +} + + +__inline VOID +ReadControlSpace( + USHORT processor, + ULONG address, + PVOID buf, + ULONG size + ) +{ + PREADCONTROLSPACE prc = NULL; + if (size <= WDBGEXTS_MAXSIZE_T - sizeof(*prc)) { + prc = (PREADCONTROLSPACE)LocalAlloc(LPTR, sizeof(*prc) + size ); + } + if (prc) { + ZeroMemory( prc->Buf, size ); + prc->Processor = processor; + prc->Address = address; + prc->BufLen = size; + Ioctl( IG_READ_CONTROL_SPACE, (PVOID)prc, sizeof(*prc) + size ); + CopyMemory( buf, prc->Buf, size ); + LocalFree( prc ); + } +} + +__inline VOID +ReadControlSpace32( + USHORT processor, + ULONG address, + PVOID buf, + ULONG size + ) +{ + PREADCONTROLSPACE32 prc = NULL; + if (size <= WDBGEXTS_MAXSIZE_T - sizeof(*prc)) { + prc = (PREADCONTROLSPACE32)LocalAlloc(LPTR, sizeof(*prc) + size ); + } + if (prc) { + ZeroMemory( prc->Buf, size ); + prc->Processor = processor; + prc->Address = address; + prc->BufLen = size; + Ioctl( IG_READ_CONTROL_SPACE, (PVOID)prc, sizeof(*prc) + size ); + CopyMemory( buf, prc->Buf, size ); + LocalFree( prc ); + } +} + +#define ReadTypedControlSpace32( _Proc, _Addr, _Buf ) \ + ReadControlSpace64( (USHORT)(_Proc), (ULONG)(_Addr), (PVOID)&(_Buf), (ULONG)sizeof(_Buf) ) + +__inline VOID +ReadControlSpace64( + USHORT processor, + ULONG64 address, + PVOID buf, + ULONG size + ) +{ + PREADCONTROLSPACE64 prc = NULL; + if (size <= WDBGEXTS_MAXSIZE_T - sizeof(*prc)) { + prc = (PREADCONTROLSPACE64)LocalAlloc(LPTR, sizeof(*prc) + size ); + } + if (prc) { + ZeroMemory( prc->Buf, size ); + prc->Processor = processor; + prc->Address = address; + prc->BufLen = size; + Ioctl( IG_READ_CONTROL_SPACE, (PVOID)prc, sizeof(*prc) + size ); + CopyMemory( buf, prc->Buf, size ); + LocalFree( prc ); + } +} + +#define ReadTypedControlSpace64( _Proc, _Addr, _Buf ) \ + ReadControlSpace64( (USHORT)(_Proc), (ULONG64)(_Addr), (PVOID)&(_Buf), (ULONG)sizeof(_Buf) ) + +__inline VOID +WriteControlSpace( + USHORT processor, + ULONG address, + PVOID buf, + ULONG size + ) +{ + PREADCONTROLSPACE64 prc = NULL; + if (size <= WDBGEXTS_MAXSIZE_T - sizeof(*prc)) { + prc = (PREADCONTROLSPACE64)LocalAlloc(LPTR, sizeof(*prc) + size ); + } + if (prc) { + ZeroMemory( prc->Buf, size ); + prc->Processor = processor; + prc->Address = address; + prc->BufLen = size; + CopyMemory( prc->Buf, buf, size ); + Ioctl( IG_WRITE_CONTROL_SPACE, (PVOID)prc, sizeof(*prc) + size ); + LocalFree( prc ); + } +} + +// #endif // _WINBASE_ + +__inline VOID +ReadIoSpace( + ULONG address, + PULONG data, + PULONG size + ) +{ + IOSPACE is; + is.Address = address; + is.Length = *size; + Ioctl( IG_READ_IO_SPACE, (PVOID)&is, sizeof(is) ); + memcpy(data, &is.Data, is.Length); + *size = is.Length; +} + +__inline VOID +ReadIoSpace32( + ULONG address, + PULONG data, + PULONG size + ) +{ + IOSPACE32 is; + is.Address = address; + is.Length = *size; + Ioctl( IG_READ_IO_SPACE, (PVOID)&is, sizeof(is) ); + memcpy(data, &is.Data, is.Length); + *size = is.Length; +} + +__inline VOID +ReadIoSpace64( + ULONG64 address, + PULONG data, + PULONG size + ) +{ + IOSPACE64 is; + is.Address = address; + is.Length = *size; + Ioctl( IG_READ_IO_SPACE, (PVOID)&is, sizeof(is) ); + memcpy(data, &is.Data, is.Length); + *size = is.Length; +} + +__inline VOID +WriteIoSpace( + ULONG address, + ULONG data, + PULONG size + ) +{ + IOSPACE is; + is.Address = (ULONG)address; + is.Length = *size; + is.Data = data; + Ioctl( IG_WRITE_IO_SPACE, (PVOID)&is, sizeof(is) ); + *size = is.Length; +} + +__inline VOID +WriteIoSpace32( + ULONG address, + ULONG data, + PULONG size + ) +{ + IOSPACE32 is; + is.Address = address; + is.Length = *size; + is.Data = data; + Ioctl( IG_WRITE_IO_SPACE, (PVOID)&is, sizeof(is) ); + *size = is.Length; +} + +__inline VOID +WriteIoSpace64( + ULONG64 address, + ULONG data, + PULONG size + ) +{ + IOSPACE64 is; + is.Address = address; + is.Length = *size; + is.Data = data; + Ioctl( IG_WRITE_IO_SPACE, (PVOID)&is, sizeof(is) ); + *size = is.Length; +} + +__inline VOID +ReadIoSpaceEx( + ULONG address, + PULONG data, + PULONG size, + ULONG interfacetype, + ULONG busnumber, + ULONG addressspace + ) +{ + IOSPACE_EX is; + is.Address = (ULONG)address; + is.Length = *size; + is.Data = 0; + is.InterfaceType = interfacetype; + is.BusNumber = busnumber; + is.AddressSpace = addressspace; + Ioctl( IG_READ_IO_SPACE_EX, (PVOID)&is, sizeof(is) ); + *data = is.Data; + *size = is.Length; +} + +__inline VOID +ReadIoSpaceEx32( + ULONG address, + PULONG data, + PULONG size, + ULONG interfacetype, + ULONG busnumber, + ULONG addressspace + ) +{ + IOSPACE_EX32 is; + is.Address = address; + is.Length = *size; + is.Data = 0; + is.InterfaceType = interfacetype; + is.BusNumber = busnumber; + is.AddressSpace = addressspace; + Ioctl( IG_READ_IO_SPACE_EX, (PVOID)&is, sizeof(is) ); + *data = is.Data; + *size = is.Length; +} + +__inline VOID +ReadIoSpaceEx64( + ULONG64 address, + PULONG data, + PULONG size, + ULONG interfacetype, + ULONG busnumber, + ULONG addressspace + ) +{ + IOSPACE_EX64 is; + is.Address = address; + is.Length = *size; + is.Data = 0; + is.InterfaceType = interfacetype; + is.BusNumber = busnumber; + is.AddressSpace = addressspace; + Ioctl( IG_READ_IO_SPACE_EX, (PVOID)&is, sizeof(is) ); + *data = is.Data; + *size = is.Length; +} + +__inline VOID +WriteIoSpaceEx( + ULONG address, + ULONG data, + PULONG size, + ULONG interfacetype, + ULONG busnumber, + ULONG addressspace + ) +{ + IOSPACE_EX is; + is.Address = (ULONG)address; + is.Length = *size; + is.Data = data; + is.InterfaceType = interfacetype; + is.BusNumber = busnumber; + is.AddressSpace = addressspace; + Ioctl( IG_WRITE_IO_SPACE_EX, (PVOID)&is, sizeof(is) ); + *size = is.Length; +} + +__inline VOID +WriteIoSpaceEx32( + ULONG address, + ULONG data, + PULONG size, + ULONG interfacetype, + ULONG busnumber, + ULONG addressspace + ) +{ + IOSPACE_EX32 is; + is.Address = address; + is.Length = *size; + is.Data = data; + is.InterfaceType = interfacetype; + is.BusNumber = busnumber; + is.AddressSpace = addressspace; + Ioctl( IG_WRITE_IO_SPACE_EX, (PVOID)&is, sizeof(is) ); + *size = is.Length; +} + +__inline VOID +WriteIoSpaceEx64( + ULONG64 address, + ULONG data, + PULONG size, + ULONG interfacetype, + ULONG busnumber, + ULONG addressspace + ) +{ + IOSPACE_EX64 is; + is.Address = address; + is.Length = *size; + is.Data = data; + is.InterfaceType = interfacetype; + is.BusNumber = busnumber; + is.AddressSpace = addressspace; + Ioctl( IG_WRITE_IO_SPACE_EX, (PVOID)&is, sizeof(is) ); + *size = is.Length; +} + +__inline VOID +ReloadSymbols( + IN PSTR Arg OPTIONAL + ) +/*++ + +Routine Description: + + Calls the debugger to reload symbols. + +Arguments: + + Args - Supplies the tail of a !reload command string. + + !reload [flags] [module[=address]] + flags: /n do not load from usermode list + /u unload symbols, no reload + /v verbose + + A value of NULL is equivalent to an empty string + +Return Value: + + None + +--*/ +{ + Ioctl(IG_RELOAD_SYMBOLS, (PVOID)Arg, Arg?((ULONG)strlen(Arg)+1):0); +} + +__inline VOID +GetSetSympath( + IN PSTR Arg, + OUT PSTR Result OPTIONAL, + IN int Length + ) +/*++ + +Routine Description: + + Calls the debugger to set or retrieve symbol search path. + +Arguments: + + Arg - Supplies new search path. If Arg is NULL or string is empty, + the search path is not changed and the current setting is + returned in Result. When the symbol search path is changed, + a call to ReloadSymbols is made implicitly. + + Result - OPTIONAL Returns the symbol search path setting. + + Length - Supplies the size of the buffer supplied by Result. + +Return Value: + + None + +--*/ +{ + GET_SET_SYMPATH gss; + gss.Args = Arg; + gss.Result = Result; + gss.Length = Length; + Ioctl(IG_GET_SET_SYMPATH, (PVOID)&gss, sizeof(gss)); +} + +#if defined(KDEXT_64BIT) + +__inline +ULONG +IsPtr64( + void + ) +{ + ULONG flag; + ULONG dw; + + if (Ioctl(IG_IS_PTR64, &dw, sizeof(dw))) { + flag = ((dw != 0) ? 1 : 0); + } else { + flag = 0; + } + return flag; +} + +__inline +ULONG +ReadListEntry( + ULONG64 Address, + PLIST_ENTRY64 List + ) +{ + ULONG cb; + if (IsPtr64()) { + return (ReadMemory(Address, (PVOID)List, sizeof(*List), &cb) && + cb == sizeof(*List)); + } else { + LIST_ENTRY32 List32; + ULONG Status; + Status = ReadMemory(Address, + (PVOID)&List32, + sizeof(List32), + &cb); + if (Status && cb == sizeof(List32)) { + List->Flink = (ULONG64)(LONG64)(LONG)List32.Flink; + List->Blink = (ULONG64)(LONG64)(LONG)List32.Blink; + return 1; + } + return 0; + } +} + +__inline +ULONG +ReadPointer( + ULONG64 Address, + PULONG64 Pointer + ) +{ + ULONG cb; + if (IsPtr64()) { + return (ReadMemory(Address, (PVOID)Pointer, sizeof(*Pointer), &cb) && + cb == sizeof(*Pointer)); + } else { + ULONG Pointer32; + ULONG Status; + Status = ReadMemory(Address, + (PVOID)&Pointer32, + sizeof(Pointer32), + &cb); + if (Status && cb == sizeof(Pointer32)) { + *Pointer = (ULONG64)(LONG64)(LONG)Pointer32; + return 1; + } + return 0; + } +} + +__inline +ULONG +WritePointer( + ULONG64 Address, + ULONG64 Pointer + ) +{ + ULONG cb; + if (IsPtr64()) { + return (WriteMemory(Address, &Pointer, sizeof(Pointer), &cb) && + cb == sizeof(Pointer)); + } else { + ULONG Pointer32 = (ULONG)Pointer; + ULONG Status; + Status = WriteMemory(Address, + &Pointer32, + sizeof(Pointer32), + &cb); + return (Status && cb == sizeof(Pointer32)) ? 1 : 0; + } +} + +/** + This does Ioctl call for type info and returns size of the type on success. + + **/ +__inline +ULONG +GetTypeSize ( + IN LPCSTR Type + ) +{ +#ifndef FEATURE_PAL + SYM_DUMP_PARAM Sym = { + sizeof (SYM_DUMP_PARAM), (PUCHAR)Type, DBG_DUMP_NO_PRINT | DBG_DUMP_GET_SIZE_ONLY, 0, + NULL, NULL, NULL, 0, NULL + }; + + return Ioctl( IG_GET_TYPE_SIZE, &Sym, Sym.size ); +#else + return (ULONG)~0; +#endif +} + +/** + GetFieldData + + Copies the value of the specified field into pOutValue assuming TypeAddress + points to start of the type in debugee. + + If the Field is NULL and the size of Type is <= 8 Whole type value is read into + pOutValue. This is to allow to read in primitive types suchas ULONG, PVOID etc. + + If address is zero this considers Type a global variable. + + It raises an exception if OutSize is less than size to be copied. + + Returns 0 on success, errorvalue (defined with SYM_DUMP_PARAM) otherwise. + + **/ +__inline +ULONG +GetFieldData ( + IN ULONG64 TypeAddress, + IN LPCSTR Type, + IN LPCSTR Field, + IN ULONG OutSize, + OUT PVOID pOutValue + ) +{ +#ifndef FEATURE_PAL + FIELD_INFO flds = {(PUCHAR)Field, NULL, 0, DBG_DUMP_FIELD_FULL_NAME | DBG_DUMP_FIELD_COPY_FIELD_DATA | DBG_DUMP_FIELD_RETURN_ADDRESS, 0, pOutValue}; + SYM_DUMP_PARAM Sym = { + sizeof (SYM_DUMP_PARAM), (PUCHAR)Type, DBG_DUMP_NO_PRINT, TypeAddress, + NULL, NULL, NULL, 1, &flds + }; + ULONG RetVal; + + if (!Field) { + Sym.nFields =0; Sym.Options |= DBG_DUMP_COPY_TYPE_DATA; + Sym.Context = pOutValue; + } + + ZeroMemory(pOutValue, OutSize); + RetVal = Ioctl( IG_DUMP_SYMBOL_INFO, &Sym, Sym.size ); + + if (OutSize < ((Field == NULL) ? 8 : flds.size)) { + // Fail + dprintf("Not enough space to read %s-%s\n", Type, Field); + RaiseException((DWORD)EXCEPTION_ACCESS_VIOLATION, 0, 0, NULL); + return 0; + } + return RetVal; +#else + return (ULONG)~0; +#endif +} + +// +// Typecast the buffer where value is to be read +// +#define GetFieldValue(Addr, Type, Field, OutValue) \ + GetFieldData(Addr, Type, Field, sizeof(OutValue), (PVOID) &(OutValue)) + +// +// Used to read in value of a short (<= 8 bytes) fields +// +__inline +ULONG64 +GetShortField ( + IN ULONG64 TypeAddress, + IN LPCSTR Name, + IN USHORT StoreAddress + ) +{ +#ifndef FEATURE_PAL + static ULONG64 SavedAddress; + static PUCHAR SavedName; + static ULONG ReadPhysical; + FIELD_INFO flds = {(PUCHAR) Name, NULL, 0, DBG_DUMP_FIELD_FULL_NAME, 0, NULL}; + SYM_DUMP_PARAM Sym = { + sizeof (SYM_DUMP_PARAM), SavedName, DBG_DUMP_NO_PRINT | ((StoreAddress & 2) ? DBG_DUMP_READ_PHYSICAL : 0), + SavedAddress, NULL, NULL, NULL, 1, &flds + }; + + + if (StoreAddress) { + Sym.sName = (PUCHAR) Name; + Sym.nFields = 0; + SavedName = (PUCHAR) Name; + Sym.addr = SavedAddress = TypeAddress; + ReadPhysical = (StoreAddress & 2); + return SavedAddress ? Ioctl( IG_DUMP_SYMBOL_INFO, &Sym, Sym.size ) : MEMORY_READ_ERROR; // zero on success + } else { + Sym.Options |= ReadPhysical ? DBG_DUMP_READ_PHYSICAL : 0; + } + + if (!Ioctl( IG_DUMP_SYMBOL_INFO, &Sym, Sym.size )) { + return flds.address; + } + return 0; +#else + return (ULONG64)~0; +#endif +} + +// +// Stores the address and type name for future reads +// +#define InitTypeRead(Addr, Type) GetShortField(Addr, #Type, 1) +#define InitTypeStrRead(Addr, TypeStr) GetShortField(Addr, TypeStr, 1) + +// +// Stores the address and type name for future reads +// +#define InitTypeReadPhysical(Addr, Type) GetShortField(Addr, #Type, 3) +#define InitTypeStrReadPhysical(Addr, TypeStr) GetShortField(Addr, TypeStr, 3) + +// +// Returns the field's value as ULONG64 if size of field is <= sizeof (ULONG64) +// +#define ReadField(Field) GetShortField(0, #Field, 0) +#define ReadFieldStr(FieldStr) GetShortField(0, FieldStr, 0) + +// +// Read in a pointer value +// +__inline +ULONG +ReadPtr( + ULONG64 Addr, + PULONG64 pPointer + ) +{ + return !ReadPointer(Addr, pPointer); +} + +/* + * ListType + * + * Routine ListType gives a callback on each element in the list of Type. + * + * Type : Name of the type to be listed + * + * NextPointer : Name of field which gives address of next element in list + * + * Context, CallbackRoutine : + * Context and the callback routine. The address field in PFIELD_INFO + * parameter of callback contains the address of next Type element in list. + * + * Address, ListByFieldAddress : + * if ListByFieldAddress is 0, Adress is the address of first element of Type List. + * + * Lists by LIST_ENTRY are also handled implicitly (by Ioctl). If the NextPointer + * is a pointer to LIST_ENTRY type, the type address is properly calculated by + * subtracting the offsets. + * + * If ListByFieldAddress is 1, the Address is considered to be the address of field + * "NextPointer" of the first Type element and first element address is derived + * from it. + * + */ + +__inline +ULONG +ListType ( + IN LPCSTR Type, + IN ULONG64 Address, + IN USHORT ListByFieldAddress, + IN LPCSTR NextPointer, + IN PVOID Context, + IN PSYM_DUMP_FIELD_CALLBACK CallbackRoutine + ) +{ +#ifndef FEATURE_PAL + FIELD_INFO flds = {(PUCHAR)NextPointer, NULL, 0, 0, 0, NULL}; + SYM_DUMP_PARAM Sym = { + sizeof (SYM_DUMP_PARAM), (PUCHAR) Type, DBG_DUMP_NO_PRINT | DBG_DUMP_LIST, Address, + &flds, Context, CallbackRoutine, 0, NULL + }; + + if (ListByFieldAddress==1) { + // + // Address is the address of "NextPointer" + // + Sym.Options |= DBG_DUMP_ADDRESS_OF_FIELD; + } + + return Ioctl( IG_DUMP_SYMBOL_INFO, &Sym, Sym.size ); +#else + return (ULONG)~0; +#endif +} + + +/** + + Routine to get offset of a "Field" of "Type" on a debugee machine. This uses + Ioctl call for type info. + Returns 0 on success, Ioctl error value otherwise. + + **/ + +__inline +ULONG +GetFieldOffset ( + IN LPCSTR Type, + IN LPCSTR Field, + OUT PULONG pOffset + ) +{ +#ifndef FEATURE_PAL + FIELD_INFO flds = { + (PUCHAR)Field, + (PUCHAR)"", + 0, + DBG_DUMP_FIELD_FULL_NAME | DBG_DUMP_FIELD_RETURN_ADDRESS, + 0, + NULL}; + + SYM_DUMP_PARAM Sym = { + sizeof (SYM_DUMP_PARAM), + (PUCHAR)Type, + DBG_DUMP_NO_PRINT, + 0, + NULL, + NULL, + NULL, + 1, + &flds + }; + + ULONG Err; + + Sym.nFields = 1; + Err = Ioctl( IG_DUMP_SYMBOL_INFO, &Sym, Sym.size ); + *pOffset = (ULONG) (flds.address - Sym.addr); + return Err; +#else + return (ULONG)~0; +#endif +} + + +#endif // defined(KDEXT_64BIT) + +__inline VOID + GetCurrentProcessHandle( + PHANDLE hp + ) +{ + Ioctl(IG_GET_CURRENT_PROCESS_HANDLE, hp, sizeof(HANDLE)); +} + +__inline VOID + GetTebAddress( + PULONGLONG Address + ) +{ + GET_TEB_ADDRESS gpt; + gpt.Address = 0; + Ioctl(IG_GET_TEB_ADDRESS, (PVOID)&gpt, sizeof(gpt)); + *Address = gpt.Address; +} + +__inline VOID + GetPebAddress( + ULONG64 CurrentThread, + PULONGLONG Address + ) +{ + GET_PEB_ADDRESS gpt; + gpt.CurrentThread = CurrentThread; + gpt.Address = 0; + Ioctl(IG_GET_PEB_ADDRESS, (PVOID)&gpt, sizeof(gpt)); + *Address = gpt.Address; +} + +__inline VOID + GetCurrentThreadAddr( + DWORD Processor, + PULONG64 Address + ) +{ + GET_CURRENT_THREAD_ADDRESS ct; + ct.Processor = Processor; + Ioctl(IG_GET_CURRENT_THREAD, (PVOID)&ct, sizeof(ct)); + *Address = ct.Address; +} + +__inline VOID + GetCurrentProcessAddr( + DWORD Processor, + ULONG64 CurrentThread, + PULONG64 Address + ) +{ + GET_CURRENT_PROCESS_ADDRESS cp; + cp.Processor = Processor; + cp.CurrentThread = CurrentThread; + Ioctl(IG_GET_CURRENT_PROCESS, (PVOID)&cp, sizeof(cp)); + *Address = cp.Address; +} + +__inline VOID +SearchMemory( + ULONG64 SearchAddress, + ULONG64 SearchLength, + ULONG PatternLength, + PVOID Pattern, + PULONG64 FoundAddress + ) +{ + SEARCHMEMORY sm; + sm.SearchAddress = SearchAddress; + sm.SearchLength = SearchLength; + sm.FoundAddress = 0; + sm.PatternLength = PatternLength; + sm.Pattern = Pattern; + Ioctl(IG_SEARCH_MEMORY, (PVOID)&sm, sizeof(sm)); + *FoundAddress = sm.FoundAddress; +} + +__inline ULONG +GetInputLine( + PCSTR Prompt, + PSTR Buffer, + ULONG BufferSize + ) +{ + GET_INPUT_LINE InLine; + InLine.Prompt = Prompt; + InLine.Buffer = Buffer; + InLine.BufferSize = BufferSize; + if (Ioctl(IG_GET_INPUT_LINE, (PVOID)&InLine, sizeof(InLine))) + { + return InLine.InputSize; + } + else + { + return 0; + } +} + +__inline BOOL +GetExpressionEx( + PCSTR Expression, + ULONG64* Value, + PCSTR* Remainder + ) +{ + GET_EXPRESSION_EX Expr; + Expr.Expression = Expression; + if (Ioctl(IG_GET_EXPRESSION_EX, (PVOID)&Expr, sizeof(Expr))) + { + *Value = Expr.Value; + + if (Remainder != NULL) + { + *Remainder = Expr.Remainder; + } + + return TRUE; + } + + return FALSE; +} + +__inline BOOL +TranslateVirtualToPhysical( + ULONG64 Virtual, + ULONG64* Physical + ) +{ + TRANSLATE_VIRTUAL_TO_PHYSICAL VToP; + VToP.Virtual = Virtual; + if (Ioctl(IG_TRANSLATE_VIRTUAL_TO_PHYSICAL, (PVOID)&VToP, sizeof(VToP))) + { + *Physical = VToP.Physical; + return TRUE; + } + + return FALSE; +} + +__inline BOOL +GetDebuggerCacheSize( + OUT PULONG64 CacheSize + ) +{ + return Ioctl(IG_GET_CACHE_SIZE, (PVOID) CacheSize, sizeof(ULONG64)); +} + +__inline BOOL +ExtMatchPatternA( + IN PCSTR Str, + IN PCSTR Pattern, + IN BOOL CaseSensitive + ) +{ + EXT_MATCH_PATTERN_A Args; + + Args.Str = Str; + Args.Pattern = Pattern; + Args.CaseSensitive = CaseSensitive; + return Ioctl(IG_MATCH_PATTERN_A, (PVOID)&Args, sizeof(Args)); +} + +#endif // FEATURE_PAL + +#endif + +#ifndef FEATURE_PAL +#pragma warning(default:4115 4201 4204 4214 4221) +#endif +#if _MSC_VER >= 1200 +#pragma warning(pop) +#endif + +#ifdef __cplusplus +} +#endif + +#endif // _WDBGEXTS_ diff --git a/src/ToolBox/SOS/Strike/metadata.cpp b/src/ToolBox/SOS/Strike/metadata.cpp new file mode 100644 index 0000000000..073b979baa --- /dev/null +++ b/src/ToolBox/SOS/Strike/metadata.cpp @@ -0,0 +1,1041 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ==++== +// + +// +// ==--== +#include "strike.h" +#include "util.h" +#include "genericstackprobe.h" + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to find the name of a TypeDef using * +* metadata API. * +* * +\**********************************************************************/ +// Caller should guard against exception +// !!! mdName should have at least mdNameLen WCHAR +static HRESULT NameForTypeDef_s(mdTypeDef tkTypeDef, IMetaDataImport *pImport, + __out_ecount (capacity_mdName) WCHAR *mdName, size_t capacity_mdName) +{ + DWORD flags; + ULONG nameLen; + + HRESULT hr = pImport->GetTypeDefProps(tkTypeDef, mdName, + mdNameLen, &nameLen, + &flags, NULL); + if (hr != S_OK) { + return hr; + } + + if (!IsTdNested(flags)) { + return hr; + } + mdTypeDef tkEnclosingClass; + hr = pImport->GetNestedClassProps(tkTypeDef, &tkEnclosingClass); + if (hr != S_OK) { + return hr; + } + WCHAR *name = (WCHAR*)_alloca((nameLen+1)*sizeof(WCHAR)); + wcscpy_s (name, nameLen+1, mdName); + hr = NameForTypeDef_s(tkEnclosingClass,pImport,mdName, capacity_mdName); + if (hr != S_OK) { + return hr; + } + size_t Len = _wcslen (mdName); + if (Len < mdNameLen-2) { + mdName[Len++] = L'+'; + mdName[Len] = L'\0'; + } + Len = mdNameLen-1 - Len; + if (Len > nameLen) { + Len = nameLen; + } + wcsncat_s (mdName,capacity_mdName,name,Len); + return hr; +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to find the name of a TypeDef using * +* metadata API. * +* * +\**********************************************************************/ +// Caller should guard against exception +// !!! mdName should have at least mdNameLen WCHAR +/* +static HRESULT NameForTypeDefNew(mdTypeDef tkTypeDef, IMDInternalImport *pImport, + WCHAR *mdName) +{ + DWORD flags; + ULONG nameLen; + char *name = (char *)_alloca((mdNameLen+1)*sizeof(char)); + char *namesp = (char *)_alloca((mdNameLen+1)*sizeof(char)); + + HRESULT hr = pImport->GetNameOfTypeDef(tkTypeDef, name, namesp); + if (FAILED(hr)) + { + return hr; + } + + strcpy (namesp, "."); + strcpy (namesp, name); + MultiByteToWideChar (CP_ACP,0,namesp,-1,mdName,mdNameLen); + return hr; +} +*/ + +/**********************************************************************\ +* Routine Description: * +* * +* Find the Module MD Importer given the name of the Module. * +* * +\**********************************************************************/ +IMetaDataImport* MDImportForModule(DacpModuleData* pModule) +{ + IMetaDataImport *pRet = NULL; + ToRelease<IXCLRDataModule> module; + HRESULT hr = g_sos->GetModule(pModule->Address, &module); + + if (SUCCEEDED(hr)) + hr = module->QueryInterface(IID_IMetaDataImport, (LPVOID *) &pRet); + + if (SUCCEEDED(hr)) + return pRet; + + return NULL; +} + +IMetaDataImport* MDImportForModule(DWORD_PTR pModule) +{ + DacpModuleData moduleData; + if(moduleData.Request(g_sos, TO_CDADDR(pModule))==S_OK) + return MDImportForModule(&moduleData); + else + return NULL; +} + +/**********************************************************************\ +* Routine Description: * +* * +* Find the name for a metadata token given an importer. * +* * +\**********************************************************************/ +HRESULT NameForToken_s(mdTypeDef mb, IMetaDataImport *pImport, __out_ecount (capacity_mdName) WCHAR *mdName, size_t capacity_mdName, + bool bClassName) +{ + mdName[0] = L'\0'; + if ((mb & 0xff000000) != mdtTypeDef + && (mb & 0xff000000) != mdtFieldDef + && (mb & 0xff000000) != mdtMethodDef) + { + //ExtOut("unsupported\n"); + return E_FAIL; + } + + HRESULT hr = E_FAIL; + + PAL_CPP_TRY + { + static WCHAR name[MAX_CLASSNAME_LENGTH]; + if ((mb & 0xff000000) == mdtTypeDef) + { + hr = NameForTypeDef_s (mb, pImport, mdName, capacity_mdName); + } + else if ((mb & 0xff000000) == mdtFieldDef) + { + mdTypeDef mdClass; + ULONG size; + hr = pImport->GetMemberProps(mb, &mdClass, + name, sizeof(name)/sizeof(WCHAR)-1, &size, + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL); + if (SUCCEEDED (hr)) + { + if (mdClass != mdTypeDefNil && bClassName) + { + hr = NameForTypeDef_s (mdClass, pImport, mdName, capacity_mdName); + wcscat_s (mdName, capacity_mdName, W(".")); + } + name[size] = L'\0'; + wcscat_s (mdName, capacity_mdName, name); + } + } + else if ((mb & 0xff000000) == mdtMethodDef) + { + mdTypeDef mdClass; + ULONG size; + hr = pImport->GetMethodProps(mb, &mdClass, + name, sizeof(name)/sizeof(WCHAR)-1, &size, + NULL, NULL, NULL, NULL, NULL); + if (SUCCEEDED (hr)) + { + if (mdClass != mdTypeDefNil && bClassName) + { + hr = NameForTypeDef_s (mdClass, pImport, mdName, capacity_mdName); + wcscat_s (mdName, capacity_mdName, W(".")); + } + name[size] = L'\0'; + wcscat_s (mdName, capacity_mdName, name); + } + } + else + { + ExtOut ("Unsupported token type\n"); + hr = E_FAIL; + } + } + PAL_CPP_CATCH_ALL + { + hr = E_FAIL; + } + PAL_CPP_ENDTRY + return hr; +} + +/**********************************************************************\ +* Routine Description: * +* * +* Find the name for a metadata token given an importer. * +* * +\**********************************************************************/ +/* +HRESULT NameForTokenNew(mdTypeDef mb, IMDInternalImport *pImport, WCHAR *mdName, size_t capacity_mdName, + bool bClassName) +{ + + // TODO: Change calls to use the secure versions (string as well as to functions defined here) + // Simply uncommenting this function will not result in a clean compile + // --chirayuk @ 11/23/2004 + + mdName[0] = L'\0'; + if ((mb & 0xff000000) != mdtTypeDef + && (mb & 0xff000000) != mdtFieldDef + && (mb & 0xff000000) != mdtMethodDef) + { + //ExtOut("unsupported\n"); + return E_FAIL; + } + + HRESULT hr; + + __try + { + static WCHAR name[MAX_CLASSNAME_LENGTH]; + if (TypeFromToken(mb) == mdtTypeDef) + { + hr = NameForTypeDefNew (mb, pImport, mdName); + } + else if (TypeFromToken(mb) == mdtFieldDef) + { + mdTypeDef mdClass; + ULONG size; + MultiByteToWideChar (CP_ACP,0,pImport->GetNameOfFieldDef(mb),-1,name,MAX_CLASSNAME_LENGTH); + + hr = pImport->GetParentToken (mb, &mdClass); + if (SUCCEEDED (hr)) + { + if (mdClass != mdTypeDefNil && bClassName) + { + hr = NameForTypeDefNew (mdClass, pImport, mdName); + _wcscat (mdName, W(".")); + } + name[size] = L'\0'; + _wcscat (mdName, name); + } + } + else if (TypeFromToken(mb) == mdtMethodDef) + { + mdTypeDef mdClass; + ULONG size; + + MultiByteToWideChar (CP_ACP,0,pImport->GetNameOfMethodDef(mb),-1,name,MAX_CLASSNAME_LENGTH); + hr = pImport->GetParentToken (mb, &mdClass); + if (SUCCEEDED (hr)) + { + if (mdClass != mdTypeDefNil && bClassName) + { + hr = NameForTypeDefNew (mdClass, pImport, mdName); + _wcscat (mdName, W(".")); + } + name[size] = L'\0'; + _wcscat (mdName, name); + } + } + else + { + ExtOut ("Unsupported token type\n"); + hr = E_FAIL; + } + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + //ExtOut("Metadata operation failure\n"); + hr = E_FAIL; + } + return hr; +} +*/ + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to find the name of a metadata token * +* using metadata API. * +* * +\**********************************************************************/ +void NameForToken_s(DWORD_PTR ModuleAddr, mdTypeDef mb, __out_ecount (capacity_mdName) WCHAR *mdName, size_t capacity_mdName, + bool bClassName) +{ + DacpModuleData ModuleData; + mdName[0] = L'\0'; + if(ModuleData.Request(g_sos, TO_CDADDR(ModuleAddr))==S_OK) + NameForToken_s(&ModuleData,mb,mdName,capacity_mdName,bClassName); +} + +BOOL IsValidToken(DWORD_PTR ModuleAddr, mdTypeDef mb) +{ + DacpModuleData ModuleData; + if(ModuleData.Request(g_sos, TO_CDADDR(ModuleAddr))==S_OK) + { + ToRelease<IMetaDataImport> pImport = MDImportForModule(&ModuleData); + if (pImport) + { + if (pImport->IsValidToken (mb)) + return TRUE; + } + } + return FALSE; +} + +void NameForToken_s(DacpModuleData *pModule, mdTypeDef mb, __out_ecount (capacity_mdName) WCHAR *mdName, size_t capacity_mdName, + bool bClassName) +{ + mdName[0] = L'\0'; + HRESULT hr = 0; + ToRelease<IMetaDataImport> pImport = MDImportForModule(pModule); + if (pImport) + { + hr = NameForToken_s (mb, pImport, mdName, capacity_mdName, bClassName); + } + + if (!pImport || !SUCCEEDED (hr)) + { + const SIZE_T capacity_moduleName = mdNameLen+19; + LPWSTR moduleName = (LPWSTR)alloca(capacity_moduleName * sizeof(WCHAR)); // for the "Dynamic Module In " below + FileNameForModule(pModule,moduleName); + if (moduleName[0] == L'\0') { + DacpAssemblyData assembly; + assembly.Request(g_sos,pModule->Assembly); + if (assembly.isDynamic) { + wcscpy_s(moduleName, capacity_moduleName, W("Dynamic ")); + } + wcscat_s (moduleName, capacity_moduleName, W("Module in ")); + if(g_sos->GetAssemblyName(pModule->Assembly, mdNameLen, g_mdName, NULL)==S_OK) + { + wcscat_s(moduleName, capacity_moduleName, g_mdName); + } + } + swprintf_s (mdName, capacity_mdName, + W(" mdToken: %08x (%ws)"), + mb, + moduleName[0] ? moduleName : W("Unknown Module") ); + } +} + +#define STRING_BUFFER_LEN 1024 + +class MDInfo +{ +public: + MDInfo (DWORD_PTR ModuleAddr) + { + m_pImport = MDImportForModule(ModuleAddr); + if (!m_pImport) + ExtOut("Unable to get IMetaDataImport for module %p\n", ModuleAddr); + m_pSigBuf = NULL; + } + + MDInfo (IMetaDataImport * pImport) + { + m_pImport = pImport; + m_pImport->AddRef(); + m_pSigBuf = NULL; + } + + void GetMethodName(mdTypeDef token, CQuickBytes *fullName); + GetSignatureStringResults GetMethodSignature(PCCOR_SIGNATURE pbSigBlob, ULONG ulSigBlob, CQuickBytes *fullName); + GetSignatureStringResults GetSignature(PCCOR_SIGNATURE pbSigBlob, ULONG ulSigBlob, CQuickBytes *fullName); + + LPCWSTR TypeDefName(mdTypeDef inTypeDef); + LPCWSTR TypeRefName(mdTypeRef tr); + LPCWSTR TypeDeforRefName(mdToken inToken); +private: + // helper to init signature buffer + void InitSigBuffer() + { + ((LPWSTR)m_pSigBuf->Ptr())[0] = L'\0'; + } + + HRESULT AddToSigBuffer(LPCWSTR string); + + HRESULT GetFullNameForMD(PCCOR_SIGNATURE pbSigBlob, ULONG ulSigBlob, LONG *plSigBlobRemaining OPTIONAL); + HRESULT GetOneElementType(PCCOR_SIGNATURE pbSigBlob, ULONG ulSigBlob, ULONG *pcb); + + ToRelease<IMetaDataImport> m_pImport; + // Signature buffer. + CQuickBytes *m_pSigBuf; + + // temporary buffer for TypeDef or TypeRef name. Consume immediately + // because other functions may overwrite it. + static WCHAR m_szTempBuf[MAX_CLASSNAME_LENGTH]; + + static WCHAR m_szName[MAX_CLASSNAME_LENGTH]; +}; + +WCHAR MDInfo::m_szTempBuf[MAX_CLASSNAME_LENGTH]; +WCHAR MDInfo::m_szName[MAX_CLASSNAME_LENGTH]; + +GetSignatureStringResults GetMethodSignatureString (PCCOR_SIGNATURE pbSigBlob, ULONG ulSigBlob, DWORD_PTR dwModuleAddr, CQuickBytes *sigString) +{ + MDInfo mdInfo(dwModuleAddr); + + return mdInfo.GetMethodSignature(pbSigBlob, ulSigBlob, sigString); +} + + +GetSignatureStringResults GetSignatureString (PCCOR_SIGNATURE pbSigBlob, ULONG ulSigBlob, DWORD_PTR dwModuleAddr, CQuickBytes *sigString) +{ + MDInfo mdInfo(dwModuleAddr); + + return mdInfo.GetSignature(pbSigBlob, ulSigBlob, sigString); +} + +void GetMethodName(mdMethodDef methodDef, IMetaDataImport * pImport, CQuickBytes *fullName) +{ + MDInfo mdInfo(pImport); + + mdInfo.GetMethodName(methodDef, fullName); +} + + +// Tables for mapping element type to text +const WCHAR *g_wszMapElementType[] = +{ + W("End"), // 0x0 + W("Void"), // 0x1 + W("Boolean"), + W("Char"), + W("I1"), + W("UI1"), + W("I2"), // 0x6 + W("UI2"), + W("I4"), + W("UI4"), + W("I8"), + W("UI8"), + W("R4"), + W("R8"), + W("String"), + W("Ptr"), // 0xf + W("ByRef"), // 0x10 + W("ValueClass"), + W("Class"), + W("CopyCtor"), + W("MDArray"), // 0x14 + W("GENArray"), + W("TypedByRef"), + W("VALUEARRAY"), + W("I"), + W("U"), + W("R"), // 0x1a + W("FNPTR"), + W("Object"), + W("SZArray"), + W("GENERICArray"), + W("CMOD_REQD"), + W("CMOD_OPT"), + W("INTERNAL"), +}; + +const WCHAR *g_wszCalling[] = +{ + W("[DEFAULT]"), + W("[C]"), + W("[STDCALL]"), + W("[THISCALL]"), + W("[FASTCALL]"), + W("[VARARG]"), + W("[FIELD]"), + W("[LOCALSIG]"), + W("[PROPERTY]"), + W("[UNMANAGED]"), +}; + +void MDInfo::GetMethodName(mdTypeDef token, CQuickBytes *fullName) +{ + if (m_pImport == NULL) { + return; + } + + HRESULT hr; + mdTypeDef memTypeDef; + ULONG nameLen; + DWORD flags; + PCCOR_SIGNATURE pbSigBlob; + ULONG ulSigBlob; + ULONG ulCodeRVA; + ULONG ulImplFlags; + + m_pSigBuf = fullName; + InitSigBuffer(); + + WCHAR szFunctionName[1024]; + + hr = m_pImport->GetMethodProps(token, &memTypeDef, + szFunctionName, _countof(szFunctionName), &nameLen, + &flags, &pbSigBlob, &ulSigBlob, &ulCodeRVA, &ulImplFlags); + if (FAILED (hr)) + { + return; + } + + szFunctionName[nameLen] = L'\0'; + m_szName[0] = L'\0'; + if (memTypeDef != mdTypeDefNil) + { + hr = NameForTypeDef_s (memTypeDef, m_pImport, m_szName, _countof(m_szName)); + if (SUCCEEDED (hr)) { + wcscat_s (m_szName, _countof(m_szName), W(".")); + } + } + wcscat_s (m_szName, _countof(m_szName), szFunctionName); + + LONG lSigBlobRemaining; + hr = GetFullNameForMD(pbSigBlob, ulSigBlob, &lSigBlobRemaining); + + // We should have consumed all signature blob. If not, dump the sig in hex. + // Also dump in hex if so requested. + if (lSigBlobRemaining != 0) + { + // Did we not consume enough, or try to consume too much? + if (lSigBlobRemaining < 0) + ExtOut("ERROR IN SIGNATURE: Signature should be larger.\n"); + else + ExtOut("ERROR IN SIGNATURE: Not all of signature blob was consumed. %d byte(s) remain\n", lSigBlobRemaining); + } + + if (FAILED(hr)) + ExtOut("ERROR!! Bad signature blob value!"); +} + + +GetSignatureStringResults MDInfo::GetMethodSignature(PCCOR_SIGNATURE pbSigBlob, ULONG ulSigBlob, CQuickBytes *fullName) +{ + if (!m_pImport) + return GSS_ERROR; + + m_pSigBuf = fullName; + InitSigBuffer(); + + m_szName[0] = '\0'; + + LONG lSigBlobRemaining; + if (FAILED(GetFullNameForMD(pbSigBlob, ulSigBlob, &lSigBlobRemaining))) + return GSS_ERROR; + + if (lSigBlobRemaining < 0) + return GSS_INSUFFICIENT_DATA; + + return GSS_SUCCESS; +} + + +GetSignatureStringResults MDInfo::GetSignature(PCCOR_SIGNATURE pbSigBlob, ULONG ulSigBlob, CQuickBytes *fullName) +{ + if (!m_pImport) + return GSS_ERROR; + + m_pSigBuf = fullName; + InitSigBuffer(); + + m_szName[0] = '\0'; + + ULONG cb; + if (FAILED(GetOneElementType(pbSigBlob, ulSigBlob, &cb))) + { + if (cb > ulSigBlob) + return GSS_INSUFFICIENT_DATA; + else + return GSS_ERROR; + } + + return GSS_SUCCESS; +} + + +inline bool isCallConv(unsigned sigByte, CorCallingConvention conv) +{ + return ((sigByte & IMAGE_CEE_CS_CALLCONV_MASK) == (unsigned) conv); +} + +#ifndef IfFailGoto +#define IfFailGoto(EXPR, LABEL) \ +do { hr = (EXPR); if(FAILED(hr)) { goto LABEL; } } while (0) +#endif + +#ifndef IfFailGo +#define IfFailGo(EXPR) IfFailGoto(EXPR, ErrExit) +#endif + +#ifndef IfFailRet +#define IfFailRet(EXPR) do { hr = (EXPR); if(FAILED(hr)) { return (hr); } } while (0) +#endif + +#ifndef _ASSERTE +#define _ASSERTE(expr) +#endif + +HRESULT MDInfo::GetFullNameForMD(PCCOR_SIGNATURE pbSigBlob, ULONG ulSigBlob, LONG *plSigBlobRemaining) +{ + ULONG cbCur = 0; + ULONG cb; + ULONG ulData = NULL; + ULONG ulArgs; + HRESULT hr = NOERROR; + + cb = CorSigUncompressData(pbSigBlob, &ulData); + + // 0 is a valid calling convention byte (IMAGE_CEE_CS_CALLCONV_DEFAULT w/ no flags) + //if (ulData == NULL) + // goto ErrExit; + + AddToSigBuffer (g_wszCalling[ulData & IMAGE_CEE_CS_CALLCONV_MASK]); + if (cb>ulSigBlob) + goto ErrExit; + cbCur += cb; + ulSigBlob -= cb; + + if (ulData & IMAGE_CEE_CS_CALLCONV_HASTHIS) + AddToSigBuffer ( W(" [hasThis]")); + if (ulData & IMAGE_CEE_CS_CALLCONV_EXPLICITTHIS) + AddToSigBuffer ( W(" [explicit]")); + + AddToSigBuffer (W(" ")); + if ( isCallConv(ulData,IMAGE_CEE_CS_CALLCONV_FIELD) ) + { + // display field type + if (FAILED(hr = GetOneElementType(&pbSigBlob[cbCur], ulSigBlob, &cb))) + goto ErrExit; + AddToSigBuffer ( W(" ")); + AddToSigBuffer ( m_szName); + if (cb>ulSigBlob) + goto ErrExit; + cbCur += cb; + ulSigBlob -= cb; + } + else + { + cb = CorSigUncompressData(&pbSigBlob[cbCur], &ulArgs); + if (cb>ulSigBlob) + goto ErrExit; + cbCur += cb; + ulSigBlob -= cb; + + if (ulData != IMAGE_CEE_CS_CALLCONV_LOCAL_SIG) + { + // display return type when it is not a local varsig + if (FAILED(hr = GetOneElementType(&pbSigBlob[cbCur], ulSigBlob, &cb))) + goto ErrExit; + AddToSigBuffer (W(" ")); + AddToSigBuffer (m_szName); + AddToSigBuffer ( W("(")); + if (cb>ulSigBlob) + goto ErrExit; + cbCur += cb; + ulSigBlob -= cb; + } + + ULONG i = 0; + while (i < ulArgs && ulSigBlob > 0) + { + ULONG ulDataUncompress; + + // Handle the sentinal for varargs because it isn't counted in the args. + CorSigUncompressData(&pbSigBlob[cbCur], &ulDataUncompress); + ++i; + + if (FAILED(hr = GetOneElementType(&pbSigBlob[cbCur], ulSigBlob, &cb))) + goto ErrExit; + if (i != ulArgs) { + AddToSigBuffer ( W(",")); + } + if (cb>ulSigBlob) + goto ErrExit; + + cbCur += cb; + ulSigBlob -= cb; + } + AddToSigBuffer ( W(")")); + } + + // Nothing consumed but not yet counted. + cb = 0; + +ErrExit: + + if (plSigBlobRemaining) + *plSigBlobRemaining = (ulSigBlob - cb); + + return hr; +} + +LPCWSTR MDInfo::TypeDefName(mdTypeDef inTypeDef) +{ + if (m_pImport == NULL) { + return W(""); + } + + HRESULT hr; + + hr = m_pImport->GetTypeDefProps( + // [IN] The import scope. + inTypeDef, // [IN] TypeDef token for inquiry. + m_szTempBuf, // [OUT] Put name here. + MAX_CLASSNAME_LENGTH , // [IN] size of name buffer in wide chars. + NULL, // [OUT] put size of name (wide chars) here. + NULL, // [OUT] Put flags here. + NULL); // [OUT] Put base class TypeDef/TypeRef here. + + if (FAILED(hr)) return (W("NoName")); + return (m_szTempBuf); +} // LPCWSTR MDInfo::TypeDefName() +LPCWSTR MDInfo::TypeRefName(mdTypeRef tr) +{ + if (m_pImport == NULL) { + return W(""); + } + + HRESULT hr; + + hr = m_pImport->GetTypeRefProps( + tr, // The class ref token. + NULL, // Resolution scope. + m_szTempBuf, // Put the name here. + MAX_CLASSNAME_LENGTH, // Size of the name buffer, wide chars. + NULL); // Put actual size of name here. + if (FAILED(hr)) return (W("NoName")); + + return (m_szTempBuf); +} // LPCWSTR MDInfo::TypeRefName() + +LPCWSTR MDInfo::TypeDeforRefName(mdToken inToken) +{ + if (RidFromToken(inToken)) + { + if (TypeFromToken(inToken) == mdtTypeDef) + return (TypeDefName((mdTypeDef) inToken)); + else if (TypeFromToken(inToken) == mdtTypeRef) + return (TypeRefName((mdTypeRef) inToken)); + else + return (W("[InvalidReference]")); + } + else + return (W("")); +} // LPCWSTR MDInfo::TypeDeforRefName() + + +HRESULT MDInfo::AddToSigBuffer(LPCWSTR string) +{ + HRESULT hr; + IfFailRet(m_pSigBuf->ReSize((_wcslen((LPWSTR)m_pSigBuf->Ptr()) + _wcslen(string) + 1) * sizeof(WCHAR))); + wcscat_s((LPWSTR)m_pSigBuf->Ptr(), m_pSigBuf->Size()/sizeof(WCHAR),string); + return NOERROR; +} + +HRESULT MDInfo::GetOneElementType(PCCOR_SIGNATURE pbSigBlob, ULONG ulSigBlob, ULONG *pcb) +{ + HRESULT hr = S_OK; // A result. + ULONG cbCur = 0; + ULONG cb; + ULONG ulData; + ULONG ulTemp; + int iTemp = 0; + mdToken tk; + const size_t capacity_buffer = 9; + + cb = CorSigUncompressData(pbSigBlob, &ulData); + + if (cb == ULONG(-1)) { + hr = E_FAIL; + goto ErrExit; + } + + cbCur += cb; + + // Handle the modifiers. + if (ulData & ELEMENT_TYPE_MODIFIER) + { + if (ulData == ELEMENT_TYPE_SENTINEL) + IfFailGo(AddToSigBuffer(W("<ELEMENT_TYPE_SENTINEL> "))); + else if (ulData == ELEMENT_TYPE_PINNED) + IfFailGo(AddToSigBuffer(W("PINNED "))); + else + { + hr = E_FAIL; + goto ErrExit; + } + if (FAILED(GetOneElementType(&pbSigBlob[cbCur], ulSigBlob-cbCur, &cb))) + goto ErrExit; + cbCur += cb; + goto ErrExit; + } + + // Handle the underlying element types. + if (ulData >= ELEMENT_TYPE_MAX) + { + hr = E_FAIL; + goto ErrExit; + } + while (ulData == ELEMENT_TYPE_PTR || ulData == ELEMENT_TYPE_BYREF) + { + IfFailGo(AddToSigBuffer(g_wszMapElementType[ulData])); + IfFailGo(AddToSigBuffer(W(" "))); + cb = CorSigUncompressData(&pbSigBlob[cbCur], &ulData); + cbCur += cb; + } + + // Generics + if (ulData == ELEMENT_TYPE_VAR) + { + IfFailGo(AddToSigBuffer(W("__Canon"))); + + // The next byte represents which generic parameter is referred to. We + // do not currently use this information, so just bypass this byte. + cbCur++; + + goto ErrExit; + } + + // A generic instance, e.g. IEnumerable<String> + if (ulData == ELEMENT_TYPE_GENERICINST) + { + // Print out the base type. + IfFailGo(GetOneElementType(&pbSigBlob[cbCur], ulSigBlob-cbCur, &cb)); + cbCur += cb; + + // Get the number of generic arguments. + ULONG numParams = 0; + IfFailGo(CorSigUncompressData(&pbSigBlob[cbCur], 1, &numParams, &cb)); + cbCur += cb; + + // Print out the list of arguments + IfFailGo(AddToSigBuffer(W("<"))); + for (ULONG i = 0; i < numParams; i++) + { + if (i > 0) + IfFailGo(AddToSigBuffer(W(","))); + + IfFailGo(GetOneElementType(&pbSigBlob[cbCur], ulSigBlob-cbCur, &cb)); + cbCur += cb; + } + IfFailGo(AddToSigBuffer(W(">"))); + goto ErrExit; + } + + // Past this point we must have something which directly maps to a value in g_wszMapElementType. + IfFailGo(AddToSigBuffer(g_wszMapElementType[ulData])); + if (CorIsPrimitiveType((CorElementType)ulData) || + ulData == ELEMENT_TYPE_TYPEDBYREF || + ulData == ELEMENT_TYPE_OBJECT || + ulData == ELEMENT_TYPE_I || + ulData == ELEMENT_TYPE_U) + { + // If this is a primitive type, we are done + goto ErrExit; + } + + AddToSigBuffer(W(" ")); + if (ulData == ELEMENT_TYPE_VALUETYPE || + ulData == ELEMENT_TYPE_CLASS || + ulData == ELEMENT_TYPE_CMOD_REQD || + ulData == ELEMENT_TYPE_CMOD_OPT) + { + cb = CorSigUncompressToken(&pbSigBlob[cbCur], &tk); + cbCur += cb; + + // get the name of type ref. Don't care if truncated + if (TypeFromToken(tk) == mdtTypeDef || TypeFromToken(tk) == mdtTypeRef) + { + IfFailGo(AddToSigBuffer(TypeDeforRefName(tk))); + } + else + { + _ASSERTE(TypeFromToken(tk) == mdtTypeSpec); + WCHAR buffer[capacity_buffer]; + _itow_s (tk, buffer, capacity_buffer, 16); + IfFailGo(AddToSigBuffer(buffer)); + } + if (ulData == ELEMENT_TYPE_CMOD_REQD || + ulData == ELEMENT_TYPE_CMOD_OPT) + { + IfFailGo(AddToSigBuffer(W(" "))); + if (FAILED(GetOneElementType(&pbSigBlob[cbCur], ulSigBlob-cbCur, &cb))) + goto ErrExit; + cbCur += cb; + } + + goto ErrExit; + } + if (ulData == ELEMENT_TYPE_SZARRAY) + { + // display the base type of SZARRAY or GENERICARRAY + if (FAILED(GetOneElementType(&pbSigBlob[cbCur], ulSigBlob-cbCur, &cb))) + goto ErrExit; + cbCur += cb; + goto ErrExit; + } + if (ulData == ELEMENT_TYPE_FNPTR) + { + cb = CorSigUncompressData(&pbSigBlob[cbCur], &ulData); + cbCur += cb; + if (ulData & IMAGE_CEE_CS_CALLCONV_EXPLICITTHIS) + IfFailGo(AddToSigBuffer(W("[explicit] "))); + if (ulData & IMAGE_CEE_CS_CALLCONV_HASTHIS) + IfFailGo(AddToSigBuffer(W("[hasThis] "))); + + IfFailGo(AddToSigBuffer(g_wszCalling[ulData & IMAGE_CEE_CS_CALLCONV_MASK])); + + // Get number of args + ULONG numArgs; + cb = CorSigUncompressData(&pbSigBlob[cbCur], &numArgs); + cbCur += cb; + + // do return type + if (FAILED(GetOneElementType(&pbSigBlob[cbCur], ulSigBlob-cbCur, &cb))) + goto ErrExit; + cbCur += cb; + + IfFailGo(AddToSigBuffer(W("("))); + while (numArgs > 0) + { + if (cbCur > ulSigBlob) + goto ErrExit; + if (FAILED(GetOneElementType(&pbSigBlob[cbCur], ulSigBlob-cbCur, &cb))) + goto ErrExit; + cbCur += cb; + --numArgs; + if (numArgs > 0) + IfFailGo(AddToSigBuffer(W(","))); + } + IfFailGo(AddToSigBuffer(W(")"))); + goto ErrExit; + } + + if (ulData == ELEMENT_TYPE_INTERNAL) + { + IfFailGo(AddToSigBuffer(W("MT "))); + + void *pvMethodTable; + cb = CorSigUncompressPointer(&pbSigBlob[cbCur], (void**)&pvMethodTable); + cbCur += cb; + + const size_t capacity_szMethodTableValue = 10; + WCHAR szMethodTableValue[10]; + itow_s_ptr((INT_PTR)pvMethodTable, szMethodTableValue, capacity_szMethodTableValue, 16); + + IfFailGo(AddToSigBuffer(szMethodTableValue)); + IfFailGo(AddToSigBuffer(W(" "))); + + IfFailGo(g_sos->GetMethodTableName(TO_CDADDR(pvMethodTable), mdNameLen, g_mdName, NULL)); + IfFailGo(AddToSigBuffer(g_mdName)); + + goto ErrExit; + } + + + if(ulData != ELEMENT_TYPE_ARRAY) return E_FAIL; + + // display the base type of SDARRAY + if (FAILED(GetOneElementType(&pbSigBlob[cbCur], ulSigBlob-cbCur, &cb))) + goto ErrExit; + cbCur += cb; + + IfFailGo(AddToSigBuffer(W(" "))); + // display the rank of MDARRAY + cb = CorSigUncompressData(&pbSigBlob[cbCur], &ulData); + cbCur += cb; + WCHAR buffer[capacity_buffer]; + _itow_s (ulData, buffer, capacity_buffer, 10); + IfFailGo(AddToSigBuffer(buffer)); + if (ulData == 0) + // we are done if no rank specified + goto ErrExit; + + IfFailGo(AddToSigBuffer(W(" "))); + // how many dimensions have size specified? + cb = CorSigUncompressData(&pbSigBlob[cbCur], &ulData); + cbCur += cb; + _itow_s (ulData, buffer, capacity_buffer, 10); + IfFailGo(AddToSigBuffer(buffer)); + if (ulData == 0) { + IfFailGo(AddToSigBuffer(W(" "))); + } + while (ulData) + { + + cb = CorSigUncompressData(&pbSigBlob[cbCur], &ulTemp); + _itow_s (ulTemp, buffer, capacity_buffer, 10); + IfFailGo(AddToSigBuffer(buffer)); + IfFailGo(AddToSigBuffer(W(" "))); + cbCur += cb; + ulData--; + } + // how many dimensions have lower bounds specified? + cb = CorSigUncompressData(&pbSigBlob[cbCur], &ulData); + cbCur += cb; + _itow_s (ulData, buffer, capacity_buffer, 10); + IfFailGo(AddToSigBuffer(buffer)); + while (ulData) + { + + cb = CorSigUncompressSignedInt(&pbSigBlob[cbCur], &iTemp); + _itow_s (iTemp, buffer, capacity_buffer, 10); + IfFailGo(AddToSigBuffer(buffer)); + IfFailGo(AddToSigBuffer(W(" "))); + cbCur += cb; + ulData--; + } + +ErrExit: + if (cbCur > ulSigBlob) + hr = E_FAIL; + *pcb = cbCur; + return hr; +} + +//***************************************************************************** +// Used when the method is tiny (< 64 bytes), and there are no local vars +//***************************************************************************** +typedef struct tagCOR_ILMETHOD_TINY : IMAGE_COR_ILMETHOD_TINY +{ + bool IsTiny() const { return((Flags_CodeSize & (CorILMethod_FormatMask >> 1)) == CorILMethod_TinyFormat); } + DWORD GetLocalVarSigTok() const { return(0); } +} COR_ILMETHOD_TINY; + + +//***************************************************************************** +// This strucuture is the 'fat' layout, where no compression is attempted. +// Note that this structure can be added on at the end, thus making it extensible +//***************************************************************************** +typedef struct tagCOR_ILMETHOD_FAT : IMAGE_COR_ILMETHOD_FAT +{ + bool IsFat() const { return((Flags & CorILMethod_FormatMask) == CorILMethod_FatFormat); } + mdToken GetLocalVarSigTok() const { return(LocalVarSigTok); } +} COR_ILMETHOD_FAT; diff --git a/src/ToolBox/SOS/Strike/ntinfo.h b/src/ToolBox/SOS/Strike/ntinfo.h new file mode 100644 index 0000000000..f3e6e91ff0 --- /dev/null +++ b/src/ToolBox/SOS/Strike/ntinfo.h @@ -0,0 +1,193 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ==++== +// + +// +// ==--== +#ifndef _ntinfo_h__ +#define _ntinfo_h__ + +//+--------------------------------------------------------------------------- +// +// forward declarations (in order to avoid type casting when accessing +// data members of the SOleTlsData structure). +// +//+--------------------------------------------------------------------------- + +class CAptCallCtrl; // see callctrl.hxx +class CSrvCallState; // see callctrl.hxx +class CObjServer; // see sobjact.hxx +class CSmAllocator; // see stg\h\smalloc.hxx +class CMessageCall; // see call.hxx +class CClientCall; // see call.hxx +class CAsyncCall; // see call.hxx +class CClipDataObject; // see ole232\clipbrd\clipdata.h +class CSurrogatedObjectList; // see com\inc\comsrgt.hxx +class CCtxCall; // see PSTable.hxx +class CPolicySet; // see PSTable.hxx +class CObjectContext; // see context.hxx +class CComApartment; // see aprtmnt.hxx +class ContextStackNode; +//+------------------------------------------------------------------- +// +// Struct: CallEntry +// +// Synopsis: Call Table Entry. +// +//+------------------------------------------------------------------- +typedef struct tagCallEntry +{ + void *pNext; // ptr to next entry + void *pvObject; // Entry object +} CallEntry; + +//+--------------------------------------------------------------------------- +// +// Enum: OLETLSFLAGS +// +// Synopsys: bit values for dwFlags field of SOleTlsData. If you just want +// to store a BOOL in TLS, use this enum and the dwFlag field. +// +//+--------------------------------------------------------------------------- +typedef enum tagOLETLSFLAGS +{ + OLETLS_LOCALTID = 0x01, // This TID is in the current process. + OLETLS_UUIDINITIALIZED = 0x02, // This Logical thread is init'd. + OLETLS_INTHREADDETACH = 0x04, // This is in thread detach. Needed + // due to NT's special thread detach + // rules. + OLETLS_CHANNELTHREADINITIALZED = 0x08,// This channel has been init'd + OLETLS_WOWTHREAD = 0x10, // This thread is a 16-bit WOW thread. + OLETLS_THREADUNINITIALIZING = 0x20, // This thread is in CoUninitialize. + OLETLS_DISABLE_OLE1DDE = 0x40, // This thread can't use a DDE window. + OLETLS_APARTMENTTHREADED = 0x80, // This is an STA apartment thread + OLETLS_MULTITHREADED = 0x100, // This is an MTA apartment thread + OLETLS_IMPERSONATING = 0x200, // This thread is impersonating + OLETLS_DISABLE_EVENTLOGGER = 0x400, // Prevent recursion in event logger + OLETLS_INNEUTRALAPT = 0x800, // This thread is in the NTA + OLETLS_DISPATCHTHREAD = 0x1000, // This is a dispatch thread + OLETLS_HOSTTHREAD = 0x2000, // This is a host thread + OLETLS_ALLOWCOINIT = 0x4000, // This thread allows inits + OLETLS_PENDINGUNINIT = 0x8000, // This thread has pending uninit + OLETLS_FIRSTMTAINIT = 0x10000,// First thread to attempt an MTA init + OLETLS_FIRSTNTAINIT = 0x20000,// First thread to attempt an NTA init + OLETLS_APTINITIALIZING = 0x40000 // Apartment Object is initializing +} OLETLSFLAGS; + + +//+--------------------------------------------------------------------------- +// +// Structure: SOleTlsData +// +// Synopsis: structure holding per thread state needed by OLE32 +// +//+--------------------------------------------------------------------------- +typedef struct tagSOleTlsData +{ + // jsimmons 5/23/2001 + // Alert Alert: nefarious folks (eg, URT) are looking in our TLS at + // various stuff. They expect that pCurrentCtx will be at a certain + // offset from the beginning of the tls struct. So don't add, delete, or + // move any members within this block. + +///////////////////////////////////////////////////////////////////////////////////////// +// ********* BEGIN "NO MUCKING AROUND" BLOCK ********* +///////////////////////////////////////////////////////////////////////////////////////// + // Docfile multiple allocator support + void *pvThreadBase; // per thread base pointer + CSmAllocator *pSmAllocator; // per thread docfile allocator + + DWORD dwApartmentID; // Per thread "process ID" + DWORD dwFlags; // see OLETLSFLAGS above + + LONG TlsMapIndex; // index in the global TLSMap + void **ppTlsSlot; // Back pointer to the thread tls slot + DWORD cComInits; // number of per-thread inits + DWORD cOleInits; // number of per-thread OLE inits + + DWORD cCalls; // number of outstanding calls + CMessageCall *pCallInfo; // channel call info + CAsyncCall *pFreeAsyncCall; // ptr to available call object for this thread. + CClientCall *pFreeClientCall; // ptr to available call object for this thread. + + CObjServer *pObjServer; // Activation Server Object for this apartment. + DWORD dwTIDCaller; // TID of current calling app + CObjectContext *pCurrentCtx; // Current context +///////////////////////////////////////////////////////////////////////////////////////// +// ********* END "NO MUCKING AROUND" BLOCK ********* +///////////////////////////////////////////////////////////////////////////////////////// + + CObjectContext *pEmptyCtx; // Empty context + + CObjectContext *pNativeCtx; // Native context + ULONGLONG ContextId; // Uniquely identifies the current context + CComApartment *pNativeApt; // Native apartment for the thread. + IUnknown *pCallContext; // call context object + CCtxCall *pCtxCall; // Context call object + + CPolicySet *pPS; // Policy set + PVOID pvPendingCallsFront;// Per Apt pending async calls + PVOID pvPendingCallsBack; + CAptCallCtrl *pCallCtrl; // call control for RPC for this apartment + + CSrvCallState *pTopSCS; // top server-side callctrl state + IMessageFilter *pMsgFilter; // temp storage for App MsgFilter + HWND hwndSTA; // STA server window same as poxid->hServerSTA + // ...needed on Win95 before oxid registration + LONG cORPCNestingLevel; // call nesting level (DBG only) + + DWORD cDebugData; // count of bytes of debug data in call + + UUID LogicalThreadId; // current logical thread id + + HANDLE hThread; // Thread handle used for cancel + HANDLE hRevert; // Token before first impersonate. + IUnknown *pAsyncRelease; // Controlling unknown for async release + // DDE data + HWND hwndDdeServer; // Per thread Common DDE server + + HWND hwndDdeClient; // Per thread Common DDE client + ULONG cServeDdeObjects; // non-zero if objects DDE should serve + // ClassCache data + LPVOID pSTALSvrsFront; // Chain of LServers registers in this thread if STA + // upper layer data + HWND hwndClip; // Clipboard window + + IDataObject *pDataObjClip; // Current Clipboard DataObject + DWORD dwClipSeqNum; // Clipboard Sequence # for the above DataObject + DWORD fIsClipWrapper; // Did we hand out the wrapper Clipboard DataObject? + IUnknown *punkState; // Per thread "state" object + // cancel data + DWORD cCallCancellation; // count of CoEnableCallCancellation + // async sends data + DWORD cAsyncSends; // count of async sends outstanding + + CAsyncCall* pAsyncCallList; // async calls outstanding + CSurrogatedObjectList *pSurrogateList; // Objects in the surrogate + + LockEntry lockEntry; // Locks currently held by the thread + CallEntry CallEntry; // client-side call chain for this thread + +#ifdef WX86OLE + IUnknown *punkStateWx86; // Per thread "state" object for Wx86 +#endif + void *pDragCursors; // Per thread drag cursor table. + + IUnknown *punkError; // Per thread error object. + ULONG cbErrorData; // Maximum size of error data. + + IUnknown *punkActiveXSafetyProvider; + +#if DBG==1 + LONG cTraceNestingLevel; // call nesting level for OLETRACE +#endif + + ContextStackNode* pContextStack; + +} SOleTlsData; + +#endif //_ntinfo_h__ + diff --git a/src/ToolBox/SOS/Strike/platformspecific.h b/src/ToolBox/SOS/Strike/platformspecific.h new file mode 100644 index 0000000000..fdbc5b52ca --- /dev/null +++ b/src/ToolBox/SOS/Strike/platformspecific.h @@ -0,0 +1,195 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ==++== +// + +// +// ==--== + +// Include platform specific declarations based on the target platform rather than the host platform. + +#ifndef __PLATFORM_SPECIFIC_INCLUDED +#define __PLATFORM_SPECIFIC_INCLUDED + +// The main debugger code already has target platform definitions for CONTEXT. +#include "../../../debug/inc/dbgtargetcontext.h" + +#ifndef FEATURE_PAL + +// The various OS structure definitions below tend to differ based soley on the size of pointers. DT_POINTER +// is a type whose size matches that of the target platform. It's integral rather than point since it is never +// legal to dereference one of these on the host. +#ifdef _TARGET_WIN64_ +typedef ULONG64 DT_POINTER; +#else +typedef ULONG32 DT_POINTER; +#endif + +struct DT_LIST_ENTRY +{ + DT_POINTER Flink; + DT_POINTER Blink; +}; + +struct DT_UNICODE_STRING +{ + USHORT Length; + USHORT MaximumLength; + DT_POINTER Buffer; +}; + +#define DT_GDI_HANDLE_BUFFER_SIZE32 34 +#define DT_GDI_HANDLE_BUFFER_SIZE64 60 + +#ifndef IMAGE_FILE_MACHINE_ARMNT +#define IMAGE_FILE_MACHINE_ARMNT 0x01c4 // ARM Thumb-2 Little-Endian +#endif + +#ifndef IMAGE_FILE_MACHINE_ARM64 +#define IMAGE_FILE_MACHINE_ARM64 0xAA64 // ARM64 Little-Endian +#endif + +#ifdef _TARGET_WIN64_ +typedef ULONG DT_GDI_HANDLE_BUFFER[DT_GDI_HANDLE_BUFFER_SIZE64]; +#else +typedef ULONG DT_GDI_HANDLE_BUFFER[DT_GDI_HANDLE_BUFFER_SIZE32]; +#endif + +struct DT_PEB +{ + BOOLEAN InheritedAddressSpace; + BOOLEAN ReadImageFileExecOptions; + BOOLEAN BeingDebugged; + BOOLEAN SpareBool; + DT_POINTER Mutant; + DT_POINTER ImageBaseAddress; + DT_POINTER Ldr; + DT_POINTER ProcessParameters; + DT_POINTER SubSystemData; + DT_POINTER ProcessHeap; + DT_POINTER FastPebLock; + DT_POINTER SparePtr1; + DT_POINTER SparePtr2; + ULONG EnvironmentUpdateCount; + DT_POINTER KernelCallbackTable; + ULONG SystemReserved[1]; + struct _dummy { + ULONG ExecuteOptions : 2; + ULONG SpareBits : 30; + }; + DT_POINTER FreeList; + ULONG TlsExpansionCounter; + DT_POINTER TlsBitmap; + ULONG TlsBitmapBits[2]; + DT_POINTER ReadOnlySharedMemoryBase; + DT_POINTER ReadOnlySharedMemoryHeap; + DT_POINTER ReadOnlyStaticServerData; + DT_POINTER AnsiCodePageData; + DT_POINTER OemCodePageData; + DT_POINTER UnicodeCaseTableData; + ULONG NumberOfProcessors; + ULONG NtGlobalFlag; + LARGE_INTEGER CriticalSectionTimeout; + DT_POINTER HeapSegmentReserve; + DT_POINTER HeapSegmentCommit; + DT_POINTER HeapDeCommitTotalFreeThreshold; + DT_POINTER HeapDeCommitFreeBlockThreshold; + ULONG NumberOfHeaps; + ULONG MaximumNumberOfHeaps; + DT_POINTER ProcessHeaps; + DT_POINTER GdiSharedHandleTable; + DT_POINTER ProcessStarterHelper; + ULONG GdiDCAttributeList; + DT_POINTER LoaderLock; + ULONG OSMajorVersion; + ULONG OSMinorVersion; + USHORT OSBuildNumber; + USHORT OSCSDVersion; + ULONG OSPlatformId; + ULONG ImageSubsystem; + ULONG ImageSubsystemMajorVersion; + ULONG ImageSubsystemMinorVersion; + DT_POINTER ImageProcessAffinityMask; + DT_GDI_HANDLE_BUFFER GdiHandleBuffer; + DT_POINTER PostProcessInitRoutine; + DT_POINTER TlsExpansionBitmap; + ULONG TlsExpansionBitmapBits[32]; + ULONG SessionId; + ULARGE_INTEGER AppCompatFlags; + ULARGE_INTEGER AppCompatFlagsUser; + DT_POINTER pShimData; + DT_POINTER AppCompatInfo; + DT_UNICODE_STRING CSDVersion; + DT_POINTER ActivationContextData; + DT_POINTER ProcessAssemblyStorageMap; + DT_POINTER SystemDefaultActivationContextData; + DT_POINTER SystemAssemblyStorageMap; + DT_POINTER MinimumStackCommit; + DT_POINTER FlsCallback; + DT_LIST_ENTRY FlsListHead; + DT_POINTER FlsBitmap; + ULONG FlsBitmapBits[FLS_MAXIMUM_AVAILABLE / (sizeof(ULONG) * 8)]; + ULONG FlsHighIndex; +}; + +struct DT_PEB_LDR_DATA +{ + BYTE Reserved1[8]; + DT_POINTER Reserved2[3]; + DT_LIST_ENTRY InMemoryOrderModuleList; +}; + +struct DT_CURDIR +{ + DT_UNICODE_STRING DosPath; + DT_POINTER Handle; +}; + +struct DT_RTL_DRIVE_LETTER_CURDIR { + USHORT Flags; + USHORT Length; + ULONG TimeStamp; + STRING DosPath; +}; + +#define DT_RTL_MAX_DRIVE_LETTERS 32 + +struct DT_RTL_USER_PROCESS_PARAMETERS +{ + ULONG MaximumLength; + ULONG Length; + ULONG Flags; + ULONG DebugFlags; + DT_POINTER ConsoleHandle; + ULONG ConsoleFlags; + DT_POINTER StandardInput; + DT_POINTER StandardOutput; + DT_POINTER StandardError; + DT_CURDIR CurrentDirectory; + DT_UNICODE_STRING DllPath; + DT_UNICODE_STRING ImagePathName; + DT_UNICODE_STRING CommandLine; + DT_POINTER Environment; + ULONG StartingX; + ULONG StartingY; + ULONG CountX; + ULONG CountY; + ULONG CountCharsX; + ULONG CountCharsY; + ULONG FillAttribute; + ULONG WindowFlags; + ULONG ShowWindowFlags; + DT_UNICODE_STRING WindowTitle; + DT_UNICODE_STRING DesktopInfo; + DT_UNICODE_STRING ShellInfo; + DT_UNICODE_STRING RuntimeData; + DT_RTL_DRIVE_LETTER_CURDIR CurrentDirectores[ DT_RTL_MAX_DRIVE_LETTERS ]; +}; + +#endif // !FEATURE_PAL + +#define DT_OS_PAGE_SIZE 4096 + +#endif // !__PLATFORM_SPECIFIC_INCLUDED diff --git a/src/ToolBox/SOS/Strike/sildasm.cpp b/src/ToolBox/SOS/Strike/sildasm.cpp new file mode 100644 index 0000000000..6bd3bb4801 --- /dev/null +++ b/src/ToolBox/SOS/Strike/sildasm.cpp @@ -0,0 +1,1090 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ==++== +// + +// +// ==--== +// +// disasm.cpp : Defines the entry point for the console application. +// +#ifndef FEATURE_PAL +#include <tchar.h> +#endif +#include "strike.h" +#include "util.h" +#include "strsafe.h" +//#ifndef FEATURE_PAL +//#include "gcinfo.h" +//#endif +#include "disasm.h" +#include <dbghelp.h> + +#include "corhdr.h" + +#include "cor.h" +#include "dacprivate.h" + +#include "openum.h" + +#include "sos_md.h" + +#define SOS_INCLUDE 1 +#include "corhlpr.h" +#include "corhlpr.cpp" + +////////////////////////////////////////////////////////////////////////////////////////////////////////// +#undef printf +#define printf ExtOut + +// typedef unsigned char BYTE; +struct OpCode +{ + int code; + const char *name; + int args; + BYTE b1; + BYTE b2; + + unsigned int getCode() { + if (b1==0xFF) return b2; + else return (0xFE00 | b2); + } +}; + +#define OPCODES_LENGTH 0x122 + +#undef OPDEF +#define OPDEF(c,s,pop,push,args,type,l,s1,s2,ctrl) {c, s, args, s1, s2}, +static OpCode opcodes[] = +{ +#include "opcode.def" +}; + +static ULONG position = 0; +static BYTE *pBuffer = NULL; + +// The UNALIGNED is because on IA64 alignment rules would prevent +// us from reading a pointer from an unaligned source. +template <typename T> +T readData ( ) { + T val = *((T UNALIGNED*)(pBuffer+position)); + position += sizeof(T); + return val; +} + +unsigned int readOpcode() +{ + unsigned int c = readData<BYTE>(); + if (c == 0xFE) + { + c = readData<BYTE>(); + c |= 0x100; + } + return c; +} + +void DisassembleToken(IMetaDataImport *i, + DWORD token) +{ + HRESULT hr; + + switch (TypeFromToken(token)) + { + default: + printf("<unknown token type %08x>", TypeFromToken(token)); + break; + + case mdtTypeDef: + { + ULONG cLen; + WCHAR szName[50]; + + hr = i->GetTypeDefProps(token, szName, 49, &cLen, NULL, NULL); + + if (FAILED(hr)) + StringCchCopyW(szName, COUNTOF(szName), W("<unknown type def>")); + + printf("%S", szName); + } + break; + + case mdtTypeRef: + { + ULONG cLen; + WCHAR szName[50]; + + hr = i->GetTypeRefProps(token, NULL, szName, 49, &cLen); + + if (FAILED(hr)) + StringCchCopyW(szName, COUNTOF(szName), W("<unknown type ref>")); + + printf("%S", szName); + } + break; + + case mdtFieldDef: + { + ULONG cLen; + WCHAR szFieldName[50]; + WCHAR szClassName[50]; + mdTypeDef mdClass; + + hr = i->GetFieldProps(token, &mdClass, szFieldName, 49, &cLen, + NULL, NULL, NULL, NULL, NULL, NULL); + + if (FAILED(hr)) + StringCchCopyW(szFieldName, COUNTOF(szFieldName), W("<unknown field def>")); + + hr = i->GetTypeDefProps(mdClass, szClassName, 49, &cLen, + NULL, NULL); + + if (FAILED(hr)) + StringCchCopyW(szClassName, COUNTOF(szClassName), W("<unknown type def>")); + + printf("%S::%S", szClassName, szFieldName); + } + break; + + case mdtMethodDef: + { + ULONG cLen; + WCHAR szFieldName[50]; + WCHAR szClassName[50]; + mdTypeDef mdClass; + + hr = i->GetMethodProps(token, &mdClass, szFieldName, 49, &cLen, + NULL, NULL, NULL, NULL, NULL); + + if (FAILED(hr)) + StringCchCopyW(szFieldName, COUNTOF(szFieldName), W("<unknown method def>")); + + hr = i->GetTypeDefProps(mdClass, szClassName, 49, &cLen, + NULL, NULL); + + if (FAILED(hr)) + StringCchCopyW(szClassName, COUNTOF(szClassName), W("<unknown type def>")); + + printf("%S::%S", szClassName, szFieldName); + } + break; + + case mdtMemberRef: + { + mdTypeRef cr = mdTypeRefNil; + LPCWSTR pMemberName; + WCHAR memberName[50]; + ULONG memberNameLen; + + hr = i->GetMemberRefProps(token, &cr, memberName, 49, + &memberNameLen, NULL, NULL); + + if (FAILED(hr)) + { + pMemberName = W("<unknown member ref>"); + } + else + pMemberName = memberName; + + ULONG cLen; + WCHAR szName[50]; + + if(TypeFromToken(cr) == mdtTypeRef) + { + if (FAILED(i->GetTypeRefProps(cr, NULL, szName, 50, &cLen))) + { + StringCchCopyW(szName, COUNTOF(szName), W("<unknown type ref>")); + } + } + else if(TypeFromToken(cr) == mdtTypeDef) + { + if (FAILED(i->GetTypeDefProps(cr, szName, 49, &cLen, + NULL, NULL))) + { + StringCchCopyW(szName, COUNTOF(szName), W("<unknown type def>")); + } + } + else if(TypeFromToken(cr) == mdtTypeSpec) + { + IMDInternalImport *pIMDI = NULL; + if (SUCCEEDED(GetMDInternalFromImport(i, &pIMDI))) + { + CQuickBytes out; + ULONG cSig; + PCCOR_SIGNATURE sig; + if (FAILED(pIMDI->GetSigFromToken(cr, &cSig, &sig))) + { + StringCchCopyW(szName, COUNTOF(szName), W("<Invalid record>")); + } + else + { + PrettyPrintType(sig, &out, pIMDI); + MultiByteToWideChar (CP_ACP, 0, asString(&out), -1, szName, 50); + } + + pIMDI->Release(); + } + else + { + StringCchCopyW(szName, COUNTOF(szName), W("<unknown type spec>")); + } + } + else + { + StringCchCopyW(szName, COUNTOF(szName), W("<unknown type token>")); + } + + printf("%S::%S ", szName, pMemberName); + } + break; + } +} + +ULONG GetILSize(DWORD_PTR ilAddr) +{ + ULONG uRet = 0; + + // workaround: read enough bytes at ilAddr to presumably get the entire header. + // Could be error prone. + + static BYTE headerArray[1024]; + HRESULT Status = g_ExtData->ReadVirtual(TO_CDADDR(ilAddr), headerArray, sizeof(headerArray), NULL); + if (SUCCEEDED(Status)) + { + COR_ILMETHOD_DECODER header((COR_ILMETHOD *)headerArray); + // uRet = header.GetHeaderSize(); + uRet = header.GetOnDiskSize((COR_ILMETHOD *)headerArray); + } + + return uRet; +} + +HRESULT DecodeILFromAddress(IMetaDataImport *pImport, TADDR ilAddr) +{ + HRESULT Status = S_OK; + + ULONG Size = GetILSize(ilAddr); + if (Size == 0) + { + ExtOut("error decoding IL\n"); + return Status; + } + + ExtOut("ilAddr = %p\n", SOS_PTR(ilAddr)); + + // Read the memory into a local buffer + ArrayHolder<BYTE> pArray = new BYTE[Size]; + Status = g_ExtData->ReadVirtual(TO_CDADDR(ilAddr), pArray, Size, NULL); + if (Status != S_OK) + { + ExtOut("Failed to read memory\n"); + return Status; + } + + DecodeIL(pImport, pArray, Size); + + return Status; +} + +void DecodeIL(IMetaDataImport *pImport, BYTE *buffer, ULONG bufSize) +{ + // First decode the header + COR_ILMETHOD *pHeader = (COR_ILMETHOD *) buffer; + COR_ILMETHOD_DECODER header(pHeader); + + // Set globals + position = 0; + pBuffer = (BYTE *) header.Code; + + UINT indentCount = 0; + ULONG endCodePosition = header.GetCodeSize(); + while(position < endCodePosition) + { + for (unsigned e=0;e<header.EHCount();e++) + { + IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_FAT ehBuff; + const IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_FAT* ehInfo; + + ehInfo = header.EH->EHClause(e,&ehBuff); + if (ehInfo->TryOffset == position) + { + printf ("%*s.try\n%*s{\n", indentCount, "", indentCount, ""); + indentCount+=2; + } + else if ((ehInfo->TryOffset + ehInfo->TryLength) == position) + { + indentCount-=2; + printf("%*s} // end .try\n", indentCount, ""); + } + + if (ehInfo->HandlerOffset == position) + { + if (ehInfo->Flags == COR_ILEXCEPTION_CLAUSE_FINALLY) + printf("%*s.finally\n%*s{\n", indentCount, "", indentCount, ""); + else + printf("%*s.catch\n%*s{\n", indentCount, "", indentCount, ""); + + indentCount+=2; + } + else if ((ehInfo->HandlerOffset + ehInfo->HandlerLength) == position) + { + indentCount-=2; + + if (ehInfo->Flags == COR_ILEXCEPTION_CLAUSE_FINALLY) + printf("%*s} // end .finally\n", indentCount, ""); + else + printf("%*s} // end .catch\n", indentCount, ""); + } + } + + printf("%*sIL_%04x: ", indentCount, "", position); + unsigned int c = readOpcode(); + OpCode opcode = opcodes[c]; + printf("%s ", opcode.name); + + switch(opcode.args) + { + case InlineNone: break; + + case ShortInlineVar: + printf("VAR OR ARG %d",readData<BYTE>()); break; + case InlineVar: + printf("VAR OR ARG %d",readData<WORD>()); break; + case InlineI: + printf("%d",readData<LONG>()); + break; + case InlineR: + printf("%f",readData<double>()); + break; + case InlineBrTarget: + printf("IL_%04x",readData<LONG>() + position); break; + case ShortInlineBrTarget: + printf("IL_%04x",readData<BYTE>() + position); break; + case InlineI8: + printf("%ld", readData<__int64>()); break; + + case InlineMethod: + case InlineField: + case InlineType: + case InlineTok: + case InlineSig: + { + LONG l = readData<LONG>(); + if (pImport != NULL) + { + DisassembleToken(pImport, l); + } + else + { + printf("TOKEN %x", l); + } + break; + } + + case InlineString: + { + LONG l = readData<LONG>(); + + ULONG numChars; + WCHAR str[84]; + + if ((pImport != NULL) && (pImport->GetUserString((mdString) l, str, 80, &numChars) == S_OK)) + { + if (numChars < 80) + str[numChars] = 0; + wcscpy_s(&str[79], 4, W("...")); + WCHAR* ptr = str; + while(*ptr != 0) { + if (*ptr < 0x20 || * ptr >= 0x80) { + *ptr = '.'; + } + ptr++; + } + + printf("\"%S\"", str); + } + else + { + printf("STRING %x", l); + } + break; + } + + case InlineSwitch: + { + LONG cases = readData<LONG>(); + LONG *pArray = new LONG[cases]; + LONG i=0; + for(i=0;i<cases;i++) + { + pArray[i] = readData<LONG>(); + } + printf("("); + for(i=0;i<cases;i++) + { + if (i != 0) + printf(", "); + printf("IL_%04x",pArray[i] + position); + } + printf(")"); + delete [] pArray; + break; + } + case ShortInlineI: + printf("%d", readData<BYTE>()); break; + case ShortInlineR: + printf("%f", readData<float>()); break; + default: printf("Error, unexpected opcode type\n"); break; + } + + printf("\n"); + } +} + +DWORD_PTR GetObj(DacpObjectData& tokenArray, UINT item) +{ + if (item < tokenArray.dwNumComponents) + { + DWORD_PTR dwAddr = (DWORD_PTR) (tokenArray.ArrayDataPtr + tokenArray.dwComponentSize*item); + DWORD_PTR objPtr; + if (SUCCEEDED(MOVE(objPtr, dwAddr))) + { + return objPtr; + } + } + return NULL; +} + + +void DisassembleToken(DacpObjectData& tokenArray, + DWORD token) +{ + switch (TypeFromToken(token)) + { + default: + printf("<unknown token type (token=%08x)>", token); + break; + + case mdtTypeDef: + { + DWORD_PTR runtimeTypeHandle = GetObj(tokenArray, RidFromToken(token)); + + DWORD_PTR runtimeType = NULL; + MOVE(runtimeType, runtimeTypeHandle + sizeof(DWORD_PTR)); + + int offset = GetObjFieldOffset(runtimeType, W("m_handle")); + + DWORD_PTR methodTable = NULL; + MOVE(methodTable, runtimeType + offset); + + if (NameForMT_s(methodTable, g_mdName,mdNameLen)) + { + printf("%x \"%S\"", token, g_mdName); + } + else + { + printf("<invalid MethodTable>"); + } + } + break; + + case mdtSignature: + case mdtTypeRef: + { + printf ("%x (%p)", token, SOS_PTR(GetObj(tokenArray, RidFromToken(token)))); + } + break; + + case mdtFieldDef: + { + printf ("%x (%p)", token, SOS_PTR(GetObj(tokenArray, RidFromToken(token)))); + } + break; + + case mdtMethodDef: + { + CLRDATA_ADDRESS runtimeMethodHandle = GetObj(tokenArray, RidFromToken(token)); + int offset = GetObjFieldOffset(runtimeMethodHandle, W("m_value")); + + TADDR runtimeMethodInfo = NULL; + MOVE(runtimeMethodInfo, runtimeMethodHandle+offset); + + offset = GetObjFieldOffset(runtimeMethodInfo, W("m_handle")); + + TADDR methodDesc = NULL; + MOVE(methodDesc, runtimeMethodInfo+offset); + + NameForMD_s((DWORD_PTR)methodDesc, g_mdName, mdNameLen); + printf ("%x %S", token, g_mdName); + } + break; + + case mdtMemberRef: + { + printf ("%x (%p)", token, SOS_PTR(GetObj(tokenArray, RidFromToken(token)))); + } + break; + case mdtString: + { + DWORD_PTR strObj = GetObj(tokenArray, RidFromToken(token)); + printf ("%x \"", token); + StringObjectContent (strObj, FALSE, 40); + printf ("\""); + } + break; + } +} + +// Similar to the function above. TODO: factor them together before checkin. +void DecodeDynamicIL(BYTE *data, ULONG Size, DacpObjectData& tokenArray) +{ + // There is no header for this dynamic guy. + // Set globals + position = 0; + pBuffer = data; + + // At this time no exception information will be displayed (fix soon) + UINT indentCount = 0; + ULONG endCodePosition = Size; + while(position < endCodePosition) + { + printf("%*sIL_%04x: ", indentCount, "", position); + unsigned int c = readOpcode(); + OpCode opcode = opcodes[c]; + printf("%s ", opcode.name); + + switch(opcode.args) + { + case InlineNone: break; + + case ShortInlineVar: + printf("VAR OR ARG %d",readData<BYTE>()); break; + case InlineVar: + printf("VAR OR ARG %d",readData<WORD>()); break; + case InlineI: + printf("%d",readData<LONG>()); + break; + case InlineR: + printf("%f",readData<double>()); + break; + case InlineBrTarget: + printf("IL_%04x",readData<LONG>() + position); break; + case ShortInlineBrTarget: + printf("IL_%04x",readData<BYTE>() + position); break; + case InlineI8: + printf("%ld", readData<__int64>()); break; + + case InlineMethod: + case InlineField: + case InlineType: + case InlineTok: + case InlineSig: + case InlineString: + { + LONG l = readData<LONG>(); + DisassembleToken(tokenArray, l); + break; + } + + case InlineSwitch: + { + LONG cases = readData<LONG>(); + LONG *pArray = new LONG[cases]; + LONG i=0; + for(i=0;i<cases;i++) + { + pArray[i] = readData<LONG>(); + } + printf("("); + for(i=0;i<cases;i++) + { + if (i != 0) + printf(", "); + printf("IL_%04x",pArray[i] + position); + } + printf(")"); + delete [] pArray; + break; + } + case ShortInlineI: + printf("%d", readData<BYTE>()); break; + case ShortInlineR: + printf("%f", readData<float>()); break; + default: printf("Error, unexpected opcode type\n"); break; + } + + printf("\n"); + } +} + + + +/******************************************************************************/ +// CQuickBytes utilities +static char* asString(CQuickBytes *out) { + SIZE_T oldSize = out->Size(); + out->ReSize(oldSize + 1); + char* cur = &((char*) out->Ptr())[oldSize]; + *cur = 0; + out->ReSize(oldSize); // Don't count the null character + return((char*) out->Ptr()); +} + +static void appendStr(CQuickBytes *out, const char* str, unsigned len=-1) { + if(len == (unsigned)(-1)) len = (unsigned)strlen(str); + SIZE_T oldSize = out->Size(); + out->ReSize(oldSize + len); + char* cur = &((char*) out->Ptr())[oldSize]; + memcpy(cur, str, len); + // Note no trailing null! +} + +static void appendChar(CQuickBytes *out, char chr) { + SIZE_T oldSize = out->Size(); + out->ReSize(oldSize + 1); + ((char*) out->Ptr())[oldSize] = chr; + // Note no trailing null! +} + +static void insertStr(CQuickBytes *out, const char* str) { + unsigned len = (unsigned)strlen(str); + SIZE_T oldSize = out->Size(); + out->ReSize(oldSize + len); + char* cur = &((char*) out->Ptr())[len]; + memmove(cur,out->Ptr(),oldSize); + memcpy(out->Ptr(), str, len); + // Note no trailing null! +} + +static void appendStrNum(CQuickBytes *out, int num) { + char buff[16]; + sprintf_s(buff, COUNTOF(buff), "%d", num); + appendStr(out, buff); +} + + +//PrettyPrinting type names +PCCOR_SIGNATURE PrettyPrintType( + PCCOR_SIGNATURE typePtr, // type to convert, + CQuickBytes *out, // where to put the pretty printed string + IMDInternalImport *pIMDI, // ptr to IMDInternal class with ComSig + DWORD formatFlags /*= formatILDasm*/) +{ + mdToken tk; + const char* str; + int typ; + CQuickBytes tmp; + CQuickBytes Appendix; + BOOL Reiterate; + int n; + + do { + Reiterate = FALSE; + switch(typ = *typePtr++) { + case ELEMENT_TYPE_VOID : + str = "void"; goto APPEND; + case ELEMENT_TYPE_BOOLEAN : + str = "bool"; goto APPEND; + case ELEMENT_TYPE_CHAR : + str = "char"; goto APPEND; + case ELEMENT_TYPE_I1 : + str = "int8"; goto APPEND; + case ELEMENT_TYPE_U1 : + str = "uint8"; goto APPEND; + case ELEMENT_TYPE_I2 : + str = "int16"; goto APPEND; + case ELEMENT_TYPE_U2 : + str = "uint16"; goto APPEND; + case ELEMENT_TYPE_I4 : + str = "int32"; goto APPEND; + case ELEMENT_TYPE_U4 : + str = "uint32"; goto APPEND; + case ELEMENT_TYPE_I8 : + str = "int64"; goto APPEND; + case ELEMENT_TYPE_U8 : + str = "uint64"; goto APPEND; + case ELEMENT_TYPE_R4 : + str = "float32"; goto APPEND; + case ELEMENT_TYPE_R8 : + str = "float64"; goto APPEND; + case ELEMENT_TYPE_U : + str = "native uint"; goto APPEND; + case ELEMENT_TYPE_I : + str = "native int"; goto APPEND; + case ELEMENT_TYPE_OBJECT : + str = "object"; goto APPEND; + case ELEMENT_TYPE_STRING : + str = "string"; goto APPEND; + case ELEMENT_TYPE_TYPEDBYREF : + str = "typedref"; goto APPEND; + APPEND: + appendStr(out, (char*)str); + break; + + case ELEMENT_TYPE_VALUETYPE : + if ((formatFlags & FormatKwInNames) != 0) + str = "valuetype "; + else str = ""; + goto DO_CLASS; + case ELEMENT_TYPE_CLASS : + if ((formatFlags & FormatKwInNames) != 0) + str = "class "; + else str = ""; + goto DO_CLASS; + + DO_CLASS: + appendStr(out, (char*)str); + typePtr += CorSigUncompressToken(typePtr, &tk); + if(IsNilToken(tk)) + { + appendStr(out, "[ERROR! NIL TOKEN]"); + } + else PrettyPrintClass(out, tk, pIMDI, formatFlags); + break; + + case ELEMENT_TYPE_SZARRAY : + insertStr(&Appendix,"[]"); + Reiterate = TRUE; + break; + + case ELEMENT_TYPE_ARRAY : + { + typePtr = PrettyPrintType(typePtr, out, pIMDI, formatFlags); + unsigned rank = CorSigUncompressData(typePtr); + // <TODO> what is the syntax for the rank 0 case? </TODO> + if (rank == 0) { + appendStr(out, "[BAD: RANK == 0!]"); + } + else { + _ASSERTE(rank != 0); +#ifdef _PREFAST_ +#pragma warning(push) +#pragma warning(disable:22009) // "Suppress PREfast warnings about integer overflow" +// PREFAST warns about using _alloca in a loop. However when we're in this switch case we do NOT +// set Reiterate to true, so we only execute through the loop once! +#pragma warning(disable:6263) // "Suppress PREfast warnings about stack overflow due to _alloca in a loop." +#endif + int* lowerBounds = (int*) _alloca(sizeof(int)*2*rank); + int* sizes = &lowerBounds[rank]; + memset(lowerBounds, 0, sizeof(int)*2*rank); + + unsigned numSizes = CorSigUncompressData(typePtr); + _ASSERTE(numSizes <= rank); + unsigned i; + for(i =0; i < numSizes; i++) + sizes[i] = CorSigUncompressData(typePtr); + + unsigned numLowBounds = CorSigUncompressData(typePtr); + _ASSERTE(numLowBounds <= rank); + for(i = 0; i < numLowBounds; i++) + typePtr+=CorSigUncompressSignedInt(typePtr,&lowerBounds[i]); + + appendChar(out, '['); + if (rank == 1 && numSizes == 0 && numLowBounds == 0) + appendStr(out, "..."); + else { + for(i = 0; i < rank; i++) + { + //if (sizes[i] != 0 || lowerBounds[i] != 0) + { + if (lowerBounds[i] == 0 && i < numSizes) + appendStrNum(out, sizes[i]); + else + { + if(i < numLowBounds) + { + appendStrNum(out, lowerBounds[i]); + appendStr(out, "..."); + if (/*sizes[i] != 0 && */i < numSizes) + appendStrNum(out, lowerBounds[i] + sizes[i] - 1); + } + } + } + if (i < rank-1) + appendChar(out, ','); + } + } + appendChar(out, ']'); +#ifdef _PREFAST_ +#pragma warning(pop) +#endif + } + } break; + + case ELEMENT_TYPE_VAR : + appendChar(out, '!'); + n = CorSigUncompressData(typePtr); + appendStrNum(out, n); + break; + + case ELEMENT_TYPE_MVAR : + appendChar(out, '!'); + appendChar(out, '!'); + n = CorSigUncompressData(typePtr); + appendStrNum(out, n); + break; + + case ELEMENT_TYPE_FNPTR : + appendStr(out, "method "); + appendStr(out, "METHOD"); // was: typePtr = PrettyPrintSignature(typePtr, 0x7FFF, "*", out, pIMDI, NULL); + break; + + case ELEMENT_TYPE_GENERICINST : + { + typePtr = PrettyPrintType(typePtr, out, pIMDI, formatFlags); + if ((formatFlags & FormatSignature) == 0) + break; + + if ((formatFlags & FormatAngleBrackets) != 0) + appendStr(out, "<"); + else + appendStr(out,"["); + unsigned numArgs = CorSigUncompressData(typePtr); + bool needComma = false; + while(numArgs--) + { + if (needComma) + appendChar(out, ','); + typePtr = PrettyPrintType(typePtr, out, pIMDI, formatFlags); + needComma = true; + } + if ((formatFlags & FormatAngleBrackets) != 0) + appendStr(out, ">"); + else + appendStr(out,"]"); + break; + } + + case ELEMENT_TYPE_PINNED : + str = " pinned"; goto MODIFIER; + case ELEMENT_TYPE_PTR : + str = "*"; goto MODIFIER; + case ELEMENT_TYPE_BYREF : + str = "&"; goto MODIFIER; + MODIFIER: + insertStr(&Appendix, str); + Reiterate = TRUE; + break; + + default: + case ELEMENT_TYPE_SENTINEL : + case ELEMENT_TYPE_END : + //_ASSERTE(!"Unknown Type"); + if(typ) + { + char sz[64]; + sprintf_s(sz,COUNTOF(sz),"/* UNKNOWN TYPE (0x%X)*/",typ); + appendStr(out, sz); + } + break; + } // end switch + } while(Reiterate); + if (Appendix.Size() > 0) + appendStr(out,asString(&Appendix)); + + return(typePtr); +} + +// Protection against null names, used by ILDASM/SOS +const char *const szStdNamePrefix[] = {"MO","TR","TD","","FD","","MD","","PA","II","MR","","CA","","PE","","","SG","","","EV", +"","","PR","","","MOR","TS","","","","","AS","","","AR","","","FL","ET","MAR"}; + +#define MAKE_NAME_IF_NONE(psz, tk) { if(!(psz && *psz)) { char* sz = (char*)_alloca(16); \ +sprintf_s(sz,16,"$%s$%X",szStdNamePrefix[tk>>24],tk&0x00FFFFFF); psz = sz; } } + +const char* PrettyPrintClass( + CQuickBytes *out, // where to put the pretty printed string + mdToken tk, // The class token to look up + IMDInternalImport *pIMDI, // ptr to IMDInternalImport class with ComSig + DWORD formatFlags /*= formatILDasm*/) +{ + if(tk == mdTokenNil) // Zero resolution scope for "somewhere here" TypeRefs + { + appendStr(out,"[*]"); + return(asString(out)); + } + if (!pIMDI->IsValidToken(tk)) + { + char str[1024]; + sprintf_s(str,COUNTOF(str)," [ERROR: INVALID TOKEN 0x%8.8X] ",tk); + appendStr(out, str); + return(asString(out)); + } + switch (TypeFromToken(tk)) + { + case mdtTypeRef: + case mdtTypeDef: + { + const char *nameSpace = 0; + const char *name = 0; + mdToken tkEncloser = mdTokenNil; + + if (TypeFromToken(tk) == mdtTypeRef) + { + if (((formatFlags & FormatAssembly) && FAILED(pIMDI->GetResolutionScopeOfTypeRef(tk, &tkEncloser))) || + FAILED(pIMDI->GetNameOfTypeRef(tk, &nameSpace, &name))) + { + char str[1024]; + sprintf_s(str, COUNTOF(str), " [ERROR: Invalid TypeRef record 0x%8.8X] ", tk); + appendStr(out, str); + return asString(out); + } + } + else + { + if (((formatFlags & FormatNamespace) == 0) || FAILED(pIMDI->GetNestedClassProps(tk,&tkEncloser))) + { + tkEncloser = mdTypeDefNil; + } + if (FAILED(pIMDI->GetNameOfTypeDef(tk, &name, &nameSpace))) + { + char str[1024]; + sprintf_s(str, COUNTOF(str), " [ERROR: Invalid TypeDef record 0x%8.8X] ", tk); + appendStr(out, str); + return asString(out); + } + } + MAKE_NAME_IF_NONE(name,tk); + if((tkEncloser == mdTokenNil) || RidFromToken(tkEncloser)) + { + if (TypeFromToken(tkEncloser) == mdtTypeRef || TypeFromToken(tkEncloser) == mdtTypeDef) + { + PrettyPrintClass(out,tkEncloser,pIMDI, formatFlags); + if (formatFlags & FormatSlashSep) + appendChar(out, '/'); + else + appendChar(out, '+'); + //nameSpace = ""; //don't print namespaces for nested classes! + } + else if (formatFlags & FormatAssembly) + { + PrettyPrintClass(out,tkEncloser,pIMDI, formatFlags); + } + } + if(TypeFromToken(tk)==mdtTypeDef) + { + unsigned L = (unsigned)strlen(name)+1; + char* szFN = NULL; + if(((formatFlags & FormatNamespace) != 0) && nameSpace && *nameSpace) + { + const char* sz = nameSpace; + L+= (unsigned)strlen(sz)+1; + szFN = new char[L]; + sprintf_s(szFN,L,"%s.",sz); + } + else + { + szFN = new char[L]; + *szFN = 0; + } + strcat_s(szFN,L, name); + appendStr(out, szFN); + if (szFN) delete[] (szFN); + } + else + { + if (((formatFlags & FormatNamespace) != 0) && nameSpace && *nameSpace) { + appendStr(out, nameSpace); + appendChar(out, '.'); + } + + appendStr(out, name); + } + } + break; + + case mdtAssemblyRef: + { + LPCSTR szName = NULL; + pIMDI->GetAssemblyRefProps(tk,NULL,NULL,&szName,NULL,NULL,NULL,NULL); + if(szName && *szName) + { + appendChar(out, '['); + appendStr(out, szName); + appendChar(out, ']'); + } + } + break; + case mdtAssembly: + { + LPCSTR szName = NULL; + pIMDI->GetAssemblyProps(tk,NULL,NULL,NULL,&szName,NULL,NULL); + if(szName && *szName) + { + appendChar(out, '['); + appendStr(out, szName); + appendChar(out, ']'); + } + } + break; + case mdtModuleRef: + { + LPCSTR szName = NULL; + pIMDI->GetModuleRefProps(tk,&szName); + if(szName && *szName) + { + appendChar(out, '['); + appendStr(out, ".module "); + appendStr(out, szName); + appendChar(out, ']'); + } + } + break; + + case mdtTypeSpec: + { + ULONG cSig; + PCCOR_SIGNATURE sig; + if (FAILED(pIMDI->GetSigFromToken(tk, &cSig, &sig))) + { + char str[128]; + sprintf_s(str, COUNTOF(str), " [ERROR: Invalid token 0x%8.8X] ", tk); + appendStr(out, str); + } + else + { + PrettyPrintType(sig, out, pIMDI, formatFlags); + } + } + break; + + case mdtModule: + break; + + default: + { + char str[128]; + sprintf_s(str,COUNTOF(str)," [ERROR: INVALID TOKEN TYPE 0x%8.8X] ",tk); + appendStr(out, str); + } + } + return(asString(out)); +} + +// This function takes a module and a token and prints the representation in the mdName buffer. +void PrettyPrintClassFromToken( + TADDR moduleAddr, // the module containing the token + mdToken tok, // the class token to look up + __out_ecount(cbName) WCHAR* mdName, // where to put the pretty printed string + size_t cbName, // the capacity of the buffer + DWORD formatFlags /*= FormatCSharp*/) +{ + // set the default value + swprintf_s(mdName, cbName, W("token_0x%8.8X"), tok); + + DacpModuleData dmd; + if (dmd.Request(g_sos, TO_CDADDR(moduleAddr)) != S_OK) + return; + + ToRelease<IMetaDataImport> pImport(MDImportForModule(&dmd)); + ToRelease<IMDInternalImport> pIMDI = NULL; + + if ((pImport == NULL) || FAILED(GetMDInternalFromImport(pImport, &pIMDI))) + return; + + CQuickBytes qb; + PrettyPrintClass(&qb, tok, pIMDI, formatFlags); + MultiByteToWideChar (CP_ACP, 0, asString(&qb), -1, mdName, (int) cbName); +} diff --git a/src/ToolBox/SOS/Strike/sos.cpp b/src/ToolBox/SOS/Strike/sos.cpp new file mode 100644 index 0000000000..351199c058 --- /dev/null +++ b/src/ToolBox/SOS/Strike/sos.cpp @@ -0,0 +1,888 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#include "strike.h" +#include "util.h" + +#include "sos.h" + + +#ifdef _ASSERTE +#undef _ASSERTE +#endif + +#define _ASSERTE(a) {;} + +#include "gcdesc.h" + + +#ifdef _ASSERTE +#undef _ASSERTE +#endif + +namespace sos +{ + template <class T> + static bool MemOverlap(T beg1, T end1, // first range + T beg2, T end2) // second range + { + if (beg2 >= beg1 && beg2 <= end1) // second range starts within first range + return true; + else if (end2 >= beg1 && end2 <= end1) // second range ends within first range + return true; + else if (beg1 >= beg2 && beg1 <= end2) // first range starts within second range + return true; + else if (end1 >= beg2 && end1 <= end2) // first range ends within second range + return true; + else + return false; + } + + + Object::Object(TADDR addr) + : mAddress(addr), mMT(0), mSize(~0), mPointers(false), mMTData(0), mTypeName(0) + { + if ((mAddress & ~ALIGNCONST) != mAddress) + sos::Throw<Exception>("Object %p is misaligned.", mAddress); + } + + Object::Object(TADDR addr, TADDR mt) + : mAddress(addr), mMT(mt & ~3), mSize(~0), mPointers(false), mMTData(0), mTypeName(0) + { + if ((mAddress & ~ALIGNCONST) != mAddress) + sos::Throw<Exception>("Object %p is misaligned.", mAddress); + } + + + Object::Object(const Object &rhs) + : mAddress(rhs.mAddress), mMT(rhs.mMT), mSize(rhs.mSize), mPointers(rhs.mPointers), mMTData(rhs.mMTData), mTypeName(rhs.mTypeName) + { + rhs.mMTData = 0; + rhs.mTypeName = 0; + } + + const Object &Object::operator=(TADDR addr) + { + if (mMTData) + delete mMTData; + + if (mTypeName) + delete mTypeName; + + mAddress = addr; + mMT = 0; + mSize = ~0; + mMTData = 0; + mTypeName = 0; + + return *this; + } + + bool Object::TryGetHeader(ULONG &outHeader) const + { + struct ObjectHeader + { + #ifdef _WIN64 + ULONG _alignpad; + #endif + ULONG SyncBlockValue; // the Index and the Bits + }; + + ObjectHeader header; + + if (SUCCEEDED(rvCache->Read(TO_TADDR(GetAddress() - sizeof(ObjectHeader)), &header, sizeof(ObjectHeader), NULL))) + { + outHeader = header.SyncBlockValue; + return true; + } + + return false; + } + + + ULONG Object::GetHeader() const + { + ULONG toReturn = 0; + if (!TryGetHeader(toReturn)) + sos::Throw<DataRead>("Failed to get header for object %p.", GetAddress()); + + return toReturn; + } + + TADDR Object::GetMT() const + { + if (mMT == NULL) + { + TADDR temp; + if (FAILED(MOVE(temp, mAddress))) + sos::Throw<DataRead>("Object %s has an invalid method table.", DMLListNearObj(mAddress)); + + if (temp == NULL) + sos::Throw<HeapCorruption>("Object %s has an invalid method table.", DMLListNearObj(mAddress)); + + mMT = temp & ~3; + } + + return mMT; + } + + TADDR Object::GetComponentMT() const + { + if (mMT != NULL && mMT != sos::MethodTable::GetArrayMT()) + return NULL; + + DacpObjectData objData; + if (FAILED(objData.Request(g_sos, TO_CDADDR(mAddress)))) + sos::Throw<DataRead>("Failed to request object data for %s.", DMLListNearObj(mAddress)); + + if (mMT == NULL) + mMT = TO_TADDR(objData.MethodTable) & ~3; + + return TO_TADDR(objData.ElementTypeHandle); + } + + const WCHAR *Object::GetTypeName() const + { + if (mTypeName == NULL) + mTypeName = CreateMethodTableName(GetMT(), GetComponentMT()); + + + if (mTypeName == NULL) + return W("<error>"); + + return mTypeName; + } + + void Object::FillMTData() const + { + if (mMTData == NULL) + { + mMTData = new DacpMethodTableData; + if (FAILED(mMTData->Request(g_sos, GetMT()))) + { + delete mMTData; + mMTData = NULL; + sos::Throw<DataRead>("Could not request method table data for object %p (MethodTable: %p).", mAddress, mMT); + } + } + } + + + void Object::CalculateSizeAndPointers() const + { + TADDR mt = GetMT(); + MethodTableInfo* info = g_special_mtCache.Lookup((DWORD_PTR)mt); + if (!info->IsInitialized()) + { + // this is the first time we see this method table, so we need to get the information + // from the target + FillMTData(); + + info->BaseSize = mMTData->BaseSize; + info->ComponentSize = mMTData->ComponentSize; + info->bContainsPointers = mMTData->bContainsPointers; + } + + if (mSize == (size_t)~0) + { + mSize = info->BaseSize; + if (info->ComponentSize) + { + // this is an array, so the size has to include the size of the components. We read the number + // of components from the target and multiply by the component size to get the size. + mSize += info->ComponentSize * GetNumComponents(GetAddress()); + } + + // On x64 we do an optimization to save 4 bytes in almost every string we create. + #ifdef _WIN64 + // Pad to min object size if necessary + if (mSize < min_obj_size) + mSize = min_obj_size; + #endif // _WIN64 + } + + mPointers = info->bContainsPointers != FALSE; + } + + size_t Object::GetSize() const + { + if (mSize == (size_t)~0) // poison value + { + CalculateSizeAndPointers(); + } + + SOS_Assert(mSize != (size_t)~0); + return mSize; + } + + + bool Object::HasPointers() const + { + if (mSize == (size_t)~0) + CalculateSizeAndPointers(); + + SOS_Assert(mSize != (size_t)~0); + return mPointers; + } + + + bool Object::VerifyMemberFields(TADDR pMT, TADDR obj) + { + WORD numInstanceFields = 0; + return VerifyMemberFields(pMT, obj, numInstanceFields); + } + + + bool Object::VerifyMemberFields(TADDR pMT, TADDR obj, WORD &numInstanceFields) + { + DacpMethodTableData vMethTable; + if (FAILED(vMethTable.Request(g_sos, pMT))) + return false; + + // Recursively verify the parent (this updates numInstanceFields) + if (vMethTable.ParentMethodTable) + { + if (!VerifyMemberFields(TO_TADDR(vMethTable.ParentMethodTable), obj, numInstanceFields)) + return false; + } + + DacpMethodTableFieldData vMethodTableFields; + + // Verify all fields on the object. + CLRDATA_ADDRESS dwAddr = vMethodTableFields.FirstField; + DacpFieldDescData vFieldDesc; + + while (numInstanceFields < vMethodTableFields.wNumInstanceFields) + { + CheckInterrupt(); + + if (FAILED(vFieldDesc.Request(g_sos, dwAddr))) + return false; + + if (vFieldDesc.Type >= ELEMENT_TYPE_MAX) + return false; + + dwAddr = vFieldDesc.NextField; + + if (!vFieldDesc.bIsStatic) + { + numInstanceFields++; + TADDR dwTmp = TO_TADDR(obj + vFieldDesc.dwOffset + sizeof(BaseObject)); + if (vFieldDesc.Type == ELEMENT_TYPE_CLASS) + { + // Is it a valid object? + if (FAILED(MOVE(dwTmp, dwTmp))) + return false; + + if (dwTmp != NULL) + { + DacpObjectData objData; + if (FAILED(objData.Request(g_sos, TO_CDADDR(dwTmp)))) + return false; + } + } + } + } + + return true; + } + + bool MethodTable::IsZombie(TADDR addr) + { + // Zombie objects are objects that reside in an unloaded AppDomain. + MethodTable mt = addr; + return _wcscmp(mt.GetName(), W("<Unloaded Type>")) == 0; + } + + void MethodTable::Clear() + { + if (mName) + { + delete [] mName; + mName = NULL; + } + } + + const WCHAR *MethodTable::GetName() const + { + if (mName == NULL) + mName = CreateMethodTableName(mMT); + + if (mName == NULL) + return W("<error>"); + + return mName; + } + + bool Object::IsValid(TADDR address, bool verifyFields) + { + DacpObjectData objectData; + if (FAILED(objectData.Request(g_sos, TO_CDADDR(address)))) + return false; + + if (verifyFields && + objectData.MethodTable != g_special_usefulGlobals.FreeMethodTable && + !MethodTable::IsZombie(TO_TADDR(objectData.MethodTable))) + { + return VerifyMemberFields(TO_TADDR(objectData.MethodTable), address); + } + + return true; + } + + bool Object::GetThinLock(ThinLockInfo &out) const + { + ULONG header = GetHeader(); + if (header & (BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX | BIT_SBLK_SPIN_LOCK)) + { + return false; + } + + out.ThreadId = header & SBLK_MASK_LOCK_THREADID; + out.Recursion = (header & SBLK_MASK_LOCK_RECLEVEL) >> SBLK_RECLEVEL_SHIFT; + + CLRDATA_ADDRESS threadPtr = NULL; + if (g_sos->GetThreadFromThinlockID(out.ThreadId, &threadPtr) != S_OK) + { + out.ThreadPtr = NULL; + } + else + { + out.ThreadPtr = TO_TADDR(threadPtr); + } + + return out.ThreadId != 0 && out.ThreadPtr != NULL; + } + + bool Object::GetStringData(__out_ecount(size) WCHAR *buffer, size_t size) const + { + SOS_Assert(IsString()); + SOS_Assert(buffer); + SOS_Assert(size > 0); + + return SUCCEEDED(g_sos->GetObjectStringData(mAddress, (ULONG32)size, buffer, NULL)); + } + + size_t Object::GetStringLength() const + { + SOS_Assert(IsString()); + + strobjInfo stInfo; + if (FAILED(MOVE(stInfo, mAddress))) + sos::Throw<DataRead>("Failed to read object data at %p.", mAddress); + + // We get the method table for free here, if we don't have it already. + SOS_Assert((mMT == NULL) || (mMT == TO_TADDR(stInfo.methodTable))); + if (mMT == NULL) + mMT = TO_TADDR(stInfo.methodTable); + + return (size_t)stInfo.m_StringLength; + } + + + RefIterator::RefIterator(TADDR obj, LinearReadCache *cache) + : mCache(cache), mGCDesc(0), mArrayOfVC(false), mDone(false), mBuffer(0), mCurrSeries(0), + i(0), mCount(0), mCurr(0), mStop(0), mObject(obj), mObjSize(0) + { + Init(); + } + + RefIterator::RefIterator(TADDR obj, CGCDesc *desc, bool arrayOfVC, LinearReadCache *cache) + : mCache(cache), mGCDesc(desc), mArrayOfVC(arrayOfVC), mDone(false), mBuffer(0), mCurrSeries(0), + i(0), mCount(0), mCurr(0), mStop(0), mObject(obj), mObjSize(0) + { + Init(); + } + + RefIterator::~RefIterator() + { + if (mBuffer) + delete [] mBuffer; + } + + const RefIterator &RefIterator::operator++() + { + if (mDone) + Throw<Exception>("Attempt to move past the end of the iterator."); + + if (!mArrayOfVC) + { + mCurr += sizeof(TADDR); + if (mCurr >= mStop) + { + mCurrSeries--; + if (mCurrSeries < mGCDesc->GetLowestSeries()) + { + mDone = true; + } + else + { + mCurr = mObject + mCurrSeries->GetSeriesOffset(); + mStop = mCurr + mCurrSeries->GetSeriesSize() + mObjSize; + } + } + } + else + { + mCurr += sizeof(TADDR); + if (mCurr >= mStop) + { + int i_last = i; + i--; + + if (i == mCount) + i = 0; + + mCurr += mCurrSeries->val_serie[i_last].skip; + mStop = mCurr + mCurrSeries->val_serie[i].nptrs * sizeof(TADDR); + } + + if (mCurr >= mObject + mObjSize - plug_skew) + mDone = true; + } + + return *this; + } + + TADDR RefIterator::operator*() const + { + return ReadPointer(mCurr); + } + + TADDR RefIterator::GetOffset() const + { + return mCurr - mObject; + } + + void RefIterator::Init() + { + TADDR mt = ReadPointer(mObject); + BOOL bContainsPointers = FALSE; + + if (!GetSizeEfficient(mObject, mt, FALSE, mObjSize, bContainsPointers)) + Throw<DataRead>("Failed to get size of object."); + + if (!bContainsPointers) + { + mDone = true; + return; + } + + if (!mGCDesc) + { + int entries = 0; + + if (FAILED(MOVE(entries, mt-sizeof(TADDR)))) + Throw<DataRead>("Failed to request number of entries."); + + // array of vc? + if (entries < 0) + { + entries = -entries; + mArrayOfVC = true; + } + else + { + mArrayOfVC = false; + } + + size_t slots = 1 + entries * sizeof(CGCDescSeries)/sizeof(TADDR); + + ArrayHolder<TADDR> buffer = new TADDR[slots]; + + ULONG fetched = 0; + CLRDATA_ADDRESS address = TO_CDADDR(mt - slots*sizeof(TADDR)); + if (FAILED(g_ExtData->ReadVirtual(address, buffer, (ULONG)(slots*sizeof(TADDR)), &fetched))) + Throw<DataRead>("Failed to request GCDesc."); + + mBuffer = buffer.Detach(); + mGCDesc = (CGCDesc*)(mBuffer + slots); + } + + mCurrSeries = mGCDesc->GetHighestSeries(); + + if (!mArrayOfVC) + { + mCurr = mObject + mCurrSeries->GetSeriesOffset(); + mStop = mCurr + mCurrSeries->GetSeriesSize() + mObjSize; + } + else + { + i = 0; + mCurr = mObject + mCurrSeries->startoffset; + mStop = mCurr + mCurrSeries->val_serie[i].nptrs * sizeof(TADDR); + mCount = (int)mGCDesc->GetNumSeries(); + } + + if (mCurr == mStop) + operator++(); + else if (mCurr >= mObject + mObjSize - plug_skew) + mDone = true; + } + + + const TADDR GCHeap::HeapStart = 0; + const TADDR GCHeap::HeapEnd = ~0; + + ObjectIterator::ObjectIterator(const DacpGcHeapDetails *heap, int numHeaps, TADDR start, TADDR stop) + : bLarge(false), mCurrObj(0), mLastObj(0), mStart(start), mEnd(stop), mSegmentEnd(0), mHeaps(heap), + mNumHeaps(numHeaps), mCurrHeap(0) + { + mAllocInfo.Init(); + SOS_Assert(numHeaps > 0); + + TADDR segStart = TO_TADDR(mHeaps[0].generation_table[GetMaxGeneration()].start_segment); + if (FAILED(mSegment.Request(g_sos, segStart, mHeaps[0]))) + sos::Throw<DataRead>("Could not request segment data at %p.", segStart); + + mCurrObj = mStart < TO_TADDR(mSegment.mem) ? TO_TADDR(mSegment.mem) : mStart; + mSegmentEnd = (segStart == TO_TADDR(mHeaps[0].ephemeral_heap_segment)) ? + TO_TADDR(mHeaps[0].alloc_allocated) : + TO_TADDR(mSegment.allocated); + + CheckSegmentRange(); + } + + bool ObjectIterator::NextSegment() + { + if (mCurrHeap >= mNumHeaps) + return false; + + TADDR next = TO_TADDR(mSegment.next); + if (next == NULL) + { + if (bLarge) + { + mCurrHeap++; + if (mCurrHeap == mNumHeaps) + return false; + + bLarge = false; + next = TO_TADDR(mHeaps[mCurrHeap].generation_table[GetMaxGeneration()].start_segment); + } + else + { + bLarge = true; + next = TO_TADDR(mHeaps[mCurrHeap].generation_table[GetMaxGeneration()+1].start_segment); + } + } + + SOS_Assert(next != NULL); + if (FAILED(mSegment.Request(g_sos, next, mHeaps[mCurrHeap]))) + sos::Throw<DataRead>("Failed to request segment data at %p.", next); + + mLastObj = 0; + mCurrObj = mStart < TO_TADDR(mSegment.mem) ? TO_TADDR(mSegment.mem) : mStart; + mSegmentEnd = (next == TO_TADDR(mHeaps[mCurrHeap].ephemeral_heap_segment)) ? + TO_TADDR(mHeaps[mCurrHeap].alloc_allocated) : + TO_TADDR(mSegment.allocated); + return CheckSegmentRange(); + } + + bool ObjectIterator::CheckSegmentRange() + { + CheckInterrupt(); + + while (!MemOverlap(mStart, mEnd, TO_TADDR(mSegment.mem), mSegmentEnd)) + if (!NextSegment()) + return false; + + // At this point we know that the current segment contains objects in + // the correct range. However, there's no telling if the user gave us + // a starting address that corresponds to an object. If mStart is a + // valid object, then we'll just start there. If it's not we'll need + // to walk the segment from the beginning to find the first aligned + // object on or after mStart. + if (mCurrObj == mStart && !Object::IsValid(mStart)) + { + // It's possible mCurrObj will equal mStart after this. That's fine. + // It means that the starting object is corrupt (and we'll figure + // that when the user calls GetNext), or IsValid was wrong. + mLastObj = 0; + mCurrObj = TO_TADDR(mSegment.mem); + while (mCurrObj < mStart) + MoveToNextObject(); + } + + return true; + } + + + + const Object &ObjectIterator::operator*() const + { + AssertSanity(); + return mCurrObj; + } + + + const Object *ObjectIterator::operator->() const + { + AssertSanity(); + return &mCurrObj; + } + + //Object ObjectIterator::GetNext() + const ObjectIterator &ObjectIterator::operator++() + { + CheckInterrupt(); + + // Assert we aren't done walking the heap. + SOS_Assert(*this); + AssertSanity(); + + MoveToNextObject(); + return *this; + } + + void ObjectIterator::MoveToNextObjectCarefully() + { + CheckInterrupt(); + + SOS_Assert(*this); + AssertSanity(); + + // Move to NextObject won't generally throw unless it fails to request the + // MethodTable of the object. At which point we won't know how large the + // current object is, nor how to move past it. In this case we'll simply + // move to the next segment if possible to continue iterating from there. + try + { + MoveToNextObject(); + } + catch(const sos::Exception &) + { + NextSegment(); + } + } + + void ObjectIterator::AssertSanity() const + { + // Assert that we are in a sane state. Function which call this assume two things: + // 1. That the current object is within the segment bounds. + // 2. That the current object is within the requested memory range. + SOS_Assert(mCurrObj >= TO_TADDR(mSegment.mem)); + SOS_Assert(mCurrObj <= TO_TADDR(mSegmentEnd - Align(min_obj_size))); + + SOS_Assert(mCurrObj >= mStart); + SOS_Assert(mCurrObj <= mEnd); + } + + void ObjectIterator::MoveToNextObject() + { + // Object::GetSize can be unaligned, so we must align it ourselves. + size_t size = (bLarge ? AlignLarge(mCurrObj.GetSize()) : Align(mCurrObj.GetSize())); + + mLastObj = mCurrObj; + mCurrObj = mCurrObj.GetAddress() + size; + + if (!bLarge) + { + // Is this the end of an allocation context? We need to know this because there can be + // allocated memory at the end of an allocation context that doesn't yet contain any objects. + // This happens because we actually allocate a minimum amount of memory (the allocation quantum) + // whenever we need to get more memory. Typically, a single allocation request won't fill this + // block, so we'll fulfill subsequent requests out of the remainder of the block until it's + // depleted. + int i; + for (i = 0; i < mAllocInfo.num; i ++) + { + if (mCurrObj == TO_TADDR(mAllocInfo.array[i].alloc_ptr)) // end of objects in this context + { + // Set mCurrObj to point after the context (alloc_limit is the end of the allocation context). + mCurrObj = TO_TADDR(mAllocInfo.array[i].alloc_limit) + Align(min_obj_size); + break; + } + } + + // We also need to look at the gen0 alloc context. + if (mCurrObj == TO_TADDR(mHeaps[mCurrHeap].generation_table[0].allocContextPtr)) + mCurrObj = TO_TADDR(mHeaps[mCurrHeap].generation_table[0].allocContextLimit) + Align(min_obj_size); + } + + if (mCurrObj > mEnd || mCurrObj >= mSegmentEnd) + NextSegment(); + } + + SyncBlkIterator::SyncBlkIterator() + : mCurr(1), mTotal(0) + { + // If DacpSyncBlockData::Request fails with the call "1", then it means + // there are no SyncBlocks in the process. + DacpSyncBlockData syncBlockData; + if (SUCCEEDED(syncBlockData.Request(g_sos, 1))) + mTotal = syncBlockData.SyncBlockCount; + + mSyncBlk = mCurr; + } + + GCHeap::GCHeap() + { + if (FAILED(mHeapData.Request(g_sos))) + sos::Throw<DataRead>("Failed to request GC heap data."); + + if (mHeapData.bServerMode) + { + mNumHeaps = mHeapData.HeapCount; + DWORD dwAllocSize = 0; + if (!ClrSafeInt<DWORD>::multiply(sizeof(CLRDATA_ADDRESS), mNumHeaps, dwAllocSize)) + sos::Throw<Exception>("Failed to get GCHeaps: Integer overflow."); + + CLRDATA_ADDRESS *heapAddrs = (CLRDATA_ADDRESS*)alloca(dwAllocSize); + if (FAILED(g_sos->GetGCHeapList(mNumHeaps, heapAddrs, NULL))) + sos::Throw<DataRead>("Failed to get GCHeaps."); + + mHeaps = new DacpGcHeapDetails[mNumHeaps]; + + for (int i = 0; i < mNumHeaps; i++) + if (FAILED(mHeaps[i].Request(g_sos, heapAddrs[i]))) + sos::Throw<DataRead>("Failed to get GC heap details at %p.", heapAddrs[i]); + } + else + { + mHeaps = new DacpGcHeapDetails[1]; + mNumHeaps = 1; + + if (FAILED(mHeaps[0].Request(g_sos))) + sos::Throw<DataRead>("Failed to request GC details data."); + } + } + + ObjectIterator GCHeap::WalkHeap(TADDR start, TADDR stop) const + { + return ObjectIterator(mHeaps, mNumHeaps, start, stop); + } + + bool GCHeap::AreGCStructuresValid() const + { + return mHeapData.bGcStructuresValid != FALSE; + } + + // SyncBlk class + SyncBlk::SyncBlk() + : mIndex(0) + { + } + + SyncBlk::SyncBlk(int index) + : mIndex(index) + { + Init(); + } + + const SyncBlk &SyncBlk::operator=(int index) + { + mIndex = index; + Init(); + + return *this; + } + + void SyncBlk::Init() + { + if (FAILED(mData.Request(g_sos, mIndex))) + sos::Throw<DataRead>("Failed to request SyncBlk at index %d.", mIndex); + } + + TADDR SyncBlk::GetAddress() const + { + SOS_Assert(mIndex); + return TO_TADDR(mData.SyncBlockPointer); + } + + TADDR SyncBlk::GetObject() const + { + SOS_Assert(mIndex); + return TO_TADDR(mData.Object); + } + + int SyncBlk::GetIndex() const + { + return mIndex; + } + + bool SyncBlk::IsFree() const + { + SOS_Assert(mIndex); + return mData.bFree != FALSE; + } + + unsigned int SyncBlk::GetMonitorHeldCount() const + { + SOS_Assert(mIndex); + return mData.MonitorHeld; + } + + unsigned int SyncBlk::GetRecursion() const + { + SOS_Assert(mIndex); + return mData.Recursion; + } + + DWORD SyncBlk::GetCOMFlags() const + { + SOS_Assert(mIndex); + #ifdef FEATURE_COMINTEROP + return mData.COMFlags; + #else + return 0; + #endif + } + + unsigned int SyncBlk::GetAdditionalThreadCount() const + { + SOS_Assert(mIndex); + return mData.AdditionalThreadCount; + } + + TADDR SyncBlk::GetHoldingThread() const + { + SOS_Assert(mIndex); + return TO_TADDR(mData.HoldingThread); + } + + TADDR SyncBlk::GetAppDomain() const + { + SOS_Assert(mIndex); + return TO_TADDR(mData.appDomainPtr); + } + + void BuildTypeWithExtraInfo(TADDR addr, unsigned int size, __inout_ecount(size) WCHAR *buffer) + { + try + { + sos::Object obj(addr); + TADDR mtAddr = obj.GetMT(); + bool isArray = sos::MethodTable::IsArrayMT(mtAddr); + bool isString = obj.IsString(); + + sos::MethodTable mt(isArray ? obj.GetComponentMT() : mtAddr); + + if (isArray) + { + swprintf_s(buffer, size, W("%s[]"), mt.GetName()); + } + else if (isString) + { + WCHAR str[32]; + obj.GetStringData(str, _countof(str)); + + _snwprintf_s(buffer, size, _TRUNCATE, W("%s: \"%s\""), mt.GetName(), str); + } + else + { + _snwprintf_s(buffer, size, _TRUNCATE, W("%s"), mt.GetName()); + } + } + catch (const sos::Exception &e) + { + int len = MultiByteToWideChar(CP_ACP, 0, e.what(), -1, NULL, 0); + + ArrayHolder<WCHAR> tmp = new WCHAR[len]; + MultiByteToWideChar(CP_ACP, 0, e.what(), -1, (WCHAR*)tmp, len); + + swprintf_s(buffer, size, W("<invalid object: '%s'>"), (WCHAR*)tmp); + } + } +} diff --git a/src/ToolBox/SOS/Strike/sos.def b/src/ToolBox/SOS/Strike/sos.def new file mode 100644 index 0000000000..c8d08e7319 --- /dev/null +++ b/src/ToolBox/SOS/Strike/sos.def @@ -0,0 +1,231 @@ +; Licensed to the .NET Foundation under one or more agreements. +; The .NET Foundation licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. +; +LIBRARY STRIKE +EXPORTS + AnalyzeOOM + analyzeoom=AnalyzeOOM + ao=AnalyzeOOM + ClrStack + clrstack=ClrStack + CLRStack=ClrStack + DumpArray + da=DumpArray + dumparray=DumpArray + DumpAssembly + dumpassembly=DumpAssembly + DumpClass + dumpclass=DumpClass + DumpDomain + dumpdomain=DumpDomain +#ifdef TRACE_GC + DumpGCLog + dumpgclog=DumpGCLog + dlog=DumpGCLog +#endif + DumpGCData + dumpgcdata=DumpGCData + dgc=DumpGCData + DumpGCConfigLog + dumpgcconfiglog=DumpGCConfigLog + dclog=DumpGCConfigLog + DumpHeap + dumpheap=DumpHeap + DumpIL + dumpil=DumpIL + DumpLog + dumplog=DumpLog + Dumplog=DumpLog + DumpMD + dumpmd=DumpMD + DumpModule + dumpmodule=DumpModule + DumpMT + dumpmt=DumpMT + DumpObj + do=DumpObj + dumpobj=DumpObj + DumpRuntimeTypes + dumpruntimetypes=DumpRuntimeTypes + Dumpruntimetypes=DumpRuntimeTypes + DumpSig + dumpsig=DumpSig + DumpSigElem + dumpsigelem=DumpSigElem + DumpStack + dumpstack=DumpStack + DumpStackObjects + dso=DumpStackObjects + dumpstackobjects=DumpStackObjects + DumpVC + dumpvc=DumpVC + EEHeap + eeheap=EEHeap + EEStack + eestack=EEStack + EHInfo + ehinfo=EHInfo + Ehinfo=EHInfo + FinalizeQueue + finalizequeue=FinalizeQueue + fq=FinalizeQueue + FindAppDomain + findappdomain=FindAppDomain + Findappdomain=FindAppDomain + FindRoots + findroots=FindRoots + GCHandles + gchandles=GCHandles + GCHeapStat + gcheapstat=GCHeapStat + GcHeapStat=GCHeapStat + heapstat=GCHeapStat + HeapStat=GCHeapStat + GCInfo + gcinfo=GCInfo + GCRoot + gcroot=GCRoot + GCWhere + gcwhere=GCWhere + GcWhere=GCWhere + Help + help=Help + HistClear + histclear=HistClear + HistInit + histinit=HistInit + HistObj + histobj=HistObj + HistObjFind + histobjfind=HistObjFind + hof=HistObjFind + HistRoot + histroot=HistRoot + HistStats + histstats=HistStats + IP2MD + ip2md=IP2MD + ListNearObj + listnearobj=ListNearObj + lno=ListNearObj + Name2EE + name2ee=Name2EE + ObjSize + objsize=ObjSize + PrintException + pe=PrintException + printexception=PrintException + Printexception=PrintException + SaveModule + savemodule=SaveModule + SOSFlush + sosflush=SOSFlush + StopOnException + soe=StopOnException + stoponexception=StopOnException + Stoponexception=StopOnException + SyncBlk + syncblk=SyncBlk + ThreadPool + threadpool=ThreadPool + tp=ThreadPool + Threads + t=Threads + threads=Threads + ThreadState + threadstate=ThreadState + Token2EE + token2ee=Token2EE + TraverseHeap + traverseheap=TraverseHeap + Traverseheap=TraverseHeap + u + U=u + VerifyHeap + verifyheap=VerifyHeap + Verifyheap=VerifyHeap + vh=VerifyHeap + VerifyObj + verifyobj=VerifyObj + vo=VerifyObj + VMMap + vmmap=VMMap + VMStat + vmstat=VMStat + +#ifdef FEATURE_COMINTEROP + COMState + comstate=COMState + RCWCleanupList + rcwcleanuplist=RCWCleanupList + Rcwcleanuplist=RCWCleanupList + DumpRCW + dumprcw=DumpRCW + Dumprcw=DumpRCW + DumpCCW + dumpccw=DumpCCW + Dumpccw=DumpCCW +#endif + +#ifdef _DEBUG + DumpPermissionSet + dps=DumpPermissionSet + dumppermissionset=DumpPermissionSet + dbgout + filthint +#endif + +#ifdef FEATURE_PAL + SetClrDebugDll + UnloadClrDebugDll +#else + _EFN_GetManagedExcepStack + _EFN_GetManagedExcepStackW + _EFN_GetManagedObjectFieldInfo + _EFN_GetManagedObjectName + _EFN_StackTrace + bpmd + BPMD=bpmd + DebugExtensionInitialize + DebugExtensionNotify + DebugExtensionUninitialize + EEVersion + eeversion=EEVersion + GCHandleLeaks + gchandleleaks=GCHandleLeaks + Gchandleleaks=GCHandleLeaks + GCHandleleaks=GCHandleLeaks + HandleCLRN + MinidumpMode + minidumpmode=MinidumpMode + Minidumpmode=MinidumpMode + PathTo + pathto=PathTo + ProcInfo + procinfo=ProcInfo + VerifyStackTrace + WatsonBuckets +#endif // !FEATURE_PAL + + +#ifdef FEATURE_CORESYSTEM +// Only documented for Apollo internal usage + Watch + watch=Watch + SuppressJitOptimization + suppressjitoptimization=SuppressJitOptimization + sjo=SuppressJitOptimization + SaveState + savestate=SaveState + StopOnCatch + stoponcatch=StopOnCatch + +// Undocumented commands - testing or very experimental + ExposeDML + exposeDML=ExposeDML + GetCodeTypeFlags + getCodeTypeFlags=GetCodeTypeFlags + TraceToCode + tracetocode=TraceToCode +#endif diff --git a/src/ToolBox/SOS/Strike/sos.h b/src/ToolBox/SOS/Strike/sos.h new file mode 100644 index 0000000000..3778235964 --- /dev/null +++ b/src/ToolBox/SOS/Strike/sos.h @@ -0,0 +1,792 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#pragma once + +#include "strike.h" +#include "util.h" + +#ifndef SOS_Assert +#ifdef _DEBUG +#define SOS_Assert(x) do { if (!(x)) sos::Throw<sos::Exception>("SOS Assert Failure: %s\n", #x); } while(0) +#else +#define SOS_Assert(x) (void)0 +#endif +#endif + +#ifdef throw +#undef throw +#endif + +#ifdef try +#undef try +#endif + +#ifdef catch +#undef catch +#endif + +class LinearReadCache; +class CGCDesc; +class CGCDescSeries; + +namespace sos +{ + class GCHeap; + + /* The base SOS Exception. Note that most commands should not attempt to be + * resilient to exceptions thrown by most functions here. Instead a top level + * try/catch at the beginning of the command which prints out the exception's + * message should be sufficient. + * Note you should not throw these directly, instead use the sos::Throw function. + */ + class Exception + { + public: + Exception(const char *format, va_list args) + { + vsprintf_s(mMsg, _countof(mMsg), format, args); + + va_end(args); + } + + inline virtual ~Exception() {} + + // from std::exception + virtual const char *what() const + { + return mMsg; + } + + const char *GetMesssage() const + { + return mMsg; + } + + protected: + char mMsg[1024]; + }; + + /* Thrown when we could not read data we expected out of the target process. + * This can be due to heap corruption, or it could just be an invalid pointer. + */ + class DataRead : public Exception + { + public: + DataRead(const char *format, va_list args) + : Exception(format, args) + { + } + }; + + /* This is thrown when we detect heap corruption in the process. + */ + class HeapCorruption : public Exception + { + public: + HeapCorruption(const char *format, va_list args) + : Exception(format, args) + { + } + }; + + // Internal helper method. Use SOS_Throw macros instead. + template <class T> + void Throw(const char *format, ...) + { + va_list args; + va_start(args, format); + + throw T(format, args); + } + + /* Checks to see if the user hit control-c. Throws an exception to escape SOS + * if so. + */ + inline void CheckInterrupt() + { + if (g_ExtControl->GetInterrupt() == S_OK) + Throw<Exception>("User interrupt."); + } + + /* ThinLock struct. Use Object::GetThinLock to fill the struct. + */ + struct ThinLockInfo + { + int ThreadId; + TADDR ThreadPtr; + int Recursion; + + ThinLockInfo() + : ThreadId(0), ThreadPtr(0), Recursion(0) + { + } + }; + + /* The MethodTable for an Object. The general pattern should be: + * MethodTable mt = someObject.GetMT(); + */ + class MethodTable + { + public: + /* Returns whether an object is from an AppDomain that has been unloaded. + * If so, we cannot validate the object's members. + * Params: + * mt - The address of the MethodTable to test for. + */ + static bool IsZombie(TADDR mt); + + /* Returns the method table for arrays. + */ + inline static TADDR GetArrayMT() + { + return TO_TADDR(g_special_usefulGlobals.ArrayMethodTable); + } + + /* Returns the method table for String objects. + */ + inline static TADDR GetStringMT() + { + return TO_TADDR(g_special_usefulGlobals.StringMethodTable); + } + + /* Returns the method table for Free objects. + */ + inline static TADDR GetFreeMT() + { + return TO_TADDR(g_special_usefulGlobals.FreeMethodTable); + } + + /* Returns true if the given method table is that of a Free object. + */ + inline static bool IsFreeMT(TADDR mt) + { + return GetFreeMT() == mt; + } + + /* Returns true if the given method table is that of an Array. + */ + inline static bool IsArrayMT(TADDR mt) + { + return GetArrayMT() == mt; + } + + /* Returns true if the given method table is that of a System.String object. + */ + inline static bool IsStringMT(TADDR mt) + { + return GetStringMT() == mt; + } + + inline static bool IsValid(TADDR mt) + { + DacpMethodTableData data; + return data.Request(g_sos, TO_CDADDR(mt)) == S_OK; + } + + public: + MethodTable(TADDR mt) + : mMT(mt), mName(0) + { + } + + MethodTable(const MethodTable &mt) + : mMT(mt.mMT), mName(mt.mName) + { + // Acquire the calculated mName field. Since we are making a copy, we will likely use + // the copy instead of the original. + mt.mName = NULL; + } + + const MethodTable &operator=(const MethodTable &mt) + { + Clear(); + + // Acquire the calculated mName field. Since we are making a copy, we will likely use + // the copy instead of the original. + mMT = mt.mMT; + mName = mt.mName; + mt.mName = NULL; + + return *this; + } + + ~MethodTable() + { + Clear(); + } + + /* Returns the class name of this MethodTable. The pointer returned is + * valid through the lifetime of the MethodTable object and should not be + * freed. + */ + const WCHAR *GetName() const; + + private: + void Clear(); + + private: + TADDR mMT; + mutable WCHAR *mName; + }; + + /* This represents an object on the GC heap in the target process. This class + * represents a single object, and is immutable after construction. All + * information about this class is lazily evaluated, so it is entirely possible + * to get exceptions when calling any member function. If this is a concern, + * call validate before attempting to call any other method on this object. + */ + class Object + { + public: + /* Attempts to determine if the target address points to a valid object. + * Note that this is a heuristic based check, so false positives could + * be possible. + * Params: + * address - The address of the object to inspect. + * verifyFields - Whether or not to validate that the fields the object + * points to are also valid. (If the object contains a + * corrupted pointer, passing true to this parameter will + * cause IsValid to return false.) In general passing + * true will make IsValid return less false positives. + */ + static bool IsValid(TADDR address, bool verifyFields=false); + + static int GetStringDataOffset() + { +#ifndef _TARGET_WIN64_ + return 8; +#else + return 0xc; +#endif + } + + public: + /* Constructor. Use Object(TADDR, TADDR) instead if you know the method table. + * Parameters: + * addr - an address to an object on the managed heap + * Throws: + * Exception - if addr is misaligned. + */ + Object(TADDR addr); + + /* Constructor. Use this constructor if you already know the method table for + * the object in question. This will save a read if the method table is needed. + * Parameters: + * addr - an address to an object on the managed heap + * Throws: + * Exception - if addr is misaligned. + */ + Object(TADDR addr, TADDR mt); + + Object(const Object &rhs); + + inline ~Object() + { + if (mMTData) + delete mMTData; + + if (mTypeName) + delete mTypeName; + } + + const Object &operator=(TADDR addr); + + // Comparison operators. These compare the underlying address of + // the object to the parameter. + inline bool operator<=(TADDR addr) { return mAddress <= addr; } + inline bool operator>=(TADDR addr) { return mAddress >= addr; } + inline bool operator<(TADDR addr) { return mAddress < addr; } + inline bool operator>(TADDR addr) { return mAddress > addr; } + inline bool operator==(TADDR addr) { return mAddress == addr; } + + /* Returns the target address of the object this represents. + */ + inline TADDR GetAddress() const + { + return mAddress; + } + + /* Returns the target address of the object this represents. + */ + inline operator TADDR() const + { + return GetAddress(); + } + + /* Returns the object header for this object. + * Throws: + * DataRead - we failed to read the object header. + */ + ULONG GetHeader() const; + + /* Gets the header for the current object, does not throw any exception. + * Params: + * outHeader - filled with the header if this function was successful. + * Returns: + * True if we successfully read the object header, false otherwise. + */ + bool TryGetHeader(ULONG &outHeader) const; + + /* Returns the method table of the object this represents. + * Throws: + * DataRead - If we failed to read the method table from the address. + * This is usually indicative of heap corruption. + * HeapCorruption - If we successfully read the target method table + * but it is invalid. (We do not do a very deep + * verification here.) + */ + TADDR GetMT() const; + + /* Returns the component method table of the object. For example, if + * this object is an array, the method table will be the general array + * MT. Calling this function tells you what type of objects can be + * placed in the array. + * Throws: + * DataRead - If we failed to read the method table from the address. + * This is usually indicative of heap corruption. + * HeapCorruption - If we successfully read the target method table + * but it is invalid. (We do not do a very deep + * verification here.) + */ + TADDR GetComponentMT() const; + + /* Returns the size of the object this represents. Note that this size + * may not be pointer aligned. + * Throws: + * DataRead - If we failed to read the method table data (which contains + * the size of the object). + */ + size_t GetSize() const; + + /* Returns true if this object contains pointers to other objects. + * Throws: + * DataRead - if we failed to read out of the object's method table. + */ + bool HasPointers() const; + + /* Gets the thinlock information for this object. + * Params: + * out - The ThinLockInfo to be filled. + * Returns: + * True if the object has a thinlock, false otherwise. If this function + * returns false, then out will be untouched. + * Throws: + * DataRead - If we could not read the object header from the object. + */ + bool GetThinLock(ThinLockInfo &out) const; + + /* Returns true if this object is a Free object (meaning it points to free + * space in the GC heap. + * Throws: + * The same as GetMT(). + */ + inline bool IsFree() const + { + return GetMT() == MethodTable::GetFreeMT(); + } + + /* Returns true if this object is a string. + * Throws: + * The same as GetMT(). + */ + inline bool IsString() const + { + return GetMT() == MethodTable::GetStringMT(); + } + + /* Returns the length of the String, if this is a string object. This + * function assumes that you have called IsString first to ensure that + * the object is indeed a string. + * Throws: + * DataRead if we could not read the contents of the object. + */ + size_t GetStringLength() const; + + /* Fills the given buffer with the contents of the String. This + * function assumes you have called IsString first to ensure that this + * object is actually a System.String. This function does not throw, + * but the results are undefined if this object is not a string. + * Params: + * buffer - The buffer to fill with the string contents. + * size - The total size of the buffer. + * Returns: + * True if the string data was successfully requested and placed in + * buffer, false otherwise. + */ + bool GetStringData(__out_ecount(size) WCHAR *buffer, size_t size) const; + + /* Returns the name of the type of this object. E.g. System.String. + * Throws: + * DataRead if we could not read the contents of the object. + * Returns: + * A string containing the type of the object. + */ + const WCHAR *GetTypeName() const; + + private: + void FillMTData() const; + void CalculateSizeAndPointers() const; + static bool VerifyMemberFields(TADDR pMT, TADDR obj); + static bool VerifyMemberFields(TADDR pMT, TADDR obj, WORD &numInstanceFields); + + protected: + // Conceptually, this class is never modified after you pass in the the object address. + // That is, there can't be anything the user does to point this object to a different + // object after construction. Since we lazy evaluate *everything*, we must be able to + // modify these variables. Hence they are mutable. + TADDR mAddress; + mutable TADDR mMT; + mutable size_t mSize; + mutable bool mPointers; + mutable DacpMethodTableData *mMTData; + mutable WCHAR *mTypeName; + }; + + /* Enumerates all the GC references (objects) contained in an object. This uses the GCDesc + * map exactly as the GC does. + */ + class RefIterator + { + public: + RefIterator(TADDR obj, LinearReadCache *cache = NULL); + RefIterator(TADDR obj, CGCDesc *desc, bool arrayOfVC, LinearReadCache *cache = NULL); + ~RefIterator(); + + /* Moves to the next reference in the object. + */ + const RefIterator &operator++(); + + /* Returns the address of the current reference. + */ + TADDR operator*() const; + + /* Gets the offset into the object where the current reference comes from. + */ + TADDR GetOffset() const; + + /* Returns true if there are more objects in the iteration, false otherwise. + * Used as: + * if (itr) + * ... + */ + inline operator void *() const + { + return (void*)!mDone; + } + + private: + void Init(); + inline TADDR ReadPointer(TADDR addr) const + { + if (mCache) + { + if (!mCache->Read(addr, &addr, false)) + Throw<DataRead>("Could not read address %p.", addr); + } + else + { + MOVE(addr, addr); + } + + return addr; + } + + private: + LinearReadCache *mCache; + CGCDesc *mGCDesc; + bool mArrayOfVC, mDone; + + TADDR *mBuffer; + CGCDescSeries *mCurrSeries; + + int i, mCount; + + TADDR mCurr, mStop, mObject; + size_t mObjSize; + }; + + + /* The Iterator used to walk the managed objects on the GC heap. + * The general usage pattern for this class is: + * for (ObjectIterator itr = gcheap.WalkHeap(); itr; ++itr) + * itr->SomeObjectMethod(); + */ + class ObjectIterator + { + friend class GCHeap; + public: + + /* Returns the next object in the GCHeap. Note that you must ensure + * that there are more objects to walk before calling this function by + * checking "if (iterator)". If this function throws an exception, + * the the iterator is invalid, and should no longer be used to walk + * the heap. This should generally only happen if we cannot read the + * MethodTable of the object to move to the next object. + * Throws: + * DataRead + */ + const ObjectIterator &operator++(); + + /* Dereference operator. This allows you to take a reference to the + * current object. Note the lifetime of this reference is valid for + * either the lifetime of the iterator or until you call operator++, + * whichever is shorter. For example. + * void Foo(const Object ¶m); + * void Bar(const ObjectIterator &itr) + * { + * Foo(*itr); + * } + */ + const Object &operator*() const; + + /* Returns a pointer to the current Object to call members on it. + * The usage pattern for the iterator is to simply use operator-> + * to call methods on the Object it points to without taking a + * direct reference to the underlying Object if at all possible. + */ + const Object *operator->() const; + + /* Returns false when the iterator has reached the end of the managed + * heap. + */ + inline operator void *() const + { + return (void*)(SIZE_T)(mCurrHeap == mNumHeaps ? 0 : 1); + } + + /* Do not use. + * TODO: Replace this functionality with int Object::GetGeneration(). + */ + bool IsCurrObjectOnLOH() const + { + SOS_Assert(*this); + return bLarge; + } + + /* Verifies the current object. Returns true if the current object is valid. + * Returns false and fills 'buffer' with the reason the object is corrupted. + * This is a deeper validation than Object::IsValid as it checks the card + * table entires for the object in addition to the rest of the references. + * This function does not throw exceptions. + * Params: + * buffer - out buffer that is filled if and only if this function returns + * false. + * size - the total size of the buffer + * Returns: + * True if the object is valid, false otherwise. + */ + bool Verify(__out_ecount(size) char *buffer, size_t size) const; + + /* The same as Verify(char*, size_t), except it does not write out the failure + * reason to a provided buffer. + * See: + * ObjectIterator::Verify(char *, size_t) + */ + bool Verify() const; + + /* Attempts to move to the next object (similar to ObjectIterator++), but + * attempts to recover from any heap corruption by skipping to the next + * segment. If Verify returns false, meaning it detected heap corruption + * at the current object, you can use MoveToNextObjectCarefully instead of + * ObjectIterator++ to attempt to keep reading from the heap. If possible, + * this function attempts to move to the next object in the same segment, + * but if that's not possible then it skips to the next segment and + * continues from there. + * Note: + * This function can throw, and if it does then the iterator is no longer + * in a valid state. No further attempts to move to the next object will + * be possible. + * Throws: + * DataRead - if the heap is corrupted and it's not possible to continue + * walking the heap + */ + void MoveToNextObjectCarefully(); + + private: + ObjectIterator(const DacpGcHeapDetails *heap, int numHeaps, TADDR start, TADDR stop); + + bool VerifyObjectMembers(__out_ecount(size) char *buffer, size_t size) const; + void BuildError(__out_ecount(count) char *out, size_t count, const char *format, ...) const; + + void AssertSanity() const; + bool NextSegment(); + bool CheckSegmentRange(); + void MoveToNextObject(); + + private: + DacpHeapSegmentData mSegment; + bool bLarge; + Object mCurrObj; + TADDR mLastObj, mStart, mEnd, mSegmentEnd; + AllocInfo mAllocInfo; + const DacpGcHeapDetails *mHeaps; + int mNumHeaps; + int mCurrHeap; + }; + + /* Reprensents an entry in the sync block table. + */ + class SyncBlk + { + friend class SyncBlkIterator; + public: + /* Constructor. + * Params: + * index - the index of the syncblk entry you wish to inspect. + * This should be in range [1, MaxEntries], but in general + * you should always use the SyncBlk iterator off of GCHeap + * and not construct these directly. + * Throws: + * DataRead - if we could not read the syncblk entry for the given index. + */ + explicit SyncBlk(int index); + + /* Returns whether or not the current entry is a "Free" SyncBlk table entry + * or not. This should be called *before* any other function here. + */ + bool IsFree() const; + + /* Returns the address of this syncblk entry (generally for display purposes). + */ + TADDR GetAddress() const; + + /* Returns the address of the object which this is syncblk is pointing to. + */ + TADDR GetObject() const; + + /* Returns the index of this entry. + */ + int GetIndex() const; + + /* Returns the COMFlags for the SyncBlk object. The return value of this + * function is undefined if FEATURE_COMINTEROP is not defined, so you should + * #ifdef the calling region yourself. + */ + DWORD GetCOMFlags() const; + + unsigned int GetMonitorHeldCount() const; + unsigned int GetRecursion() const; + unsigned int GetAdditionalThreadCount() const; + + /* Returns the thread which holds this monitor (this is the clr!Thread object). + */ + TADDR GetHoldingThread() const; + TADDR GetAppDomain() const; + + private: + /* Copy constructor unimplemented due to how expensive this is. Use references + * instead. + */ + SyncBlk(const SyncBlk &rhs); + SyncBlk(); + void Init(); + const SyncBlk &operator=(int index); + + private: + int mIndex; + DacpSyncBlockData mData; + }; + + /* An iterator over syncblks. The common usage for this class is: + * for (SyncBlkIterator itr; itr; ++itr) + * itr->SomeSyncBlkFunction(); + */ + class SyncBlkIterator + { + public: + SyncBlkIterator(); + + /* Moves to the next SyncBlk in the table. + */ + inline const SyncBlkIterator &operator++() + { + SOS_Assert(mCurr <= mTotal); + mSyncBlk = ++mCurr; + + return *this; + } + + inline const SyncBlk &operator*() const + { + SOS_Assert(mCurr <= mTotal); + return mSyncBlk; + } + + inline const SyncBlk *operator->() const + { + SOS_Assert(mCurr <= mTotal); + return &mSyncBlk; + } + + inline operator void *() const + { + return (void*)(SIZE_T)(mCurr <= mTotal ? 1 : 0); + } + + private: + int mCurr, mTotal; + SyncBlk mSyncBlk; + }; + + /* An class which contains information about the GCHeap. + */ + class GCHeap + { + public: + static const TADDR HeapStart; // A constant signifying the start of the GC heap. + static const TADDR HeapEnd; // A constant signifying the end of the GC heap. + + public: + /* Constructor. + * Throws: + * DataRead + */ + GCHeap(); + + /* Returns an ObjectIterator which allows you to walk the objects on the managed heap. + * This ObjectIterator is valid for the duration of the GCHeap's lifetime. Note that + * if you specify an address at which you wish to start walking the heap it need + * not point directly to a managed object. However, if it does not, WalkHeap + * will need to walk the segment that address resides in to find the first object + * after that address, and if it encounters any heap corruption along the way, + * it may be impossible to walk the heap from the address specified. + * + * Params: + * start - The starting address at which you want to start walking the heap. + * This need not point directly to an object on the heap. + * end - The ending address at which you want to stop walking the heap. This + * need not point directly to an object on the heap. + * validate - Whether or not you wish to validate the GC heap as you walk it. + * Throws: + * DataRead + */ + ObjectIterator WalkHeap(TADDR start = HeapStart, TADDR stop = HeapEnd) const; + + /* Returns true if the GC Heap structures are in a valid state for traversal. + * Returns false if not (e.g. if we are in the middle of a relocation). + */ + bool AreGCStructuresValid() const; + + private: + DacpGcHeapDetails *mHeaps; + DacpGcHeapData mHeapData; + int mNumHeaps; + }; + + // convenience functions + /* A temporary wrapper function for Object::IsValid. There are too many locations + * in SOS which need to use IsObject but have a wide variety of internal + * representations for an object address. Until it can all be unified as TADDR, + * this is what they will use. + */ + template <class T> + bool IsObject(T addr, bool verifyFields=false) + { + return Object::IsValid(TO_TADDR(addr), verifyFields); + } + + + void BuildTypeWithExtraInfo(TADDR addr, unsigned int size, __inout_ecount(size) WCHAR *buffer); +} diff --git a/src/ToolBox/SOS/Strike/sos.targets b/src/ToolBox/SOS/Strike/sos.targets new file mode 100644 index 0000000000..3a8185c6da --- /dev/null +++ b/src/ToolBox/SOS/Strike/sos.targets @@ -0,0 +1,166 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="dogfood"> + <!--Import the settings--> + <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\clr.props" /> + <Import Project="$(ClrBase)\src\debug\SetDebugTargetLocal.props" /> + <Import Project="$(ClrBase)\src\debug\XPlatCommon.props" /> + + <!-- Handle cross platform debugging: SOS is different from the DAC --> + <!-- in that one binary includes code targeting multiple platforms. --> + <!-- This means that there may be more than one SosTarget msbuild --> + <!-- symbol or SOS_TARGET_XYZ conditional defined at the same time. --> + <!-- The next section is what determines what code is built in which--> + <!-- binary. --> + <PropertyGroup> + <!-- Code level conditional compilation symbols to support x-plat --> + <CDefines Condition="'$(BuildArchitecture)' == 'i386'">$(CDefines);SOS_TARGET_X86=1</CDefines> + <CDefines Condition="'$(BuildArchitecture)' == 'arm' or '$(BuildArchitecture)' == 'i386'">$(CDefines);SOS_TARGET_ARM=1</CDefines> + <CDefines Condition="'$(BuildArchitecture)' == 'amd64'">$(CDefines);SOS_TARGET_AMD64=1</CDefines> + <CDefines Condition="'$(BuildArchitecture)' == 'amd64' or '$(BuildArchitecture)' == 'arm64'">$(CDefines);SOS_TARGET_ARM64=1</CDefines> + + <CDefines Condition="'$(BuildArchitecture)' == 'amd64' or '$(BuildArchitecture)' == 'arm64'">$(CDefines);_TARGET_WIN64_=1</CDefines> + </PropertyGroup> + + <!--Leaf project Properties--> + <PropertyGroup> + <SosSourceDir>$(ClrBase)\src\toolbox\sos\strike</SosSourceDir> + <CompilerWarnings> + </CompilerWarnings> + <UserIncludes> + $(SosSourceDir)\inc; + $(UserIncludes); + $(Clrbase)\src\vm; + $(Clrbase)\src\inc; + $(Clrbase)\src\gcdump; + $(Clrbase)\src\Debug\shim; + $(Clrbase)\src\coreclr\hosts\inc + </UserIncludes> + <OutputName>SOS</OutputName> + <FileToMarkForSigning>$(BinariesDirectory)\$(OutputName).dll</FileToMarkForSigning> + <TargetType>DYNLINK</TargetType> + <LinkSubsystem>windows</LinkSubsystem> + <Nxcompat>true</Nxcompat> + <SynchronizePass2Drain>true</SynchronizePass2Drain> + <DllDef>$(IntermediateOutputDirectory)\SOS.def</DllDef> + <DllEntryPoint>_DllMainCRTStartup</DllEntryPoint> + <LinkNoLibraries>true</LinkNoLibraries> + <UseMsvcrt>false</UseMsvcrt> + <Ltcg>false</Ltcg> + <LinkUseCMT>true</LinkUseCMT> + <UseStl>true</UseStl> + <ClAdditionalOptions Condition="'$(DebugBuild)' == 'true'">$(ClAdditionalOptions) -DDEBUG -D_DEBUG</ClAdditionalOptions> + <ClAdditionalOptions Condition="!('$(DebugBuild)' == 'true')">$(ClAdditionalOptions) -DFAST=1</ClAdditionalOptions> + <ClOptimization Condition="!('$(DebugBuild)' == 'true')">MaxSpeed</ClOptimization> + <ClAdditionalOptions Condition="'$(FeatureCoreSystem)' == 'true'">$(ClAdditionalOptions) -DFEATURE_CORESYSTEM</ClAdditionalOptions> + <MscOptimizations Condition="!('$(DebugBuild)' == 'true')"> + </MscOptimizations> + <ClAdditionalOptions>$(ClAdditionalOptions) -DSTRIKE -D_MT=1 -DORCAS=0 -DMDA_SUPPORTED -DDEBUGGING_SUPPORTED -DEnC_SUPPORTED -DPROFILING_SUPPORTED -DFEATURE_COMINTEROP -DFEATURE_COMINTEROP_UNMANAGED_ACTIVATION -DFEATURE_COMINTEROP_MANAGED_ACTIVATION -DFEATURE_COMINTEROP_APARTMENT_SUPPORT -DFEATURE_RWLOCK -DFEATURE_PREJIT -DFEATURE_STACK_PROBE -DFEATURE_SVR_GC -DFEATURE_CAS_POLICY -DFEATURE_CLICKONCE -DFEATURE_CRYPTO -DFEATURE_IMPERSONATION -DFEATURE_ISOSTORE -DFEATURE_MACL -DFEATURE_WATSON -DFEATURE_X509 -DFEATURE_X509_SECURESTRINGS -DFEATURE_COMINTEROP_REGISTRATION -DFEATURE_MIXEDMODE -DFEATURE_PERFMON -DFEATURE_REFLECTION_ONLY_LOAD -DFEATURE_FUSION -DFEATURE_SYNTHETIC_CULTURES -DFEATURE_SORT_TABLES -DFEATURE_CODEPAGES_FILE -DFEATURE_VALIDATOR -DFEATURE_WIN32_REGISTRY -DFEATURE_REMOTING -DFEATURE_SERIALIZATION -DFEATURE_ISYM_READER -DFEATURE_LOADER_OPTIMIZATION -DFEATURE_IPCMAN -DFEATURE_STRONGNAME_DELAY_SIGNING_ALLOWED -DFEATURE_MULTIMODULE_ASSEMBLIES -DFEATURE_METHOD_RENTAL -DFEATURE_APTCA -DFEATURE_USE_LCID -DFEATURE_BCL_FORMATTING -DENABLE_DOWNLEVEL_FOR_NLS -DFEATURE_INCLUDE_ALL_INTERFACES -DFEATURE_NONGENERIC_COLLECTIONS -DFEATURE_APPDOMAINMANAGER_INITOPTIONS -DFEATURE_COMPRESSEDSTACK -DFEATURE_PLS -DFEATURE_SYNCHRONIZATIONCONTEXT -DFEATURE_SYNCHRONIZATIONCONTEXT_WAIT -DUEF_CHAINING_SUPPORTED -DFEATURE_LEAK_CULTURE_INFO -DFEATURE_UEF_CHAINMANAGER -DFEATURE_CORRUPTING_EXCEPTIONS -DFEATURE_APPDOMAIN_RESOURCE_MONITORING -DFEATURE_EXCEPTION_NOTIFICATIONS -DFEATURE_LEGACY_THREADPOOL -DFEATURE_INTEROP_DEBUGGING</ClAdditionalOptions> + <ExceptionHandling>$(Fullcxxeh)</ExceptionHandling> + <!-- As part of X-plat DAC feature work we decided to put x86 SOS inside binaries\x86 subdirectory. + The pdb file will be places following the same subdir rule SymbolsDir\x86. + --> + <BinplaceRoot Condition="'$(BuildX86Sos)' == 'true'">$(BinariesDirectory)\x86</BinplaceRoot> + <BinplaceRoot Condition="'$(BuildX64Sos)' == 'true'">$(BinariesDirectory)\x64</BinplaceRoot> + <!-- Inhibit default behaviour of copying sos.pdb to Symbols.pri dir. --> + <BinplaceSymbols Condition="'$(BuildX86Sos)' == 'true' or '$(BuildX64Sos)' == 'true'">false</BinplaceSymbols> + <Verdir>$(InternalPath)\NDP\inc</Verdir> + <Bindir>$(BinariesDirectory)</Bindir> + </PropertyGroup> + + <ItemGroup> + <TargetLib Include="$(SdkLibPath)\kernel32.lib" /> + <TargetLib Include="$(SdkLibPath)\user32.lib" /> + <TargetLib Include="$(SdkLibPath)\ole32.lib" /> + <TargetLib Include="$(SdkLibPath)\oleaut32.lib" /> + <TargetLib Include="$(SdkLibPath)\dbghelp.lib" /> + <TargetLib Include="$(SdkLibPath)\uuid.lib" /> + <TargetLib Include="$(SdkLibPath)\version.lib" /> + <TargetLib Include="$(SdkLibPath)\dbgeng.lib" /> + <TargetLib Include="$(SdkLibPath)\advapi32.lib" /> + <TargetLib Include="$(SdkLibPath)\psapi.lib" /> + + <TargetLib Condition="'$(DebugBuild)' == 'true'" Include="$(VCPublicLibPath)\libcmtd.lib" /> + <TargetLib Condition="'$(DebugBuild)' != 'true'" Include="$(VCPublicLibPath)\libcmt.lib" /> + + <TargetLib Condition="'$(BuildX86Sos)' != 'true' and '$(BuildX64Sos)' != 'true'" Include="$(ClrLibPath)\corguids.lib"> + <ProjectReference>$(ClrSrcDirectory)inc\corguids.nativeproj</ProjectReference> + </TargetLib> + <TargetLib Condition="'$(BuildX86Sos)' == 'true'" Include="$(ClrLibPath)\corguids_x86.lib"> + <ProjectReference>$(ClrSrcDirectory)incx86\corguids.nativeproj</ProjectReference> + </TargetLib> + <TargetLib Condition="'$(BuildX64Sos)' == 'true'" Include="$(ClrLibPath)\corguids_amd64.lib"> + <ProjectReference>$(ClrSrcDirectory)incamd64\corguids.nativeproj</ProjectReference> + </TargetLib> + + <TargetLib Include="$(ClrLibPath)\debugshim$(XPlatHostLibSuffix).lib"> + <ProjectReference>$(ClrSrcDirectory)\Debug\shim\$(XPlatHostLibBuildDir)\debugshim.nativeproj</ProjectReference> + </TargetLib> + <TargetLib Include="$(ClrLibPath)\dbgutil$(XPlatHostLibSuffix).lib"> + <ProjectReference>$(ClrSrcDirectory)\Debug\dbgutil\$(XPlatHostLibBuildDir)\dbgutil.nativeproj</ProjectReference> + </TargetLib> + </ItemGroup> + <ItemGroup> + <TargetLib Include="$(SdkLibPath)\ntdll.lib" /> + </ItemGroup> + <ItemGroup> + <RCResourceFile Condition="'$(FeatureCoreSystem)'!='true'" Include="$(SosSourceDir)\Native.rc" /> + <RCResourceFile Condition="'$(FeatureCoreSystem)'=='true'" Include="$(SosSourceDir)\ApolloNative.rc" /> + </ItemGroup> + <ItemGroup> + <CppCompile Include="$(SosSourceDir)\disasm.cpp" /> + <CppCompile Include="$(SosSourceDir)\dllsext.cpp" /> + <CppCompile Include="$(SosSourceDir)\eeheap.cpp" /> + <CppCompile Include="$(SosSourceDir)\EventCallbacks.cpp" /> + <CppCompile Include="$(SosSourceDir)\ExpressionNode.cpp" /> + <CppCompile Include="$(SosSourceDir)\exts.cpp" /> + <CppCompile Include="$(SosSourceDir)\gchist.cpp" /> + <CppCompile Include="$(SosSourceDir)\gcroot.cpp" /> + <CppCompile Include="$(SosSourceDir)\metadata.cpp" /> + <CppCompile Include="$(SosSourceDir)\sildasm.cpp" /> + <CppCompile Include="$(SosSourceDir)\sos.cpp" /> + <CppCompile Include="$(SosSourceDir)\stressLogDump.cpp" /> + <CppCompile Include="$(SosSourceDir)\strike.cpp" /> + <CppCompile Include="$(SosSourceDir)\util.cpp" /> + <CppCompile Include="$(SosSourceDir)\vm.cpp" /> + <CppCompile Include="$(SosSourceDir)\WatchCmd.cpp" /> + <CppPreprocess Include="$(SosSourceDir)\SOS.def"> + <FinalOutput>$(IntermediateOutputDirectory)\SOS.def</FinalOutput> + <AdditionalOptions>$(ClAdditionalOptions) /TC</AdditionalOptions> + </CppPreprocess> + </ItemGroup> + <ItemGroup> + <CppCompile Condition="'$(BuildArchitecture)' == 'i386'" Include="$(SosSourceDir)\disasmX86.cpp" /> + <CppCompile Condition="'$(BuildArchitecture)' == 'amd64'" Include="$(SosSourceDir)\disasmX86.cpp" /> + <CppCompile Condition="'$(BuildArchitecture)' == 'arm' or '$(BuildArchitecture)' == 'i386'" Include="$(SosSourceDir)\disasmARM.cpp" /> + <CppCompile Condition="'$(BuildArchitecture)' == 'arm64' or '$(BuildArchitecture)' == 'amd64'" Include="$(SosSourceDir)\disasmARM64.cpp" /> + <RotorX86Sources Include="$(SosSourceDir)\disasmX86.cpp" /> + </ItemGroup> + <ItemGroup> + <DataFile Include="$(SosSourceDir)\sos_stacktrace.h" /> + </ItemGroup> + <ItemGroup> + <PublishPartGenerated Include="$(SosSourceDir)\sos_stacktrace.h"> + <Visibility>Intra</Visibility> + <FileType>Include</FileType> + </PublishPartGenerated> + </ItemGroup> + + <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\clr.targets" /> + + <PropertyGroup> + <BuildLinkedDependsOn>$(BuildLinkedDependsOn);GenLongNameSOS</BuildLinkedDependsOn> + </PropertyGroup> + + <UsingTask TaskName="GetFileVersionTask" AssemblyFile="$(ClrSrcDirectory)dlls\mscordac\GetFileVersion.dll"/> + + <Target Name="GenLongNameSOS" + Inputs="$(Verdir)\version.h;$(IntermediateOutputDirectory)\sos.dll" + Outputs="sos_upd"> + <GetFileVersionTask FilePath="$(IntermediateOutputDirectory)\sos.dll"> + <Output TaskParameter="FileVersion" PropertyName="SOSFileVersion"/> + </GetFileVersionTask> + <Exec Command="$(PerlCommand) -I$(DevDivToolsPath) $(ClrSrcDirectory)dlls\mscordac\Update.pl $(IntermediateOutputDirectory)\sos.dll sos $(HostMachineArch) $(_EnvironmentMachineArch) $(SOSFileVersion) $(Bindir) echo" StandardOutputImportance="Normal" /> + + </Target> + +</Project> diff --git a/src/ToolBox/SOS/Strike/sos_md.h b/src/ToolBox/SOS/Strike/sos_md.h new file mode 100644 index 0000000000..f1a34cd706 --- /dev/null +++ b/src/ToolBox/SOS/Strike/sos_md.h @@ -0,0 +1,926 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ==++== +// + +// +// ==--== + +#ifndef __SOS_MD_H__ +#define __SOS_MD_H__ + +#define IfErrGoTo(s, label) { \ + hresult = (s); \ + if(FAILED(hresult)){ \ + goto label; }} + +class CQuickBytes; + +// TODO: Cleanup code to allow SOS to directly include the metadata header files. +//#include "MetaData.h" +//#include "corpriv.h" + +/* + * + * Metadata definitions needed for PrettyPrint functions. + * The original definitions for the types and interfaces below exist in + * inc\MetaData.h and inc\CorPriv.h + * TODO: + * Cleanup code to allow SOS to directly include the metadata header files. + * Currently it's extremely difficult due to symbol redefinitions. + * Always keep the definitions below in sync with the originals. + * NOTES: + * Since SOS runs in a native debugger session, since it does not use EnC, + * and in roder to minimize the amount of duplication we changed the + * method definitions that deal with UTSemReadWrite* arguments to take + * void* arguments instead. + * Also, some of the interface methods take CQuickBytes as arguments. + * If these methods are ever used it becomes crucial to maintain binary + * compatibility b/w the CQuickBytes defined in SOS and the definition + * from the EE. + * + */ +typedef enum tagEnumType +{ + MDSimpleEnum = 0x0, // simple enumerator that doesn't allocate memory + MDDynamicArrayEnum = 0x2, // dynamic array that holds tokens + MDCustomEnum = 0x3, // Custom enumerator that doesnt work with the enum functions +} EnumType; + +struct HENUMInternal +{ + DWORD m_tkKind; // kind of tables that the enum is holding the result + ULONG m_ulCount; // count of total entries holding by the enumerator + + EnumType m_EnumType; + + struct { + ULONG m_ulStart; + ULONG m_ulEnd; + ULONG m_ulCur; + } u; + + // m_cursor will go away when we no longer support running EE with uncompressed + // format. WHEN WE REMOVE THIS, REMOVE ITS VESTIAGES FROM ZeroEnum as well + // + char m_cursor[32]; // cursor holding query result for read/write mode +}; + +typedef struct _MDDefaultValue +{ +#if DBG_TARGET_BIGENDIAN + _MDDefaultValue(void) + { + m_bType = ELEMENT_TYPE_END; + } + ~_MDDefaultValue(void) + { + if (m_bType == ELEMENT_TYPE_STRING) + { + delete[] m_wzValue; + } + } +#endif + + // type of default value + BYTE m_bType; // CorElementType for the default value + + // the default value + union + { + BOOL m_bValue; // ELEMENT_TYPE_BOOLEAN + CHAR m_cValue; // ELEMENT_TYPE_I1 + BYTE m_byteValue; // ELEMENT_TYPE_UI1 + SHORT m_sValue; // ELEMENT_TYPE_I2 + USHORT m_usValue; // ELEMENT_TYPE_UI2 + LONG m_lValue; // ELEMENT_TYPE_I4 + ULONG m_ulValue; // ELEMENT_TYPE_UI4 + LONGLONG m_llValue; // ELEMENT_TYPE_I8 + ULONGLONG m_ullValue; // ELEMENT_TYPE_UI8 + FLOAT m_fltValue; // ELEMENT_TYPE_R4 + DOUBLE m_dblValue; // ELEMENT_TYPE_R8 + LPCWSTR m_wzValue; // ELEMENT_TYPE_STRING + IUnknown *m_unkValue; // ELEMENT_TYPE_CLASS + }; + ULONG m_cbSize; // default value size (for blob) + +} MDDefaultValue; + +typedef struct +{ + RID m_ridFieldCur; // indexing to the field table + RID m_ridFieldEnd; // end index to field table +} MD_CLASS_LAYOUT; + +typedef struct +{ + USHORT usMajorVersion; // Major Version. + USHORT usMinorVersion; // Minor Version. + USHORT usBuildNumber; // Build Number. + USHORT usRevisionNumber; // Revision Number. + LPCSTR szLocale; // Locale. + DWORD *rProcessor; // Processor array. + ULONG ulProcessor; // [IN/OUT] Size of the processor array/Actual # of entries filled in. + OSINFO *rOS; // OSINFO array. + ULONG ulOS; // [IN/OUT]Size of the OSINFO array/Actual # of entries filled in. +} AssemblyMetaDataInternal; + +typedef struct +{ + mdMethodDef m_memberdef; + DWORD m_dwSemantics; +} ASSOCIATE_RECORD; + +typedef BOOL (*PSIGCOMPARE)(PCCOR_SIGNATURE, DWORD, PCCOR_SIGNATURE, DWORD, void*); + +EXTERN_GUID(IID_IMDInternalImport, 0xce0f34ed, 0xbbc6, 0x11d2, 0x94, 0x1e, 0x0, 0x0, 0xf8, 0x8, 0x34, 0x60); +#undef INTERFACE +#define INTERFACE IMDInternalImport +DECLARE_INTERFACE_(IMDInternalImport, IUnknown) +{ + //***************************************************************************** + // return the count of entries of a given kind in a scope + // For example, pass in mdtMethodDef will tell you how many MethodDef + // contained in a scope + //***************************************************************************** + STDMETHOD_(ULONG, GetCountWithTokenKind)(// return hresult + DWORD tkKind) PURE; // [IN] pass in the kind of token. + + //***************************************************************************** + // enumerator for typedef + //***************************************************************************** + STDMETHOD(EnumTypeDefInit)( // return hresult + HENUMInternal *phEnum) PURE; // [OUT] buffer to fill for enumerator data + + STDMETHOD_(ULONG, EnumTypeDefGetCount)( + HENUMInternal *phEnum) PURE; // [IN] the enumerator to retrieve information + + STDMETHOD_(void, EnumTypeDefReset)( + HENUMInternal *phEnum) PURE; // [IN] the enumerator to retrieve information + + STDMETHOD_(bool, EnumTypeDefNext)( // return hresult + HENUMInternal *phEnum, // [IN] input enum + mdTypeDef *ptd) PURE; // [OUT] return token + + STDMETHOD_(void, EnumTypeDefClose)( + HENUMInternal *phEnum) PURE; // [IN] the enumerator to retrieve information + + //***************************************************************************** + // enumerator for MethodImpl + //***************************************************************************** + STDMETHOD(EnumMethodImplInit)( // return hresult + mdTypeDef td, // [IN] TypeDef over which to scope the enumeration. + HENUMInternal *phEnumBody, // [OUT] buffer to fill for enumerator data for MethodBody tokens. + HENUMInternal *phEnumDecl) PURE; // [OUT] buffer to fill for enumerator data for MethodDecl tokens. + + STDMETHOD_(ULONG, EnumMethodImplGetCount)( + HENUMInternal *phEnumBody, // [IN] MethodBody enumerator. + HENUMInternal *phEnumDecl) PURE; // [IN] MethodDecl enumerator. + + STDMETHOD_(void, EnumMethodImplReset)( + HENUMInternal *phEnumBody, // [IN] MethodBody enumerator. + HENUMInternal *phEnumDecl) PURE; // [IN] MethodDecl enumerator. + + STDMETHOD(EnumMethodImplNext)( // return hresult (S_OK = TRUE, S_FALSE = FALSE or error code) + HENUMInternal *phEnumBody, // [IN] input enum for MethodBody + HENUMInternal *phEnumDecl, // [IN] input enum for MethodDecl + mdToken *ptkBody, // [OUT] return token for MethodBody + mdToken *ptkDecl) PURE; // [OUT] return token for MethodDecl + + STDMETHOD_(void, EnumMethodImplClose)( + HENUMInternal *phEnumBody, // [IN] MethodBody enumerator. + HENUMInternal *phEnumDecl) PURE; // [IN] MethodDecl enumerator. + + //***************************************** + // Enumerator helpers for memberdef, memberref, interfaceimp, + // event, property, exception, param + //***************************************** + + STDMETHOD(EnumGlobalFunctionsInit)( // return hresult + HENUMInternal *phEnum) PURE; // [OUT] buffer to fill for enumerator data + + STDMETHOD(EnumGlobalFieldsInit)( // return hresult + HENUMInternal *phEnum) PURE; // [OUT] buffer to fill for enumerator data + + STDMETHOD(EnumInit)( // return S_FALSE if record not found + DWORD tkKind, // [IN] which table to work on + mdToken tkParent, // [IN] token to scope the search + HENUMInternal *phEnum) PURE; // [OUT] the enumerator to fill + + STDMETHOD(EnumAllInit)( // return S_FALSE if record not found + DWORD tkKind, // [IN] which table to work on + HENUMInternal *phEnum) PURE; // [OUT] the enumerator to fill + + STDMETHOD_(bool, EnumNext)( + HENUMInternal *phEnum, // [IN] the enumerator to retrieve information + mdToken *ptk) PURE; // [OUT] token to scope the search + + STDMETHOD_(ULONG, EnumGetCount)( + HENUMInternal *phEnum) PURE; // [IN] the enumerator to retrieve information + + STDMETHOD_(void, EnumReset)( + HENUMInternal *phEnum) PURE; // [IN] the enumerator to be reset + + STDMETHOD_(void, EnumClose)( + HENUMInternal *phEnum) PURE; // [IN] the enumerator to be closed + + //***************************************** + // Enumerator helpers for declsecurity. + //***************************************** + STDMETHOD(EnumPermissionSetsInit)( // return S_FALSE if record not found + mdToken tkParent, // [IN] token to scope the search + CorDeclSecurity Action, // [IN] Action to scope the search + HENUMInternal *phEnum) PURE; // [OUT] the enumerator to fill + + //***************************************** + // Enumerator helpers for CustomAttribute + //***************************************** + STDMETHOD(EnumCustomAttributeByNameInit)(// return S_FALSE if record not found + mdToken tkParent, // [IN] token to scope the search + LPCSTR szName, // [IN] CustomAttribute's name to scope the search + HENUMInternal *phEnum) PURE; // [OUT] the enumerator to fill + + //***************************************** + // Nagivator helper to navigate back to the parent token given a token. + // For example, given a memberdef token, it will return the containing typedef. + // + // the mapping is as following: + // ---given child type---------parent type + // mdMethodDef mdTypeDef + // mdFieldDef mdTypeDef + // mdInterfaceImpl mdTypeDef + // mdParam mdMethodDef + // mdProperty mdTypeDef + // mdEvent mdTypeDef + // + //***************************************** + STDMETHOD(GetParentToken)( + mdToken tkChild, // [IN] given child token + mdToken *ptkParent) PURE; // [OUT] returning parent + + //***************************************** + // Custom value helpers + //***************************************** + STDMETHOD(GetCustomAttributeProps)( // S_OK or error. + mdCustomAttribute at, // [IN] The attribute. + mdToken *ptkType) PURE; // [OUT] Put attribute type here. + + STDMETHOD(GetCustomAttributeAsBlob)( + mdCustomAttribute cv, // [IN] given custom value token + void const **ppBlob, // [OUT] return the pointer to internal blob + ULONG *pcbSize) PURE; // [OUT] return the size of the blob + + // returned void in v1.0/v1.1 + STDMETHOD (GetScopeProps)( + LPCSTR *pszName, // [OUT] scope name + GUID *pmvid) PURE; // [OUT] version id + + // finding a particular method + STDMETHOD(FindMethodDef)( + mdTypeDef classdef, // [IN] given typedef + LPCSTR szName, // [IN] member name + PCCOR_SIGNATURE pvSigBlob, // [IN] point to a blob value of CLR signature + ULONG cbSigBlob, // [IN] count of bytes in the signature blob + mdMethodDef *pmd) PURE; // [OUT] matching memberdef + + // return a iSeq's param given a MethodDef + STDMETHOD(FindParamOfMethod)( // S_OK or error. + mdMethodDef md, // [IN] The owning method of the param. + ULONG iSeq, // [IN] The sequence # of the param. + mdParamDef *pparamdef) PURE; // [OUT] Put ParamDef token here. + + //***************************************** + // + // GetName* functions + // + //***************************************** + + // return the name and namespace of typedef + STDMETHOD(GetNameOfTypeDef)( + mdTypeDef classdef, // given classdef + LPCSTR *pszname, // return class name(unqualified) + LPCSTR *psznamespace) PURE; // return the name space name + + STDMETHOD(GetIsDualOfTypeDef)( + mdTypeDef classdef, // [IN] given classdef. + ULONG *pDual) PURE; // [OUT] return dual flag here. + + STDMETHOD(GetIfaceTypeOfTypeDef)( + mdTypeDef classdef, // [IN] given classdef. + ULONG *pIface) PURE; // [OUT] 0=dual, 1=vtable, 2=dispinterface + + // get the name of either methoddef + STDMETHOD(GetNameOfMethodDef)( // return the name of the memberdef in UTF8 + mdMethodDef md, // given memberdef + LPCSTR *pszName) PURE; + + STDMETHOD(GetNameAndSigOfMethodDef)( + mdMethodDef methoddef, // [IN] given memberdef + PCCOR_SIGNATURE *ppvSigBlob, // [OUT] point to a blob value of CLR signature + ULONG *pcbSigBlob, // [OUT] count of bytes in the signature blob + LPCSTR *pszName) PURE; + + // return the name of a FieldDef + STDMETHOD(GetNameOfFieldDef)( + mdFieldDef fd, // given memberdef + LPCSTR *pszName) PURE; + + // return the name of typeref + STDMETHOD(GetNameOfTypeRef)( + mdTypeRef classref, // [IN] given typeref + LPCSTR *psznamespace, // [OUT] return typeref name + LPCSTR *pszname) PURE; // [OUT] return typeref namespace + + // return the resolutionscope of typeref + STDMETHOD(GetResolutionScopeOfTypeRef)( + mdTypeRef classref, // given classref + mdToken *ptkResolutionScope) PURE; + + // Find the type token given the name. + STDMETHOD(FindTypeRefByName)( + LPCSTR szNamespace, // [IN] Namespace for the TypeRef. + LPCSTR szName, // [IN] Name of the TypeRef. + mdToken tkResolutionScope, // [IN] Resolution Scope fo the TypeRef. + mdTypeRef *ptk) PURE; // [OUT] TypeRef token returned. + + // return the TypeDef properties + // returned void in v1.0/v1.1 + STDMETHOD(GetTypeDefProps)( + mdTypeDef classdef, // given classdef + DWORD *pdwAttr, // return flags on class, tdPublic, tdAbstract + mdToken *ptkExtends) PURE; // [OUT] Put base class TypeDef/TypeRef here + + // return the item's guid + STDMETHOD(GetItemGuid)( + mdToken tkObj, // [IN] given item. + CLSID *pGuid) PURE; // [out[ put guid here. + + // Get enclosing class of the NestedClass. + STDMETHOD(GetNestedClassProps)( // S_OK or error + mdTypeDef tkNestedClass, // [IN] NestedClass token. + mdTypeDef *ptkEnclosingClass) PURE; // [OUT] EnclosingClass token. + + // Get count of Nested classes given the enclosing class. + STDMETHOD(GetCountNestedClasses)( // return count of Nested classes. + mdTypeDef tkEnclosingClass, // Enclosing class. + ULONG *pcNestedClassesCount) PURE; + + // Return array of Nested classes given the enclosing class. + STDMETHOD(GetNestedClasses)( // Return actual count. + mdTypeDef tkEnclosingClass, // [IN] Enclosing class. + mdTypeDef *rNestedClasses, // [OUT] Array of nested class tokens. + ULONG ulNestedClasses, // [IN] Size of array. + ULONG *pcNestedClasses) PURE; + + // return the ModuleRef properties + // returned void in v1.0/v1.1 + STDMETHOD(GetModuleRefProps)( + mdModuleRef mur, // [IN] moduleref token + LPCSTR *pszName) PURE; // [OUT] buffer to fill with the moduleref name + + //***************************************** + // + // GetSig* functions + // + //***************************************** + STDMETHOD(GetSigOfMethodDef)( + mdMethodDef methoddef, // [IN] given memberdef + ULONG *pcbSigBlob, // [OUT] count of bytes in the signature blob + PCCOR_SIGNATURE *ppSig) PURE; + + STDMETHOD(GetSigOfFieldDef)( + mdMethodDef methoddef, // [IN] given memberdef + ULONG *pcbSigBlob, // [OUT] count of bytes in the signature blob + PCCOR_SIGNATURE *ppSig) PURE; + + STDMETHOD(GetSigFromToken)( + mdToken tk, + ULONG * pcbSig, + PCCOR_SIGNATURE * ppSig) PURE; + + + + //***************************************** + // get method property + //***************************************** + STDMETHOD(GetMethodDefProps)( + mdMethodDef md, // The method for which to get props. + DWORD *pdwFlags) PURE; + + //***************************************** + // return method implementation informaiton, like RVA and implflags + //***************************************** + // returned void in v1.0/v1.1 + STDMETHOD(GetMethodImplProps)( + mdToken tk, // [IN] MethodDef + ULONG *pulCodeRVA, // [OUT] CodeRVA + DWORD *pdwImplFlags) PURE; // [OUT] Impl. Flags + + //***************************************** + // return method implementation informaiton, like RVA and implflags + //***************************************** + STDMETHOD(GetFieldRVA)( + mdFieldDef fd, // [IN] fielddef + ULONG *pulCodeRVA) PURE; // [OUT] CodeRVA + + //***************************************** + // get field property + //***************************************** + STDMETHOD(GetFieldDefProps)( + mdFieldDef fd, // [IN] given fielddef + DWORD *pdwFlags) PURE; // [OUT] return fdPublic, fdPrive, etc flags + + //***************************************************************************** + // return default value of a token(could be paramdef, fielddef, or property + //***************************************************************************** + STDMETHOD(GetDefaultValue)( + mdToken tk, // [IN] given FieldDef, ParamDef, or Property + MDDefaultValue *pDefaultValue) PURE;// [OUT] default value to fill + + + //***************************************** + // get dispid of a MethodDef or a FieldDef + //***************************************** + STDMETHOD(GetDispIdOfMemberDef)( // return hresult + mdToken tk, // [IN] given methoddef or fielddef + ULONG *pDispid) PURE; // [OUT] Put the dispid here. + + //***************************************** + // return TypeRef/TypeDef given an InterfaceImpl token + //***************************************** + STDMETHOD(GetTypeOfInterfaceImpl)( // return the TypeRef/typedef token for the interfaceimpl + mdInterfaceImpl iiImpl, // given a interfaceimpl + mdToken *ptkType) PURE; + + //***************************************** + // look up function for TypeDef + //***************************************** + STDMETHOD(FindTypeDef)( + LPCSTR szNamespace, // [IN] Namespace for the TypeDef. + LPCSTR szName, // [IN] Name of the TypeDef. + mdToken tkEnclosingClass, // [IN] TypeRef/TypeDef Token for the enclosing class. + mdTypeDef *ptypedef) PURE; // [IN] return typedef + + //***************************************** + // return name and sig of a memberref + //***************************************** + STDMETHOD(GetNameAndSigOfMemberRef)( // return name here + mdMemberRef memberref, // given memberref + PCCOR_SIGNATURE *ppvSigBlob, // [OUT] point to a blob value of CLR signature + ULONG *pcbSigBlob, // [OUT] count of bytes in the signature blob + LPCSTR *pszName) PURE; + + //***************************************************************************** + // Given memberref, return the parent. It can be TypeRef, ModuleRef, MethodDef + //***************************************************************************** + STDMETHOD(GetParentOfMemberRef)( + mdMemberRef memberref, // given memberref + mdToken *ptkParent) PURE; // return the parent token + + STDMETHOD(GetParamDefProps)( + mdParamDef paramdef, // given a paramdef + USHORT *pusSequence, // [OUT] slot number for this parameter + DWORD *pdwAttr, // [OUT] flags + LPCSTR *pszName) PURE; // [OUT] return the name of the parameter + + STDMETHOD(GetPropertyInfoForMethodDef)( // Result. + mdMethodDef md, // [IN] memberdef + mdProperty *ppd, // [OUT] put property token here + LPCSTR *pName, // [OUT] put pointer to name here + ULONG *pSemantic) PURE; // [OUT] put semantic here + + //***************************************** + // class layout/sequence information + //***************************************** + STDMETHOD(GetClassPackSize)( // return error if class doesn't have packsize + mdTypeDef td, // [IN] give typedef + ULONG *pdwPackSize) PURE; // [OUT] 1, 2, 4, 8, or 16 + + STDMETHOD(GetClassTotalSize)( // return error if class doesn't have total size info + mdTypeDef td, // [IN] give typedef + ULONG *pdwClassSize) PURE; // [OUT] return the total size of the class + + STDMETHOD(GetClassLayoutInit)( + mdTypeDef td, // [IN] give typedef + MD_CLASS_LAYOUT *pLayout) PURE; // [OUT] set up the status of query here + + STDMETHOD(GetClassLayoutNext)( + MD_CLASS_LAYOUT *pLayout, // [IN|OUT] set up the status of query here + mdFieldDef *pfd, // [OUT] return the fielddef + ULONG *pulOffset) PURE; // [OUT] return the offset/ulSequence associate with it + + //***************************************** + // marshal information of a field + //***************************************** + STDMETHOD(GetFieldMarshal)( // return error if no native type associate with the token + mdFieldDef fd, // [IN] given fielddef + PCCOR_SIGNATURE *pSigNativeType, // [OUT] the native type signature + ULONG *pcbNativeType) PURE; // [OUT] the count of bytes of *ppvNativeType + + + //***************************************** + // property APIs + //***************************************** + // find a property by name + STDMETHOD(FindProperty)( + mdTypeDef td, // [IN] given a typdef + LPCSTR szPropName, // [IN] property name + mdProperty *pProp) PURE; // [OUT] return property token + + // returned void in v1.0/v1.1 + STDMETHOD(GetPropertyProps)( + mdProperty prop, // [IN] property token + LPCSTR *szProperty, // [OUT] property name + DWORD *pdwPropFlags, // [OUT] property flags. + PCCOR_SIGNATURE *ppvSig, // [OUT] property type. pointing to meta data internal blob + ULONG *pcbSig) PURE; // [OUT] count of bytes in *ppvSig + + //********************************** + // Event APIs + //********************************** + STDMETHOD(FindEvent)( + mdTypeDef td, // [IN] given a typdef + LPCSTR szEventName, // [IN] event name + mdEvent *pEvent) PURE; // [OUT] return event token + + // returned void in v1.0/v1.1 + STDMETHOD(GetEventProps)( + mdEvent ev, // [IN] event token + LPCSTR *pszEvent, // [OUT] Event name + DWORD *pdwEventFlags, // [OUT] Event flags. + mdToken *ptkEventType) PURE; // [OUT] EventType class + + + //********************************** + // find a particular associate of a property or an event + //********************************** + STDMETHOD(FindAssociate)( + mdToken evprop, // [IN] given a property or event token + DWORD associate, // [IN] given a associate semantics(setter, getter, testdefault, reset, AddOn, RemoveOn, Fire) + mdMethodDef *pmd) PURE; // [OUT] return method def token + + // Note, void function in v1.0/v1.1 + STDMETHOD(EnumAssociateInit)( + mdToken evprop, // [IN] given a property or an event token + HENUMInternal *phEnum) PURE; // [OUT] cursor to hold the query result + + // returned void in v1.0/v1.1 + STDMETHOD(GetAllAssociates)( + HENUMInternal *phEnum, // [IN] query result form GetPropertyAssociateCounts + ASSOCIATE_RECORD *pAssociateRec, // [OUT] struct to fill for output + ULONG cAssociateRec) PURE; // [IN] size of the buffer + + + //********************************** + // Get info about a PermissionSet. + //********************************** + // returned void in v1.0/v1.1 + STDMETHOD(GetPermissionSetProps)( + mdPermission pm, // [IN] the permission token. + DWORD *pdwAction, // [OUT] CorDeclSecurity. + void const **ppvPermission, // [OUT] permission blob. + ULONG *pcbPermission) PURE; // [OUT] count of bytes of pvPermission. + + //**************************************** + // Get the String given the String token. + // Returns a pointer to the string, or NULL in case of error. + //**************************************** + STDMETHOD(GetUserString)( + mdString stk, // [IN] the string token. + ULONG *pchString, // [OUT] count of characters in the string. + BOOL *pbIs80Plus, // [OUT] specifies where there are extended characters >= 0x80. + LPCWSTR *pwszUserString) PURE; + + //***************************************************************************** + // p-invoke APIs. + //***************************************************************************** + STDMETHOD(GetPinvokeMap)( + mdToken tk, // [IN] FieldDef, MethodDef. + DWORD *pdwMappingFlags, // [OUT] Flags used for mapping. + LPCSTR *pszImportName, // [OUT] Import name. + mdModuleRef *pmrImportDLL) PURE; // [OUT] ModuleRef token for the target DLL. + + //***************************************************************************** + // helpers to convert a text signature to a com format + //***************************************************************************** + STDMETHOD(ConvertTextSigToComSig)( // Return hresult. + BOOL fCreateTrIfNotFound, // [IN] create typeref if not found + LPCSTR pSignature, // [IN] class file format signature + CQuickBytes *pqbNewSig, // [OUT] place holder for CLR signature + ULONG *pcbCount) PURE; // [OUT] the result size of signature + + //***************************************************************************** + // Assembly MetaData APIs. + //***************************************************************************** + // returned void in v1.0/v1.1 + STDMETHOD(GetAssemblyProps)( + mdAssembly mda, // [IN] The Assembly for which to get the properties. + const void **ppbPublicKey, // [OUT] Pointer to the public key. + ULONG *pcbPublicKey, // [OUT] Count of bytes in the public key. + ULONG *pulHashAlgId, // [OUT] Hash Algorithm. + LPCSTR *pszName, // [OUT] Buffer to fill with name. + AssemblyMetaDataInternal *pMetaData,// [OUT] Assembly MetaData. + DWORD *pdwAssemblyFlags) PURE;// [OUT] Flags. + + // returned void in v1.0/v1.1 + STDMETHOD(GetAssemblyRefProps)( + mdAssemblyRef mdar, // [IN] The AssemblyRef for which to get the properties. + const void **ppbPublicKeyOrToken, // [OUT] Pointer to the public key or token. + ULONG *pcbPublicKeyOrToken, // [OUT] Count of bytes in the public key or token. + LPCSTR *pszName, // [OUT] Buffer to fill with name. + AssemblyMetaDataInternal *pMetaData,// [OUT] Assembly MetaData. + const void **ppbHashValue, // [OUT] Hash blob. + ULONG *pcbHashValue, // [OUT] Count of bytes in the hash blob. + DWORD *pdwAssemblyRefFlags) PURE; // [OUT] Flags. + + // returned void in v1.0/v1.1 + STDMETHOD(GetFileProps)( + mdFile mdf, // [IN] The File for which to get the properties. + LPCSTR *pszName, // [OUT] Buffer to fill with name. + const void **ppbHashValue, // [OUT] Pointer to the Hash Value Blob. + ULONG *pcbHashValue, // [OUT] Count of bytes in the Hash Value Blob. + DWORD *pdwFileFlags) PURE; // [OUT] Flags. + + // returned void in v1.0/v1.1 + STDMETHOD(GetExportedTypeProps)( + mdExportedType mdct, // [IN] The ExportedType for which to get the properties. + LPCSTR *pszNamespace, // [OUT] Namespace. + LPCSTR *pszName, // [OUT] Name. + mdToken *ptkImplementation, // [OUT] mdFile or mdAssemblyRef that provides the ExportedType. + mdTypeDef *ptkTypeDef, // [OUT] TypeDef token within the file. + DWORD *pdwExportedTypeFlags) PURE; // [OUT] Flags. + + // returned void in v1.0/v1.1 + STDMETHOD(GetManifestResourceProps)( + mdManifestResource mdmr, // [IN] The ManifestResource for which to get the properties. + LPCSTR *pszName, // [OUT] Buffer to fill with name. + mdToken *ptkImplementation, // [OUT] mdFile or mdAssemblyRef that provides the ExportedType. + DWORD *pdwOffset, // [OUT] Offset to the beginning of the resource within the file. + DWORD *pdwResourceFlags) PURE;// [OUT] Flags. + + STDMETHOD(FindExportedTypeByName)( // S_OK or error + LPCSTR szNamespace, // [IN] Namespace of the ExportedType. + LPCSTR szName, // [IN] Name of the ExportedType. + mdExportedType tkEnclosingType, // [IN] ExportedType for the enclosing class. + mdExportedType *pmct) PURE; // [OUT] Put ExportedType token here. + + STDMETHOD(FindManifestResourceByName)( // S_OK or error + LPCSTR szName, // [IN] Name of the ManifestResource. + mdManifestResource *pmmr) PURE; // [OUT] Put ManifestResource token here. + + STDMETHOD(GetAssemblyFromScope)( // S_OK or error + mdAssembly *ptkAssembly) PURE; // [OUT] Put token here. + + STDMETHOD(GetCustomAttributeByName)( // S_OK or error + mdToken tkObj, // [IN] Object with Custom Attribute. + LPCUTF8 szName, // [IN] Name of desired Custom Attribute. + const void **ppData, // [OUT] Put pointer to data here. + ULONG *pcbData) PURE; // [OUT] Put size of data here. + + // Note: The return type of this method was void in v1 + STDMETHOD(GetTypeSpecFromToken)( // S_OK or error. + mdTypeSpec typespec, // [IN] Signature token. + PCCOR_SIGNATURE *ppvSig, // [OUT] return pointer to token. + ULONG *pcbSig) PURE; // [OUT] return size of signature. + + STDMETHOD(SetUserContextData)( // S_OK or E_NOTIMPL + IUnknown *pIUnk) PURE; // The user context. + + STDMETHOD_(BOOL, IsValidToken)( // True or False. + mdToken tk) PURE; // [IN] Given token. + + STDMETHOD(TranslateSigWithScope)( + IMDInternalImport *pAssemImport, // [IN] import assembly scope. + const void *pbHashValue, // [IN] hash value for the import assembly. + ULONG cbHashValue, // [IN] count of bytes in the hash value. + PCCOR_SIGNATURE pbSigBlob, // [IN] signature in the importing scope + ULONG cbSigBlob, // [IN] count of bytes of signature + IMetaDataAssemblyEmit *pAssemEmit, // [IN] assembly emit scope. + IMetaDataEmit *emit, // [IN] emit interface + CQuickBytes *pqkSigEmit, // [OUT] buffer to hold translated signature + ULONG *pcbSig) PURE; // [OUT] count of bytes in the translated signature + + // since SOS does not need to call method below, change return value to IUnknown* (from IMetaModelCommon*) + STDMETHOD_(IUnknown*, GetMetaModelCommon)( // Return MetaModelCommon interface. + ) PURE; + + STDMETHOD_(IUnknown *, GetCachedPublicInterface)(BOOL fWithLock) PURE; // return the cached public interface + STDMETHOD(SetCachedPublicInterface)(IUnknown *pUnk) PURE; // no return value + // since SOS does not use the next 2 methods replace UTSemReadWrite* with void* in the signature + STDMETHOD_(void*, GetReaderWriterLock)() PURE; // return the reader writer lock + STDMETHOD(SetReaderWriterLock)(void * pSem) PURE; + + STDMETHOD_(mdModule, GetModuleFromScope)() PURE; // [OUT] Put mdModule token here. + + + //----------------------------------------------------------------- + // Additional custom methods + + // finding a particular method + STDMETHOD(FindMethodDefUsingCompare)( + mdTypeDef classdef, // [IN] given typedef + LPCSTR szName, // [IN] member name + PCCOR_SIGNATURE pvSigBlob, // [IN] point to a blob value of CLR signature + ULONG cbSigBlob, // [IN] count of bytes in the signature blob + PSIGCOMPARE pSignatureCompare, // [IN] Routine to compare signatures + void* pSignatureArgs, // [IN] Additional info to supply the compare function + mdMethodDef *pmd) PURE; // [OUT] matching memberdef + + // Additional v2 methods. + + //***************************************** + // return a field offset for a given field + //***************************************** + STDMETHOD(GetFieldOffset)( + mdFieldDef fd, // [IN] fielddef + ULONG *pulOffset) PURE; // [OUT] FieldOffset + + STDMETHOD(GetMethodSpecProps)( + mdMethodSpec ms, // [IN] The method instantiation + mdToken *tkParent, // [OUT] MethodDef or MemberRef + PCCOR_SIGNATURE *ppvSigBlob, // [OUT] point to the blob value of meta data + ULONG *pcbSigBlob) PURE; // [OUT] actual size of signature blob + + STDMETHOD(GetTableInfoWithIndex)( + ULONG index, // [IN] pass in the table index + void **pTable, // [OUT] pointer to table at index + void **pTableSize) PURE; // [OUT] size of table at index + + STDMETHOD(ApplyEditAndContinue)( + void *pDeltaMD, // [IN] the delta metadata + ULONG cbDeltaMD, // [IN] length of pData + IMDInternalImport **ppv) PURE; // [OUT] the resulting metadata interface + + //********************************** + // Generics APIs + //********************************** + STDMETHOD(GetGenericParamProps)( // S_OK or error. + mdGenericParam rd, // [IN] The type parameter + ULONG* pulSequence, // [OUT] Parameter sequence number + DWORD* pdwAttr, // [OUT] Type parameter flags (for future use) + mdToken *ptOwner, // [OUT] The owner (TypeDef or MethodDef) + DWORD *reserved, // [OUT] The kind (TypeDef/Ref/Spec, for future use) + LPCSTR *szName) PURE; // [OUT] The name + + STDMETHOD(GetGenericParamConstraintProps)( // S_OK or error. + mdGenericParamConstraint rd, // [IN] The constraint token + mdGenericParam *ptGenericParam, // [OUT] GenericParam that is constrained + mdToken *ptkConstraintType) PURE; // [OUT] TypeDef/Ref/Spec constraint + + //***************************************************************************** + // This function gets the "built for" version of a metadata scope. + // NOTE: if the scope has never been saved, it will not have a built-for + // version, and an empty string will be returned. + //***************************************************************************** + STDMETHOD(GetVersionString)( // S_OK or error. + LPCSTR *pVer) PURE; // [OUT] Put version string here. + + + STDMETHOD(SafeAndSlowEnumCustomAttributeByNameInit)(// return S_FALSE if record not found + mdToken tkParent, // [IN] token to scope the search + LPCSTR szName, // [IN] CustomAttribute's name to scope the search + HENUMInternal *phEnum) PURE; // [OUT] The enumerator + + STDMETHOD(SafeAndSlowEnumCustomAttributeByNameNext)(// return S_FALSE if record not found + mdToken tkParent, // [IN] token to scope the search + LPCSTR szName, // [IN] CustomAttribute's name to scope the search + HENUMInternal *phEnum, // [IN] The enumerator + mdCustomAttribute *mdAttribute) PURE; // [OUT] The custom attribute that was found + + + STDMETHOD(GetTypeDefRefTokenInTypeSpec)(// return S_FALSE if enclosing type does not have a token + mdTypeSpec tkTypeSpec, // [IN] TypeSpec token to look at + mdToken *tkEnclosedToken) PURE; // [OUT] The enclosed type token + +#define MD_STREAM_VER_1X 0x10000 +#define MD_STREAM_VER_2_B1 0x10001 +#define MD_STREAM_VER_2 0x20000 + STDMETHOD_(DWORD, GetMetadataStreamVersion)() PURE; //returns DWORD with major version of + // MD stream in senior word and minor version--in junior word + + STDMETHOD(GetNameOfCustomAttribute)(// S_OK or error + mdCustomAttribute mdAttribute, // [IN] The Custom Attribute + LPCUTF8 *pszNamespace, // [OUT] Namespace of Custom Attribute. + LPCUTF8 *pszName) PURE; // [OUT] Name of Custom Attribute. + + STDMETHOD(SetOptimizeAccessForSpeed)(// S_OK or error + BOOL fOptSpeed) PURE; + + STDMETHOD(SetVerifiedByTrustedSource)(// S_OK or error + BOOL fVerified) PURE; + +}; // IMDInternalImport + +EXTERN_GUID(IID_IMetaDataHelper, 0xad93d71d, 0xe1f2, 0x11d1, 0x94, 0x9, 0x0, 0x0, 0xf8, 0x8, 0x34, 0x60); + +#undef INTERFACE +#define INTERFACE IMetaDataHelper +DECLARE_INTERFACE_(IMetaDataHelper, IUnknown) +{ + // helper functions + // This function is exposing the ability to translate signature from a given + // source scope to a given target scope. + // + STDMETHOD(TranslateSigWithScope)( + IMetaDataAssemblyImport *pAssemImport, // [IN] importing assembly interface + const void *pbHashValue, // [IN] Hash Blob for Assembly. + ULONG cbHashValue, // [IN] Count of bytes. + IMetaDataImport *import, // [IN] importing interface + PCCOR_SIGNATURE pbSigBlob, // [IN] signature in the importing scope + ULONG cbSigBlob, // [IN] count of bytes of signature + IMetaDataAssemblyEmit *pAssemEmit, // [IN] emit assembly interface + IMetaDataEmit *emit, // [IN] emit interface + PCOR_SIGNATURE pvTranslatedSig, // [OUT] buffer to hold translated signature + ULONG cbTranslatedSigMax, + ULONG *pcbTranslatedSig) PURE;// [OUT] count of bytes in the translated signature + + STDMETHOD(GetMetadata)( + ULONG ulSelect, // [IN] Selector. + void **ppData) PURE; // [OUT] Put pointer to data here. + + STDMETHOD_(IUnknown *, GetCachedInternalInterface)(BOOL fWithLock) PURE; // S_OK or error + STDMETHOD(SetCachedInternalInterface)(IUnknown * pUnk) PURE; // S_OK or error + // since SOS does not use the next 2 methods replace UTSemReadWrite* with void* in the signature + STDMETHOD_(void*, GetReaderWriterLock)() PURE; // return the reader writer lock + STDMETHOD(SetReaderWriterLock)(void * pSem) PURE; +}; + +/********************************************************************************/ + +// Fine grained formatting flags used by the PrettyPrint APIs below. +// Upto FormatStubInfo they mirror the values used by TypeString, after that +// they're used to enable specifying differences between the ILDASM-style +// output and the C#-like output prefered by the rest of SOS. +typedef enum +{ + FormatBasic = 0x00000000, // Not a bitmask, simply the tersest flag settings possible + FormatNamespace = 0x00000001, // Include namespace and/or enclosing class names in type names + FormatFullInst = 0x00000002, // Include namespace and assembly in generic types (regardless of other flag settings) + FormatAssembly = 0x00000004, // Include assembly display name in type names + FormatSignature = 0x00000008, // Include signature in method names + FormatNoVersion = 0x00000010, // Suppress version and culture information in all assembly names + FormatDebug = 0x00000020, // For debug printing of types only + FormatAngleBrackets = 0x00000040, // Whether generic types are C<T> or C[T] + FormatStubInfo = 0x00000080, // Include stub info like {unbox-stub} + // following flags are not present in TypeString::FormatFlags + FormatSlashSep = 0x00000100, // Whether nested types are NS.C1/C2 or NS.C1+C2 + FormatKwInNames = 0x00000200, // Whether "class" and "valuetype" appear in type names in certain instances + FormatCSharp = 0x0000004b, // Used to generate a C#-like string representation of the token + FormatILDasm = 0x000003ff, // Used to generate an ILDASM-style string representation of the token +} +PPFormatFlags; + +char* asString(CQuickBytes *out); + +PCCOR_SIGNATURE PrettyPrintType( + PCCOR_SIGNATURE typePtr, // type to convert, + CQuickBytes *out, // where to put the pretty printed string + IMDInternalImport *pIMDI, // ptr to IMDInternal class with ComSig + DWORD formatFlags = FormatILDasm); + +const char* PrettyPrintClass( + CQuickBytes *out, // where to put the pretty printed string + mdToken tk, // The class token to look up + IMDInternalImport *pIMDI, // ptr to IMDInternalImport class with ComSig + DWORD formatFlags = FormatILDasm); + +// We have a proliferation of functions that translate a (module/token) pair to +// a string, but none of them were as complete as PrettyPrintClass. Most were +// not handling generic instantiations appropriately. PrettyPrintClassFromToken +// provides this missing functionality. If passed "FormatCSharp" it will generate +// a name fitting the format used throughout SOS, with the exception of !dumpil +// (due to its ILDASM ancestry). +// TODO: Refactor the code in PrettyPrintClassFromToken, NameForTypeDef_s, +// TODO: NameForToken_s, MDInfo::TypeDef/RefName +void PrettyPrintClassFromToken( + TADDR moduleAddr, // the module containing the token + mdToken tok, // the class token to look up + __out_ecount(cbName) WCHAR* mdName, // where to put the pretty printed string + size_t cbName, // the capacity of the buffer + DWORD formatFlags = FormatCSharp); // the format flags for the types + +inline HRESULT GetMDInternalFromImport(IMetaDataImport* pIMDImport, IMDInternalImport **ppIMDI) +{ + HRESULT hresult = E_FAIL; + IUnknown *pUnk = NULL; + IMetaDataHelper *pIMDH = NULL; + + IfErrGoTo(pIMDImport->QueryInterface(IID_IMetaDataHelper, (void**)&pIMDH), Cleanup); + pUnk = pIMDH->GetCachedInternalInterface(FALSE); + if (pUnk == NULL) + goto Cleanup; + IfErrGoTo(pUnk->QueryInterface(IID_IMDInternalImport, (void**)ppIMDI), Cleanup); + +Cleanup: + if (pUnk) + pUnk->Release(); + if (pIMDH != NULL) + pIMDH->Release(); + + return hresult; +} + +#endif + diff --git a/src/ToolBox/SOS/Strike/sos_stacktrace.h b/src/ToolBox/SOS/Strike/sos_stacktrace.h new file mode 100644 index 0000000000..4aba4ea52c --- /dev/null +++ b/src/ToolBox/SOS/Strike/sos_stacktrace.h @@ -0,0 +1,174 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ==++== +// + +// +// ==--== +/* --------------------------------------------------------------------------- + + SOS_Stacktrace.h + + API exported from SOS.DLL for retrieving managed stack traces. + This extension function is called through the Windows Debugger extension + interfaces (dbgeng.h). + + Additional functions exported from SOS are documented here as well. + +Notes: + +HRESULT CALLBACK _EFN_StackTrace( + PDEBUG_CLIENT client, + WCHAR wszTextOut[], + UINT *puiTextLength, + LPVOID pTransitionContexts, + UINT *puiTransitionContextCount, + UINT uiSizeOfContext); + +uiSizeOfContext must be either sizeof(SimpleContext) or sizeof(CONTEXT) for the +architecture (x86, IA64, x64). + +if wszTextOut is NULL and *puiTextLength is non-NULL, the function will return +the necessary string length in *puiTextLength. + +If wszTextOut is non-NULL, the function will fill wszTextOut up to the point +given by *puiTextLength, returning success if there was enough room in the +buffer or E_OUTOFMEMORY if the buffer wasn't long enough. + +The transition portion of the function will be completely ignored if +pTransitionContexts and puiTransitionContextCount are both NULL. Some callers +would just like text output of the function names. + +If pTransitionContexts is NULL and puiTransitionContextCount is non NULL, the +function will return the necessary number of context entries in +*puiTransitionContextCount. + +If pTransitionContexts is non NULL, the function will treat it as an array of +structures of length *puiTransitionContextCount. The structure size is given +by uiSizeOfContext, and must be the size of SimpleContext or CONTEXT for the +architecture. + +wszTextOut will be written in the following format: + +"<ModuleName>!<Function Name>[+<offset in hex>] +... +(TRANSITION) +..." + +if the offset in hex is 0, no offset will be written (this matches KB output). + +If there is no managed code on the thread currently in context, +SOS_E_NOMANAGEDCODE will be returned. + ------------------------------------------------------------------------ */ +#ifndef __STACKTRACE_H +#define __STACKTRACE_H +#include <windows.h> +#include <winerror.h> + +#ifndef FACILITY_SOS +#define FACILITY_SOS 0xa0 +#endif + +#ifndef EMAKEHR +#define EMAKEHR(val) MAKE_HRESULT(SEVERITY_ERROR, FACILITY_SOS, val) +#endif + +// Custom Error returns +#define SOS_E_NOMANAGEDCODE EMAKEHR(0x1000) // No managed code on the stack + +// Flags +// +// Turn on SOS_STACKTRACE_SHOWADDRESSES to see EBP ESP in front of each +// module!functionname line. By default this is off. +#define SOS_STACKTRACE_SHOWADDRESSES 0x00000001 + +struct StackTrace_SimpleContext +{ + ULONG64 StackOffset; // esp on x86 + ULONG64 FrameOffset; // ebp + ULONG64 InstructionOffset; // eip +}; + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +HRESULT CALLBACK _EFN_StackTrace( + PDEBUG_CLIENT client, + __out_ecount(*puiTextLength) WCHAR wszTextOut[], + size_t *puiTextLength, + LPVOID pTransitionContexts, + size_t *puiTransitionContextCount, + size_t uiSizeOfContext, + DWORD Flags); + +/* --------------------------------------------------------------------------- + + Additional functions are exported from SOS, and are useful + for debugging tasks with managed object pointers. + + ------------------------------------------------------------------------ */ + +// _EFN_GetManagedExcepStack - given a managed exception object address, returns a string +// version of the stack trace contained inside. +// +// StackObjAddr - a managed object pointer, must be derived from System.Exception +// szStackString - the string returned (out parameter) +// cbString - number of characters available in the string buffer. +// +// The output will be truncated of cbString is not long enough for the full stack trace. +HRESULT _EFN_GetManagedExcepStack( + PDEBUG_CLIENT client, + ULONG64 StackObjAddr, + __out_ecount(cbString) PSTR szStackString, + ULONG cbString + ); + +// _EFN_GetManagedExcepStackW - same as _EFN_GetManagedExcepStack, but returns +// the stack as a wide string. +HRESULT _EFN_GetManagedExcepStackW( + PDEBUG_CLIENT client, + ULONG64 StackObjAddr, + __out_ecount(cchString) PWSTR wszStackString, + ULONG cchString + ); + +// _EFN_GetManagedObjectName - given a managed object pointer, return the type name +// +// objAddr - a managed object pointer +// szName - a buffer to be filled with the full type name +// cbName - the number of characters available in the buffer +// +HRESULT _EFN_GetManagedObjectName( + PDEBUG_CLIENT client, + ULONG64 objAddr, + __out_ecount(cbName) PSTR szName, + ULONG cbName + ); + +// _EFN_GetManagedObjectFieldInfo - given an object pointer and a field name, returns +// the offset to the field from the start of the object, +// and the field's value. +// +// objAddr - a managed object pointer +// szFieldName - the field name you are interested in +// pValue - the field value is written here. This parameter can be NULL. +// pOffset - the offset from objAddr to the field. This parameter can be NULL. +// +// At least one of pValue and pOffset must be non-NULL. +HRESULT _EFN_GetManagedObjectFieldInfo( + PDEBUG_CLIENT client, + ULONG64 objAddr, + __out_ecount (mdNameLen) PSTR szFieldName, + PULONG64 pValue, + PULONG pOffset + ); + +#ifdef __cplusplus +} +#endif // __cplusplus : extern "C" + +#endif // __STACKTRACE_H + diff --git a/src/ToolBox/SOS/Strike/sos_unixexports.src b/src/ToolBox/SOS/Strike/sos_unixexports.src new file mode 100644 index 0000000000..ed811b6572 --- /dev/null +++ b/src/ToolBox/SOS/Strike/sos_unixexports.src @@ -0,0 +1,54 @@ +; Licensed to the .NET Foundation under one or more agreements. +; The .NET Foundation licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +bpmd +ClrStack +DumpArray +DumpAssembly +DumpClass +DumpDomain +DumpGCData +DumpHeap +DumpIL +DumpLog +DumpMD +DumpModule +DumpMT +DumpObj +DumpRuntimeTypes +DumpSig +DumpSigElem +DumpStack +DumpStackObjects +DumpVC +EEHeap +GCWhere +EEStack +EHInfo +FindAppDomain +GCInfo +GCRoot +Help +HistClear +HistInit +HistObj +HistObjFind +HistRoot +HistStats +IP2MD +Name2EE +PrintException +StopOnCatch +Threads +ThreadState +Token2EE +u +VerifyHeap +VerifyStackTrace + +_EFN_GetManagedExcepStack +_EFN_GetManagedExcepStackW +_EFN_GetManagedObjectFieldInfo +_EFN_GetManagedObjectName +_EFN_StackTrace diff --git a/src/ToolBox/SOS/Strike/sosdocs.txt b/src/ToolBox/SOS/Strike/sosdocs.txt new file mode 100644 index 0000000000..594fca1bc2 --- /dev/null +++ b/src/ToolBox/SOS/Strike/sosdocs.txt @@ -0,0 +1,2572 @@ +------------------------------------------------------------------------------- +NOTE: THIS FILE CONTAINS SOS DOCUMENTATION. THE FORMAT OF THE FILE IS: + +<optional comments> +COMMAND: <cmd name, all lower case> +<descriptive text of the command> +\\ <these are two backslashes, immediately followed by a newline> + +<repeat the sequence above> + +The first command is "contents" which is the general help screen. The rest +correspond to SOS command names. This file is embedded as a resource in the SOS +binary. Be sure to list any new commands here. +------------------------------------------------------------------------------- + + + +COMMAND: contents. +SOS is a debugger extension DLL designed to aid in the debugging of managed +programs. Functions are listed by category, then roughly in order of +importance. Shortcut names for popular functions are listed in parenthesis. +Type "!help <functionname>" for detailed info on that function. + +Object Inspection Examining code and stacks +----------------------------- ----------------------------- +DumpObj (do) Threads +DumpArray (da) ThreadState +DumpStackObjects (dso) IP2MD +DumpHeap U +DumpVC DumpStack +GCRoot EEStack +ObjSize CLRStack +FinalizeQueue GCInfo +PrintException (pe) EHInfo +TraverseHeap BPMD + COMState + +Examining CLR data structures Diagnostic Utilities +----------------------------- ----------------------------- +DumpDomain VerifyHeap +EEHeap VerifyObj +Name2EE FindRoots +SyncBlk HeapStat +DumpMT GCWhere +DumpClass ListNearObj (lno) +DumpMD GCHandles +Token2EE GCHandleLeaks +EEVersion FinalizeQueue (fq) +DumpModule FindAppDomain +ThreadPool SaveModule +DumpAssembly ProcInfo +DumpSigElem StopOnException (soe) +DumpRuntimeTypes DumpLog +DumpSig VMMap +RCWCleanupList VMStat +DumpIL MinidumpMode +DumpRCW AnalyzeOOM (ao) +DumpCCW + +Examining the GC history Other +----------------------------- ----------------------------- +HistInit FAQ +HistRoot +HistObj +HistObjFind +HistClear +\\ + +COMMAND: faq. +>> Where can I get the right version of SOS for my build? + +If you are running version 1.1 or 2.0 of the CLR, SOS.DLL is installed in the +same directory as the main CLR dll (CLR.DLL). Newer versions of the +Windows Debugger provide a command to make it easy to load the right copy of +SOS.DLL: + + ".loadby sos clr" + +That will load the SOS extension DLL from the same place that CLR.DLL is +loaded in the process. You shouldn't attempt to use a version of SOS.DLL that +doesn't match the version of CLR.DLL. You can find the version of +CLR.DLL by running + + "lmvm clr" + +in the debugger. Note that if you are running CoreCLR (e.g. Silverlight) +then you should replace "clr" with "coreclr". + +If you are using a dump file created on another machine, it is a little bit +more complex. You need to make sure the mscordacwks.dll file that came with +that install is on your symbol path, and you need to load the corresponding +version of sos.dll (typing .load <full path to sos.dll> rather than using the +.loadby shortcut). Within the Microsoft corpnet, we keep tagged versions +of mscordacwks.dll, with names like mscordacwks_<architecture>_<version>.dll +that the Windows Debugger can load. If you have the correct symbol path to the +binaries for that version of the Runtime, the Windows Debugger will load the +correct mscordacwks.dll file. + +>> I have a chicken and egg problem. I want to use SOS commands, but the CLR + isn't loaded yet. What can I do? + +In the debugger at startup you can type: + + "sxe clrn" + +Let the program run, and it will stop with the notice + + "CLR notification: module 'mscorlib' loaded" + +At this time you can use SOS commands. To turn off spurious notifications, +type: + + "sxd clrn" + +>> I got the following error message. Now what? + + 0:000> .loadby sos clr + 0:000> !DumpStackObjects + Failed to find runtime DLL (clr.dll), 0x80004005 + Extension commands need clr.dll in order to have something to do. + 0:000> + +This means that the CLR is not loaded yet, or has been unloaded. You need to +wait until your managed program is running in order to use these commands. If +you have just started the program a good way to do this is to type + + bp clr!EEStartup "g @$ra" + +in the debugger, and let it run. After the function EEStartup is finished, +there will be a minimal managed environment for executing SOS commands. + +>> I have a partial memory minidump, and !DumpObj doesn't work. Why? + +In order to run SOS commands, many CLR data structures need to be traversed. +When creating a minidump without full memory, special functions are called at +dump creation time to bring those structures into the minidump, and allow a +minimum set of SOS debugging commands to work. At this time, those commands +that can provide full or partial output are: + +CLRStack +Threads +Help +PrintException +EEVersion + +For a minidump created with this minimal set of functionality in mind, you +will get an error message when running any other commands. A full memory dump +(obtained with ".dump /ma <filename>" in the Windows Debugger) is often the +best way to debug a managed program at this level. + +>> What other tools can I use to find my bug? + +Turn on Managed Debugging Assistants. These enable additional runtime diagnostics, +particularly in the area of PInvoke/Interop. Adam Nathan has written some great +information about that: + +http://blogs.msdn.com/adam_nathan/ + +>> Does SOS support DML? + +Yes. SOS respects the .prefer_dml option in the debugger. If this setting is +turned on, then SOS will output DML by default. Alternatively, you may leave +it off and add /D to the beginning of a command to get DML based output for it. +Not all SOS commands support DML output. + +\\ + +COMMAND: stoponexception. +!StopOnException [-derived] + [-create | -create2] + <Exception> + [<Pseudo-register number>] + +!StopOnException helps when you want the Windows Debugger to stop on a +particular managed exception, say a System.OutOfMemoryException, but continue +running if other exceptions are thrown. The command can be used in two ways: + +1) When you just want to stop on one particular CLR exception + + At the debugger prompt, anytime after loading SOS, type: + + !StopOnException -create System.OutOfMemoryException 1 + + The pseudo-register number (1) indicates that SOS can use register $t1 for + maintaining the breakpoint. The -create parameter allows SOS to go ahead + and set up the breakpoint as a first-chance exception. -create2 would set + it up as a 2nd-chance exception. + +2) When you need more complex logic for stopping on a CLR exception + + !StopOnException can be used purely as a predicate in a larger expression. + If you type: + + !StopOnException System.OutOfMemoryException 3 + + then register $t3 will be set to 1 if the last thrown exception on the + current thread is a System.OutOfMemoryException. Otherwise, $t3 will be set + to 0. Using the Windows Debugger scripting language, you could chain + such calls together to stop on various exception types. You'll have to + manually create such predicates, for example: + + sxe -c "!soe System.OutOfMemoryException 3; + !soe -derived System.IOException 4; + .if(@$t3==1 || @$t4==1) { .echo 'stop' } .else {g}" + +The -derived option will cause StopOnException to set the pseudo-register to +1 even if the thrown exception type doesn't exactly match the exception type +given, but merely derives from it. So, "-derived System.Exception" would catch +every exception in the System.Exception heirarchy. + +The pseudo-register number is optional. If you don't pass a number, SOS will +use pseudo-register $t1. + +Note that !PrintException with no parameters will print out the last thrown +exception on the current thread (if any). You can use !soe as a shortcut for +!StopOnException. +\\ + +COMMAND: minidumpmode. +!MinidumpMode <0 or 1> + +Minidumps created with ".dump /m" or ".dump" have a very small set of +CLR-specific data, just enough to run a subset of SOS commands correctly. You +are able to run other SOS commands, but they may fail with unexpected errors +because required areas of memory are not mapped in or only partially mapped +in. At this time, SOS cannot reliably detect if a dump file is of this type +(for one thing, custom dump commands can map in additional memory, but there +is no facility to read meta-information about this memory). You can turn this +option on to protect against running unsafe commands against small minidumps. + +By default, MinidumpMode is 0, so there is no restriction on commands that will +run against a minidump. +\\ + +COMMAND: dumpobj. +!DumpObj [-nofields] <object address> + +This command allows you to examine the fields of an object, as well as learn +important properties of the object such as the EEClass, the MethodTable, and +the size. + +You might find an object pointer by running !DumpStackObjects and choosing +from the resultant list. Here is a simple object: + + 0:000> !DumpObj a79d40 + Name: Customer + MethodTable: 009038ec + EEClass: 03ee1b84 + Size: 20(0x14) bytes + (C:\pub\unittest.exe) + Fields: + MT Field Offset Type VT Attr Value Name + 009038ec 4000008 4 Customer 0 instance 00a79ce4 name + 009038ec 4000009 8 Bank 0 instance 00a79d2c bank + +Note that fields of type Customer and Bank are themselves objects, and you can +run !DumpObj on them too. You could look at the field directly in memory using +the offset given. "dd a79d40+8 l1" would allow you to look at the bank field +directly. Be careful about using this to set memory breakpoints, since objects +can move around in the garbage collected heap. + +What else can you do with an object? You might run !GCRoot, to determine what +roots are keeping it alive. Or you can find all objects of that type with +"!DumpHeap -type Customer". + +The column VT contains the value 1 if the field is a valuetype structure, and +0 if the field contains a pointer to another object. For valuetypes, you can +take the MethodTable pointer in the MT column, and the Value and pass them to +the command !DumpVC. + +The abbreviation !do can be used for brevity. + +The arguments in detail: +-nofields: do not print fields of the object, useful for objects like + String +\\ + +COMMAND: dumparray. +!DumpArray + [-start <startIndex>] + [-length <length>] + [-details] + [-nofields] + <array object address> + +This command allows you to examine elements of an array object. +The arguments in detail: + -start <startIndex>: optional, only supported for single dimension array. + Specify from which index the command shows the elements. + -length <length>: optional, only supported for single dimension array. + Specify how many elements to show. + -details: optional. Ask the command to print out details + of the element using !DumpObj and !DumpVC format. + -nofields: optional, only takes effect when -details is used. Do + not print fields of the elements. Useful for arrays of + objects like String + + Example output: + + 0:000> !dumparray -start 2 -length 3 -details 00ad28d0 + Name: Value[] + MethodTable: 03e41044 + EEClass: 03e40fc0 + Size: 132(0x84) bytes + Array: Rank 1, Number of elements 10, Type VALUETYPE + Element Type: Value + [2] 00ad28f0 + Name: Value + MethodTable 03e40f4c + EEClass: 03ef1698 + Size: 20(0x14) bytes + (C:\bugs\225271\arraytest.exe) + Fields: + MT Field Offset Type Attr Value Name + 5b9a628c 4000001 0 System.Int32 instance 2 x + 5b9a628c 4000002 4 System.Int32 instance 4 y + 5b9a628c 4000003 8 System.Int32 instance 6 z + [3] 00ad28fc + Name: Value + MethodTable 03e40f4c + EEClass: 03ef1698 + Size: 20(0x14) bytes + (C:\bugs\225271\arraytest.exe) + Fields: + MT Field Offset Type Attr Value Name + 5b9a628c 4000001 0 System.Int32 instance 3 x + 5b9a628c 4000002 4 System.Int32 instance 6 y + 5b9a628c 4000003 8 System.Int32 instance 9 z + [4] 00ad2908 + Name: Value + MethodTable 03e40f4c + EEClass: 03ef1698 + Size: 20(0x14) bytes + (C:\bugs\225271\arraytest.exe) + Fields: + MT Field Offset Type Attr Value Name + 5b9a628c 4000001 0 System.Int32 instance 4 x + 5b9a628c 4000002 4 System.Int32 instance 8 y + 5b9a628c 4000003 8 System.Int32 instance 12 z + + +\\ + +COMMAND: dumpstackobjects. +!DumpStackObjects [-verify] [top stack [bottom stack]] + +This command will display any managed objects it finds within the bounds of +the current stack. Combined with the stack tracing commands like K and +!CLRStack, it is a good aid to determining the values of locals and +parameters. + +If you use the -verify option, each non-static CLASS field of an object +candidate is validated. This helps to eliminate false positives. It is not +on by default because very often in a debugging scenario, you are +interested in objects with invalid fields. + +The abbreviation !dso can be used for brevity. +\\ + +COMMAND: dumpheap. +!DumpHeap [-stat] + [-strings] + [-short] + [-min <size>] + [-max <size>] + [-live] + [-dead] + [-thinlock] + [-startAtLowerBound] + [-mt <MethodTable address>] + [-type <partial type name>] + [start [end]] + +!DumpHeap is a powerful command that traverses the garbage collected heap, +collection statistics about objects. With it's various options, it can look for +particular types, restrict to a range, or look for ThinLocks (see !SyncBlk +documentation). Finally, it will provide a warning if it detects excessive +fragmentation in the GC heap. + +When called without options, the output is first a list of objects in the heap, +followed by a report listing all the types found, their size and number: + + 0:000> !dumpheap + Address MT Size + 00a71000 0015cde8 12 Free + 00a7100c 0015cde8 12 Free + 00a71018 0015cde8 12 Free + 00a71024 5ba58328 68 + 00a71068 5ba58380 68 + 00a710ac 5ba58430 68 + 00a710f0 5ba5dba4 68 + ... + total 619 objects + Statistics: + MT Count TotalSize Class Name + 5ba7607c 1 12 System.Security.Permissions.HostProtectionResource + 5ba75d54 1 12 System.Security.Permissions.SecurityPermissionFlag + 5ba61f18 1 12 System.Collections.CaseInsensitiveComparer + ... + 0015cde8 6 10260 Free + 5ba57bf8 318 18136 System.String + ... + +"Free" objects are simply regions of space the garbage collector can use later. +If 30% or more of the heap contains "Free" objects, the process may suffer from +heap fragmentation. This is usually caused by pinning objects for a long time +combined with a high rate of allocation. Here is example output where !DumpHeap +provides a warning about fragmentation: + + <After the Statistics section> + Fragmented blocks larger than 1MB: + Addr Size Followed by + 00a780c0 1.5MB 00bec800 System.Byte[] + 00da4e38 1.2MB 00ed2c00 System.Byte[] + 00f16df0 1.2MB 01044338 System.Byte[] + +The arguments in detail: + +-stat Restrict the output to the statistical type summary +-strings Restrict the output to a statistical string value summary +-short Limits output to just the address of each object. This allows you + to easily pipe output from the command to another debugger + command for automation. +-min Ignore objects less than the size given in bytes +-max Ignore objects larger than the size given in bytes +-live Only print live objects +-dead Only print dead objects (objects which will be collected in the + next full GC) +-thinlock Report on any ThinLocks (an efficient locking scheme, see !SyncBlk + documentation for more info) +-startAtLowerBound + Force heap walk to begin at lower bound of a supplied address range. + (During plan phase, the heap is often not walkable because objects + are being moved. In this case, DumpHeap may report spurious errors, + in particular bad objects. It may be possible to traverse more of + the heap after the reported bad object. Even if you specify an + address range, !DumpHeap will start its walk from the beginning of + the heap by default. If it finds a bad object before the specified + range, it will stop before displaying the part of the heap in which + you are interested. This switch will force !DumpHeap to begin its + walk at the specified lower bound. You must supply the address of a + good object as the lower bound for this to work. Display memory at + the address of the bad object to manually find the next method + table (use !dumpmt to verify). If the GC is currently in a call to + memcopy, You may also be able to find the next object's address by + adding the size to the start address given as parameters.) +-mt List only those objects with the MethodTable given +-type List only those objects whose type name is a substring match of the + string provided. +start Begin listing from this address +end Stop listing at this address + +A special note about -type: Often, you'd like to find not only Strings, but +System.Object arrays that are constrained to contain Strings. ("new +String[100]" actually creates a System.Object array, but it can only hold +System.String object pointers). You can use -type in a special way to find +these arrays. Just pass "-type System.String[]" and those Object arrays will +be returned. More generally, "-type <Substring of interesting type>[]". + +The start/end parameters can be obtained from the output of !EEHeap -gc. For +example, if you only want to list objects in the large heap segment: + + 0:000> !eeheap -gc + Number of GC Heaps: 1 + generation 0 starts at 0x00c32754 + generation 1 starts at 0x00c32748 + generation 2 starts at 0x00a71000 + segment begin allocated size + 00a70000 00a71000 010443a8 005d33a8(6108072) + Large object heap starts at 0x01a71000 + segment begin allocated size + 01a70000 01a71000 01a75000 0x00004000(16384) + Total Size 0x5d73a8(6124456) + ------------------------------ + GC Heap Size 0x5d73a8(6124456) + + 0:000> !dumpheap 1a71000 1a75000 + Address MT Size + 01a71000 5ba88bd8 2064 + 01a71810 0019fe48 2032 Free + 01a72000 5ba88bd8 4096 + 01a73000 0019fe48 4096 Free + 01a74000 5ba88bd8 4096 + total 5 objects + Statistics: + MT Count TotalSize Class Name + 0019fe48 2 6128 Free + 5ba88bd8 3 10256 System.Object[] + Total 5 objects + +Finally, if GC heap corruption is present, you may see an error like this: + + 0:000> !dumpheap -stat + object 00a73d24: does not have valid MT + curr_object : 00a73d24 + Last good object: 00a73d14 + ---------------- + +That indicates a serious problem. See the help for !VerifyHeap for more +information on diagnosing the cause. +\\ + +COMMAND: dumpvc. +!DumpVC <MethodTable address> <Address> + +!DumpVC allows you to examine the fields of a value class. In C#, this is a +struct, and lives on the stack or within an Object on the GC heap. You need +to know the MethodTable address to tell SOS how to interpret the fields, as +a value class is not a first-class object with it's own MethodTable as the +first field. For example: + + 0:000> !DumpObj a79d98 + Name: Mainy + MethodTable: 009032d8 + EEClass: 03ee1424 + Size: 28(0x1c) bytes + (C:\pub\unittest.exe) + Fields: + MT Field Offset Type Attr Value Name + 0090320c 4000010 4 VALUETYPE instance 00a79d9c m_valuetype + 009032d8 400000f 4 CLASS static 00a79d54 m_sExcep + +m_valuetype is a value type. The value in the MT column (0090320c) is the +MethodTable for it, and the Value column provides the start address: + + 0:000> !DumpVC 0090320c 00a79d9c + Name: Funny + MethodTable 0090320c + EEClass: 03ee14b8 + Size: 28(0x1c) bytes + (C:\pub\unittest.exe) + Fields: + MT Field Offset Type Attr Value Name + 0090320c 4000001 0 CLASS instance 00a743d8 signature + 0090320c 4000002 8 System.Int32 instance 2345 m1 + 0090320c 4000003 10 System.Boolean instance 1 b1 + 0090320c 4000004 c System.Int32 instance 1234 m2 + 0090320c 4000005 4 CLASS instance 00a79d98 backpointer + +!DumpVC is quite a specialized function. Some managed programs make heavy use +of value classes, while others do not. +\\ + +COMMAND: gcroot. +!GCRoot [-nostacks] <Object address> + +!GCRoot looks for references (or roots) to an object. These can exist in four +places: + + 1. On the stack + 2. Within a GC Handle + 3. In an object ready for finalization + 4. As a member of an object found in 1, 2 or 3 above. + +First, all stacks will be searched for roots, then handle tables, and finally +the freachable queue of the finalizer. Some caution about the stack roots: +!GCRoot doesn't attempt to determine if a stack root it encountered is valid +or is old (discarded) data. You would have to use !CLRStack and !U to +disassemble the frame that the local or argument value belongs to in order to +determine if it is still in use. + +Because people often want to restrict the search to gc handles and freachable +objects, there is a -nostacks option. +\\ + +COMMAND: objsize. +!ObjSize [<Object address>] + +With no parameters, !ObjSize lists the size of all objects found on managed +threads. It also enumerates all GCHandles in the process, and totals the size +of any objects pointed to by those handles. In calculating object size, +!ObjSize includes the size of all child objects in addition to the parent. + +For example, !DumpObj lists a size of 20 bytes for this Customer object: + + 0:000> !do a79d40 + Name: Customer + MethodTable: 009038ec + EEClass: 03ee1b84 + Size: 20(0x14) bytes + (C:\pub\unittest.exe) + Fields: + MT Field Offset Type Attr Value Name + 009038ec 4000008 4 CLASS instance 00a79ce4 name + 009038ec 4000009 8 CLASS instance 00a79d2c bank + 009038ec 400000a c System.Boolean instance 1 valid + +but !ObjSize lists 152 bytes: + + 0:000> !ObjSize a79d40 + sizeof(00a79d40) = 152 ( 0x98) bytes (Customer) + +This is because a Customer points to a Bank, has a name, and the Bank points to +an Address string. You can use !ObjSize to identify any particularly large +objects, such as a managed cache in a web server. + +While running ObjSize with no arguments may point to specific roots that hold +onto large amounts of memory it does not provide information regarding the +amount of managed memory that is still alive. This is due to the fact that a +number of roots can share a common subgraph, and that part will be reported in +the size of all the roots that reference the subgraph. + +Please note the -aggregate parameter to !ObjSize has been removed. Please see +'!DumpHeap -live' and '!DumpHeap -dead' for that functionality. + +\\ + +COMMAND: finalizequeue. +!FinalizeQueue [-detail] | [-allReady] [-short] + +This command lists the objects registered for finalization. Here is output from +a simple program: + + 0:000> !finalizequeue + SyncBlocks to be cleaned up: 0 + MTA Interfaces to be released: 0 + STA Interfaces to be released: 1 + generation 0 has 4 finalizable objects (0015bc90->0015bca0) + generation 1 has 0 finalizable objects (0015bc90->0015bc90) + generation 2 has 0 finalizable objects (0015bc90->0015bc90) + Ready for finalization 0 objects (0015bca0->0015bca0) + Statistics: + MT Count TotalSize Class Name + 5ba6cf78 1 24 Microsoft.Win32.SafeHandles.SafeFileHandle + 5ba5db04 1 68 System.Threading.Thread + 5ba73e28 2 112 System.IO.StreamWriter + Total 4 objects + +The GC heap is divided into generations, and objects are listed accordingly. We +see that only generation 0 (the youngest generation) has any objects registered +for finalization. The notation "(0015bc90->0015bca0)" means that if you look at +memory in that range, you'll see the object pointers that are registered: + +0:000> dd 15bc90 15bca0-4 +0015bc90 00a743f4 00a79f00 00a7b3d8 00a7b47c + +You could run !DumpObj on any of those pointers to learn more. In this example, +there are no objects ready for finalization, presumably because they still have +roots (You can use !GCRoot to find out). The statistics section provides a +higher-level summary of the objects registered for finalization. Note that +objects ready for finalization are also included in the statistics (if any). + +Specifying -short will inhibit any display related to SyncBlocks or RCWs. + +The arguments in detail: + +-allReady Specifying this argument will allow for the display of all objects + that are ready for finalization, whether they are already marked by + the GC as such, or whether the next GC will. The objects that are + not in the "Ready for finalization" list are finalizable objects that + are no longer rooted. This option can be very expensive, as it + verifies whether all the objects in the finalizable queues are still + rooted or not. +-short Limits the output to just the address of each object. If used in + conjunction with -allReady it enumerates all objects that have a + finalizer that are no longer rooted. If used independently it lists + all objects in the finalizable and "ready for finalization" queues. +-detail Will display extra information on any SyncBlocks that need to be + cleaned up, and on any RuntimeCallableWrappers (RCWs) that await + cleanup. Both of these data structures are cached and cleaned up by + the finalizer thread when it gets a chance to run. +\\ + +COMMAND: printexception. +!PrintException [-nested] [-lines] [-ccw] [<Exception object address>] [<CCW pointer>] + +This will format fields of any object derived from System.Exception. One of the +more useful aspects is that it will format the _stackTrace field, which is a +binary array. If _stackTraceString field is not filled in, that can be helpful +for debugging. You can of course use !DumpObj on the same exception object to +explore more fields. + +If called with no parameters, PrintException will look for the last outstanding +exception on the current thread and print it. This will be the same exception +that shows up in a run of !Threads. + +!PrintException will notify you if there are any nested exceptions on the +current managed thread. (A nested exception occurs when you throw another +exception within a catch handler already being called for another exception). +If there are nested exceptions, you can re-run !PrintException with the +"-nested" option to get full details on the nested exception objects. The +!Threads command will also tell you which threads have nested exceptions. + +!PrintException can display source information if available, by specifying the +-lines command line argument. + +!PrintException prints the exception object corresponding to a given CCW pointer, +which can be specified using the -ccw option. + +The abbreviation !pe can be used for brevity. +\\ + +COMMAND: traverseheap. +!TraverseHeap [-xml] [-verify] <filename> + +!TraverseHeap writes out a file in a format understood by the CLR Profiler. +You can download the CLR Profiler from this link: + +http://www.microsoft.com/downloads/details.aspx?FamilyId=86CE6052-D7F4-4AEB- +9B7A-94635BEEBDDA&displaylang=en + +It creates a graphical display of the GC heap to help you analyze the state of +your application. + +If you pass the -verify option it will do more sanity checking of the heap +as it dumps it. Use this option if heap corruption is suspected. + +If you pass the "-xml" flag, the file is instead written out in an easy to +understand xml format: + + <gcheap> + <types> + <type id="1" name="System.String"> + ... + </types> + <roots> + <root kind="handle" address="0x00a73ff0"/> + <root kind="stack" address="0x0069f0e0"/> + ... + </roots> + <objects> + <object address="0x00b73030" typeid="1" size="300"/> + <object address="0x00b75054" typeid="5" size="20"> + <member address="0x00b75088" /> + ... + </object> + ... + </objects> + </gcheap> + +You can break into your process, load SOS, take a snapshot of your heap with +this function, then continue. +\\ +COMMAND: threadstate. +!ThreadState value + +The !Threads command outputs, among other things, the state of the thread. +This is a bit field which corresponds to various states the thread is in. +To check the state of the thread, simply pass that bit field from the +output of !Threads into !ThreadState. + +Example: + 0:003> !Threads + ThreadCount: 2 + UnstartedThread: 0 + BackgroundThread: 1 + PendingThread: 0 + DeadThread: 0 + Hosted Runtime: no + PreEmptive GC Alloc Lock + ID OSID ThreadOBJ State GC Context Domain Count APT Exception + 0 1 250 0019b068 a020 Disabled 02349668:02349fe8 0015def0 0 MTA + 2 2 944 001a6020 b220 Enabled 00000000:00000000 0015def0 0 MTA (Finalizer) + 0:003> !ThreadState b220 + Legal to Join + Background + CLR Owns + CoInitialized + In Multi Threaded Apartment + +Possible thread states: + Thread Abort Requested + GC Suspend Pending + User Suspend Pending + Debug Suspend Pending + GC On Transitions + Legal to Join + Yield Requested + Hijacked by the GC + Blocking GC for Stack Overflow + Background + Unstarted + Dead + CLR Owns + CoInitialized + In Single Threaded Apartment + In Multi Threaded Apartment + Reported Dead + Fully initialized + Task Reset + Sync Suspended + Debug Will Sync + Stack Crawl Needed + Suspend Unstarted + Aborted + Thread Pool Worker Thread + Interruptible + Interrupted + Completion Port Thread + Abort Initiated + Finalized + Failed to Start + Detached +\\ +COMMAND: threads. +!Threads [-live] [-special] + +!Threads lists all the mananaged threads in the process. + +-live: optional. Only print threads associated with a live thread. +-special: optional. With this switch, the command will display all the special + threads created by CLR. Those threads might not be managed threads + so they might not be shown in the first part of the command's + output. Example of special threads include: GC threads (in + concurrent GC and server GC), Debugger helper threads, Finalizer + threads, AppDomain Unload threads, and Threadpool timer threads. + +Each thread has many attributes, many of which can be ignored. The important +ones are discussed below: + +There are three ID columns: + +1) The debugger shorthand ID (When the runtime is hosted this column might + display the special string "<<<<" when this internal thread object is not + associated with any physical thread - this may happen when the host reuses + the runtime internal thread object) +2) The CLR Thread ID +3) The OS thread ID. + +If PreEmptiveGC is enabled for a thread, then a garbage collection +can occur while that thread is running. For example, if you break in while +a managed thread is making a PInvoke call to a Win32 function, that thread +will be in PreEmptive GC mode. + +The Domain column indicates what AppDomain the thread is currently executing +in. You can pass this value to !DumpDomain to find out more. + +The APT column gives the COM apartment mode. + +Exception will list the last thrown exception (if any) for the thread. More +details can be obtained by passing the pointer value to !PrintException. If +you get the notation "(nested exceptions)", you can get details on those +exceptions by switching to the thread in question, and running +"!PrintException -nested". +\\ + +COMMAND: clrstack. +!CLRStack [-a] [-l] [-p] [-n] [-f] +!CLRStack [-a] [-l] [-p] [-i] [variable name] [frame] + +CLRStack attempts to provide a true stack trace for managed code only. It is +handy for clean, simple traces when debugging straightforward managed +programs. The -p parameter will show arguments to the managed function. The +-l parameter can be used to show information on local variables in a frame. +SOS can't retrieve local names at this time, so the output for locals is in +the format <local address> = <value>. The -a (all) parameter is a short-cut +for -l and -p combined. + +The -f option (full mode) displays the native frames intermixing them with +the managed frames and the assembly name and function offset for the managed +frames. + +If the debugger has the option SYMOPT_LOAD_LINES specified (either by the +.lines or .symopt commands), SOS will look up the symbols for every managed +frame and if successful will display the corresponding source file name and +line number. The -n (No line numbers) parameter can be specified to disable +this behavior. + +When you see methods with the name "[Frame:...", that indicates a transition +between managed and unmanaged code. You could run !IP2MD on the return +addresses in the call stack to get more information on each managed method. + +On x64 platforms, Transition Frames are not displayed at this time. To avoid +heavy optimization of parameters and locals one can request the JIT compiler +to not optimize functions in the managed app by creating a file myapp.ini +(if your program is myapp.exe) in the same directory. Put the following lines +in myapp.ini and re-run: + +[.NET Framework Debugging Control] +GenerateTrackingInfo=1 +AllowOptimize=0 + +The -i option is a new EXPERIMENTAL addition to CLRStack and will use the ICorDebug +interfaces to display the managed stack and variables. With this option you can also +view and expand arrays and fields for managed variables. If a stack frame number is +specified in the command line, CLRStack will show you the parameters and/or locals +only for that frame (provided you specify -l or -p or -a of course). If a variable +name and a stack frame number are specified in the command line, CLRStack will show +you the parameters and/or locals for that frame, and will also show you the fields +for that variable name you specified. Here are some examples: + !CLRStack -i -a : This will show you all parameters and locals for all frames + !CLRStack -i -a 3 : This will show you all parameters and locals, for frame 3 + !CLRStack -i var1 0 : This will show you the fields of 'var1' for frame 0 + !CLRStack -i var1.abc 2 : This will show you the fields of 'var1', and expand + 'var1.abc' to show you the fields of the 'abc' field, + for frame 2. + !CLRStack -i var1.[basetype] 0 : This will show you the fields of 'var1', and + expand the base type of 'var1' to show you its + fields. + !CLRStack -i var1.[6] 0 : If 'var1' is an array, this will show you the element + at index 6 in the array, along with its fields +The -i options uses DML output for a better debugging experience, so typically you +should only need to execute "!CLRStack -i", and from there, click on the DML +hyperlinks to inspect the different managed stack frames and managed variables. +\\ + +COMMAND: ip2md. +!IP2MD <Code address> + +Given an address in managed JITTED code, IP2MD attempts to find the MethodDesc +associated with it. For example, this output from K: + + 0:000> K + ChildEBP RetAddr + 00a79c78 03ef02ab image00400000!Mainy.Top()+0xb + 00a79c78 03ef01a6 image00400000!Mainy.Level(Int32)+0xb + 00a79c78 5d3725a1 image00400000!Mainy.Main()+0xee + 0012ea04 5d512f59 clr!CallDescrWorkerInternal+0x30 + 0012ee34 5d7946aa clr!CallDescrWorker+0x109 + + 0:000> !IP2MD 03ef01a6 + MethodDesc: 00902f40 + Method Name: Mainy.Main() + Class: 03ee1424 + MethodTable: 009032d8 + mdToken: 0600000d + Module: 001caa38 + IsJitted: yes + CodeAddr: 03ef00b8 + Transparency: Critical + Source file: c:\Code\prj.mini\exc.cs @ 39 + +We have taken a return address into Mainy.Main, and discovered information +about that method. You could run !U, !DumpMT, !DumpClass, !DumpMD, or +!DumpModule on the fields listed to learn more. + +The "Source line" output will only be present if the debugger can find the +symbols for the managed module containing the given <code address>, and if the +debugger is configured to load line number information. +\\ + +COMMAND: u. +!U [-gcinfo] [-ehinfo] [-n] [-o] <MethodDesc address> | <Code address> + +Presents an annotated disassembly of a managed method when given a MethodDesc +pointer for the method, or a code address within the method body. Unlike the +debugger "U" function, the entire method from start to finish is printed, +with annotations that convert metadata tokens to names. + + <example output> + ... + 03ef015d b901000000 mov ecx,0x1 + 03ef0162 ff156477a25b call dword ptr [mscorlib_dll+0x3c7764 (5ba27764)] (System.Console.InitializeStdOutError(Boolean), mdToken: 06000713) + 03ef0168 a17c20a701 mov eax,[01a7207c] (Object: SyncTextWriter) + 03ef016d 89442414 mov [esp+0x14],eax + +If you pass the -gcinfo flag, you'll get inline display of the GCInfo for +the method. You can also obtain this information with the !GCInfo command. + +If you pass the -ehinfo flag, you'll get inline display of exception info +for the method. (Beginning and end of try/finally/catch handlers, etc.). +You can also obtain this information with the !EHInfo command. + +If you pass the -o flag, the byte offset of each instruction from the +beginning of the method will be printed in addition to the absolute address of +the instruction. + +If the debugger has the option SYMOPT_LOAD_LINES specified (either by the +.lines or .symopt commands), and if symbols are available for the managed +module containing the method being examined, the output of the command will +include the source file name and line number corresponding to the +disassembly. The -n (No line numbers) flag can be specified to disable this +behavior. + + <example output> + ... + c:\Code\prj.mini\exc.cs @ 38: + 001b00b0 8b0d3020ab03 mov ecx,dword ptr ds:[3AB2030h] ("Break in debugger. When done type <Enter> to continue: ") + 001b00b6 e8d5355951 call mscorlib_ni+0x8b3690 (51743690) (System.Console.Write(System.String), mdToken: 0600091b) + 001b00bb 90 nop + + c:\Code\prj.mini\exc.cs @ 39: + 001b00bc e863cdc651 call mscorlib_ni+0xf8ce24 (51e1ce24) (System.Console.ReadLine(), mdToken: 060008f6) + >>> 001b00c1 90 nop + ... +\\ + +COMMAND: dumpstack. +!DumpStack [-EE] [-n] [top stack [bottom stack]] + +[x86 and x64 documentation] + +This command provides a verbose stack trace obtained by "scraping." Therefore +the output is very noisy and potentially confusing. The command is good for +viewing the complete call stack when "kb" gets confused. For best results, +make sure you have valid symbols. + +-EE will only show managed functions. + +If the debugger has the option SYMOPT_LOAD_LINES specified (either by the +.lines or .symopt commands), SOS will look up the symbols for every managed +frame and if successful will display the corresponding source file name and +line number. The -n (No line numbers) parameter can be specified to disable +this behavior. + +You can also pass a stack range to limit the output. Use the debugger +extension !teb to get the top and bottom stack values. + +\\ + +COMMAND: eestack. +!EEStack [-short] [-EE] + +This command runs !DumpStack on all threads in the process. The -EE option is +passed directly to !DumpStack. The -short option tries to narrow down the +output to "interesting" threads only, which is defined by + +1) The thread has taken a lock. +2) The thread has been "hijacked" in order to allow a garbage collection. +3) The thread is currently in managed code. + +See the documentation for !DumpStack for more info. +\\ + +COMMAND: ehinfo. +!EHInfo (<MethodDesc address> | <Code address>) + +!EHInfo shows the exception handling blocks in a jitted method. For each +handler, it shows the type, including code addresses and offsets for the clause +block and the handler block. For a TYPED handler, this would be the "try" and +"catch" blocks respectively. + +Sample output: + + 0:000> !ehinfo 33bbd3a + MethodDesc: 03310f68 + Method Name: MainClass.Main() + Class: 03571358 + MethodTable: 0331121c + mdToken: 0600000b + Module: 001e2fd8 + IsJitted: yes + CodeAddr: 033bbca0 + Transparency: Critical + + EHHandler 0: TYPED catch(System.IO.FileNotFoundException) + Clause: [033bbd2b, 033bbd3c] [8b, 9c] + Handler: [033bbd3c, 033bbd50] [9c, b0] + + EHHandler 1: FINALLY + Clause: [033bbd83, 033bbda3] [e3, 103] + Handler: [033bbda3, 033bbdc5] [103, 125] + + EHHandler 2: TYPED catch(System.Exception) + Clause: [033bbd7a, 033bbdc5] [da, 125] + Handler: [033bbdc5, 033bbdd6] [125, 136] + +\\ + +COMMAND: gcinfo. +!GCInfo (<MethodDesc address> | <Code address>) + +!GCInfo is especially useful for CLR Devs who are trying to determine if there +is a bug in the JIT Compiler. It parses the GCEncoding for a method, which is a +compressed stream of data indicating when registers or stack locations contain +managed objects. It is important to keep track of this information, because if +a garbage collection occurs, the collector needs to know where roots are so it +can update them with new object pointer values. + +Here is sample output where you can see the change in register state. Normally +you would print this output out and read it alongside a disassembly of the +method. For example, the notation "reg EDI becoming live" at offset 0x11 of the +method might correspond to a "mov edi,ecx" statement. + + 0:000> !gcinfo 5b68dbb8 (5b68dbb8 is the start of a JITTED method) + entry point 5b68dbb8 + preJIT generated code + GC info 5b9f2f09 + Method info block: + method size = 0036 + prolog size = 19 + epilog size = 8 + epilog count = 1 + epilog end = yes + saved reg. mask = 000B + ebp frame = yes + fully interruptible=yes + double align = no + security check = no + exception handlers = no + local alloc = no + edit & continue = no + varargs = no + argument count = 4 + stack frame size = 1 + untracked count = 5 + var ptr tab count = 0 + epilog at 002E + 36 D4 8C C7 AA | + 93 F3 40 05 | + + Pointer table: + 14 | [EBP+14H] an untracked local + 10 | [EBP+10H] an untracked local + 0C | [EBP+0CH] an untracked local + 08 | [EBP+08H] an untracked local + 44 | [EBP-04H] an untracked local + F1 79 | 0011 reg EDI becoming live + 72 | 0013 reg ESI becoming live + 83 | 0016 push ptr 0 + 8B | 0019 push ptr 1 + 93 | 001C push ptr 2 + 9B | 001F push ptr 3 + 56 | 0025 reg EDX becoming live + 4A | 0027 reg ECX becoming live + 0E | 002D reg ECX becoming dead + 10 | 002D reg EDX becoming dead + E0 | 002D pop 4 ptrs + F0 31 | 0036 reg ESI becoming dead + 38 | 0036 reg EDI becoming dead + FF | + +This function is important for CLR Devs, but very difficult for anyone else to +make sense of it. You would usually come to use it if you suspect a gc heap +corruption bug caused by invalid GCEncoding for a particular method. +\\ + +COMMAND: comstate. +!COMState + +!COMState lists the com apartment model for each thread, as well as a Context +pointer if provided. +\\ + +COMMAND: bpmd. +!BPMD [-nofuturemodule] <module name> <method name> [<il offset>] +!BPMD <source file name>:<line number> +!BPMD -md <MethodDesc> +!BPMD -list +!BPMD -clear <pending breakpoint number> +!BPMD -clearall + +!BPMD provides managed breakpoint support. If it can resolve the method name +to a loaded, jitted or ngen'd function it will create a breakpoint with "bp". +If not then either the module that contains the method hasn't been loaded yet +or the module is loaded, but the function is not jitted yet. In these cases, +!bpmd asks the Windows Debugger to receive CLR Notifications, and waits to +receive news of module loads and JITs, at which time it will try to resolve +the function to a breakpoint. -nofuturemodule can be used to suppress +creating a breakpoint against a module that has not yet been loaded. + +Management of the list of pending breakpoints can be done via !BPMD -list, +!BPMD -clear, and !BPMD -clearall commands. !BPMD -list generates a list of +all of the pending breakpoints. If the pending breakpoint has a non-zero +module id, then that pending breakpoint is specific to function in that +particular loaded module. If the pending breakpoint has a zero module id, then +the breakpoint applies to modules that have not yet been loaded. Use +!BPMD -clear or !BPMD -clearall to remove pending breakpoints from the list. + +This brings up a good question: "I want to set a breakpoint on the main +method of my application. How can I do this?" + + 1) If you know the full path to SOS, use this command and skip to step 6 + .load <the full path to sos.dll> + + 2) If you don't know the full path to sos, its usually next to clr.dll + You can wait for clr to load and then find it. + Start the debugger and type: + sxe -c "" clrn + 3) g + 4) You'll get the following notification from the debugger: + "CLR notification: module 'mscorlib' loaded" + 5) Now you can load SOS. Type + .loadby sos clr + + 6) Add the breakpoint with command such as: + !bpmd myapp.exe MyApp.Main + 7) g + 8) You will stop at the start of MyApp.Main. If you type "bl" you will + see the breakpoint listed. + +You can specify breakpoints by file and line number if: + a) You have some version of .Net Framework installed on your machine. Any OS from + Vista onwards should have .Net Framework installed by default. + b) You have PDBs for the managed modules that need breakpoints, and your symbol + path points to those PDBs. +This is often easier than module and method name syntax. For example: + !bpmd Demo.cs:15 + + +To correctly specify explicitly implemented methods make sure to retrieve the +method name from the metadata, or from the output of the "!dumpmt -md" command. +For example: + + public interface I1 + { + void M1(); + } + public class ExplicitItfImpl : I1 + { + ... + void I1.M1() // this method's name is 'I1.M1' + { ... } + } + + !bpmd myapp.exe ExplicitItfImpl.I1.M1 + + +!BPMD works equally well with generic types. Adding a breakpoint on a generic +type sets breakpoints on all already JIT-ted generic methods and sets a pending +breakpoint for any instantiation that will be JIT-ted in the future. + +Example for generics: + Given the following two classes: + + class G3<T1, T2, T3> + { + ... + public void F(T1 p1, T2 p2, T3 p3) + { ... } + } + + public class G1<T> { + // static method + static public void G<W>(W w) + { ... } + } + + One would issue the following commands to set breapoints on G3.F() and + G1.G(): + + !bpmd myapp.exe G3`3.F + !bpmd myapp.exe G1`1.G + +And for explicitly implemented methods on generic interfaces: + public interface IT1<T> + { + void M1(T t); + } + + public class ExplicitItfImpl<U> : IT1<U> + { + ... + void IT1<U>.M1(U u) // this method's name is 'IT1<U>.M1' + { ... } + } + + !bpmd bpmd.exe ExplicitItfImpl`1.IT1<U>.M1 + +Additional examples: + If IT1 and ExplicitItfImpl are types declared inside another class, + Outer, the bpmd command would become: + + !bpmd bpmd.exe Outer+ExplicitItfImpl`1.Outer.IT1<U>.M1 + + (note that the fully qualified type name for ExplicitItfImpl became + Outer+ExplicitItfImpl, using the '+' separator, while the method name + is Outer.IT1<U>.M1, using a '.' as the separator) + + Furthermore, if the Outer class resides in a namespace, NS, the bpmd + command to use becomes: + + !bpmd bpmd.exe NS.Outer+ExplicitItfImpl`1.NS.Outer.IT1<U>.M1 + +!BPMD does not accept offsets nor parameters in the method name. You can add +an IL offset as an optional parameter seperate from the name. If there are overloaded +methods, !bpmd will set a breakpoint for all of them. + +In the case of hosted environments such as SQL, the module name may be +complex, like 'price, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. +For this case, just be sure to surround the module name with single quotes, +like: + +!bpmd 'price, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' Price.M2 + +\\ + +COMMAND: dumpdomain. +!DumpDomain [<Domain address>] + +When called with no parameters, !DumpDomain will list all the AppDomains in the +process. It enumerates each Assembly loaded into those AppDomains as well. +In addition to your application domain, and any domains it might create, there +are two special domains: the Shared Domain and the System Domain. + +Any Assembly pointer in the output can be passed to !DumpAssembly. Any Module +pointer in the output can be passed to !DumpModule. Any AppDomain pointer can +be passed to !DumpDomain to limit output only to that AppDomain. Other +functions provide an AppDomain pointer as well, such as !Threads where it lists +the current AppDomain for each thread. +\\ + +COMMAND: eeheap. +!EEHeap [-gc] [-loader] + +!EEHeap enumerates process memory consumed by internal CLR data structures. You +can limit the output by passing "-gc" or "-loader". All information will be +displayed otherwise. + +The information for the Garbage Collector lists the ranges of each Segment in +the managed heap. This can be useful if you believe you have an object pointer. +If the pointer falls within a segment range given by "!EEHeap -gc", then you do +have an object pointer, and can attempt to run "!DumpObj" on it. + +Here is output for a simple program: + + 0:000> !eeheap -gc + Number of GC Heaps: 1 + generation 0 starts at 0x00a71018 + generation 1 starts at 0x00a7100c + generation 2 starts at 0x00a71000 + segment begin allocated size + 00a70000 00a71000 00a7e01c 0000d01c(53276) + Large object heap starts at 0x01a71000 + segment begin allocated size + 01a70000 01a71000 01a76000 0x00005000(20480) + Total Size 0x1201c(73756) + ------------------------------ + GC Heap Size 0x1201c(73756) + +So the total size of the GC Heap is only 72K. On a large web server, with +multiple processors, you can expect to see a GC Heap of 400MB or more. The +Garbage Collector attempts to collect and reclaim memory only when required to +by memory pressure for better performance. You can also see the notion of +"generations," wherein the youngest objects live in generation 0, and +long-lived objects eventually get "promoted" to generation 2. + +The loader output lists various private heaps associated with AppDomains. It +also lists heaps associated with the JIT compiler, and heaps associated with +Modules. For example: + + 0:000> !EEHeap -loader + Loader Heap: + -------------------------------------- + System Domain: 5e0662a0 + LowFrequencyHeap:008f0000(00002000:00001000) Size: 0x00001000 bytes. + HighFrequencyHeap:008f2000(00008000:00001000) Size: 0x00001000 bytes. + StubHeap:008fa000(00002000:00001000) Size: 0x00001000 bytes. + Total size: 0x3000(12288)bytes + -------------------------------------- + Shared Domain: 5e066970 + LowFrequencyHeap:00920000(00002000:00001000) 03e30000(00010000:00003000) Size: 0x00004000 bytes. + Wasted: 0x00001000 bytes. + HighFrequencyHeap:00922000(00008000:00001000) Size: 0x00001000 bytes. + StubHeap:0092a000(00002000:00001000) Size: 0x00001000 bytes. + Total size: 0x6000(24576)bytes + -------------------------------------- + Domain 1: 14f000 + LowFrequencyHeap:00900000(00002000:00001000) 03ee0000(00010000:00003000) Size: 0x00004000 bytes. + Wasted: 0x00001000 bytes. + HighFrequencyHeap:00902000(00008000:00003000) Size: 0x00003000 bytes. + StubHeap:0090a000(00002000:00001000) Size: 0x00001000 bytes. + Total size: 0x8000(32768)bytes + -------------------------------------- + Jit code heap: + Normal JIT:03ef0000(00010000:00002000) Size: 0x00002000 bytes. + Total size: 0x2000(8192)bytes + -------------------------------------- + Module Thunk heaps: + Module 5ba22410: Size: 0x00000000 bytes. + Module 001c1320: Size: 0x00000000 bytes. + Module 001c03f0: Size: 0x00000000 bytes. + Module 001caa38: Size: 0x00000000 bytes. + Total size: 0x0(0)bytes + -------------------------------------- + Module Lookup Table heaps: + Module 5ba22410:Size: 0x00000000 bytes. + Module 001c1320:Size: 0x00000000 bytes. + Module 001c03f0:Size: 0x00000000 bytes. + Module 001caa38:03ec0000(00010000:00002000) Size: 0x00002000 bytes. + Total size: 0x2000(8192)bytes + -------------------------------------- + Total LoaderHeap size: 0x15000(86016)bytes + ======================================= + +By using !EEHeap to keep track of the growth of these private heaps, we are +able to rule out or include them as a source of a memory leak. +\\ + +COMMAND: name2ee. +!Name2EE <module name> <type or method name> +!Name2EE <module name>!<type or method name> + +This function allows you to turn a class name into a MethodTable and EEClass. +It turns a method name into a MethodDesc. Here is an example for a method: + + 0:000> !name2ee unittest.exe MainClass.Main + Module: 001caa38 + Token: 0x0600000d + MethodDesc: 00902f40 + Name: MainClass.Main() + JITTED Code Address: 03ef00b8 + +and for a class: + + 0:000> !name2ee unittest!MainClass + Module: 001caa38 + Token: 0x02000005 + MethodTable: 009032d8 + EEClass: 03ee1424 + Name: MainClass + +The module you are "browsing" with Name2EE needs to be loaded in the process. +To get a type name exactly right, first browse the module with ILDASM. You +can also pass * as the <module name> to search all loaded managed modules. +<module name> can also be the debugger's name for a module, such as +mscorlib or image00400000. + +The Windows Debugger syntax of <module>!<type> is also supported. You can +use an asterisk on the left of the !, but the type on the right side needs +to be fully qualified. + +If you are looking for a way to display a static field of a class (and you +don't have an instance of the class, so !dumpobj won't help you), note that +once you have the EEClass, you can run !DumpClass, which will display the +value of all static fields. + +There is yet one more way to specify a module name. In the case of modules +loaded from an assembly store (such as a SQL db) rather than disk, the +module name will look like this: + +price, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + +For this kind of module, simply use price as the module name: + + 0:044> !name2ee price Price + Module: 10f028b0 (price, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null) + Token: 0x02000002 + MethodTable: 11a47ae0 + EEClass: 11a538c8 + Name: Price + +Where are we getting these module names from? Run !DumpDomain to see a list of +all loaded modules in all domains. And remember that you can browse all the +types in a module with !DumpModule -mt <module pointer>. +\\ + +COMMAND: syncblk. +!SyncBlk [-all | <syncblk number>] + +A SyncBlock is a holder for extra information that doesn't need to be created +for every object. It can hold COM Interop data, HashCodes, and locking +information for thread-safe operations. + +When called without arguments, !SyncBlk will print the list of SyncBlocks +corresponding to objects that are owned by a thread. For example, a + + lock(MyObject) + { + .... + } + +statement will set MyObject to be owned by the current thread. A SyncBlock will +be created for MyObject, and the thread ownership information stored there +(this is an oversimplification, see NOTE below). If another thread tries to +execute the same code, they won't be able to enter the block until the first +thread exits. + +This makes !SyncBlk useful for detecting managed deadlocks. Consider that the +following code is executed by Threads A & B: + + Resource r1 = new Resource(); + Resource r2 = new Resource(); + + ... + + lock(r1) lock(r2) + { { + lock(r2) lock(r1) + { { + ... ... + } } + } } + +This is a deadlock situation, as Thread A could take r1, and Thread B r2, +leaving both threads with no option but to wait forever in the second lock +statement. !SyncBlk will detect this with the following output: + + 0:003> !syncblk + Index SyncBlock MonitorHeld Recursion Owning Thread Info SyncBlock Owner + 238 001e40ec 3 1 001e4e60 e04 3 00a7a194 Resource + 239 001e4124 3 1 001e5980 ab8 4 00a7a1a4 Resource + +It means that Thread e04 owns object 00a7a194, and Thread ab8 owns object +00a7a1a4. Combine that information with the call stacks of the deadlock: + +(threads 3 and 4 have similar output) + 0:003> k + ChildEBP RetAddr + 0404ea04 77f5c524 SharedUserData!SystemCallStub+0x4 + 0404ea08 77e75ee0 ntdll!NtWaitForMultipleObjects+0xc + 0404eaa4 5d9de9d6 KERNEL32!WaitForMultipleObjectsEx+0x12c + 0404eb38 5d9def80 clr!Thread::DoAppropriateAptStateWait+0x156 + 0404ecc4 5d9dd8bb clr!Thread::DoAppropriateWaitWorker+0x360 + 0404ed20 5da628dd clr!Thread::DoAppropriateWait+0xbb + 0404ede4 5da4e2e2 clr!CLREvent::Wait+0x29d + 0404ee70 5da4dd41 clr!AwareLock::EnterEpilog+0x132 + 0404ef34 5da4efa3 clr!AwareLock::Enter+0x2c1 + 0404f09c 5d767880 clr!AwareLock::Contention+0x483 + 0404f1c4 03f00229 clr!JITutil_MonContention+0x2c0 + 0404f1f4 5b6ef077 image00400000!Worker.Work()+0x79 + ... + +By looking at the code corresponding to Worker.Work()+0x79 (run "!u 03f00229"), +you can see that thread 3 is attempting to acquire the Resource 00a7a1a4, which +is owned by thread 4. + +NOTE: +It is not always the case that a SyncBlock will be created for every object +that is locked by a thread. In version 2.0 of the CLR and above, a mechanism +called a ThinLock will be used if there is not already a SyncBlock for the +object in question. ThinLocks will not be reported by the !SyncBlk command. +You can use "!DumpHeap -thinlock" to list objects locked in this way. +\\ + +COMMAND: dumpmt. +!DumpMT [-MD] <MethodTable address> + +Examine a MethodTable. Each managed object has a MethodTable pointer at the +start. If you pass the "-MD" flag, you'll also see a list of all the methods +defined on the object. +\\ + +COMMAND: dumpclass. +!DumpClass <EEClass address> + +The EEClass is a data structure associated with an object type. !DumpClass +will show attributes, as well as list the fields of the type. The output is +similar to !DumpObj. Although static field values will be displayed, +non-static values won't because you need an instance of an object for that. + +You can get an EEClass to look at from !DumpMT, !DumpObj, !Name2EE, and +!Token2EE among others. +\\ + +COMMAND: dumpmd. +!DumpMD <MethodDesc address> + +This command lists information about a MethodDesc. You can use !IP2MD to turn +a code address in a managed function into a MethodDesc: + + 0:000> !dumpmd 902f40 + Method Name: Mainy.Main() + Class: 03ee1424 + MethodTable: 009032d8 + mdToken: 0600000d + Module: 001caa78 + IsJitted: yes + CodeAddr: 03ef00b8 + +If IsJitted is "yes," you can run !U on the CodeAddr pointer to see a +disassembly of the JITTED code. You can also call !DumpClass, !DumpMT, +!DumpModule on the Class, MethodTable and Module fields above. +\\ + +COMMAND: token2ee. +!Token2EE <module name> <token> + +This function allows you to turn a metadata token into a MethodTable or +MethodDesc. Here is an example showing class tokens being resolved: + + 0:000> !token2ee unittest.exe 02000003 + Module: 001caa38 + Token: 0x02000003 + MethodTable: 0090375c + EEClass: 03ee1ae0 + Name: Bank + 0:000> !token2ee image00400000 02000004 + Module: 001caa38 + Token: 0x02000004 + MethodTable: 009038ec + EEClass: 03ee1b84 + Name: Customer + +The module you are "browsing" with Token2EE needs to be loaded in the process. +This function doesn't see much use, especially since a tool like ILDASM can +show the mapping between metadata tokens and types/methods in a friendlier way. +But it could be handy sometimes. + +You can pass "*" for <module name> to find what that token maps to in every +loaded managed module. <module name> can also be the debugger's name for a +module, such as mscorlib or image00400000. +\\ + +COMMAND: eeversion. +!EEVersion + +This prints the Common Language Runtime version. It also tells you if the code +is running in "Workstation" or "Server" mode, a distinction which affects the +garbage collector. The most apparent difference in the debugger is that in +"Server" mode there is one dedicated garbage collector thread per CPU. + +A handy supplement to this function is to also run "lm v m clr". That +will provide more details about the CLR, including where clr.dll is +loaded from. +\\ + +COMMAND: dumpmodule. +!DumpModule [-mt] <Module address> + +You can get a Module address from !DumpDomain, !DumpAssembly and other +functions. Here is sample output: + + 0:000> !DumpModule 1caa50 + Name: C:\pub\unittest.exe + Attributes: PEFile + Assembly: 001ca248 + LoaderHeap: 001cab3c + TypeDefToMethodTableMap: 03ec0010 + TypeRefToMethodTableMap: 03ec0024 + MethodDefToDescMap: 03ec0064 + FieldDefToDescMap: 03ec00a4 + MemberRefToDescMap: 03ec00e8 + FileReferencesMap: 03ec0128 + AssemblyReferencesMap: 03ec012c + MetaData start address: 00402230 (1888 bytes) + +The Maps listed map metadata tokens to CLR data structures. Without going into +too much detail, you can examine memory at those addresses to find the +appropriate structures. For example, the TypeDefToMethodTableMap above can be +examined: + + 0:000> dd 3ec0010 + 03ec0010 00000000 00000000 0090320c 0090375c + 03ec0020 009038ec ... + +This means TypeDef token 2 maps to a MethodTable with the value 0090320c. You +can run !DumpMT to verify that. The MethodDefToDescMap takes a MethodDef token +and maps it to a MethodDesc, which can be passed to !DumpMD. + +There is a new option "-mt", which will display the types defined in a module, +and the types referenced by the module. For example: + + 0:000> !dumpmodule -mt 1aa580 + Name: C:\pub\unittest.exe + ...<etc>... + MetaData start address: 0040220c (1696 bytes) + + Types defined in this module + + MT TypeDef Name + -------------------------------------------------------------------------- + 030d115c 0x02000002 Funny + 030d1228 0x02000003 Mainy + + Types referenced in this module + + MT TypeRef Name + -------------------------------------------------------------------------- + 030b6420 0x01000001 System.ValueType + 030b5cb0 0x01000002 System.Object + 030fceb4 0x01000003 System.Exception + 0334e374 0x0100000c System.Console + 03167a50 0x0100000e System.Runtime.InteropServices.GCHandle + 0336a048 0x0100000f System.GC + +\\ + +COMMAND: threadpool. +!ThreadPool + +This command lists basic information about the ThreadPool, including the number +of work requests in the queue, number of completion port threads, and number of +timers. +\\ + +COMMAND: dumpassembly. +!DumpAssembly <Assembly address> + +Example output: + + 0:000> !dumpassembly 1ca248 + Parent Domain: 0014f000 + Name: C:\pub\unittest.exe + ClassLoader: 001ca060 + Module Name + 001caa50 C:\pub\unittest.exe + +An assembly can consist of multiple modules, and those will be listed. You can +get an Assembly address from the output of !DumpDomain. +\\ + +COMMAND: dumpruntimetypes. +!DumpRuntimeTypes + +!DumpRuntimeTypes finds all System.RuntimeType objects in the gc heap and +prints the type name and MethodTable they refer too. Sample output: + + Address Domain MT Type Name + ------------------------------------------------------------------------------ + a515f4 14a740 5baf8d28 System.TypedReference + a51608 14a740 5bb05764 System.Globalization.BaseInfoTable + a51958 14a740 5bb05b24 System.Globalization.CultureInfo + a51a44 14a740 5bb06298 System.Globalization.GlobalizationAssembly + a51de0 14a740 5bb069c8 System.Globalization.TextInfo + a56b98 14a740 5bb12d28 System.Security.Permissions.HostProtectionResource + a56bbc 14a740 5baf7248 System.Int32 + a56bd0 14a740 5baf3fdc System.String + a56cfc 14a740 5baf36a4 System.ValueType + ... + +This command will print a "?" in the domain column if the type is loaded into multiple +AppDomains. For example: + + 0:000> !DumpRuntimeTypes + Address Domain MT Type Name + ------------------------------------------------------------------------------ + 28435a0 ? 3f6a8c System.TypedReference + 28435b4 ? 214d6c System.ValueType + 28435c8 ? 216314 System.Enum + 28435dc ? 2147cc System.Object + 284365c ? 3cd57c System.IntPtr + 2843670 ? 3feaac System.Byte + 2843684 ? 23a544c System.IEquatable`1[[System.IntPtr, mscorlib]] + 2843784 ? 3c999c System.Int32 + 2843798 ? 3caa04 System.IEquatable`1[[System.Int32, mscorlib]] + +\\ + +COMMAND: dumpsig. +!DumpSig <sigaddr> <moduleaddr> + +This command dumps the signature of a method or field given by <sigaddr>. This is +useful when you are debugging parts of the runtime which returns a raw PCCOR_SIGNATURE +structure and need to know what its contents are. + +Sample output for a method: + 0:000> !dumpsig 0x000007fe`ec20879d 0x000007fe`eabd1000 + [DEFAULT] [hasThis] Void (Boolean,String,String) + +The first section of the output is the calling convention. This includes, but is not +limited to, "[DEFAULT]", "[C]", "[STDCALL]", "[THISCALL]", and so on. The second +portion of the output is either "[hasThis]" or "[explicit]" for whether the method +is an instance method or a static method respectively. The third portion of the +output is the return value (in this case a "void"). Finally, the method's arguments +are printed as the final portion of the output. + +Sample output for a field: + 0:000> !dumpsig 0x000007fe`eb7fd8cd 0x000007fe`eabd1000 + [FIELD] ValueClass System.RuntimeTypeHandle + +!DumpSig will also work with generics. Here is the output for the following +function: + public A Test(IEnumerable<B> n) + + 0:000> !dumpsig 00000000`00bc2437 000007ff00043178 + [DEFAULT] [hasThis] __Canon (Class System.Collections.Generic.IEnumerable`1<__Canon>) + +\\ + +COMMAND: dumpsigelem. +!DumpSigElem <sigaddr> <moduleaddr> + +This command dumps a single element of a signature object. For most circumstances, +you should use !DumpSig to look at individual signature objects, but if you find a +signature that has been corrupted in some manner you can use !DumpSigElem to read out +the valid portions of it. + +If we look at a valid signature object for a method we see the following: + 0:000> !dumpsig 0x000007fe`ec20879d 0x000007fe`eabd1000 + [DEFAULT] [hasThis] Void (Boolean,String,String) + +We can look at the individual elements of this object by adding the offsets into the +object which correspond to the return value and parameters: + 0:000> !dumpsigelem 0x000007fe`ec20879d+2 0x000007fe`eabd1000 + Void + 0:000> !dumpsigelem 0x000007fe`ec20879d+3 0x000007fe`eabd1000 + Boolean + 0:000> !dumpsigelem 0x000007fe`ec20879d+4 0x000007fe`eabd1000 + String + 0:000> !dumpsigelem 0x000007fe`ec20879d+5 0x000007fe`eabd1000 + String + +We can do something similar for fields. Here is the full signature of a field: + 0:000> !dumpsig 0x000007fe`eb7fd8cd 0x000007fe`eabd1000 + [FIELD] ValueClass System.RuntimeTypeHandle + +Using !DumpSigElem we can find the type of the field by adding the offset of it (1) to +the address of the signature: + 0:000> !dumpsigelem 0x000007fe`eb7fd8cd+1 0x000007fe`eabd1000 + ValueClass System.RuntimeTypeHandle + +!DumpSigElem will also work with generics. Let a function be defined as follows: + public A Test(IEnumerable<B> n) + +The elements of this signature can be obtained by adding offsets into the signature +when calling !DumpSigElem: + + 0:000> !dumpsigelem 00000000`00bc2437+2 000007ff00043178 + __Canon + 0:000> !dumpsigelem 00000000`00bc2437+4 000007ff00043178 + Class System.Collections.Generic.IEnumerable`1<__Canon> + +The actual offsets that you should add are determined by the contents of the +signature itself. By trial and error you should be able to find various elements +of the signature. + +\\ + +COMMAND: rcwcleanuplist. +!RCWCleanupList [address] + +A RuntimeCallableWrapper is an internal CLR structure used to host COM objects +which are exposed to managed code. This is exposed to managed code through the +System.__ComObject class, and when objects of this type are collected, and a +reference to the underlying COM object is no longer needed, the corresponding +RCW is cleaned up. If you are trying to debug an issue related to one of these +RCWs, then you can use the !RCWCleanupList function to display which COM objects +will be released the next time a cleanup occurs. + +If given an address, this function will display the RCWCleanupList at that address. +If no address is specified, it displays the default cleanup list, printing the +wrapper, the context, and the thread of the object. + +Example: + 0:002> !rcwcleanuplist 001c04d0 + RuntimeCallableWrappers (RCW) to be cleaned: + RCW CONTEXT THREAD Apartment + 1d54e0 192008 181180 STA + 1d4140 192178 0 MTA + 1dff50 192178 0 MTA + MTA Interfaces to be released: 2 + STA Interfaces to be released: 1 + +Note that CLR keeps track of which RCWs are bound to which managed objects through +the SyncBlock of the object. As such, you can see more information about RCW +objects through the !SyncBlk command. You can find more information about RCW +cleanup through the !FinalizeQueue command. + +\\ + +COMMAND: dumpil. +!DumpIL <Managed DynamicMethod object> | + <DynamicMethodDesc pointer> | + <MethodDesc pointer> | + /i <IL pointer> + +!DumpIL prints the IL code associated with a managed method. We added this +function specifically to debug DynamicMethod code which was constructed on +the fly. Happily it works for non-dynamic code as well. + +You can use it in four ways: + + 1) If you have a System.Reflection.Emit.DynamicMethod object, just pass + the pointer as the first argument. + 2) If you have a DynamicMethodDesc pointer you can use that to print the + IL associated with the dynamic method. + 3) If you have an ordinary MethodDesc, you can see the IL for that as well, + just pass it as the first argument. + 4) If you have a pointer directly to the IL, specify /i followed by the + the IL address. This is useful for writers of profilers that instrument + IL. + + +Note that dynamic IL is constructed a bit differently. Rather than referring +to metadata tokens, the IL points to objects in a managed object array. Here +is a simple example of the output for a dynamic method: + + 0:000> !dumpil b741dc + This is dynamic IL. Exception info is not reported at this time. + If a token is unresolved, run "!do <addr>" on the addr given + in parenthesis. You can also look at the token table yourself, by + running "!DumpArray 00b77388". + + IL_0000: ldstr 70000002 "Inside invoked method " + IL_0005: call 6000003 System.Console.WriteLine(System.String) + IL_000a: ldc.i4.1 + IL_000b: newarr 2000004 "System.Int32" + IL_0010: stloc.0 + IL_0011: ldloc.0 + IL_0012: ret + +\\ + +COMMAND: verifyheap. +!VerifyHeap + +!VerifyHeap is a diagnostic tool that checks the garbage collected heap for +signs of corruption. It walks objects one by one in a pattern like this: + + o = firstobject; + while(o != endobject) + { + o.ValidateAllFields(); + o = (Object *) o + o.Size(); + } + +If an error is found, !VerifyHeap will report it. I'll take a perfectly good +object and corrupt it: + + 0:000> !DumpObj a79d40 + Name: Customer + MethodTable: 009038ec + EEClass: 03ee1b84 + Size: 20(0x14) bytes + (C:\pub\unittest.exe) + Fields: + MT Field Offset Type Attr Value Name + 009038ec 4000008 4 CLASS instance 00a79ce4 name + 009038ec 4000009 8 CLASS instance 00a79d2c bank + 009038ec 400000a c System.Boolean instance 1 valid + + 0:000> ed a79d40+4 01 (change the name field to the bogus pointer value 1) + 0:000> !VerifyHeap + object 01ee60dc: bad member 00000003 at 01EE6168 + Last good object: 01EE60C4. + +If this gc heap corruption exists, there is a serious bug in your own code or +in the CLR. In user code, an error in constructing PInvoke calls can cause +this problem, and running with Managed Debugging Assistants is advised. If that +possibility is eliminated, consider contacting Microsoft Product Support for +help. + +\\ + +COMMAND: verifyobj. +!VerifyObj <object address> + +!VerifyObj is a diagnostic tool that checks the object that is passed as an +argument for signs of corruption. + + 0:002> !verifyobj 028000ec + object 0x28000ec does not have valid method table + + 0:002> !verifyobj 0680017c + object 0x680017c: bad member 00000001 at 06800184 + +\\ + +COMMAND: findroots. +!FindRoots -gen <N> | -gen any | <object address> + +The "-gen" form causes the debugger to break in the debuggee on the next +collection of the specified generation. The effect is reset as soon as the +break occurs, in other words, if you need to break on the next collection you +would need to reissue the command. + +The last form of this command is meant to be used after the break caused by the +other forms has occurred. Now the debuggee is in the right state for +!FindRoots to be able to identify roots for objects from the current condemned +generations. + +!FindRoots is a diagnostic command that is meant to answer the following +question: + +"I see that GCs are happening, however my objects have still not been +collected. Why? Who is holding onto them?" + +The process of answering the question would go something like this: + +1. Find out the generation of the object of interest using the !GCWhere +command, say it is gen 1: + !GCWhere <object address> + +2. Instruct the runtime to stop the next time it collects that generation using +the !FindRoots command: + !FindRoots -gen 1 + g + +3. When the next GC starts, and has proceeded past the mark phase a CLR +notification will cause a break in the debugger: + (fd0.ec4): CLR notification exception - code e0444143 (first chance) + CLR notification: GC - end of mark phase. + Condemned generation: 1. + +4. Now we can use the !FindRoots <object address> to find out the cross +generational references to the object of interest. In other words, even if the +object is not referenced by any "proper" root it may still be referenced by an +older object (from an older generation), from a generation that has not yet been +scheduled for collection. At this point !FindRoots will search those older +generations too, and report those roots. + 0:002> !findroots 06808094 + older generations::Root: 068012f8(AAA.Test+a)-> + 06808094(AAA.Test+b) + + +\\ + +COMMAND: heapstat. +!HeapStat [-inclUnrooted | -iu] + +This command shows the generation sizes for each heap and the total, how much free +space there is in each generation on each heap. If the -inclUnrooted option is +specified the report will include information about the managed objects from the +GC heap that are not rooted anymore. + +Sample output: + + 0:002> !heapstat + Heap Gen0 Gen1 Gen2 LOH + Heap0 177904 12 306956 8784 + Heap1 159652 12 12 16 + Total 337556 24 306968 8800 + + Free space: Percentage + Heap0 28 12 12 64 SOH: 0% LOH: 0% + Heap1 104 12 12 16 SOH: 0% LOH:100% + Total 132 24 24 80 + + 0:002> !heapstat -inclUnrooted + Heap Gen0 Gen1 Gen2 LOH + Heap0 177904 12 306956 8784 + Heap1 159652 12 12 16 + Total 337556 24 306968 8800 + + Free space: Percentage + Heap0 28 12 12 64 SOH: 0% LOH: 0% + Heap1 104 12 12 16 SOH: 0% LOH:100% + Total 132 24 24 80 + + Unrooted objects: Percentage + Heap0 152212 0 306196 0 SOH: 94% LOH: 0% + Heap1 155704 0 0 0 SOH: 97% LOH: 0% + Total 307916 0 306196 0 + +The percentage column contains a breakout of free or unrooted bytes to total bytes. + +\\ + +COMMAND: analyzeoom. +!AnalyzeOOM + +!AnalyzeOOM displays the info of the last OOM occurred on an allocation request to +the GC heap (in Server GC it displays OOM, if any, on each GC heap). + +To see the managed exception(s) use the !Threads command which will show you +managed exception(s), if any, on each managed thread. If you do see an +OutOfMemoryException exception you can use the !PrintException command on it. +To get the full callstack use the "kb" command in the debugger for that thread. +For example, to display thread 3's stack use ~3kb. + +OOM exceptions could be because of the following reasons: + +1) allocation request to GC heap + in which case you will see JIT_New* on the call stack because managed code called new. +2) other runtime allocation failure + for example, failure to expand the finalize queue when GC.ReRegisterForFinalize is + called. +3) some other code you use throws a managed OOM exception + for example, some .NET framework code converts a native OOM exception to managed + and throws it. + +The !AnalyzeOOM command aims to help you with investigating 1) which is the most +difficult because it requires some internal info from GC. The only exception is +we don't support allocating objects larger than 2GB on CLR v2.0 or prior. And this +command will not display any managed OOM because we will throw OOM right away +instead of even trying to allocate it on the GC heap. + +There are 2 legitimate scenarios where GC would return OOM to allocation requests - +one is if the process is running out of VM space to reserve a segment; the other +is if the system is running out physical memory (+ page file if you have one) so +GC can not commit memory it needs. You can look at these scenarios by using performance +counters or debugger commands. For example for the former scenario the "!address +-summary" debugger command will show you the largest free region in the VM. For +the latter scenario you can look at the "Memory\% Committed Bytes In Use" see +if you are running low on commit space. One important thing to keep in mind is +when you do this kind of memory analysis it could an aftereffect and doesn't +completely agree with what this command tells you, in which case the command should +be respected because it truly reflects what happened during GC. + +The other cases should be fairly obvious from the callstack. + +Sample output: + +0:011> !ao +---------Heap 2 --------- +Managed OOM occurred after GC #28 (Requested to allocate 1234 bytes) +Reason: Didn't have enough memory to commit +Detail: SOH: Didn't have enough memory to grow the internal GC datastructures (800000 bytes) - + on GC entry available commit space was 500 MB +---------Heap 4 --------- +Managed OOM occurred after GC #12 (Requested to allocate 100000 bytes) +Reason: Didn't have enough memory to allocate an LOH segment +Detail: LOH: Failed to reserve memory (16777216 bytes) + +\\ + +COMMAND: gcwhere. +!GCWhere <object address> + +!GCWhere displays the location in the GC heap of the argument passed in. + + 0:002> !GCWhere 02800038 + Address Gen Heap segment begin allocated size + 02800038 2 0 02800000 02800038 0282b740 12 + +When the argument lies in the managed heap, but is not a valid *object* address +the "size" is displayed as 0: + + 0:002> !GCWhere 0280003c + Address Gen Heap segment begin allocated size + 0280003c 2 0 02800000 02800038 0282b740 0 + +\\ + +COMMAND: listnearobj. +!ListNearObj <object address> + +!ListNearObj is a diagnostic tool that displays the object preceeding and +succeeding the address passed in: + +The command looks for the address in the GC heap that looks like a valid +beginning of a managed object (based on a valid method table) and the object +following the argument address. + + 0:002> !ListNearObj 028000ec + Before: 0x28000a4 72 (0x48 ) System.StackOverflowException + After: 0x2800134 72 (0x48 ) System.Threading.ThreadAbortException + Heap local consistency confirmed. + + 0:002> !ListNearObj 028000f0 + Before: 0x28000ec 72 (0x48 ) System.ExecutionEngineException + After: 0x2800134 72 (0x48 ) System.Threading.ThreadAbortException + Heap local consistency confirmed. + +The command considers the heap as "locally consistent" if: + prev_obj_addr + prev_obj_size = arg_addr && arg_obj + arg_size = next_obj_addr +OR + prev_obj_addr + prev_obj_size = next_obj_addr + +When the condition is not satisfied: + + 0:002> !lno 028000ec + Before: 0x28000a4 72 (0x48 ) System.StackOverflowException + After: 0x2800134 72 (0x48 ) System.Threading.ThreadAbortException + Heap local consistency not confirmed. + +\\ + +COMMAND: dumplog. +!DumpLog [-addr <addressOfStressLog>] [<Filename>] + +To aid in diagnosing hard-to-reproduce stress failures, the CLR team added an +in-memory log capability. The idea was to avoid using locks or I/O which could +disturb a fragile repro environment. The !DumpLog function allows you to write +that log out to a file. If no Filename is specified, the file "Stresslog.txt" +in the current directory is created. + +The optional argument addr allows one to specify a stress log other then the +default one. + + 0:000> !DumpLog + Attempting to dump Stress log to file 'StressLog.txt' + ................. + SUCCESS: Stress log dumped + +To turn on the stress log, set the following registry keys under +HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework: + + +(DWORD) StressLog = 1 +(DWORD) LogFacility = 0xffffffbf (this is a bit mask, almost all logging is on. + This is also the default value if the key + isn't specified) +(DWORD) StressLogSize = 65536 (this is the default value if the key isn't + specified) +(DWORD) LogLevel = 6 (this is the default value if the key isn't + specified. The higher the number the more + detailed logs are generated. The maximum + value is decimal 10) + +StressLogSize is the size in bytes of the in-memory log allocated for each +thread in the process. In the case above, each thread gets a 64K log. You +could increase this to get more logging, but more memory will be required for +this log in the process. For example, 20 threads with 524288 bytes per thread +has a memory demand of 10 Megabytes. The stress log is circular so new entries +will replace older ones on threads which have reached their buffer limit. + +The log facilities are defined as follows: + GC 0x00000001 + GCINFO 0x00000002 + STUBS 0x00000004 + JIT 0x00000008 + LOADER 0x00000010 + METADATA 0x00000020 + SYNC 0x00000040 + EEMEM 0x00000080 + GCALLOC 0x00000100 + CORDB 0x00000200 + CLASSLOADER 0x00000400 + CORPROF 0x00000800 + REMOTING 0x00001000 + DBGALLOC 0x00002000 + EH 0x00004000 + ENC 0x00008000 + ASSERT 0x00010000 + VERIFIER 0x00020000 + THREADPOOL 0x00040000 + GCROOTS 0x00080000 + INTEROP 0x00100000 + MARSHALER 0x00200000 + IJW 0x00400000 + ZAP 0x00800000 + STARTUP 0x01000000 + APPDOMAIN 0x02000000 + CODESHARING 0x04000000 + STORE 0x08000000 + SECURITY 0x10000000 + LOCKS 0x20000000 + BCL 0x40000000 + +Here is some sample output: + + 3560 9.981137099 : `SYNC` RareEnablePremptiveGC: entering. + Thread state = a030 + + 3560 9.981135033 : `GC`GCALLOC`GCROOTS` ========== ENDGC 4194 (gen = 2, + collect_classes = 0) ==========={ + + 3560 9.981125826 : `GC` Segment mem 00C61000 alloc + = 00D071F0 used 00D09254 committed 00D17000 + + 3560 9.981125726 : `GC` Generation 0 [00CED07C, 00000000 + ] cur = 00000000 + + 3560 9.981125529 : `GC` Generation 1 [00CED070, 00000000 + ] cur = 00000000 + + 3560 9.981125103 : `GC` Generation 2 [00C61000, 00000000 + ] cur = 00000000 + + 3560 9.981124963 : `GC` GC Heap 00000000 + + 3560 9.980618994 : `GC`GCROOTS` GcScanHandles (Promotion Phase = 0) + +The first column is the OS thread ID for the thread appending to the log, +the second column is the timestamp, the third is the facility category for the +log entry, and the fourth contains the log message. The facility field is +expressed as `facility1`facility2`facility3`. This facilitates the creation of +filters for displaying only specific message categories. To make sense of this +log, you would probably want the Shared Source CLI to find out exactly where +the log comes from. +\\ + +COMMAND: findappdomain. +!FindAppDomain <Object address> + +!FindAppDomain will attempt to resolve the AppDomain of an object. For example, +using an Object Pointer from the output of !DumpStackObjects: + + 0:000> !findappdomain 00a79d98 + AppDomain: 0014f000 + Name: unittest.exe + ID: 1 + +You can find out more about the AppDomain with the !DumpDomain command. Not +every object has enough clues about it's origin to determine the AppDomain. +Objects with Finalizers are the easiest case, as the CLR needs to be able to +call those when an AppDomain shuts down. +\\ + +COMMAND: savemodule. +!SaveModule <Base address> <Filename> + +This command allows you to take a image loaded in memory and write it to a +file. This is especially useful if you are debugging a full memory dump, and +don't have the original DLLs or EXEs. This is most often used to save a managed +binary to a file, so you can disassemble the code and browse types with ILDASM. + +The base address of an image can be found with the "LM" debugger command: + + 0:000> lm + start end module name + 00400000 00408000 image00400000 (deferred) + 10200000 102ac000 MSVCR80D (deferred) + 5a000000 5a0b1000 mscoree (deferred) + 5a140000 5a29e000 clrjit (deferred) + 5b660000 5c440000 mscorlib_dll (deferred) + 5d1d0000 5e13c000 clr (deferred) + ... + +If I wanted to save a copy of clr.dll, I could run: + + 0:000> !SaveModule 5d1d0000 c:\pub\out.tmp + 4 sections in file + section 0 - VA=1000, VASize=e82da9, FileAddr=400, FileSize=e82e00 + section 1 - VA=e84000, VASize=24d24, FileAddr=e83200, FileSize=ec00 + section 2 - VA=ea9000, VASize=5a8, FileAddr=e91e00, FileSize=600 + section 3 - VA=eaa000, VASize=c183c, FileAddr=e92400, FileSize=c1a00 + +The diagnostic output indicates that the operation was successful. If +c:\pub\out.tmp already exists, it will be overwritten. +\\ + +COMMAND: gchandles. +!GCHandles [-type handletype] [-stat] [-perdomain] + +!GCHandles provides statistics about GCHandles in the process. + +Paremeters: + stat - Only display the statistics and not the list of handles and + what they point to. + perdomain - Break down the statistics by the app domain in which + the handles reside. + type - A type of handle to filter it by. The handle types are: + Pinned + RefCounted + WeakShort + WeakLong + Strong + Variable + AsyncPinned + SizedRef + +Sometimes the source of a memory leak is a GCHandle leak. For example, code +might keep a 50 Megabyte array alive because a strong GCHandle points to it, +and the handle was discarded without freeing it. + +The most common handles are "Strong Handles," which keep the object they point +to alive until the handle is explicitly freed. "Pinned Handles" are used to +prevent the garbage collector from moving an object during collection. These +should be used sparingly, and for short periods of time. If you don't follow +that precept, the gc heap can become very fragmented. + +Here is sample output from a very simple program. Note that the "RefCount" +field only applies to RefCount Handles, and this field will contain the +reference count: + + 0:000> !GCHandles + Handle Type Object Size RefCount Type + 001611c0 Strong 01d00b58 84 System.IndexOutOfRangeException + 001611c4 Strong 01d00b58 84 System.IndexOutOfRangeException + 001611c8 Strong 01d1b48c 40 System.Diagnostics.LogSwitch + 001611d0 Strong 01cfd2c0 36 System.Security.PermissionSet + 001611d4 Strong 01cf7484 56 System.Object[] + 001611d8 Strong 01cf1238 32 System.SharedStatics + 001611dc Strong 01cf11c8 84 System.Threading.ThreadAbortException + 001611e0 Strong 01cf1174 84 System.Threading.ThreadAbortException + 001611e4 Strong 01cf1120 84 System.ExecutionEngineException + 001611e8 Strong 01cf10cc 84 System.StackOverflowException + 001611ec Strong 01cf1078 84 System.OutOfMemoryException + 001611f0 Strong 01cf1024 84 System.Exception + 001611f8 Strong 01cf2068 48 System.Threading.Thread + 001611fc Strong 01cf1328 112 System.AppDomain + 001613ec Pinned 02cf3268 8176 System.Object[] + 001613f0 Pinned 02cf2258 4096 System.Object[] + 001613f4 Pinned 02cf2038 528 System.Object[] + 001613f8 Pinned 01cf121c 12 System.Object + 001613fc Pinned 02cf1010 4116 System.Object[] + + Statistics: + MT Count TotalSize Class Name + 563266dc 1 12 System.Object + 56329708 1 32 System.SharedStatics + 5632bc38 1 36 System.Security.PermissionSet + 5635f934 1 40 System.Diagnostics.LogSwitch + 5632759c 1 48 System.Threading.Thread + 5632735c 1 84 System.ExecutionEngineException + 56327304 1 84 System.StackOverflowException + 563272ac 1 84 System.OutOfMemoryException + 563270c4 1 84 System.Exception + 56328914 1 112 System.AppDomain + 56335f78 2 168 System.IndexOutOfRangeException + 563273b4 2 168 System.Threading.ThreadAbortException + 563208d0 5 16972 System.Object[] + Total 19 objects + + Handles: + Strong Handles: 14 + Pinned Handles: 5 +\\ + +COMMAND: gchandleleaks. +!GCHandleLeaks + +This command is an aid in tracking down GCHandle leaks. It searches all of +memory for any references to the Strong and Pinned GCHandles in the process, +and reports what it found. If a handle is found, you'll see the address of the +reference. This might be a stack address or a field within an object, for +example. If a handle is not found in memory, you'll get notification of that +too. + +The command has diagnostic output which doesn't need to be repeated here. One +thing to keep in mind is that anytime you search all of memory for a value, you +can get false positives because even though the value was found, it might be +garbage in that no code knows about the address. You can also get false +negatives because a user is free to pass that GCHandle to unmanaged code that +might store the handle in a strange way (shifting bits, for example). + +For example, a GCHandle valuetype is stored on the stack with the low bit set +if it points to a Pinned handle. So !GCHandleLeaks ignores the low bit in it's +searches. + +That said, if a serious leak is going on, you'll get a ever-growing set of +handle addresses that couldn't be found. +\\ + +COMMAND: vmmap. +!VMMap + +!VMMap traverses the virtual address space and lists the type of protection +applied to each region. Sample output: + + 0:000> !VMMap + Start Stop Length AllocProtect Protect State Type + 00000000-0000ffff 00010000 NA Free + 00010000-00011fff 00002000 RdWr RdWr Commit Private + 00012000-0001ffff 0000e000 NA Free + 00020000-00020fff 00001000 RdWr RdWr Commit Private + 00021000-0002ffff 0000f000 NA Free + 00030000-00030fff 00001000 RdWr Reserve Private + ... +\\ + +COMMAND: vmstat. +!VMStat + +Provides a summary view of the virtual address space, ordered by each type of +protection applied to that memory (free, reserved, committed, private, mapped, +image). The TOTAL column is (AVERAGE * BLK COUNT). Sample output below: + + 0:000> !VMStat + ~~~~ ~~~~~~~ ~~~~~~~ ~~~~~~~ ~~~~~~~~~ ~~~~~ + TYPE MINIMUM MAXIMUM AVERAGE BLK COUNT TOTAL + Free: + Small 4,096 65,536 48,393 27 1,306,611 + Medium 139,264 528,384 337,920 4 1,351,680 + Large 6,303,744 974,778,368 169,089,706 12 2,029,076,472 + Summary 4,096 974,778,368 47,249,646 43 2,031,734,778 + + Reserve: + Small 4,096 65,536 43,957 41 1,802,237 + Medium 249,856 1,019,904 521,557 6 3,129,342 + Large 2,461,696 16,703,488 11,956,224 3 35,868,672 + Summary 4,096 16,703,488 816,005 50 40,800,250 + +\\ + +COMMAND: procinfo. +!ProcInfo [-env] [-time] [-mem] + +!ProcInfo lists the environment variables for the process, kernel CPU time, as +well as memory usage statistics. +\\ + +COMMAND: histinit. +!HistInit + +Before running any of the Hist - family commands you need to initialize the SOS +structures from the stress log saved in the debuggee. This is achieved by the +HistInit command. + +Sample output: + + 0:001> !HistInit + Attempting to read Stress log + STRESS LOG: + facilitiesToLog = 0xffffffff + levelToLog = 6 + MaxLogSizePerThread = 0x10000 (65536) + MaxTotalLogSize = 0x1000000 (16777216) + CurrentTotalLogChunk = 9 + ThreadsWithLogs = 3 + Clock frequency = 3.392 GHz + Start time 15:26:31 + Last message time 15:26:56 + Total elapsed time 25.077 sec + ..................................... + ---------------------------- 2407 total entries ----------------------------- + + + SUCCESS: GCHist structures initialized + +\\ + +COMMAND: histobjfind. +!HistObjFind <obj_address> + +To examine log entries related to an object whose present address is known one +would use this command. The output of this command contains all entries that +reference the object: + + 0:003> !HistObjFind 028970d4 + GCCount Object Message + --------------------------------------------------------- + 2296 028970d4 Promotion for root 01e411b8 (MT = 5b6c5cd8) + 2296 028970d4 Relocation NEWVALUE for root 00223fc4 + 2296 028970d4 Relocation NEWVALUE for root 01e411b8 + ... + 2295 028970d4 Promotion for root 01e411b8 (MT = 5b6c5cd8) + 2295 028970d4 Relocation NEWVALUE for root 00223fc4 + 2295 028970d4 Relocation NEWVALUE for root 01e411b8 + ... + +\\ + +COMMAND: histroot. +!HistRoot <root> + +The root value obtained from !HistObjFind can be used to track the movement of +an object through the GCs. + +HistRoot provides information related to both promotions and relocations of the +root specified as the argument. + + 0:003> !HistRoot 01e411b8 + GCCount Value MT Promoted? Notes + --------------------------------------------------------- + 2296 028970d4 5b6c5cd8 yes + 2295 028970d4 5b6c5cd8 yes + 2294 028970d4 5b6c5cd8 yes + 2293 028970d4 5b6c5cd8 yes + 2292 028970d4 5b6c5cd8 yes + 2291 028970d4 5b6c5cd8 yes + 2290 028970d4 5b6c5cd8 yes + 2289 028970d4 5b6c5cd8 yes + 2288 028970d4 5b6c5cd8 yes + 2287 028970d4 5b6c5cd8 yes + 2286 028970d4 5b6c5cd8 yes + 2285 028970d4 5b6c5cd8 yes + 322 028970e8 5b6c5cd8 yes Duplicate promote/relocs + ... + +\\ + +COMMAND: histobj. +!HistObj <obj_address> + +This command examines all stress log relocation records and displays the chain +of GC relocations that may have led to the address passed in as an argument. +Conceptually the output is: + + GenN obj_address root1, root2, root3, + GenN-1 prev_obj_addr root1, root2, + GenN-2 prev_prev_oa root1, root4, + ... + +Sample output: + 0:003> !HistObj 028970d4 + GCCount Object Roots + --------------------------------------------------------- + 2296 028970d4 00223fc4, 01e411b8, + 2295 028970d4 00223fc4, 01e411b8, + 2294 028970d4 00223fc4, 01e411b8, + 2293 028970d4 00223fc4, 01e411b8, + 2292 028970d4 00223fc4, 01e411b8, + 2291 028970d4 00223fc4, 01e411b8, + 2290 028970d4 00223fc4, 01e411b8, + 2289 028970d4 00223fc4, 01e411b8, + 2288 028970d4 00223fc4, 01e411b8, + 2287 028970d4 00223fc4, 01e411b8, + 2286 028970d4 00223fc4, 01e411b8, + 2285 028970d4 00223fc4, 01e411b8, + 322 028970d4 01e411b8, + 0 028970d4 + +\\ + +COMMAND: histclear. +!HistClear + +This command releases any resources used by the Hist-family of commands. +Generally there's no need to call this explicitly, as each HistInit will first +cleanup the previous resources. + + 0:003> !HistClear + Completed successfully. + +\\ + +COMMAND: dumprcw. +!DumpRCW <RCW address> + +This command lists information about a Runtime Callable Wrapper. You can use +!DumpObj to obtain the RCW address corresponding to a managed object. + +The output contains all COM interface pointers that the RCW holds on to, which +is useful for investigating lifetime issues of interop-heavy applications. +\\ + +COMMAND: dumpccw. +!DumpCCW <CCW address or COM IP> + +This command lists information about a COM Callable Wrapper. You can use +!DumpObj to obtain the CCW address corresponding to a managed object or pass +a COM interface pointer to which the object has been marshaled. + +The output contains the COM reference count of the CCW, which is useful for +investigating lifetime issues of interop-heavy applications. +\\ + + diff --git a/src/ToolBox/SOS/Strike/sosdocsunix.txt b/src/ToolBox/SOS/Strike/sosdocsunix.txt new file mode 100644 index 0000000000..52ec86dc4e --- /dev/null +++ b/src/ToolBox/SOS/Strike/sosdocsunix.txt @@ -0,0 +1,1713 @@ +------------------------------------------------------------------------------- +NOTE: THIS FILE CONTAINS SOS DOCUMENTATION. THE FORMAT OF THE FILE IS: + +<optional comments> +COMMAND: <cmd name, all lower case> +<descriptive text of the command> +\\ <these are two backslashes, immediately followed by a newline> + +<repeat the sequence above> + +The first command is "contents" which is the general help screen. The rest +correspond to SOS command names. This file is embedded as a resource in the SOS +binary. Be sure to list any new commands here. +------------------------------------------------------------------------------- + + + +COMMAND: contents. +SOS is a debugger extension DLL designed to aid in the debugging of managed +programs. Functions are listed by category, then roughly in order of +importance. Shortcut names for popular functions are listed in parenthesis. +Type "soshelp <functionname>" for detailed info on that function. + +Object Inspection Examining code and stacks +----------------------------- ----------------------------- +DumpObj (dumpobj) Threads (clrthreads) +DumpArray ThreadState +DumpStackObjects (dso) IP2MD (ip2md) +DumpHeap (dumpheap) u (clru) +DumpVC DumpStack (dumpstack) +GCRoot (gcroot) EEStack (eestack) +PrintException (pe) ClrStack (clrstack) + GCInfo + EHInfo + bpmd (bpmd) + +Examining CLR data structures Diagnostic Utilities +----------------------------- ----------------------------- +DumpDomain VerifyHeap +EEHeap (eeheap) FindAppDomain +Name2EE (name2ee) DumpLog (dumplog) +DumpMT (dumpmt) +DumpClass (dumpclass) +DumpMD (dumpmd) +Token2EE +DumpModule (dumpmodule) +DumpAssembly +DumpRuntimeTypes +DumpIL (dumpil) +DumpSig +DumpSigElem + +Examining the GC history Other +----------------------------- ----------------------------- +HistInit (histinit) FAQ +HistRoot (histroot) Help (soshelp) +HistObj (histobj) +HistObjFind (histobjfind) +HistClear (histclear) +\\ + +COMMAND: faq. +>> Where can I get the right version of SOS for my build? + +If you are running a xplat version of coreclr, the sos module (exact name +is platform dependent) is installed in the same directory as the main coreclr +module. There is also an lldb sos plugin command that allows the path where +the sos, dac and dbi modules are loaded: + + "setsospath /home/user/coreclr/bin/Product/Linux.x64.Debug"" + +If you are using a dump file created on another machine, it is a little bit +more complex. You need to make sure the dac module that came with that install +is in the directory set with the above command. + +>> I have a chicken and egg problem. I want to use SOS commands, but the CLR + isn't loaded yet. What can I do? + +TBD + +>> I got the following error message. Now what? + + + (lldb) sos DumpStackObjects + The coreclr module is not loaded yet in the target process + (lldb) + +This means that the clr is not loaded yet, or has been unloaded. You need to +wait until your managed program is running in order to use these commands. If +you have just started the program a good way to do this is to type + + breakpoint set coreclr`EEStartup + +in the debugger, and let it run. After the function EEStartup is finished, +there will be a minimal managed environment for executing SOS commands. + +\\ + +COMMAND: dumpobj. +DumpObj [-nofields] <object address> + +This command allows you to examine the fields of an object, as well as learn +important properties of the object such as the EEClass, the MethodTable, and +the size. + +You might find an object pointer by running DumpStackObjects and choosing +from the resultant list. Here is a simple object: + + (lldb) dumpobj a79d40 + Name: Customer + MethodTable: 009038ec + EEClass: 03ee1b84 + Size: 20(0x14) bytes + (/home/user/pub/unittest) + Fields: + MT Field Offset Type VT Attr Value Name + 009038ec 4000008 4 Customer 0 instance 00a79ce4 name + 009038ec 4000009 8 Bank 0 instance 00a79d2c bank + +Note that fields of type Customer and Bank are themselves objects, and you can +run DumpObj on them too. You could look at the field directly in memory using +the offset given. "dd a79d40+8 l1" would allow you to look at the bank field +directly. Be careful about using this to set memory breakpoints, since objects +can move around in the garbage collected heap. + +What else can you do with an object? You might run GCRoot, to determine what +roots are keeping it alive. Or you can find all objects of that type with +"dumpheap -type Customer". + +The column VT contains the value 1 if the field is a valuetype structure, and +0 if the field contains a pointer to another object. For valuetypes, you can +take the MethodTable pointer in the MT column, and the Value and pass them to +the command DumpVC. + +The arguments in detail: +-nofields: do not print fields of the object, useful for objects like String +\\ + +COMMAND: dumparray. +DumpArray + [-start <startIndex>] + [-length <length>] + [-details] + [-nofields] + <array object address> + +This command allows you to examine elements of an array object. +The arguments in detail: + -start <startIndex>: optional, only supported for single dimension array. + Specify from which index the command shows the elements. + -length <length>: optional, only supported for single dimension array. + Specify how many elements to show. + -details: optional. Ask the command to print out details + of the element using DumpObj and DumpVC format. + -nofields: optional, only takes effect when -details is used. Do + not print fields of the elements. Useful for arrays of + objects like String + + Example output: + + (lldb) sos DumpArray -start 2 -length 3 -details 00ad28d0 + Name: Value[] + MethodTable: 03e41044 + EEClass: 03e40fc0 + Size: 132(0x84) bytes + Array: Rank 1, Number of elements 10, Type VALUETYPE + Element Type: Value + [2] 00ad28f0 + Name: Value + MethodTable 03e40f4c + EEClass: 03ef1698 + Size: 20(0x14) bytes + (/home/user/bugs/225271/arraytest) + Fields: + MT Field Offset Type Attr Value Name + 5b9a628c 4000001 0 System.Int32 instance 2 x + 5b9a628c 4000002 4 System.Int32 instance 4 y + 5b9a628c 4000003 8 System.Int32 instance 6 z + [3] 00ad28fc + Name: Value + MethodTable 03e40f4c + EEClass: 03ef1698 + Size: 20(0x14) bytes + (/home/user/bugs/225271/arraytest) + Fields: + MT Field Offset Type Attr Value Name + 5b9a628c 4000001 0 System.Int32 instance 3 x + 5b9a628c 4000002 4 System.Int32 instance 6 y + 5b9a628c 4000003 8 System.Int32 instance 9 z + [4] 00ad2908 + Name: Value + MethodTable 03e40f4c + EEClass: 03ef1698 + Size: 20(0x14) bytes + (/home/user/bugs/225271/arraytest.exe) + Fields: + MT Field Offset Type Attr Value Name + 5b9a628c 4000001 0 System.Int32 instance 4 x + 5b9a628c 4000002 4 System.Int32 instance 8 y + 5b9a628c 4000003 8 System.Int32 instance 12 z + + +\\ + +COMMAND: dumpstackobjects. +DumpStackObjects [-verify] [top stack [bottom stack]] + +This command will display any managed objects it finds within the bounds of +the current stack. Combined with the stack tracing commands like K and +CLRStack, it is a good aid to determining the values of locals and +parameters. + +If you use the -verify option, each non-static CLASS field of an object +candidate is validated. This helps to eliminate false positives. It is not +on by default because very often in a debugging scenario, you are +interested in objects with invalid fields. + +The abbreviation dso can be used for brevity. +\\ + +COMMAND: dumpheap. +DumpHeap [-stat] + [-strings] + [-short] + [-min <size>] + [-max <size>] + [-live] + [-dead] + [-thinlock] + [-startAtLowerBound] + [-mt <MethodTable address>] + [-type <partial type name>] + [start [end]] + +DumpHeap is a powerful command that traverses the garbage collected heap, +collection statistics about objects. With it's various options, it can look for +particular types, restrict to a range, or look for ThinLocks (see SyncBlk +documentation). Finally, it will provide a warning if it detects excessive +fragmentation in the GC heap. + +When called without options, the output is first a list of objects in the heap, +followed by a report listing all the types found, their size and number: + + (lldb) dumpheap + Address MT Size + 00a71000 0015cde8 12 Free + 00a7100c 0015cde8 12 Free + 00a71018 0015cde8 12 Free + 00a71024 5ba58328 68 + 00a71068 5ba58380 68 + 00a710ac 5ba58430 68 + 00a710f0 5ba5dba4 68 + ... + total 619 objects + Statistics: + MT Count TotalSize Class Name + 5ba7607c 1 12 System.Security.Permissions.HostProtectionResource + 5ba75d54 1 12 System.Security.Permissions.SecurityPermissionFlag + 5ba61f18 1 12 System.Collections.CaseInsensitiveComparer + ... + 0015cde8 6 10260 Free + 5ba57bf8 318 18136 System.String + ... + +"Free" objects are simply regions of space the garbage collector can use later. +If 30% or more of the heap contains "Free" objects, the process may suffer from +heap fragmentation. This is usually caused by pinning objects for a long time +combined with a high rate of allocation. Here is example output where DumpHeap +provides a warning about fragmentation: + + <After the Statistics section> + Fragmented blocks larger than 1MB: + Addr Size Followed by + 00a780c0 1.5MB 00bec800 System.Byte[] + 00da4e38 1.2MB 00ed2c00 System.Byte[] + 00f16df0 1.2MB 01044338 System.Byte[] + +The arguments in detail: + +-stat Restrict the output to the statistical type summary +-strings Restrict the output to a statistical string value summary +-short Limits output to just the address of each object. This allows you + to easily pipe output from the command to another debugger + command for automation. +-min Ignore objects less than the size given in bytes +-max Ignore objects larger than the size given in bytes +-live Only print live objects +-dead Only print dead objects (objects which will be collected in the + next full GC) +-thinlock Report on any ThinLocks (an efficient locking scheme, see SyncBlk + documentation for more info) +-startAtLowerBound + Force heap walk to begin at lower bound of a supplied address range. + (During plan phase, the heap is often not walkable because objects + are being moved. In this case, DumpHeap may report spurious errors, + in particular bad objects. It may be possible to traverse more of + the heap after the reported bad object. Even if you specify an + address range, DumpHeap will start its walk from the beginning of + the heap by default. If it finds a bad object before the specified + range, it will stop before displaying the part of the heap in which + you are interested. This switch will force DumpHeap to begin its + walk at the specified lower bound. You must supply the address of a + good object as the lower bound for this to work. Display memory at + the address of the bad object to manually find the next method + table (use DumpMT to verify). If the GC is currently in a call to + memcopy, You may also be able to find the next object's address by + adding the size to the start address given as parameters.) +-mt List only those objects with the MethodTable given +-type List only those objects whose type name is a substring match of the + string provided. +start Begin listing from this address +end Stop listing at this address + +A special note about -type: Often, you'd like to find not only Strings, but +System.Object arrays that are constrained to contain Strings. ("new +String[100]" actually creates a System.Object array, but it can only hold +System.String object pointers). You can use -type in a special way to find +these arrays. Just pass "-type System.String[]" and those Object arrays will +be returned. More generally, "-type <Substring of interesting type>[]". + +The start/end parameters can be obtained from the output of eeheap -gc. For +example, if you only want to list objects in the large heap segment: + + (lldb) eeheap -gc + Number of GC Heaps: 1 + generation 0 starts at 0x00c32754 + generation 1 starts at 0x00c32748 + generation 2 starts at 0x00a71000 + segment begin allocated size + 00a70000 00a71000 010443a8 005d33a8(6108072) + Large object heap starts at 0x01a71000 + segment begin allocated size + 01a70000 01a71000 01a75000 0x00004000(16384) + Total Size 0x5d73a8(6124456) + ------------------------------ + GC Heap Size 0x5d73a8(6124456) + + (lldb) dumpheap 1a71000 1a75000 + Address MT Size + 01a71000 5ba88bd8 2064 + 01a71810 0019fe48 2032 Free + 01a72000 5ba88bd8 4096 + 01a73000 0019fe48 4096 Free + 01a74000 5ba88bd8 4096 + total 5 objects + Statistics: + MT Count TotalSize Class Name + 0019fe48 2 6128 Free + 5ba88bd8 3 10256 System.Object[] + Total 5 objects + +Finally, if GC heap corruption is present, you may see an error like this: + + (lldb) dumpheap -stat + object 00a73d24: does not have valid MT + curr_object : 00a73d24 + Last good object: 00a73d14 + ---------------- + +That indicates a serious problem. See the help for VerifyHeap for more +information on diagnosing the cause. +\\ + +COMMAND: dumpvc. +DumpVC <MethodTable address> <Address> + +DumpVC allows you to examine the fields of a value class. In C#, this is a +struct, and lives on the stack or within an Object on the GC heap. You need +to know the MethodTable address to tell SOS how to interpret the fields, as +a value class is not a first-class object with it's own MethodTable as the +first field. For example: + + (lldb) sos DumpObj a79d98 + Name: Mainy + MethodTable: 009032d8 + EEClass: 03ee1424 + Size: 28(0x1c) bytes + (/home/user/pub/unittest) + Fields: + MT Field Offset Type Attr Value Name + 0090320c 4000010 4 VALUETYPE instance 00a79d9c m_valuetype + 009032d8 400000f 4 CLASS static 00a79d54 m_sExcep + +m_valuetype is a value type. The value in the MT column (0090320c) is the +MethodTable for it, and the Value column provides the start address: + + (lldb) sos DumpVC 0090320c 00a79d9c + Name: Funny + MethodTable 0090320c + EEClass: 03ee14b8 + Size: 28(0x1c) bytes + (/home/user/pub/unittest) + Fields: + MT Field Offset Type Attr Value Name + 0090320c 4000001 0 CLASS instance 00a743d8 signature + 0090320c 4000002 8 System.Int32 instance 2345 m1 + 0090320c 4000003 10 System.Boolean instance 1 b1 + 0090320c 4000004 c System.Int32 instance 1234 m2 + 0090320c 4000005 4 CLASS instance 00a79d98 backpointer + +DumpVC is quite a specialized function. Some managed programs make heavy use +of value classes, while others do not. +\\ + +COMMAND: gcroot. +GCRoot [-nostacks] <Object address> + +GCRoot looks for references (or roots) to an object. These can exist in four +places: + + 1. On the stack + 2. Within a GC Handle + 3. In an object ready for finalization + 4. As a member of an object found in 1, 2 or 3 above. + +First, all stacks will be searched for roots, then handle tables, and finally +the freachable queue of the finalizer. Some caution about the stack roots: +GCRoot doesn't attempt to determine if a stack root it encountered is valid +or is old (discarded) data. You would have to use CLRStack and U to +disassemble the frame that the local or argument value belongs to in order to +determine if it is still in use. + +Because people often want to restrict the search to gc handles and freachable +objects, there is a -nostacks option. +\\ + +COMMAND: pe. +COMMAND: printexception. +PrintException [-nested] [-lines] [-ccw] [<Exception object address>] [<CCW pointer>] + +This will format fields of any object derived from System.Exception. One of the +more useful aspects is that it will format the _stackTrace field, which is a +binary array. If _stackTraceString field is not filled in, that can be helpful +for debugging. You can of course use DumpObj on the same exception object to +explore more fields. + +If called with no parameters, PrintException will look for the last outstanding +exception on the current thread and print it. This will be the same exception +that shows up in a run of clrthreads. + +PrintException will notify you if there are any nested exceptions on the +current managed thread. (A nested exception occurs when you throw another +exception within a catch handler already being called for another exception). +If there are nested exceptions, you can re-run PrintException with the +"-nested" option to get full details on the nested exception objects. The +clrthreads command will also tell you which threads have nested exceptions. + +PrintException can display source information if available, by specifying the +-lines command line argument. + +PrintException prints the exception object corresponding to a given CCW pointer, +which can be specified using the -ccw option. + +The abbreviation 'pe' can be used for brevity. +\\ + +COMMAND: threadstate. +ThreadState value + +The clrthreads command outputs, among other things, the state of the thread. +This is a bit field which corresponds to various states the thread is in. +To check the state of the thread, simply pass that bit field from the +output of clrthreads into ThreadState. + +Example: + (lldb) clrthreads + ThreadCount: 2 + UnstartedThread: 0 + BackgroundThread: 1 + PendingThread: 0 + DeadThread: 0 + Hosted Runtime: no + PreEmptive GC Alloc Lock + ID OSID ThreadOBJ State GC Context Domain Count APT Exception + 0 1 250 0019b068 a020 Disabled 02349668:02349fe8 0015def0 0 MTA + 2 2 944 001a6020 b220 Enabled 00000000:00000000 0015def0 0 MTA (Finalizer) + 0:003> sos ThreadState b220 + Legal to Join + Background + CLR Owns + CoInitialized + In Multi Threaded Apartment + +Possible thread states: + Thread Abort Requested + GC Suspend Pending + User Suspend Pending + Debug Suspend Pending + GC On Transitions + Legal to Join + Yield Requested + Hijacked by the GC + Blocking GC for Stack Overflow + Background + Unstarted + Dead + CLR Owns + CoInitialized + In Single Threaded Apartment + In Multi Threaded Apartment + Reported Dead + Fully initialized + Task Reset + Sync Suspended + Debug Will Sync + Stack Crawl Needed + Suspend Unstarted + Aborted + Thread Pool Worker Thread + Interruptible + Interrupted + Completion Port Thread + Abort Initiated + Finalized + Failed to Start + Detached +\\ +COMMAND: threads. +COMMAND: clrthreads. +Threads [-live] [-special] + +Threads (clrthreads) lists all the mananaged threads in the process. + +-live: optional. Only print threads associated with a live thread. +-special: optional. With this switch, the command will display all the special + threads created by CLR. Those threads might not be managed threads + so they might not be shown in the first part of the command's + output. Example of special threads include: GC threads (in + concurrent GC and server GC), Debugger helper threads, Finalizer + threads, AppDomain Unload threads, and Threadpool timer threads. + +Each thread has many attributes, many of which can be ignored. The important +ones are discussed below: + +There are three ID columns: + +1) The debugger shorthand ID (When the runtime is hosted this column might + display the special string "<<<<" when this internal thread object is not + associated with any physical thread - this may happen when the host reuses + the runtime internal thread object) +2) The CLR Thread ID +3) The OS thread ID. + +If PreEmptiveGC is enabled for a thread, then a garbage collection +can occur while that thread is running. For example, if you break in while +a managed thread is making a PInvoke call to a Win32 function, that thread +will be in PreEmptive GC mode. + +The Domain column indicates what AppDomain the thread is currently executing +in. You can pass this value to DumpDomain to find out more. + +The APT column gives the COM apartment mode. + +Exception will list the last thrown exception (if any) for the thread. More +details can be obtained by passing the pointer value to PrintException. If +you get the notation "(nested exceptions)", you can get details on those +exceptions by switching to the thread in question, and running +"PrintException -nested". +\\ + +COMMAND: clrstack. +CLRStack [-a] [-l] [-p] [-n] [-f] +CLRStack [-a] [-l] [-p] [-i] [variable name] [frame] + +CLRStack attempts to provide a true stack trace for managed code only. It is +handy for clean, simple traces when debugging straightforward managed +programs. The -p parameter will show arguments to the managed function. The +-l parameter can be used to show information on local variables in a frame. +SOS can't retrieve local names at this time, so the output for locals is in +the format <local address> = <value>. The -a (all) parameter is a short-cut +for -l and -p combined. + +The -f option (full mode) displays the native frames intermixing them with +the managed frames and the assembly name and function offset for the managed +frames. + +If the debugger has the option SYMOPT_LOAD_LINES specified (either by the +.lines or .symopt commands), SOS will look up the symbols for every managed +frame and if successful will display the corresponding source file name and +line number. The -n (No line numbers) parameter can be specified to disable +this behavior. + +When you see methods with the name "[Frame:...", that indicates a transition +between managed and unmanaged code. You could run IP2MD on the return +addresses in the call stack to get more information on each managed method. + +On x64 platforms, Transition Frames are not displayed at this time. To avoid +heavy optimization of parameters and locals one can request the JIT compiler +to not optimize functions in the managed app by creating a file myapp.ini +(if your program is myapp.exe) in the same directory. Put the following lines +in myapp.ini and re-run: + +[.NET Framework Debugging Control] +GenerateTrackingInfo=1 +AllowOptimize=0 + +The -i option is a new EXPERIMENTAL addition to CLRStack and will use the ICorDebug +interfaces to display the managed stack and variables. With this option you can also +view and expand arrays and fields for managed variables. If a stack frame number is +specified in the command line, CLRStack will show you the parameters and/or locals +only for that frame (provided you specify -l or -p or -a of course). If a variable +name and a stack frame number are specified in the command line, CLRStack will show +you the parameters and/or locals for that frame, and will also show you the fields +for that variable name you specified. Here are some examples: + clrstack -i -a : This will show you all parameters and locals for all frames + clrstack -i -a 3 : This will show you all parameters and locals, for frame 3 + clrstack -i var1 0 : This will show you the fields of 'var1' for frame 0 + clrstack -i var1.abc 2 : This will show you the fields of 'var1', and expand + 'var1.abc' to show you the fields of the 'abc' field, + for frame 2. + clrstack -i var1.[basetype] 0 : This will show you the fields of 'var1', and + expand the base type of 'var1' to show you its + fields. + clrstack -i var1.[6] 0 : If 'var1' is an array, this will show you the element + at index 6 in the array, along with its fields +The -i options uses DML output for a better debugging experience, so typically you +should only need to execute "clrstack -i", and from there, click on the DML +hyperlinks to inspect the different managed stack frames and managed variables. +\\ + +COMMAND: ip2md. +IP2MD <Code address> + +Given an address in managed JITTED code, IP2MD attempts to find the MethodDesc +associated with it. For example, this output from K: + + (lldb) bt + ... + frame #9: 0x00007fffffffbf60 0x00007ffff61c6d89 libcoreclr.so`MethodDesc::DoPrestub(this=0x00007ffff041f870, pDispatchingMT=0x0000000000000000) + 3001 at prestub.cpp:1490 + frame #10: 0x00007fffffffc140 0x00007ffff61c5f17 libcoreclr.so`::PreStubWorker(pTransitionBlock=0x00007fffffffc9a8, pMD=0x00007ffff041f870) + 1399 at prestub.cpp:1037 + frame #11: 0x00007fffffffc920 0x00007ffff5f5238c libcoreclr.so`ThePreStub + 92 at theprestubamd64.S:800 + frame #12: 0x00007fffffffca10 0x00007ffff04981cc + frame #13: 0x00007fffffffca30 0x00007ffff049773c + frame #14: 0x00007fffffffca80 0x00007ffff04975ad + ... + frame #22: 0x00007fffffffcc90 0x00007ffff5f51a0f libcoreclr.so`CallDescrWorkerInternal + 124 at calldescrworkeramd64.S:863 + frame #23: 0x00007fffffffccb0 0x00007ffff5d6d6dc libcoreclr.so`CallDescrWorkerWithHandler(pCallDescrData=0x00007fffffffce80, fCriticalCall=0) + 476 at callhelpers.cpp:88 + frame #24: 0x00007fffffffcd00 0x00007ffff5d6eb38 libcoreclr.so`MethodDescCallSite::CallTargetWorker(this=0x00007fffffffd0c8, pArguments=0x00007fffffffd048) + 2504 at callhelpers.cpp:633 + + (lldb) ip2md 0x00007ffff049773c + MethodDesc: 00007ffff7f71920 + Method Name: Microsoft.Win32.SafeHandles.SafeFileHandle.Open(System.Func`1<Int32>) + Class: 00007ffff0494bf8 + MethodTable: 00007ffff7f71a58 + mdToken: 0000000006000008 + Module: 00007ffff7f6b938 + IsJitted: yes + CodeAddr: 00007ffff04976c0 + Transparency: Critical + +We have taken a return address into Mainy.Main, and discovered information +about that method. You could run U, DumpMT, DumpClass, DumpMD, or +DumpModule on the fields listed to learn more. + +The "Source line" output will only be present if the debugger can find the +symbols for the managed module containing the given <code address>, and if the +debugger is configured to load line number information. +\\ + +COMMAND: clru. +COMMAND: u. +U [-gcinfo] [-ehinfo] [-n] [-o] <MethodDesc address> | <Code address> + +Presents an annotated disassembly of a managed method when given a MethodDesc +pointer for the method, or a code address within the method body. Unlike the +debugger "U" function, the entire method from start to finish is printed, +with annotations that convert metadata tokens to names. + + <example output> + ... + 03ef015d b901000000 mov ecx,0x1 + 03ef0162 ff156477a25b call dword ptr [mscorlib_dll+0x3c7764 (5ba27764)] (System.Console.InitializeStdOutError(Boolean), mdToken: 06000713) + 03ef0168 a17c20a701 mov eax,[01a7207c] (Object: SyncTextWriter) + 03ef016d 89442414 mov [esp+0x14],eax + +If you pass the -gcinfo flag, you'll get inline display of the GCInfo for +the method. You can also obtain this information with the GCInfo command. + +If you pass the -ehinfo flag, you'll get inline display of exception info +for the method. (Beginning and end of try/finally/catch handlers, etc.). +You can also obtain this information with the EHInfo command. + +If you pass the -o flag, the byte offset of each instruction from the +beginning of the method will be printed in addition to the absolute address of +the instruction. + +If the debugger has the option SYMOPT_LOAD_LINES specified (either by the +.lines or .symopt commands), and if symbols are available for the managed +module containing the method being examined, the output of the command will +include the source file name and line number corresponding to the +disassembly. The -n (No line numbers) flag can be specified to disable this +behavior. + + <example output> + ... + c:\Code\prj.mini\exc.cs @ 38: + 001b00b0 8b0d3020ab03 mov ecx,dword ptr ds:[3AB2030h] ("Break in debugger. When done type <Enter> to continue: ") + 001b00b6 e8d5355951 call mscorlib_ni+0x8b3690 (51743690) (System.Console.Write(System.String), mdToken: 0600091b) + 001b00bb 90 nop + + c:\Code\prj.mini\exc.cs @ 39: + 001b00bc e863cdc651 call mscorlib_ni+0xf8ce24 (51e1ce24) (System.Console.ReadLine(), mdToken: 060008f6) + >>> 001b00c1 90 nop + ... +\\ + +COMMAND: dumpstack. +DumpStack [-EE] [-n] [top stack [bottom stack]] + +[x86 and x64 documentation] + +This command provides a verbose stack trace obtained by "scraping." Therefore +the output is very noisy and potentially confusing. The command is good for +viewing the complete call stack when "kb" gets confused. For best results, +make sure you have valid symbols. + +-EE will only show managed functions. + +If the debugger has the option SYMOPT_LOAD_LINES specified (either by the +.lines or .symopt commands), SOS will look up the symbols for every managed +frame and if successful will display the corresponding source file name and +line number. The -n (No line numbers) parameter can be specified to disable +this behavior. + +You can also pass a stack range to limit the output. +\\ + +COMMAND: eestack. +EEStack [-short] [-EE] + +This command runs DumpStack on all threads in the process. The -EE option is +passed directly to DumpStack. The -short option tries to narrow down the +output to "interesting" threads only, which is defined by + +1) The thread has taken a lock. +2) The thread has been "hijacked" in order to allow a garbage collection. +3) The thread is currently in managed code. + +See the documentation for DumpStack for more info. +\\ + +COMMAND: ehinfo. +EHInfo (<MethodDesc address> | <Code address>) + +EHInfo shows the exception handling blocks in a jitted method. For each +handler, it shows the type, including code addresses and offsets for the clause +block and the handler block. For a TYPED handler, this would be the "try" and +"catch" blocks respectively. + +Sample output: + + (lldb) sos EHInfo 33bbd3a + MethodDesc: 03310f68 + Method Name: MainClass.Main() + Class: 03571358 + MethodTable: 0331121c + mdToken: 0600000b + Module: 001e2fd8 + IsJitted: yes + CodeAddr: 033bbca0 + Transparency: Critical + + EHHandler 0: TYPED catch(System.IO.FileNotFoundException) + Clause: [033bbd2b, 033bbd3c] [8b, 9c] + Handler: [033bbd3c, 033bbd50] [9c, b0] + + EHHandler 1: FINALLY + Clause: [033bbd83, 033bbda3] [e3, 103] + Handler: [033bbda3, 033bbdc5] [103, 125] + + EHHandler 2: TYPED catch(System.Exception) + Clause: [033bbd7a, 033bbdc5] [da, 125] + Handler: [033bbdc5, 033bbdd6] [125, 136] + +\\ + +COMMAND: gcinfo. +GCInfo (<MethodDesc address> | <Code address>) + +GCInfo is especially useful for CLR Devs who are trying to determine if there +is a bug in the JIT Compiler. It parses the GCEncoding for a method, which is a +compressed stream of data indicating when registers or stack locations contain +managed objects. It is important to keep track of this information, because if +a garbage collection occurs, the collector needs to know where roots are so it +can update them with new object pointer values. + +Here is sample output where you can see the change in register state. Normally +you would print this output out and read it alongside a disassembly of the +method. For example, the notation "reg EDI becoming live" at offset 0x11 of the +method might correspond to a "mov edi,ecx" statement. + + (lldb) sos GCInfo 5b68dbb8 (5b68dbb8 is the start of a JITTED method) + entry point 5b68dbb8 + preJIT generated code + GC info 5b9f2f09 + Method info block: + method size = 0036 + prolog size = 19 + epilog size = 8 + epilog count = 1 + epilog end = yes + saved reg. mask = 000B + ebp frame = yes + fully interruptible=yes + double align = no + security check = no + exception handlers = no + local alloc = no + edit & continue = no + varargs = no + argument count = 4 + stack frame size = 1 + untracked count = 5 + var ptr tab count = 0 + epilog at 002E + 36 D4 8C C7 AA | + 93 F3 40 05 | + + Pointer table: + 14 | [EBP+14H] an untracked local + 10 | [EBP+10H] an untracked local + 0C | [EBP+0CH] an untracked local + 08 | [EBP+08H] an untracked local + 44 | [EBP-04H] an untracked local + F1 79 | 0011 reg EDI becoming live + 72 | 0013 reg ESI becoming live + 83 | 0016 push ptr 0 + 8B | 0019 push ptr 1 + 93 | 001C push ptr 2 + 9B | 001F push ptr 3 + 56 | 0025 reg EDX becoming live + 4A | 0027 reg ECX becoming live + 0E | 002D reg ECX becoming dead + 10 | 002D reg EDX becoming dead + E0 | 002D pop 4 ptrs + F0 31 | 0036 reg ESI becoming dead + 38 | 0036 reg EDI becoming dead + FF | + +This function is important for CLR Devs, but very difficult for anyone else to +make sense of it. You would usually come to use it if you suspect a gc heap +corruption bug caused by invalid GCEncoding for a particular method. +\\ + +COMMAND: bpmd. +bpmd [-nofuturemodule] <module name> <method name> [<il offset>] +bpmd -md <MethodDesc> +bpmd -list +bpmd -clear <pending breakpoint number> +bpmd -clearall + +bpmd provides managed breakpoint support. If it can resolve the method name +to a loaded, jitted or ngen'd function it will create a breakpoint with "bp". +If not then either the module that contains the method hasn't been loaded yet +or the module is loaded, but the function is not jitted yet. In these cases, +bpmd asks the Windows Debugger to receive CLR Notifications, and waits to +receive news of module loads and JITs, at which time it will try to resolve +the function to a breakpoint. -nofuturemodule can be used to suppress +creating a breakpoint against a module that has not yet been loaded. + +Management of the list of pending breakpoints can be done via bpmd -list, +bpmd -clear, and bpmd -clearall commands. bpmd -list generates a list of +all of the pending breakpoints. If the pending breakpoint has a non-zero +module id, then that pending breakpoint is specific to function in that +particular loaded module. If the pending breakpoint has a zero module id, then +the breakpoint applies to modules that have not yet been loaded. Use +bpmd -clear or bpmd -clearall to remove pending breakpoints from the list. + +This brings up a good question: "I want to set a breakpoint on the main +method of my application. How can I do this?" + + 1) Stop after coreclr is loaded - TBD + + 2) Add the breakpoint with command such as: + bpmd myapp.exe MyApp.Main + 3) g + 4) You will stop at the start of MyApp.Main. If you type "bl" you will + see the breakpoint listed. + +To correctly specify explicitly implemented methods make sure to retrieve the +method name from the metadata, or from the output of the "dumpmt -md" command. +For example: + + public interface I1 + { + void M1(); + } + public class ExplicitItfImpl : I1 + { + ... + void I1.M1() // this method's name is 'I1.M1' + { ... } + } + + bpmd myapp.exe ExplicitItfImpl.I1.M1 + + +bpmd works equally well with generic types. Adding a breakpoint on a generic +type sets breakpoints on all already JIT-ted generic methods and sets a pending +breakpoint for any instantiation that will be JIT-ted in the future. + +Example for generics: + Given the following two classes: + + class G3<T1, T2, T3> + { + ... + public void F(T1 p1, T2 p2, T3 p3) + { ... } + } + + public class G1<T> { + // static method + static public void G<W>(W w) + { ... } + } + + One would issue the following commands to set breapoints on G3.F() and + G1.G(): + + bpmd myapp.exe G3`3.F + bpmd myapp.exe G1`1.G + +And for explicitly implemented methods on generic interfaces: + public interface IT1<T> + { + void M1(T t); + } + + public class ExplicitItfImpl<U> : IT1<U> + { + ... + void IT1<U>.M1(U u) // this method's name is 'IT1<U>.M1' + { ... } + } + + bpmd bpmd.exe ExplicitItfImpl`1.IT1<U>.M1 + +Additional examples: + If IT1 and ExplicitItfImpl are types declared inside another class, + Outer, the bpmd command would become: + + bpmd bpmd.exe Outer+ExplicitItfImpl`1.Outer.IT1<U>.M1 + + (note that the fully qualified type name for ExplicitItfImpl became + Outer+ExplicitItfImpl, using the '+' separator, while the method name + is Outer.IT1<U>.M1, using a '.' as the separator) + + Furthermore, if the Outer class resides in a namespace, NS, the bpmd + command to use becomes: + + bpmd bpmd.exe NS.Outer+ExplicitItfImpl`1.NS.Outer.IT1<U>.M1 + +bpmd does not accept offsets nor parameters in the method name. You can add +an IL offset as an optional parameter seperate from the name. If there are overloaded +methods, bpmd will set a breakpoint for all of them. + +In the case of hosted environments such as SQL, the module name may be +complex, like 'price, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. +For this case, just be sure to surround the module name with single quotes, +like: + +bpmd 'price, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' Price.M2 + +\\ + +COMMAND: dumpdomain. +DumpDomain [<Domain address>] + +When called with no parameters, DumpDomain will list all the AppDomains in the +process. It enumerates each Assembly loaded into those AppDomains as well. +In addition to your application domain, and any domains it might create, there +are two special domains: the Shared Domain and the System Domain. + +Any Assembly pointer in the output can be passed to DumpAssembly. Any Module +pointer in the output can be passed to DumpModule. Any AppDomain pointer can +be passed to DumpDomain to limit output only to that AppDomain. Other +functions provide an AppDomain pointer as well, such as clrthreads where it lists +the current AppDomain for each thread. +\\ + +COMMAND: eeheap. +EEHeap [-gc] [-loader] + +EEHeap enumerates process memory consumed by internal CLR data structures. You +can limit the output by passing "-gc" or "-loader". All information will be +displayed otherwise. + +The information for the Garbage Collector lists the ranges of each Segment in +the managed heap. This can be useful if you believe you have an object pointer. +If the pointer falls within a segment range given by "eeheap -gc", then you do +have an object pointer, and can attempt to run "dumpobj" on it. + +Here is output for a simple program: + + (lldb) eeheap -gc + Number of GC Heaps: 1 + generation 0 starts at 0x00a71018 + generation 1 starts at 0x00a7100c + generation 2 starts at 0x00a71000 + segment begin allocated size + 00a70000 00a71000 00a7e01c 0000d01c(53276) + Large object heap starts at 0x01a71000 + segment begin allocated size + 01a70000 01a71000 01a76000 0x00005000(20480) + Total Size 0x1201c(73756) + ------------------------------ + GC Heap Size 0x1201c(73756) + +So the total size of the GC Heap is only 72K. On a large web server, with +multiple processors, you can expect to see a GC Heap of 400MB or more. The +Garbage Collector attempts to collect and reclaim memory only when required to +by memory pressure for better performance. You can also see the notion of +"generations," wherein the youngest objects live in generation 0, and +long-lived objects eventually get "promoted" to generation 2. + +The loader output lists various private heaps associated with AppDomains. It +also lists heaps associated with the JIT compiler, and heaps associated with +Modules. For example: + + (lldb) eeheap -loader + Loader Heap: + -------------------------------------- + System Domain: 5e0662a0 + LowFrequencyHeap:008f0000(00002000:00001000) Size: 0x00001000 bytes. + HighFrequencyHeap:008f2000(00008000:00001000) Size: 0x00001000 bytes. + StubHeap:008fa000(00002000:00001000) Size: 0x00001000 bytes. + Total size: 0x3000(12288)bytes + -------------------------------------- + Shared Domain: 5e066970 + LowFrequencyHeap:00920000(00002000:00001000) 03e30000(00010000:00003000) Size: 0x00004000 bytes. + Wasted: 0x00001000 bytes. + HighFrequencyHeap:00922000(00008000:00001000) Size: 0x00001000 bytes. + StubHeap:0092a000(00002000:00001000) Size: 0x00001000 bytes. + Total size: 0x6000(24576)bytes + -------------------------------------- + Domain 1: 14f000 + LowFrequencyHeap:00900000(00002000:00001000) 03ee0000(00010000:00003000) Size: 0x00004000 bytes. + Wasted: 0x00001000 bytes. + HighFrequencyHeap:00902000(00008000:00003000) Size: 0x00003000 bytes. + StubHeap:0090a000(00002000:00001000) Size: 0x00001000 bytes. + Total size: 0x8000(32768)bytes + -------------------------------------- + Jit code heap: + Normal JIT:03ef0000(00010000:00002000) Size: 0x00002000 bytes. + Total size: 0x2000(8192)bytes + -------------------------------------- + Module Thunk heaps: + Module 5ba22410: Size: 0x00000000 bytes. + Module 001c1320: Size: 0x00000000 bytes. + Module 001c03f0: Size: 0x00000000 bytes. + Module 001caa38: Size: 0x00000000 bytes. + Total size: 0x0(0)bytes + -------------------------------------- + Module Lookup Table heaps: + Module 5ba22410:Size: 0x00000000 bytes. + Module 001c1320:Size: 0x00000000 bytes. + Module 001c03f0:Size: 0x00000000 bytes. + Module 001caa38:03ec0000(00010000:00002000) Size: 0x00002000 bytes. + Total size: 0x2000(8192)bytes + -------------------------------------- + Total LoaderHeap size: 0x15000(86016)bytes + ======================================= + +By using eeheap to keep track of the growth of these private heaps, we are +able to rule out or include them as a source of a memory leak. +\\ + +COMMAND: name2ee. +Name2EE <module name> <type or method name> +Name2EE <module name>!<type or method name> + +This function allows you to turn a class name into a MethodTable and EEClass. +It turns a method name into a MethodDesc. Here is an example for a method: + + (lldb) name2ee unittest.exe MainClass.Main + Module: 001caa38 + Token: 0x0600000d + MethodDesc: 00902f40 + Name: MainClass.Main() + JITTED Code Address: 03ef00b8 + +and for a class: + + (lldb) name2ee unittest!MainClass + Module: 001caa38 + Token: 0x02000005 + MethodTable: 009032d8 + EEClass: 03ee1424 + Name: MainClass + +The module you are "browsing" with Name2EE needs to be loaded in the process. +To get a type name exactly right, first browse the module with ILDASM. You +can also pass * as the <module name> to search all loaded managed modules. +<module name> can also be the debugger's name for a module, such as +mscorlib or image00400000. + +The <module>!<type> syntax is also supported. You can use an asterisk on the +left of the !, but the type on the right side needs to be fully qualified. + +If you are looking for a way to display a static field of a class (and you +don't have an instance of the class, so dumpobj won't help you), note that +once you have the EEClass, you can run DumpClass, which will display the +value of all static fields. + +There is yet one more way to specify a module name. In the case of modules +loaded from an assembly store (such as a SQL db) rather than disk, the +module name will look like this: + +price, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + +For this kind of module, simply use price as the module name: + + 0:044> name2ee price Price + Module: 10f028b0 (price, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null) + Token: 0x02000002 + MethodTable: 11a47ae0 + EEClass: 11a538c8 + Name: Price + +Where are we getting these module names from? Run DumpDomain to see a list of +all loaded modules in all domains. And remember that you can browse all the +types in a module with DumpModule -mt <module pointer>. +\\ + +COMMAND: dumpmt. +DumpMT [-MD] <MethodTable address> + +Examine a MethodTable. Each managed object has a MethodTable pointer at the +start. If you pass the "-MD" flag, you'll also see a list of all the methods +defined on the object. +\\ + +COMMAND: dumpclass. +DumpClass <EEClass address> + +The EEClass is a data structure associated with an object type. DumpClass +will show attributes, as well as list the fields of the type. The output is +similar to DumpObj. Although static field values will be displayed, +non-static values won't because you need an instance of an object for that. + +You can get an EEClass to look at from DumpMT, DumpObj, Name2EE, and +Token2EE among others. +\\ + +COMMAND: dumpmd. +DumpMD <MethodDesc address> + +This command lists information about a MethodDesc. You can use ip2md to turn +a code address in a managed function into a MethodDesc: + + (lldb) dumpmd 902f40 + Method Name: Mainy.Main() + Class: 03ee1424 + MethodTable: 009032d8 + mdToken: 0600000d + Module: 001caa78 + IsJitted: yes + CodeAddr: 03ef00b8 + +If IsJitted is "yes," you can run U on the CodeAddr pointer to see a +disassembly of the JITTED code. You can call also DumpClass, DumpMT, +DumpModule on the Class, MethodTable and Module fields above. +\\ + +COMMAND: token2ee. +Token2EE <module name> <token> + +This function allows you to turn a metadata token into a MethodTable or +MethodDesc. Here is an example showing class tokens being resolved: + + (lldb) sos Token2EE unittest.exe 02000003 + Module: 001caa38 + Token: 0x02000003 + MethodTable: 0090375c + EEClass: 03ee1ae0 + Name: Bank + (lldb) sos Token2EE image00400000 02000004 + Module: 001caa38 + Token: 0x02000004 + MethodTable: 009038ec + EEClass: 03ee1b84 + Name: Customer + +The module you are "browsing" with Token2EE needs to be loaded in the process. +This function doesn't see much use, especially since a tool like ILDASM can +show the mapping between metadata tokens and types/methods in a friendlier way. +But it could be handy sometimes. + +You can pass "*" for <module name> to find what that token maps to in every +loaded managed module. <module name> can also be the debugger's name for a +module, such as mscorlib or image00400000. +\\ + +COMMAND: dumpmodule. +DumpModule [-mt] <Module address> + +You can get a Module address from DumpDomain, DumpAssembly and other +functions. Here is sample output: + + (lldb) sos DumpModule 1caa50 + Name: /home/user/pub/unittest + Attributes: PEFile + Assembly: 001ca248 + LoaderHeap: 001cab3c + TypeDefToMethodTableMap: 03ec0010 + TypeRefToMethodTableMap: 03ec0024 + MethodDefToDescMap: 03ec0064 + FieldDefToDescMap: 03ec00a4 + MemberRefToDescMap: 03ec00e8 + FileReferencesMap: 03ec0128 + AssemblyReferencesMap: 03ec012c + MetaData start address: 00402230 (1888 bytes) + +The Maps listed map metadata tokens to CLR data structures. Without going into +too much detail, you can examine memory at those addresses to find the +appropriate structures. For example, the TypeDefToMethodTableMap above can be +examined: + + (lldb) dd 3ec0010 + 03ec0010 00000000 00000000 0090320c 0090375c + 03ec0020 009038ec ... + +This means TypeDef token 2 maps to a MethodTable with the value 0090320c. You +can run DumpMT to verify that. The MethodDefToDescMap takes a MethodDef token +and maps it to a MethodDesc, which can be passed to dumpmd. + +There is a new option "-mt", which will display the types defined in a module, +and the types referenced by the module. For example: + + (lldb) sos DumpModule -mt 1aa580 + Name: /home/user/pub/unittest + ...<etc>... + MetaData start address: 0040220c (1696 bytes) + + Types defined in this module + + MT TypeDef Name + -------------------------------------------------------------------------- + 030d115c 0x02000002 Funny + 030d1228 0x02000003 Mainy + + Types referenced in this module + + MT TypeRef Name + -------------------------------------------------------------------------- + 030b6420 0x01000001 System.ValueType + 030b5cb0 0x01000002 System.Object + 030fceb4 0x01000003 System.Exception + 0334e374 0x0100000c System.Console + 03167a50 0x0100000e System.Runtime.InteropServices.GCHandle + 0336a048 0x0100000f System.GC + +\\ + +COMMAND: dumpassembly. +DumpAssembly <Assembly address> + +Example output: + + (lldb) sos DumpAssembly 1ca248 + Parent Domain: 0014f000 + Name: /home/user/pub/unittest + ClassLoader: 001ca060 + Module Name + 001caa50 /home/user/pub/unittest + +An assembly can consist of multiple modules, and those will be listed. You can +get an Assembly address from the output of DumpDomain. +\\ + +COMMAND: dumpruntimetypes. +DumpRuntimeTypes + +DumpRuntimeTypes finds all System.RuntimeType objects in the gc heap and +prints the type name and MethodTable they refer too. Sample output: + + Address Domain MT Type Name + ------------------------------------------------------------------------------ + a515f4 14a740 5baf8d28 System.TypedReference + a51608 14a740 5bb05764 System.Globalization.BaseInfoTable + a51958 14a740 5bb05b24 System.Globalization.CultureInfo + a51a44 14a740 5bb06298 System.Globalization.GlobalizationAssembly + a51de0 14a740 5bb069c8 System.Globalization.TextInfo + a56b98 14a740 5bb12d28 System.Security.Permissions.HostProtectionResource + a56bbc 14a740 5baf7248 System.Int32 + a56bd0 14a740 5baf3fdc System.String + a56cfc 14a740 5baf36a4 System.ValueType + ... + +This command will print a "?" in the domain column if the type is loaded into multiple +AppDomains. For example: + + (lldb) sos DumpRuntimeTypes + Address Domain MT Type Name + ------------------------------------------------------------------------------ + 28435a0 ? 3f6a8c System.TypedReference + 28435b4 ? 214d6c System.ValueType + 28435c8 ? 216314 System.Enum + 28435dc ? 2147cc System.Object + 284365c ? 3cd57c System.IntPtr + 2843670 ? 3feaac System.Byte + 2843684 ? 23a544c System.IEquatable`1[[System.IntPtr, mscorlib]] + 2843784 ? 3c999c System.Int32 + 2843798 ? 3caa04 System.IEquatable`1[[System.Int32, mscorlib]] +\\ + +COMMAND: dumpsig. +DumpSig <sigaddr> <moduleaddr> + +This command dumps the signature of a method or field given by <sigaddr>. This is +useful when you are debugging parts of the runtime which returns a raw PCCOR_SIGNATURE +structure and need to know what its contents are. + +Sample output for a method: + 0:000> sos DumpSig 0x000007fe`ec20879d 0x000007fe`eabd1000 + [DEFAULT] [hasThis] Void (Boolean,String,String) + +The first section of the output is the calling convention. This includes, but is not +limited to, "[DEFAULT]", "[C]", "[STDCALL]", "[THISCALL]", and so on. The second +portion of the output is either "[hasThis]" or "[explicit]" for whether the method +is an instance method or a static method respectively. The third portion of the +output is the return value (in this case a "void"). Finally, the method's arguments +are printed as the final portion of the output. + +Sample output for a field: + 0:000> sos DumpSig 0x000007fe`eb7fd8cd 0x000007fe`eabd1000 + [FIELD] ValueClass System.RuntimeTypeHandle + +DumpSig will also work with generics. Here is the output for the following +function: + public A Test(IEnumerable<B> n) + + 0:000> sos DumpSig 00000000`00bc2437 000007ff00043178 + [DEFAULT] [hasThis] __Canon (Class System.Collections.Generic.IEnumerable`1<__Canon>) +\\ + +COMMAND: dumpsigelem. +DumpSigElem <sigaddr> <moduleaddr> + +This command dumps a single element of a signature object. For most circumstances, +you should use DumpSig to look at individual signature objects, but if you find a +signature that has been corrupted in some manner you can use DumpSigElem to read out +the valid portions of it. + +If we look at a valid signature object for a method we see the following: + 0:000> dumpsig 0x000007fe`ec20879d 0x000007fe`eabd1000 + [DEFAULT] [hasThis] Void (Boolean,String,String) + +We can look at the individual elements of this object by adding the offsets into the +object which correspond to the return value and parameters: + 0:000> sos DumpSigElem 0x000007fe`ec20879d+2 0x000007fe`eabd1000 + Void + 0:000> sos DumpSigElem 0x000007fe`ec20879d+3 0x000007fe`eabd1000 + Boolean + 0:000> sos DumpSigElem 0x000007fe`ec20879d+4 0x000007fe`eabd1000 + String + 0:000> sos DumpSigElem 0x000007fe`ec20879d+5 0x000007fe`eabd1000 + String + +We can do something similar for fields. Here is the full signature of a field: + 0:000> dumpsig 0x000007fe`eb7fd8cd 0x000007fe`eabd1000 + [FIELD] ValueClass System.RuntimeTypeHandle + +Using DumpSigElem we can find the type of the field by adding the offset of it (1) to +the address of the signature: + 0:000> sos DumpSigElem 0x000007fe`eb7fd8cd+1 0x000007fe`eabd1000 + ValueClass System.RuntimeTypeHandle + +DumpSigElem will also work with generics. Let a function be defined as follows: + public A Test(IEnumerable<B> n) + +The elements of this signature can be obtained by adding offsets into the signature +when calling DumpSigElem: + + 0:000> sos DumpSigElem 00000000`00bc2437+2 000007ff00043178 + __Canon + 0:000> sos DumpSigElem 00000000`00bc2437+4 000007ff00043178 + Class System.Collections.Generic.IEnumerable`1<__Canon> + +The actual offsets that you should add are determined by the contents of the +signature itself. By trial and error you should be able to find various elements +of the signature. +\\ + +COMMAND: dumpil. +DumpIL <Managed DynamicMethod object> | + <DynamicMethodDesc pointer> | + <MethodDesc pointer> | + /i <IL pointer> + +DumpIL prints the IL code associated with a managed method. We added this +function specifically to debug DynamicMethod code which was constructed on +the fly. Happily it works for non-dynamic code as well. + +You can use it in four ways: + + 1) If you have a System.Reflection.Emit.DynamicMethod object, just pass + the pointer as the first argument. + 2) If you have a DynamicMethodDesc pointer you can use that to print the + IL associated with the dynamic method. + 3) If you have an ordinary MethodDesc, you can see the IL for that as well, + just pass it as the first argument. + 4) If you have a pointer directly to the IL, specify /i followed by the + the IL address. This is useful for writers of profilers that instrument + IL. + + +Note that dynamic IL is constructed a bit differently. Rather than referring +to metadata tokens, the IL points to objects in a managed object array. Here +is a simple example of the output for a dynamic method: + + 0:000> sos DumpIL b741dc + This is dynamic IL. Exception info is not reported at this time. + If a token is unresolved, run "sos DumpObj <addr>" on the addr given + in parenthesis. You can also look at the token table yourself, by + running "DumpArray 00b77388". + + IL_0000: ldstr 70000002 "Inside invoked method " + IL_0005: call 6000003 System.Console.WriteLine(System.String) + IL_000a: ldc.i4.1 + IL_000b: newarr 2000004 "System.Int32" + IL_0010: stloc.0 + IL_0011: ldloc.0 + IL_0012: ret +\\ + +COMMAND: verifyheap. +VerifyHeap + +VerifyHeap is a diagnostic tool that checks the garbage collected heap for +signs of corruption. It walks objects one by one in a pattern like this: + + o = firstobject; + while(o != endobject) + { + o.ValidateAllFields(); + o = (Object *) o + o.Size(); + } + +If an error is found, VerifyHeap will report it. I'll take a perfectly good +object and corrupt it: + + (lldb) dumpobj a79d40 + Name: Customer + MethodTable: 009038ec + EEClass: 03ee1b84 + Size: 20(0x14) bytes + (/home/user/pub/unittest) + Fields: + MT Field Offset Type Attr Value Name + 009038ec 4000008 4 CLASS instance 00a79ce4 name + 009038ec 4000009 8 CLASS instance 00a79d2c bank + 009038ec 400000a c System.Boolean instance 1 valid + + (lldb) ed a79d40+4 01 (change the name field to the bogus pointer value 1) + (lldb) sos VerifyHeap + object 01ee60dc: bad member 00000003 at 01EE6168 + Last good object: 01EE60C4. + +If this gc heap corruption exists, there is a serious bug in your own code or +in the CLR. In user code, an error in constructing PInvoke calls can cause +this problem, and running with Managed Debugging Assistants is advised. If that +possibility is eliminated, consider contacting Microsoft Product Support for +help. +\\ + +COMMAND: dumplog. +DumpLog [-addr <addressOfStressLog>] [<Filename>] + +To aid in diagnosing hard-to-reproduce stress failures, the CLR team added an +in-memory log capability. The idea was to avoid using locks or I/O which could +disturb a fragile repro environment. The DumpLog function allows you to write +that log out to a file. If no Filename is specified, the file "Stresslog.txt" +in the current directory is created. + +The optional argument addr allows one to specify a stress log other then the +default one. + + (lldb) dumplog + Attempting to dump Stress log to file 'StressLog.txt' + ................. + SUCCESS: Stress log dumped + +To turn on the stress log, set the following registry keys under +HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework: + + +(DWORD) StressLog = 1 +(DWORD) LogFacility = 0xffffffbf (this is a bit mask, almost all logging is on. + This is also the default value if the key + isn't specified) +(DWORD) StressLogSize = 65536 (this is the default value if the key isn't + specified) +(DWORD) LogLevel = 6 (this is the default value if the key isn't + specified. The higher the number the more + detailed logs are generated. The maximum + value is decimal 10) + +StressLogSize is the size in bytes of the in-memory log allocated for each +thread in the process. In the case above, each thread gets a 64K log. You +could increase this to get more logging, but more memory will be required for +this log in the process. For example, 20 threads with 524288 bytes per thread +has a memory demand of 10 Megabytes. The stress log is circular so new entries +will replace older ones on threads which have reached their buffer limit. + +The log facilities are defined as follows: + GC 0x00000001 + GCINFO 0x00000002 + STUBS 0x00000004 + JIT 0x00000008 + LOADER 0x00000010 + METADATA 0x00000020 + SYNC 0x00000040 + EEMEM 0x00000080 + GCALLOC 0x00000100 + CORDB 0x00000200 + CLASSLOADER 0x00000400 + CORPROF 0x00000800 + REMOTING 0x00001000 + DBGALLOC 0x00002000 + EH 0x00004000 + ENC 0x00008000 + ASSERT 0x00010000 + VERIFIER 0x00020000 + THREADPOOL 0x00040000 + GCROOTS 0x00080000 + INTEROP 0x00100000 + MARSHALER 0x00200000 + IJW 0x00400000 + ZAP 0x00800000 + STARTUP 0x01000000 + APPDOMAIN 0x02000000 + CODESHARING 0x04000000 + STORE 0x08000000 + SECURITY 0x10000000 + LOCKS 0x20000000 + BCL 0x40000000 + +Here is some sample output: + + 3560 9.981137099 : `SYNC` RareEnablePremptiveGC: entering. + Thread state = a030 + + 3560 9.981135033 : `GC`GCALLOC`GCROOTS` ========== ENDGC 4194 (gen = 2, + collect_classes = 0) ==========={ + + 3560 9.981125826 : `GC` Segment mem 00C61000 alloc + = 00D071F0 used 00D09254 committed 00D17000 + + 3560 9.981125726 : `GC` Generation 0 [00CED07C, 00000000 + ] cur = 00000000 + + 3560 9.981125529 : `GC` Generation 1 [00CED070, 00000000 + ] cur = 00000000 + + 3560 9.981125103 : `GC` Generation 2 [00C61000, 00000000 + ] cur = 00000000 + + 3560 9.981124963 : `GC` GC Heap 00000000 + + 3560 9.980618994 : `GC`GCROOTS` GcScanHandles (Promotion Phase = 0) + +The first column is the OS thread ID for the thread appending to the log, +the second column is the timestamp, the third is the facility category for the +log entry, and the fourth contains the log message. The facility field is +expressed as `facility1`facility2`facility3`. This facilitates the creation of +filters for displaying only specific message categories. To make sense of this +log, you would probably want the Shared Source CLI to find out exactly where +the log comes from. +\\ + +COMMAND: findappdomain. +FindAppDomain <Object address> + +FindAppDomain will attempt to resolve the AppDomain of an object. For example, +using an Object Pointer from the output of DumpStackObjects: + + (lldb) sos FindAppDomain 00a79d98 + AppDomain: 0014f000 + Name: unittest.exe + ID: 1 + +You can find out more about the AppDomain with the DumpDomain command. Not +every object has enough clues about it's origin to determine the AppDomain. +Objects with Finalizers are the easiest case, as the CLR needs to be able to +call those when an AppDomain shuts down. +\\ + +COMMAND: histinit. +HistInit + +Before running any of the Hist - family commands you need to initialize the SOS +structures from the stress log saved in the debuggee. This is achieved by the +HistInit command. + +Sample output: + + (lldb) histinit + Attempting to read Stress log + STRESS LOG: + facilitiesToLog = 0xffffffff + levelToLog = 6 + MaxLogSizePerThread = 0x10000 (65536) + MaxTotalLogSize = 0x1000000 (16777216) + CurrentTotalLogChunk = 9 + ThreadsWithLogs = 3 + Clock frequency = 3.392 GHz + Start time 15:26:31 + Last message time 15:26:56 + Total elapsed time 25.077 sec + ..................................... + ---------------------------- 2407 total entries ----------------------------- + + + SUCCESS: GCHist structures initialized + +\\ + +COMMAND: histobjfind. +HistObjFind <obj_address> + +To examine log entries related to an object whose present address is known one +would use this command. The output of this command contains all entries that +reference the object: + + (lldb) histobjfind 028970d4 + GCCount Object Message + --------------------------------------------------------- + 2296 028970d4 Promotion for root 01e411b8 (MT = 5b6c5cd8) + 2296 028970d4 Relocation NEWVALUE for root 00223fc4 + 2296 028970d4 Relocation NEWVALUE for root 01e411b8 + ... + 2295 028970d4 Promotion for root 01e411b8 (MT = 5b6c5cd8) + 2295 028970d4 Relocation NEWVALUE for root 00223fc4 + 2295 028970d4 Relocation NEWVALUE for root 01e411b8 + ... + +\\ + +COMMAND: histroot. +HistRoot <root> + +The root value obtained from !HistObjFind can be used to track the movement of +an object through the GCs. + +HistRoot provides information related to both promotions and relocations of the +root specified as the argument. + + (lldb) histroot 01e411b8 + GCCount Value MT Promoted? Notes + --------------------------------------------------------- + 2296 028970d4 5b6c5cd8 yes + 2295 028970d4 5b6c5cd8 yes + 2294 028970d4 5b6c5cd8 yes + 2293 028970d4 5b6c5cd8 yes + 2292 028970d4 5b6c5cd8 yes + 2291 028970d4 5b6c5cd8 yes + 2290 028970d4 5b6c5cd8 yes + 2289 028970d4 5b6c5cd8 yes + 2288 028970d4 5b6c5cd8 yes + 2287 028970d4 5b6c5cd8 yes + 2286 028970d4 5b6c5cd8 yes + 2285 028970d4 5b6c5cd8 yes + 322 028970e8 5b6c5cd8 yes Duplicate promote/relocs + ... + +\\ + +COMMAND: histobj. +HistObj <obj_address> + +This command examines all stress log relocation records and displays the chain +of GC relocations that may have led to the address passed in as an argument. +Conceptually the output is: + + GenN obj_address root1, root2, root3, + GenN-1 prev_obj_addr root1, root2, + GenN-2 prev_prev_oa root1, root4, + ... + +Sample output: + (lldb) histobj 028970d4 + GCCount Object Roots + --------------------------------------------------------- + 2296 028970d4 00223fc4, 01e411b8, + 2295 028970d4 00223fc4, 01e411b8, + 2294 028970d4 00223fc4, 01e411b8, + 2293 028970d4 00223fc4, 01e411b8, + 2292 028970d4 00223fc4, 01e411b8, + 2291 028970d4 00223fc4, 01e411b8, + 2290 028970d4 00223fc4, 01e411b8, + 2289 028970d4 00223fc4, 01e411b8, + 2288 028970d4 00223fc4, 01e411b8, + 2287 028970d4 00223fc4, 01e411b8, + 2286 028970d4 00223fc4, 01e411b8, + 2285 028970d4 00223fc4, 01e411b8, + 322 028970d4 01e411b8, + 0 028970d4 + +\\ + +COMMAND: histclear. +HistClear + +This command releases any resources used by the Hist-family of commands. +Generally there's no need to call this explicitly, as each HistInit will first +cleanup the previous resources. + + (lldb) histclear + Completed successfully. + +\\ diff --git a/src/ToolBox/SOS/Strike/stressLogDump.cpp b/src/ToolBox/SOS/Strike/stressLogDump.cpp new file mode 100644 index 0000000000..f277f92434 --- /dev/null +++ b/src/ToolBox/SOS/Strike/stressLogDump.cpp @@ -0,0 +1,549 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ==++== +// + +// +// ==--== + +#include "strike.h" +#include "util.h" +#include <stdio.h> +#include <ctype.h> + +#ifndef STRESS_LOG +#define STRESS_LOG +#endif // STRESS_LOG +#define STRESS_LOG_READONLY +#include "stresslog.h" + + +void GcHistClear(); +void GcHistAddLog(LPCSTR msg, StressMsg* stressMsg); + + +/*********************************************************************************/ +static const WCHAR* getTime(const FILETIME* time, __out_ecount (buffLen) WCHAR* buff, int buffLen) +{ + SYSTEMTIME systemTime; + static const WCHAR badTime[] = W("BAD TIME"); + + if (!FileTimeToSystemTime(time, &systemTime)) + return badTime; + +#ifdef FEATURE_PAL + int length = _snwprintf(buff, buffLen, W("%02d:%02d:%02d"), systemTime.wHour, systemTime.wMinute, systemTime.wSecond); + if (length <= 0) + return badTime; +#else // FEATURE_PAL + static const WCHAR format[] = W("HH:mm:ss"); + + SYSTEMTIME localTime; + SystemTimeToTzSpecificLocalTime(NULL, &systemTime, &localTime); + + // we want a non null buff for the following + int ret = GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &localTime, format, buff, buffLen); + if (ret == 0) + return badTime; +#endif // FEATURE_PAL else + + return buff; +} + +/*********************************************************************************/ +static inline __int64& toInt64(FILETIME& t) +{ + return *((__int64 *) &t); +} + +/*********************************************************************************/ +ThreadStressLog* ThreadStressLog::FindLatestThreadLog() const +{ + const ThreadStressLog* latestLog = 0; + for (const ThreadStressLog* ptr = this; ptr != NULL; ptr = ptr->next) + { + if (ptr->readPtr != NULL) + if (latestLog == 0 || ptr->readPtr->timeStamp > latestLog->readPtr->timeStamp) + latestLog = ptr; + } + return const_cast<ThreadStressLog*>(latestLog); +} + +const char *getFacilityName(DWORD_PTR lf) +{ + struct FacilityName_t { size_t lf; const char* lfName; }; + #define DEFINE_LOG_FACILITY(logname, value) {logname, #logname}, + static FacilityName_t facilities[] = + { + #include <loglf.h> + { LF_ALWAYS, "LF_ALWAYS" } + }; + static char buff[1024] = "`"; + if ( lf == LF_ALL ) + { + return "`ALL`"; + } + else + { + buff[1] = '\0'; + for ( int i = 0; i < 32; ++i ) + { + if ( lf & 0x1 ) + { + strcat_s ( buff, _countof(buff), &(facilities[i].lfName[3]) ); + strcat_s ( buff, _countof(buff), "`" ); + } + lf >>= 1; + } + return buff; + } +} + +/***********************************************************************************/ +/* recognize special pretty printing instructions in the format string */ +/* Note that this function might have side effect such that args array value might */ +/* be altered if format string contains %s */ +// TODO: This function assumes the pointer size of the target equals the pointer size of the host +// TODO: replace uses of void* with appropriate TADDR or CLRDATA_ADDRESS +void formatOutput(struct IDebugDataSpaces* memCallBack, ___in FILE* file, __inout __inout_z char* format, unsigned threadId, double timeStamp, DWORD_PTR facility, ___in void** args) +{ + fprintf(file, "%4x %13.9f : ", threadId, timeStamp); + fprintf(file, "%-20s ", getFacilityName ( facility )); + + CQuickBytes fullname; + char* ptr = format; + void** argsPtr = args; + const SIZE_T capacity_buff = 2048; + LPWSTR buff = (LPWSTR)alloca(capacity_buff * sizeof(WCHAR)); + static char formatCopy[256]; + + int iArgCount = 0; + + strcpy_s(formatCopy, _countof(formatCopy), format); + for(;;) + { + char c = *ptr++; + if (c == 0) + break; + if (c == '{') // Reverse the '{' 's because the log is displayed backwards + ptr[-1] = '}'; + else if (c == '}') + ptr[-1] = '{'; + else if (c == '%') + { + argsPtr++; // This format will consume one of the args + if (*ptr == '%') + { + ptr++; // skip the whole %% + --argsPtr; // except for a %% + } + else if (*ptr == 'p') + { // It is a %p + ptr++; + if (isalpha(*ptr)) + { // It is a special %p formatter + // Print the string up to that point + c = *ptr; + *ptr = 0; // Terminate the string temporarily + fprintf(file, format, args[0], args[1], args[2], args[3], args[4], args[5], args[6]); + *ptr = c; // Put it back + + // move the argument pointers past the part the was printed + format = ptr + 1; + args = argsPtr; + iArgCount = -1; + DWORD_PTR arg = DWORD_PTR(argsPtr[-1]); + + switch (c) + { + case 'M': // format as a method Desc + if (g_bDacBroken) + { + fprintf(file," (MethodDesc: %p)",arg); + } + else + { + if (!IsMethodDesc(arg)) + { + if (arg != 0) + fprintf(file, " (BAD Method)"); + } + else + { + DacpMethodDescData MethodDescData; + MethodDescData.Request(g_sos,(CLRDATA_ADDRESS)arg); + + static WCHAR wszNameBuffer[1024]; // should be large enough + if (g_sos->GetMethodDescName(arg, 1024, wszNameBuffer, NULL) != S_OK) + { + wcscpy_s(wszNameBuffer, _countof(wszNameBuffer), W("UNKNOWN METHODDESC")); + } + + wcscpy_s(buff, capacity_buff, wszNameBuffer); + fprintf(file, " (%S)", wszNameBuffer); + } + } + break; + + // fall through + case 'T': // format as a MethodTable + if (g_bDacBroken) + { + fprintf(file, "(MethodTable: %p)",arg); + } + else + { + if (arg & 3) + { + arg &= ~3; // GC steals the lower bits for its own use during GC. + fprintf(file, " Low Bit(s) Set"); + } + if (!IsMethodTable(arg)) + { + fprintf(file, " (BAD MethodTable)"); + } + else + { + NameForMT_s (arg, g_mdName, mdNameLen); + fprintf(file, " (%S)", g_mdName); + } + } + break; + + case 'V': + { // format as a C vtable pointer + char Symbol[1024]; + ULONG64 Displacement; + HRESULT hr = g_ExtSymbols->GetNameByOffset(TO_CDADDR(arg), Symbol, 1024, NULL, &Displacement); + if (SUCCEEDED(hr) && Symbol[0] != '\0' && Displacement == 0) + fprintf(file, " (%s)", Symbol); + else + fprintf(file, " (Unknown VTable)"); + } + break; + case 'K': + { // format a frame in stack trace + char Symbol[1024]; + ULONG64 Displacement; + HRESULT hr = g_ExtSymbols->GetNameByOffset (TO_CDADDR(arg), Symbol, 1024, NULL, &Displacement); + if (SUCCEEDED (hr) && Symbol[0] != '\0') + { + fprintf (file, " (%s", Symbol); + if (Displacement) + { + fprintf (file, "+%#x", Displacement); + } + fprintf (file, ")"); + } + else + fprintf (file, " (Unknown function)"); + } + break; + default: + format = ptr; // Just print the character. + } + } + } + else if (*ptr == 's' || (*ptr == 'h' && *(ptr+1) == 's' && ++ptr)) + { + HRESULT hr; + + // need to _alloca, instead of declaring a local buffer + // since we may have more than one %s in the format + ULONG cbStrBuf = 256; + char* strBuf = (char *)_alloca(cbStrBuf); + + hr = memCallBack->ReadVirtual(TO_CDADDR((char* )args[iArgCount]), strBuf, cbStrBuf, 0); + if (hr != S_OK) + { + strcpy_s(strBuf, cbStrBuf, "(#Could not read address of string#)"); + } + + args[iArgCount] = strBuf; + } + else if (*ptr == 'S' || (*ptr == 'l' && *(ptr+1) == 's' && ++ptr)) + { + HRESULT hr; + + // need to _alloca, instead of declaring a local buffer + // since we may have more than one %s in the format + ULONG cbWstrBuf = 256 * sizeof(WCHAR); + WCHAR* wstrBuf = (WCHAR *)_alloca(cbWstrBuf); + + hr = memCallBack->ReadVirtual(TO_CDADDR((char* )args[iArgCount]), wstrBuf, cbWstrBuf, 0); + if (hr != S_OK) + { + wcscpy_s(wstrBuf, cbWstrBuf/sizeof(WCHAR), W("(#Could not read address of string#)")); + } + + args[iArgCount] = wstrBuf; + } + iArgCount++; + } + } + // Print anything after the last special format instruction. + fprintf(file, format, args[0], args[1], args[2], args[3], args[4], args[5], args[6]); + fprintf(file, "\n"); +} + +void __cdecl +vDoOut(BOOL bToConsole, FILE* file, PCSTR Format, ...) +{ + va_list Args; + + va_start(Args, Format); + + if (bToConsole) + { + g_ExtControl->OutputVaList(DEBUG_OUTPUT_NORMAL, Format, Args); + } + else + { + vfprintf(file, Format, Args); + } + + va_end(Args); +} + + +/*********************************************************************************/ +HRESULT StressLog::Dump(ULONG64 outProcLog, const char* fileName, struct IDebugDataSpaces* memCallBack) +{ + ULONG64 g_hThisInst; + BOOL bDoGcHist = (fileName == NULL); + FILE* file = NULL; + + // Fetch the circular buffer bookeeping data + StressLog inProcLog; + HRESULT hr = memCallBack->ReadVirtual(UL64_TO_CDA(outProcLog), &inProcLog, sizeof(StressLog), 0); + if (hr != S_OK) + { + return hr; + } + if (inProcLog.logs.Load() == NULL || inProcLog.moduleOffset == 0) + { + ExtOut ( "----- No thread logs in the image: The stress log was probably not initialized correctly. -----\n"); + return S_FALSE; + } + + g_hThisInst = (ULONG64) inProcLog.moduleOffset; + + if (bDoGcHist) + { + GcHistClear(); + } + else + { + ExtOut("Writing to file: %s\n", fileName); + ExtOut("Stress log in module 0x%p\n", SOS_PTR(g_hThisInst)); + ExtOut("Stress log address = 0x%p\n", SOS_PTR(outProcLog)); + } + // Fetch the circular buffers for each thread into the 'logs' list + ThreadStressLog* logs = 0; + + CLRDATA_ADDRESS outProcPtr = TO_CDADDR(inProcLog.logs.Load()); + ThreadStressLog* inProcPtr; + ThreadStressLog** logsPtr = &logs; + int threadCtr = 0; + unsigned __int64 lastTimeStamp = 0;// timestamp of last log entry + + while(outProcPtr != 0) { + inProcPtr = new ThreadStressLog; + hr = memCallBack->ReadVirtual(outProcPtr, inProcPtr, sizeof (*inProcPtr), 0); + if (hr != S_OK || inProcPtr->chunkListHead == NULL) + { + delete inProcPtr; + goto FREE_MEM; + } + + CLRDATA_ADDRESS outProcListHead = TO_CDADDR(inProcPtr->chunkListHead); + CLRDATA_ADDRESS outProcChunkPtr = outProcListHead; + StressLogChunk ** chunksPtr = &inProcPtr->chunkListHead; + StressLogChunk * inProcPrevChunkPtr = NULL; + BOOL curPtrInitialized = FALSE; + do + { + StressLogChunk * inProcChunkPtr = new StressLogChunk; + hr = memCallBack->ReadVirtual (outProcChunkPtr, inProcChunkPtr, sizeof (*inProcChunkPtr), NULL); + if (hr != S_OK || !inProcChunkPtr->IsValid ()) + { + if (hr != S_OK) + ExtOut ("ReadVirtual failed with code hr = %x.\n", hr ); + else + ExtOut ("Invalid stress log chunk: %p", SOS_PTR(outProcChunkPtr)); + + // Now cleanup + delete inProcChunkPtr; + // if this is the first time through, inProcPtr->chunkListHead may still contain + // the out-of-process value for the chunk pointer. NULL it to avoid AVs + if (TO_CDADDR(inProcPtr->chunkListHead) == outProcListHead) + inProcPtr->chunkListHead = NULL; + delete inProcPtr; + goto FREE_MEM; + } + + if (!curPtrInitialized && outProcChunkPtr == TO_CDADDR(inProcPtr->curWriteChunk)) + { + inProcPtr->curPtr = (StressMsg *)((BYTE *)inProcChunkPtr + ((BYTE *)inProcPtr->curPtr - (BYTE *)inProcPtr->curWriteChunk)); + inProcPtr->curWriteChunk = inProcChunkPtr; + curPtrInitialized = TRUE; + } + + outProcChunkPtr = TO_CDADDR(inProcChunkPtr->next); + *chunksPtr = inProcChunkPtr; + chunksPtr = &inProcChunkPtr->next; + inProcChunkPtr->prev = inProcPrevChunkPtr; + inProcPrevChunkPtr = inProcChunkPtr; + + if (outProcChunkPtr == outProcListHead) + { + inProcChunkPtr->next = inProcPtr->chunkListHead; + inProcPtr->chunkListHead->prev = inProcChunkPtr; + inProcPtr->chunkListTail = inProcChunkPtr; + } + } while (outProcChunkPtr != outProcListHead); + + if (!curPtrInitialized) + { + delete inProcPtr; + goto FREE_MEM; + } + + // TODO: fix on 64 bit + inProcPtr->Activate (); + if (inProcPtr->readPtr->timeStamp > lastTimeStamp) + { + lastTimeStamp = inProcPtr->readPtr->timeStamp; + } + + outProcPtr = TO_CDADDR(inProcPtr->next); + *logsPtr = inProcPtr; + logsPtr = &inProcPtr->next; + threadCtr++; + } + + if (!bDoGcHist && ((file = fopen(fileName, "w")) == NULL)) + { + hr = GetLastError(); + goto FREE_MEM; + } + hr = S_FALSE; // return false if there are no message to print to the log + + vDoOut(bDoGcHist, file, "STRESS LOG:\n" + " facilitiesToLog = 0x%x\n" + " levelToLog = %d\n" + " MaxLogSizePerThread = 0x%x (%d)\n" + " MaxTotalLogSize = 0x%x (%d)\n" + " CurrentTotalLogChunk = %d\n" + " ThreadsWithLogs = %d\n", + inProcLog.facilitiesToLog, inProcLog.levelToLog, inProcLog.MaxSizePerThread, inProcLog.MaxSizePerThread, + inProcLog.MaxSizeTotal, inProcLog.MaxSizeTotal, inProcLog.totalChunk.Load(), threadCtr); + + FILETIME endTime; + double totalSecs; + totalSecs = ((double) (lastTimeStamp - inProcLog.startTimeStamp)) / inProcLog.tickFrequency; + toInt64(endTime) = toInt64(inProcLog.startTime) + ((__int64) (totalSecs * 1.0E7)); + + WCHAR timeBuff[64]; + vDoOut(bDoGcHist, file, " Clock frequency = %5.3f GHz\n", inProcLog.tickFrequency / 1.0E9); + vDoOut(bDoGcHist, file, " Start time %S\n", getTime(&inProcLog.startTime, timeBuff, 64)); + vDoOut(bDoGcHist, file, " Last message time %S\n", getTime(&endTime, timeBuff, 64)); + vDoOut(bDoGcHist, file, " Total elapsed time %5.3f sec\n", totalSecs); + + if (!bDoGcHist) + { + fprintf(file, "\nTHREAD TIMESTAMP FACILITY MESSAGE\n"); + fprintf(file, " ID (sec from start)\n"); + fprintf(file, "--------------------------------------------------------------------------------------\n"); + } + char format[257]; + format[256] = format[0] = 0; + void** args; + unsigned msgCtr; + msgCtr = 0; + for (;;) + { + ThreadStressLog* latestLog = logs->FindLatestThreadLog(); + + if (IsInterrupt()) + { + vDoOut(bDoGcHist, file, "----- Interrupted by user -----\n"); + break; + } + + if (latestLog == 0) + { + break; + } + + StressMsg* latestMsg = latestLog->readPtr; + if (latestMsg->formatOffset != 0 && !latestLog->CompletedDump()) + { + TADDR taFmt = (latestMsg->formatOffset) + TO_TADDR(g_hThisInst); + hr = memCallBack->ReadVirtual(TO_CDADDR(taFmt), format, 256, 0); + if (hr != S_OK) + strcpy_s(format, _countof(format), "Could not read address of format string"); + + double deltaTime = ((double) (latestMsg->timeStamp - inProcLog.startTimeStamp)) / inProcLog.tickFrequency; + if (bDoGcHist) + { + if (strcmp(format, ThreadStressLog::TaskSwitchMsg()) == 0) + { + latestLog->threadId = (unsigned)(size_t)latestMsg->args[0]; + } + GcHistAddLog(format, latestMsg); + } + else + { + if (strcmp(format, ThreadStressLog::TaskSwitchMsg()) == 0) + { + fprintf (file, "Task was switched from %x\n", (unsigned)(size_t)latestMsg->args[0]); + latestLog->threadId = (unsigned)(size_t)latestMsg->args[0]; + } + else + { + args = latestMsg->args; + formatOutput(memCallBack, file, format, latestLog->threadId, deltaTime, latestMsg->facility, args); + } + } + msgCtr++; + } + + latestLog->readPtr = latestLog->AdvanceRead(); + if (latestLog->CompletedDump()) + { + latestLog->readPtr = NULL; + if (!bDoGcHist) + { + fprintf(file, "------------ Last message from thread %x -----------\n", latestLog->threadId); + } + } + + if (msgCtr % 64 == 0) + { + ExtOut("."); // to indicate progress + if (msgCtr % (64*64) == 0) + ExtOut("\n"); + } + } + ExtOut("\n"); + + vDoOut(bDoGcHist, file, "---------------------------- %d total entries ------------------------------------\n", msgCtr); + if (!bDoGcHist) + { + fclose(file); + } + +FREE_MEM: + // clean up the 'logs' list + while (logs) { + ThreadStressLog* temp = logs; + logs = logs->next; + delete temp; + } + + return hr; +} + diff --git a/src/ToolBox/SOS/Strike/strike.cpp b/src/ToolBox/SOS/Strike/strike.cpp new file mode 100644 index 0000000000..731e2f505d --- /dev/null +++ b/src/ToolBox/SOS/Strike/strike.cpp @@ -0,0 +1,14462 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ==++== +// + +// +// ==--== + +// =========================================================================== +// STRIKE.CPP +// =========================================================================== +// +// History: +// 09/07/99 Microsoft Created +// +//************************************************************************************************ +// SOS is the native debugging extension designed to support investigations into CLR (mis-) +// behavior by both users of the runtime as well as the code owners. It allows inspection of +// internal structures, of user visible entities, as well as execution control. +// +// This is the main SOS file hosting the implementation of all the exposed commands. A good +// starting point for understanding the semantics of these commands is the sosdocs.txt file. +// +// #CrossPlatformSOS +// SOS currently supports cross platform debugging from x86 to ARM. It takes a different approach +// from the DAC: whereas for the DAC we produce one binary for each supported host-target +// architecture pair, for SOS we produce only one binary for each host architecture; this one +// binary contains code for all supported target architectures. In doing this SOS depends on two +// assumptions: +// . that the debugger will load the appropriate DAC, and +// . that the host and target word size is identical. +// The second assumption is identical to the DAC assumption, and there will be considerable effort +// required (in the EE, the DAC, and SOS) if we ever need to remove it. +// +// In an ideal world SOS would be able to retrieve all platform specific information it needs +// either from the debugger or from DAC. However, SOS has taken some subtle and not so subtle +// dependencies on the CLR and the target platform. +// To resolve this problem, SOS now abstracts the target behind the IMachine interface, and uses +// calls on IMachine to take target-specific actions. It implements X86Machine, ARMMachine, and +// AMD64Machine. An instance of these exists in each appropriate host (e.g. the X86 version of SOS +// contains instaces of X86Machine and ARMMachine, the ARM version contains an instance of +// ARMMachine, and the AMD64 version contains an instance of AMD64Machine). The code included in +// each version if determined by the SosTarget*** MSBuild symbols, and SOS_TARGET_*** conditional +// compilation symbols (as specified in sos.targets). +// +// Most of the target specific code is hosted in disasm.h/.cpp, and disasmX86.cpp, disasmARM.cpp. +// Some code currently under _TARGET_*** ifdefs may need to be reviewed/revisited. +// +// Issues: +// The one-binary-per-host decision does have some drawbacks: +// . Currently including system headers or even CLR headers will only account for the host +// target, IOW, when building the X86 version of SOS, CONTEXT will refer to the X86 CONTEXT +// structure, so we need to be careful when debugging ARM targets. The CONTEXT issue is +// partially resolved by CROSS_PLATFORM_CONTEXT (there is still a need to be very careful +// when handling arrays of CONTEXTs - see _EFN_StackTrace for details on this). +// . For larger includes (e.g. GC info), we will need to include files in specific namespaces, +// with specific _TARGET_*** macros defined in order to avoid name clashes and ensure correct +// system types are used. +// ----------------------------------------------------------------------------------------------- + +#define DO_NOT_DISABLE_RAND //this is a standalone tool, and can use rand() + +#include <windows.h> +#include <winver.h> +#include <winternl.h> +#include <psapi.h> +#ifndef FEATURE_PAL +#include <list> +#endif // !FEATURE_PAL +#include <wchar.h> + +#include "platformspecific.h" + +#define NOEXTAPI +#define KDEXT_64BIT +#include <wdbgexts.h> +#undef DECLARE_API +#undef StackTrace + +#include <dbghelp.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stddef.h> + +#include "strike.h" +#include "sos.h" + +#ifndef STRESS_LOG +#define STRESS_LOG +#endif // STRESS_LOG +#define STRESS_LOG_READONLY +#include "stresslog.h" + +#include "util.h" + +#include "corhdr.h" +#include "cor.h" +#include "cordebug.h" +#include "dacprivate.h" +#include "corexcep.h" + +#define CORHANDLE_MASK 0x1 +#define SWITCHED_OUT_FIBER_OSID 0xbaadf00d; + +#define DEFINE_EXT_GLOBALS + +#include "data.h" +#include "disasm.h" + +#include "predeftlsslot.h" + +#include "hillclimbing.h" + +#include "sos_md.h" + +#ifndef FEATURE_PAL + +#include "ExpressionNode.h" +#include "WatchCmd.h" + +#include <set> +#include <algorithm> +#include <vector> + +#include "tls.h" + +typedef struct _VM_COUNTERS { + SIZE_T PeakVirtualSize; + SIZE_T VirtualSize; + ULONG PageFaultCount; + SIZE_T PeakWorkingSetSize; + SIZE_T WorkingSetSize; + SIZE_T QuotaPeakPagedPoolUsage; + SIZE_T QuotaPagedPoolUsage; + SIZE_T QuotaPeakNonPagedPoolUsage; + SIZE_T QuotaNonPagedPoolUsage; + SIZE_T PagefileUsage; + SIZE_T PeakPagefileUsage; +} VM_COUNTERS; +typedef VM_COUNTERS *PVM_COUNTERS; + +const PROCESSINFOCLASS ProcessVmCounters = static_cast<PROCESSINFOCLASS>(3); + +#endif // !FEATURE_PAL + +BOOL CallStatus; +BOOL ControlC = FALSE; + +IMetaDataDispenserEx *pDisp = NULL; +WCHAR g_mdName[mdNameLen]; + +#ifndef FEATURE_PAL +HMODULE g_hInstance = NULL; +#include <vector> +#include <algorithm> +#endif // !FEATURE_PAL + +#ifdef _MSC_VER +#pragma warning(disable:4244) // conversion from 'unsigned int' to 'unsigned short', possible loss of data +#pragma warning(disable:4189) // local variable is initialized but not referenced +#endif + +#ifdef FEATURE_PAL +#define SOSPrefix "" +#else +#define SOSPrefix "!" +#endif + +#if defined _X86_ && !defined FEATURE_PAL +// disable FPO for X86 builds +#pragma optimize("y", off) +#endif + +#undef assert + +#ifdef _MSC_VER +#pragma warning(default:4244) +#pragma warning(default:4189) +#endif + +#ifndef FEATURE_PAL +#include "ntinfo.h" +#endif // FEATURE_PAL + +#ifndef IfFailRet +#define IfFailRet(EXPR) do { Status = (EXPR); if(FAILED(Status)) { return (Status); } } while (0) +#endif + +#ifdef FEATURE_PAL + +#define NOTHROW +#define MINIDUMP_NOT_SUPPORTED() + +#else // !FEATURE_PAL + +#define MINIDUMP_NOT_SUPPORTED() \ + if (IsMiniDumpFile()) \ + { \ + ExtOut("This command is not supported in a minidump without full memory\n"); \ + ExtOut("To try the command anyway, run !MinidumpMode 0\n"); \ + return Status; \ + } + +#define NOTHROW (std::nothrow) + +#include "safemath.h" + +DECLARE_API (MinidumpMode) +{ + INIT_API (); + DWORD_PTR Value=0; + + CMDValue arg[] = + { // vptr, type + {&Value, COHEX} + }; + + size_t nArg; + if (!GetCMDOption(args, NULL, 0, arg, _countof(arg), &nArg)) + { + return Status; + } + if (nArg == 0) + { + // Print status of current mode + ExtOut("Current mode: %s - unsafe minidump commands are %s.\n", + g_InMinidumpSafeMode ? "1" : "0", + g_InMinidumpSafeMode ? "disabled" : "enabled"); + } + else + { + if (Value != 0 && Value != 1) + { + ExtOut("Mode must be 0 or 1\n"); + return Status; + } + + g_InMinidumpSafeMode = (BOOL) Value; + ExtOut("Unsafe minidump commands are %s.\n", + g_InMinidumpSafeMode ? "disabled" : "enabled"); + } + + return Status; +} + +#endif // FEATURE_PAL + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to get the MethodDesc for a given eip * +* * +\**********************************************************************/ +DECLARE_API(IP2MD) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + BOOL dml = FALSE; + TADDR IP = 0; + CMDOption option[] = + { // name, vptr, type, hasValue +#ifndef FEATURE_PAL + {"/d", &dml, COBOOL, FALSE}, +#endif + }; + CMDValue arg[] = + { // vptr, type + {&IP, COHEX}, + }; + size_t nArg; + + if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg)) + { + return Status; + } + EnableDMLHolder dmlHolder(dml); + + if (IP == 0) + { + ExtOut("%s is not IP\n", args); + return Status; + } + + CLRDATA_ADDRESS cdaStart = TO_CDADDR(IP); + CLRDATA_ADDRESS pMD; + + + if ((Status = g_sos->GetMethodDescPtrFromIP(cdaStart, &pMD)) != S_OK) + { + ExtOut("Failed to request MethodData, not in JIT code range\n"); + return Status; + } + + DMLOut("MethodDesc: %s\n", DMLMethodDesc(pMD)); + DumpMDInfo(TO_TADDR(pMD), cdaStart, FALSE /* fStackTraceFormat */); + + WCHAR filename[MAX_LONGPATH]; + ULONG linenum; + // symlines will be non-zero only if SYMOPT_LOAD_LINES was set in the symbol options + ULONG symlines = 0; + if (SUCCEEDED(g_ExtSymbols->GetSymbolOptions(&symlines))) + { + symlines &= SYMOPT_LOAD_LINES; + } + + if (symlines != 0 && + SUCCEEDED(GetLineByOffset(TO_CDADDR(IP), &linenum, filename, _countof(filename)))) + { + ExtOut("Source file: %S @ %d\n", filename, linenum); + } + + return Status; +} + +// (MAX_STACK_FRAMES is also used by x86 to prevent infinite loops in _EFN_StackTrace) +#define MAX_STACK_FRAMES 1000 + +#ifdef _TARGET_WIN64_ + +// I use a global set of frames for stack walking on win64 because the debugger's +// GetStackTrace function doesn't provide a way to find out the total size of a stackwalk, +// and I'd like to have a reasonably big maximum without overflowing the stack by declaring +// the buffer locally and I also want to get a managed trace in a low memory environment +// (so no dynamic allocation if possible). +DEBUG_STACK_FRAME g_Frames[MAX_STACK_FRAMES]; +AMD64_CONTEXT g_X64FrameContexts[MAX_STACK_FRAMES]; + +static HRESULT +GetContextStackTrace(PULONG pnumFrames) +{ + PDEBUG_CONTROL4 debugControl4; + HRESULT hr; + + // Do we have advanced capability? + if ((hr = g_ExtControl->QueryInterface(__uuidof(IDebugControl4), (void **)&debugControl4)) == S_OK) + { + // GetContextStackTrace fills g_X64FrameContexts as an array of + // contexts packed as target architecture contexts. We cannot + // safely cast this as an array of CROSS_PLATFORM_CONTEXT, since + // sizeof(CROSS_PLATFORM_CONTEXT) != sizeof(TGT_CONTEXT) + hr = debugControl4->GetContextStackTrace( + NULL, + 0, + g_Frames, + MAX_STACK_FRAMES, + g_X64FrameContexts, + MAX_STACK_FRAMES*g_targetMachine->GetContextSize(), + g_targetMachine->GetContextSize(), + pnumFrames); + + debugControl4->Release(); + } + return hr; +} + +#endif // _TARGET_WIN64_ + +/**********************************************************************\ +* Routine Description: * +* * +* This function displays the stack trace. It looks at each DWORD * +* on stack. If the DWORD is a return address, the symbol name or +* managed function name is displayed. * +* * +\**********************************************************************/ +void DumpStackInternal(DumpStackFlag *pDSFlag) +{ + ReloadSymbolWithLineInfo(); + + ULONG64 StackOffset; + g_ExtRegisters->GetStackOffset (&StackOffset); + if (pDSFlag->top == 0) { + pDSFlag->top = TO_TADDR(StackOffset); + } + size_t value; + while (g_ExtData->ReadVirtual(TO_CDADDR(pDSFlag->top), &value, sizeof(size_t), NULL) != S_OK) { + if (IsInterrupt()) + return; + pDSFlag->top = NextOSPageAddress(pDSFlag->top); + } + +#ifndef FEATURE_PAL + if (pDSFlag->end == 0) { + // Find the current stack range + NT_TIB teb; + ULONG64 dwTebAddr=0; + + g_ExtSystem->GetCurrentThreadTeb(&dwTebAddr); + if (SafeReadMemory(TO_TADDR(dwTebAddr), &teb, sizeof(NT_TIB), NULL)) + { + if (pDSFlag->top > TO_TADDR(teb.StackLimit) + && pDSFlag->top <= TO_TADDR(teb.StackBase)) + { + if (pDSFlag->end == 0 || pDSFlag->end > TO_TADDR(teb.StackBase)) + pDSFlag->end = TO_TADDR(teb.StackBase); + } + } + } +#endif // FEATURE_PAL + + if (pDSFlag->end == 0) + { + ExtOut("TEB information is not available so a stack size of 0xFFFF is assumed\n"); + pDSFlag->end = pDSFlag->top + 0xFFFF; + } + + if (pDSFlag->end < pDSFlag->top) + { + ExtOut("Wrong option: stack selection wrong\n"); + return; + } + + DumpStackWorker(*pDSFlag); +} + + +DECLARE_API(DumpStack) +{ + INIT_API_NO_RET_ON_FAILURE(); + + MINIDUMP_NOT_SUPPORTED(); + + DumpStackFlag DSFlag; + DSFlag.fEEonly = FALSE; + DSFlag.fSuppressSrcInfo = FALSE; + DSFlag.top = 0; + DSFlag.end = 0; + + BOOL dml = FALSE; + CMDOption option[] = { + // name, vptr, type, hasValue + {"-EE", &DSFlag.fEEonly, COBOOL, FALSE}, + {"-n", &DSFlag.fSuppressSrcInfo, COBOOL, FALSE}, +#ifndef FEATURE_PAL + {"/d", &dml, COBOOL, FALSE} +#endif + }; + CMDValue arg[] = { + // vptr, type + {&DSFlag.top, COHEX}, + {&DSFlag.end, COHEX} + }; + size_t nArg; + if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg)) + return Status; + + // symlines will be non-zero only if SYMOPT_LOAD_LINES was set in the symbol options + ULONG symlines = 0; + if (!DSFlag.fSuppressSrcInfo && SUCCEEDED(g_ExtSymbols->GetSymbolOptions(&symlines))) + { + symlines &= SYMOPT_LOAD_LINES; + } + DSFlag.fSuppressSrcInfo = DSFlag.fSuppressSrcInfo || (symlines == 0); + + EnableDMLHolder enabledml(dml); + + ULONG id = 0; + g_ExtSystem->GetCurrentThreadSystemId(&id); + ExtOut("OS Thread Id: 0x%x ", id); + g_ExtSystem->GetCurrentThreadId(&id); + ExtOut("(%d)\n", id); + + DumpStackInternal(&DSFlag); + return Status; +} + + +/**********************************************************************\ +* Routine Description: * +* * +* This function displays the stack trace for threads that EE knows * +* from ThreadStore. * +* * +\**********************************************************************/ +DECLARE_API (EEStack) +{ + INIT_API(); + + MINIDUMP_NOT_SUPPORTED(); + + DumpStackFlag DSFlag; + DSFlag.fEEonly = FALSE; + DSFlag.fSuppressSrcInfo = FALSE; + DSFlag.top = 0; + DSFlag.end = 0; + + BOOL bShortList = FALSE; + BOOL dml = FALSE; + CMDOption option[] = + { // name, vptr, type, hasValue + {"-EE", &DSFlag.fEEonly, COBOOL, FALSE}, + {"-short", &bShortList, COBOOL, FALSE}, +#ifndef FEATURE_PAL + {"/d", &dml, COBOOL, FALSE} +#endif + }; + + if (!GetCMDOption(args, option, _countof(option), NULL, 0, NULL)) + { + return Status; + } + + EnableDMLHolder enableDML(dml); + + ULONG Tid; + g_ExtSystem->GetCurrentThreadId(&Tid); + + DacpThreadStoreData ThreadStore; + if ((Status = ThreadStore.Request(g_sos)) != S_OK) + { + ExtOut("Failed to request ThreadStore\n"); + return Status; + } + + CLRDATA_ADDRESS CurThread = ThreadStore.firstThread; + while (CurThread) + { + if (IsInterrupt()) + break; + + DacpThreadData Thread; + if ((Status = Thread.Request(g_sos, CurThread)) != S_OK) + { + ExtOut("Failed to request Thread at %p\n", CurThread); + return Status; + } + + ULONG id=0; + if (g_ExtSystem->GetThreadIdBySystemId (Thread.osThreadId, &id) != S_OK) + { + CurThread = Thread.nextThread; + continue; + } + + ExtOut("---------------------------------------------\n"); + ExtOut("Thread %3d\n", id); + BOOL doIt = FALSE; + + +#define TS_Hijacked 0x00000080 + + if (!bShortList) + { + doIt = TRUE; + } + else if ((Thread.lockCount > 0) || (Thread.state & TS_Hijacked)) + { + // TODO: bring back || (int)vThread.m_pFrame != -1 { + doIt = TRUE; + } + else + { + ULONG64 IP; + g_ExtRegisters->GetInstructionOffset (&IP); + JITTypes jitType; + TADDR methodDesc; + TADDR gcinfoAddr; + IP2MethodDesc (TO_TADDR(IP), methodDesc, jitType, gcinfoAddr); + if (methodDesc) + { + doIt = TRUE; + } + } + + if (doIt) + { + g_ExtSystem->SetCurrentThreadId(id); + DSFlag.top = 0; + DSFlag.end = 0; + DumpStackInternal(&DSFlag); + } + + CurThread = Thread.nextThread; + } + + g_ExtSystem->SetCurrentThreadId(Tid); + return Status; +} + +HRESULT DumpStackObjectsRaw(size_t nArg, __in_z LPSTR exprBottom, __in_z LPSTR exprTop, BOOL bVerify) +{ + size_t StackTop = 0; + size_t StackBottom = 0; + if (nArg==0) + { + ULONG64 StackOffset; + g_ExtRegisters->GetStackOffset(&StackOffset); + + StackTop = TO_TADDR(StackOffset); + } + else + { + StackTop = GetExpression(exprTop); + if (StackTop == 0) + { + ExtOut("wrong option: %s\n", exprTop); + return E_FAIL; + } + + if (nArg==2) + { + StackBottom = GetExpression(exprBottom); + if (StackBottom == 0) + { + ExtOut("wrong option: %s\n", exprBottom); + return E_FAIL; + } + } + } + +#ifndef FEATURE_PAL + NT_TIB teb; + ULONG64 dwTebAddr=0; + HRESULT hr = g_ExtSystem->GetCurrentThreadTeb(&dwTebAddr); + if (SUCCEEDED(hr) && SafeReadMemory (TO_TADDR(dwTebAddr), &teb, sizeof (NT_TIB), NULL)) + { + if (StackTop > TO_TADDR(teb.StackLimit) && StackTop <= TO_TADDR(teb.StackBase)) + { + if (StackBottom == 0 || StackBottom > TO_TADDR(teb.StackBase)) + StackBottom = TO_TADDR(teb.StackBase); + } + } +#endif + + if (StackBottom == 0) + StackBottom = StackTop + 0xFFFF; + + if (StackBottom < StackTop) + { + ExtOut("Wrong option: stack selection wrong\n"); + return E_FAIL; + } + + // We can use the gc snapshot to eliminate object addresses that are + // not on the gc heap. + if (!g_snapshot.Build()) + { + ExtOut("Unable to determine bounds of gc heap\n"); + return E_FAIL; + } + + // Print thread ID. + ULONG id = 0; + g_ExtSystem->GetCurrentThreadSystemId (&id); + ExtOut("OS Thread Id: 0x%x ", id); + g_ExtSystem->GetCurrentThreadId (&id); + ExtOut("(%d)\n", id); + + DumpStackObjectsHelper(StackTop, StackBottom, bVerify); + return S_OK; +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to dump the address and name of all * +* Managed Objects on the stack. * +* * +\**********************************************************************/ +DECLARE_API(DumpStackObjects) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + StringHolder exprTop, exprBottom; + + BOOL bVerify = FALSE; + BOOL dml = FALSE; + CMDOption option[] = + { // name, vptr, type, hasValue + {"-verify", &bVerify, COBOOL, FALSE}, +#ifndef FEATURE_PAL + {"/d", &dml, COBOOL, FALSE} +#endif + }; + CMDValue arg[] = + { // vptr, type + {&exprTop.data, COSTRING}, + {&exprBottom.data, COSTRING} + }; + size_t nArg; + + if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg)) + { + return Status; + } + + EnableDMLHolder enableDML(dml); + + return DumpStackObjectsRaw(nArg, exprBottom.data, exprTop.data, bVerify); +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to dump the contents of a MethodDesc * +* for a given address * +* * +\**********************************************************************/ +DECLARE_API(DumpMD) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + DWORD_PTR dwStartAddr = NULL; + BOOL dml = FALSE; + + CMDOption option[] = + { // name, vptr, type, hasValue +#ifndef FEATURE_PAL + {"/d", &dml, COBOOL, FALSE}, +#endif + }; + CMDValue arg[] = + { // vptr, type + {&dwStartAddr, COHEX}, + }; + size_t nArg; + + if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg)) + { + return Status; + } + + EnableDMLHolder dmlHolder(dml); + + DumpMDInfo(dwStartAddr); + + return Status; +} + +BOOL GatherDynamicInfo(TADDR DynamicMethodObj, DacpObjectData *codeArray, + DacpObjectData *tokenArray, TADDR *ptokenArrayAddr) +{ + BOOL bRet = FALSE; + int iOffset; + DacpObjectData objData; // temp object + + if (codeArray == NULL || tokenArray == NULL) + return bRet; + + if (objData.Request(g_sos, TO_CDADDR(DynamicMethodObj)) != S_OK) + return bRet; + + iOffset = GetObjFieldOffset(DynamicMethodObj, objData.MethodTable, W("m_resolver")); + if (iOffset <= 0) + return bRet; + + TADDR resolverPtr; + if (FAILED(MOVE(resolverPtr, DynamicMethodObj + iOffset))) + return bRet; + + if (objData.Request(g_sos, TO_CDADDR(resolverPtr)) != S_OK) + return bRet; + + iOffset = GetObjFieldOffset(resolverPtr, objData.MethodTable, W("m_code")); + if (iOffset <= 0) + return bRet; + + TADDR codePtr; + if (FAILED(MOVE(codePtr, resolverPtr + iOffset))) + return bRet; + + if (codeArray->Request(g_sos, TO_CDADDR(codePtr)) != S_OK) + return bRet; + + if (codeArray->dwComponentSize != 1) + return bRet; + + // We also need the resolution table + iOffset = GetObjFieldOffset (resolverPtr, objData.MethodTable, W("m_scope")); + if (iOffset <= 0) + return bRet; + + TADDR scopePtr; + if (FAILED(MOVE(scopePtr, resolverPtr + iOffset))) + return bRet; + + if (objData.Request(g_sos, TO_CDADDR(scopePtr)) != S_OK) + return bRet; + + iOffset = GetObjFieldOffset (scopePtr, objData.MethodTable, W("m_tokens")); + if (iOffset <= 0) + return bRet; + + TADDR tokensPtr; + if (FAILED(MOVE(tokensPtr, scopePtr + iOffset))) + return bRet; + + if (objData.Request(g_sos, TO_CDADDR(tokensPtr)) != S_OK) + return bRet; + + iOffset = GetObjFieldOffset(tokensPtr, objData.MethodTable, W("_items")); + if (iOffset <= 0) + return bRet; + + TADDR itemsPtr; + MOVE (itemsPtr, tokensPtr + iOffset); + + *ptokenArrayAddr = itemsPtr; + + if (tokenArray->Request(g_sos, TO_CDADDR(itemsPtr)) != S_OK) + return bRet; + + bRet = TRUE; // whew. + return bRet; +} + +DECLARE_API(DumpIL) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + DWORD_PTR dwStartAddr = NULL; + DWORD_PTR dwDynamicMethodObj = NULL; + BOOL dml = FALSE; + BOOL fILPointerDirectlySpecified = FALSE; + + CMDOption option[] = + { // name, vptr, type, hasValue + {"/d", &dml, COBOOL, FALSE}, + {"/i", &fILPointerDirectlySpecified, COBOOL, FALSE}, + }; + CMDValue arg[] = + { // vptr, type + {&dwStartAddr, COHEX}, + }; + size_t nArg; + + if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg)) + { + return Status; + } + + EnableDMLHolder dmlHolder(dml); + if (dwStartAddr == NULL) + { + ExtOut("Must pass a valid expression\n"); + return Status; + } + + if (fILPointerDirectlySpecified) + { + return DecodeILFromAddress(NULL, dwStartAddr); + } + + if (!g_snapshot.Build()) + { + ExtOut("Unable to build snapshot of the garbage collector state\n"); + return Status; + } + + if (g_snapshot.GetHeap(dwStartAddr) != NULL) + { + dwDynamicMethodObj = dwStartAddr; + } + + if (dwDynamicMethodObj == NULL) + { + // We have been given a MethodDesc + DacpMethodDescData MethodDescData; + if (MethodDescData.Request(g_sos, TO_CDADDR(dwStartAddr)) != S_OK) + { + ExtOut("%p is not a MethodDesc\n", SOS_PTR(dwStartAddr)); + return Status; + } + + if (MethodDescData.bIsDynamic && MethodDescData.managedDynamicMethodObject) + { + dwDynamicMethodObj = TO_TADDR(MethodDescData.managedDynamicMethodObject); + if (dwDynamicMethodObj == NULL) + { + ExtOut("Unable to print IL for DynamicMethodDesc %p\n", SOS_PTR(dwDynamicMethodObj)); + return Status; + } + } + else + { + // This is not a dynamic method, print the IL for it. + // Get the module + DacpModuleData dmd; + if (dmd.Request(g_sos, MethodDescData.ModulePtr) != S_OK) + { + ExtOut("Unable to get module\n"); + return Status; + } + + ToRelease<IMetaDataImport> pImport = MDImportForModule(&dmd); + if (pImport == NULL) + { + ExtOut("bad import\n"); + return Status; + } + + ULONG pRva; + DWORD dwFlags; + if (pImport->GetRVA(MethodDescData.MDToken, &pRva, &dwFlags) != S_OK) + { + ExtOut("error in import\n"); + return Status; + } + + CLRDATA_ADDRESS ilAddrClr; + if (g_sos->GetILForModule(MethodDescData.ModulePtr, pRva, &ilAddrClr) != S_OK) + { + ExtOut("FindIL failed\n"); + return Status; + } + + TADDR ilAddr = TO_TADDR(ilAddrClr); + IfFailRet(DecodeILFromAddress(pImport, ilAddr)); + } + } + + if (dwDynamicMethodObj != NULL) + { + // We have a DynamicMethod managed object, let us visit the town and paint. + DacpObjectData codeArray; + DacpObjectData tokenArray; + DWORD_PTR tokenArrayAddr; + if (!GatherDynamicInfo (dwDynamicMethodObj, &codeArray, &tokenArray, &tokenArrayAddr)) + { + DMLOut("Error gathering dynamic info from object at %s.\n", DMLObject(dwDynamicMethodObj)); + return Status; + } + + // Read the memory into a local buffer + BYTE *pArray = new NOTHROW BYTE[(SIZE_T)codeArray.dwNumComponents]; + if (pArray == NULL) + { + ExtOut("Not enough memory to read IL\n"); + return Status; + } + + Status = g_ExtData->ReadVirtual(UL64_TO_CDA(codeArray.ArrayDataPtr), pArray, (ULONG)codeArray.dwNumComponents, NULL); + if (Status != S_OK) + { + ExtOut("Failed to read memory\n"); + delete [] pArray; + return Status; + } + + // Now we have a local copy of the IL, and a managed array for token resolution. + // Visit our IL parser with this info. + ExtOut("This is dynamic IL. Exception info is not reported at this time.\n"); + ExtOut("If a token is unresolved, run \"!do <addr>\" on the addr given\n"); + ExtOut("in parenthesis. You can also look at the token table yourself, by\n"); + ExtOut("running \"!DumpArray %p\".\n\n", SOS_PTR(tokenArrayAddr)); + DecodeDynamicIL(pArray, (ULONG)codeArray.dwNumComponents, tokenArray); + + delete [] pArray; + } + return Status; +} + +void DumpSigWorker ( + DWORD_PTR dwSigAddr, + DWORD_PTR dwModuleAddr, + BOOL fMethod) +{ + // + // Find the length of the signature and copy it into the debugger process. + // + + ULONG cbSig = 0; + const ULONG cbSigInc = 256; + ArrayHolder<COR_SIGNATURE> pSig = new NOTHROW COR_SIGNATURE[cbSigInc]; + if (pSig == NULL) + { + ReportOOM(); + return; + } + + CQuickBytes sigString; + for (;;) + { + if (IsInterrupt()) + return; + + ULONG cbCopied; + if (!SafeReadMemory(TO_TADDR(dwSigAddr + cbSig), pSig + cbSig, cbSigInc, &cbCopied)) + return; + cbSig += cbCopied; + + sigString.ReSize(0); + GetSignatureStringResults result; + if (fMethod) + result = GetMethodSignatureString(pSig, cbSig, dwModuleAddr, &sigString); + else + result = GetSignatureString(pSig, cbSig, dwModuleAddr, &sigString); + + if (GSS_ERROR == result) + return; + + if (GSS_SUCCESS == result) + break; + + // If we didn't get the full amount back, and we failed to parse the + // signature, it's not valid because of insufficient data + if (cbCopied < 256) + { + ExtOut("Invalid signature\n"); + return; + } + +#ifdef _PREFAST_ +#pragma warning(push) +#pragma warning(disable:6280) // "Suppress PREFast warning about mismatch alloc/free" +#endif + + PCOR_SIGNATURE pSigNew = (PCOR_SIGNATURE)realloc(pSig, cbSig+cbSigInc); + +#ifdef _PREFAST_ +#pragma warning(pop) +#endif + + if (pSigNew == NULL) + { + ExtOut("Out of memory\n"); + return; + } + + pSig = pSigNew; + } + + ExtOut("%S\n", (PCWSTR)sigString.Ptr()); +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to dump a signature object. * +* * +\**********************************************************************/ +DECLARE_API(DumpSig) +{ + INIT_API(); + + MINIDUMP_NOT_SUPPORTED(); + + // + // Fetch arguments + // + + StringHolder sigExpr; + StringHolder moduleExpr; + CMDValue arg[] = + { + {&sigExpr.data, COSTRING}, + {&moduleExpr.data, COSTRING} + }; + size_t nArg; + if (!GetCMDOption(args, NULL, 0, arg, _countof(arg), &nArg)) + { + return Status; + } + if (nArg != 2) + { + ExtOut("!DumpSig <sigaddr> <moduleaddr>\n"); + return Status; + } + + DWORD_PTR dwSigAddr = GetExpression(sigExpr.data); + DWORD_PTR dwModuleAddr = GetExpression(moduleExpr.data); + + if (dwSigAddr == 0 || dwModuleAddr == 0) + { + ExtOut("Invalid parameters %s %s\n", sigExpr.data, moduleExpr.data); + return Status; + } + + DumpSigWorker(dwSigAddr, dwModuleAddr, TRUE); + return Status; +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to dump a portion of a signature object. * +* * +\**********************************************************************/ +DECLARE_API(DumpSigElem) +{ + INIT_API(); + + MINIDUMP_NOT_SUPPORTED(); + + + // + // Fetch arguments + // + + StringHolder sigExpr; + StringHolder moduleExpr; + CMDValue arg[] = + { + {&sigExpr.data, COSTRING}, + {&moduleExpr.data, COSTRING} + }; + size_t nArg; + if (!GetCMDOption(args, NULL, 0, arg, _countof(arg), &nArg)) + { + return Status; + } + + if (nArg != 2) + { + ExtOut("!DumpSigElem <sigaddr> <moduleaddr>\n"); + return Status; + } + + DWORD_PTR dwSigAddr = GetExpression(sigExpr.data); + DWORD_PTR dwModuleAddr = GetExpression(moduleExpr.data); + + if (dwSigAddr == 0 || dwModuleAddr == 0) + { + ExtOut("Invalid parameters %s %s\n", sigExpr.data, moduleExpr.data); + return Status; + } + + DumpSigWorker(dwSigAddr, dwModuleAddr, FALSE); + return Status; +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to dump the contents of an EEClass from * +* a given address +* * +\**********************************************************************/ +DECLARE_API(DumpClass) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + DWORD_PTR dwStartAddr = 0; + BOOL dml = FALSE; + + CMDOption option[] = + { // name, vptr, type, hasValue +#ifndef FEATURE_PAL + {"/d", &dml, COBOOL, FALSE}, +#endif + }; + CMDValue arg[] = + { // vptr, type + {&dwStartAddr, COHEX} + }; + + size_t nArg; + if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg)) + { + return Status; + } + + if (nArg == 0) + { + ExtOut("Missing EEClass address\n"); + return Status; + } + + EnableDMLHolder dmlHolder(dml); + + CLRDATA_ADDRESS methodTable; + if ((Status=g_sos->GetMethodTableForEEClass(TO_CDADDR(dwStartAddr), &methodTable)) != S_OK) + { + ExtOut("Invalid EEClass address\n"); + return Status; + } + + DacpMethodTableData mtdata; + if ((Status=mtdata.Request(g_sos, TO_CDADDR(methodTable)))!=S_OK) + { + ExtOut("EEClass has an invalid MethodTable address\n"); + return Status; + } + + sos::MethodTable mt = TO_TADDR(methodTable); + ExtOut("Class Name: %S\n", mt.GetName()); + + WCHAR fileName[MAX_LONGPATH]; + FileNameForModule(TO_TADDR(mtdata.Module), fileName); + ExtOut("mdToken: %p\n", mtdata.cl); + ExtOut("File: %S\n", fileName); + + CLRDATA_ADDRESS ParentEEClass = NULL; + if (mtdata.ParentMethodTable) + { + DacpMethodTableData mtdataparent; + if ((Status=mtdataparent.Request(g_sos, TO_CDADDR(mtdata.ParentMethodTable)))!=S_OK) + { + ExtOut("EEClass has an invalid MethodTable address\n"); + return Status; + } + ParentEEClass = mtdataparent.Class; + } + + DMLOut("Parent Class: %s\n", DMLClass(ParentEEClass)); + DMLOut("Module: %s\n", DMLModule(mtdata.Module)); + DMLOut("Method Table: %s\n", DMLMethodTable(methodTable)); + ExtOut("Vtable Slots: %x\n", mtdata.wNumVirtuals); + ExtOut("Total Method Slots: %x\n", mtdata.wNumVtableSlots); + ExtOut("Class Attributes: %x ", mtdata.dwAttrClass); + + if (IsTdInterface(mtdata.dwAttrClass)) + ExtOut("Interface, "); + if (IsTdAbstract(mtdata.dwAttrClass)) + ExtOut("Abstract, "); + if (IsTdImport(mtdata.dwAttrClass)) + ExtOut("ComImport, "); + + ExtOut("\n"); + + DacpMethodTableTransparencyData transparency; + if (SUCCEEDED(transparency.Request(g_sos, methodTable))) + { + ExtOut("Transparency: %s\n", GetTransparency(transparency)); + } + + DacpMethodTableFieldData vMethodTableFields; + if (SUCCEEDED(vMethodTableFields.Request(g_sos, methodTable))) + { + ExtOut("NumInstanceFields: %x\n", vMethodTableFields.wNumInstanceFields); + ExtOut("NumStaticFields: %x\n", vMethodTableFields.wNumStaticFields); + + if (vMethodTableFields.wNumThreadStaticFields != 0) + { + ExtOut("NumThreadStaticFields: %x\n", vMethodTableFields.wNumThreadStaticFields); + } + + + if (vMethodTableFields.wContextStaticsSize) + { + ExtOut("ContextStaticOffset: %x\n", vMethodTableFields.wContextStaticOffset); + ExtOut("ContextStaticsSize: %x\n", vMethodTableFields.wContextStaticsSize); + } + + + if (vMethodTableFields.wNumInstanceFields + vMethodTableFields.wNumStaticFields > 0) + { + DisplayFields(methodTable, &mtdata, &vMethodTableFields, NULL, TRUE, FALSE); + } + } + + return Status; +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to dump the contents of a MethodTable * +* from a given address * +* * +\**********************************************************************/ +DECLARE_API(DumpMT) +{ + DWORD_PTR dwStartAddr=0; + DWORD_PTR dwOriginalAddr; + + INIT_API(); + + MINIDUMP_NOT_SUPPORTED(); + + BOOL bDumpMDTable = FALSE; + BOOL dml = FALSE; + + CMDOption option[] = + { // name, vptr, type, hasValue + {"-MD", &bDumpMDTable, COBOOL, FALSE}, +#ifndef FEATURE_PAL + {"/d", &dml, COBOOL, FALSE} +#endif + }; + CMDValue arg[] = + { // vptr, type + {&dwStartAddr, COHEX} + }; + size_t nArg; + if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg)) + { + return Status; + } + + EnableDMLHolder dmlHolder(dml); + TableOutput table(2, 16, AlignLeft, false); + + if (nArg == 0) + { + Print("Missing MethodTable address\n"); + return Status; + } + + dwOriginalAddr = dwStartAddr; + dwStartAddr = dwStartAddr&~3; + + if (!IsMethodTable(dwStartAddr)) + { + Print(dwOriginalAddr, " is not a MethodTable\n"); + return Status; + } + + DacpMethodTableData vMethTable; + vMethTable.Request(g_sos, TO_CDADDR(dwStartAddr)); + + if (vMethTable.bIsFree) + { + Print("Free MethodTable\n"); + return Status; + } + + table.WriteRow("EEClass:", EEClassPtr(vMethTable.Class)); + + table.WriteRow("Module:", ModulePtr(vMethTable.Module)); + + sos::MethodTable mt = (TADDR)dwStartAddr; + table.WriteRow("Name:", mt.GetName()); + + WCHAR fileName[MAX_LONGPATH]; + FileNameForModule(TO_TADDR(vMethTable.Module), fileName); + table.WriteRow("mdToken:", Pointer(vMethTable.cl)); + table.WriteRow("File:", fileName[0] ? fileName : W("Unknown Module")); + + table.WriteRow("BaseSize:", PrefixHex(vMethTable.BaseSize)); + table.WriteRow("ComponentSize:", PrefixHex(vMethTable.ComponentSize)); + table.WriteRow("Slots in VTable:", Decimal(vMethTable.wNumMethods)); + + table.SetColWidth(0, 29); + table.WriteRow("Number of IFaces in IFaceMap:", Decimal(vMethTable.wNumInterfaces)); + + if (bDumpMDTable) + { + table.ReInit(4, POINTERSIZE_HEX, AlignRight); + table.SetColAlignment(3, AlignLeft); + table.SetColWidth(2, 6); + + Print("--------------------------------------\n"); + Print("MethodDesc Table\n"); + + table.WriteRow("Entry", "MethodDesc", "JIT", "Name"); + + for (DWORD n = 0; n < vMethTable.wNumMethods; n++) + { + JITTypes jitType; + DWORD_PTR methodDesc=0; + DWORD_PTR gcinfoAddr; + + CLRDATA_ADDRESS entry; + if (g_sos->GetMethodTableSlot(dwStartAddr, n, &entry) != S_OK) + { + PrintLn("<error getting slot ", Decimal(n), ">"); + continue; + } + + IP2MethodDesc((DWORD_PTR)entry, methodDesc, jitType, gcinfoAddr); + table.WriteColumn(0, entry); + table.WriteColumn(1, MethodDescPtr(methodDesc)); + + if (jitType == TYPE_UNKNOWN && methodDesc != NULL) + { + // We can get a more accurate jitType from NativeCodeAddr of the methoddesc, + // because the methodtable entry hasn't always been patched. + DacpMethodDescData tmpMethodDescData; + if (tmpMethodDescData.Request(g_sos, TO_CDADDR(methodDesc)) == S_OK) + { + DacpCodeHeaderData codeHeaderData; + if (codeHeaderData.Request(g_sos,tmpMethodDescData.NativeCodeAddr) == S_OK) + { + jitType = (JITTypes) codeHeaderData.JITType; + } + } + } + + const char *pszJitType = "NONE"; + if (jitType == TYPE_JIT) + pszJitType = "JIT"; + else if (jitType == TYPE_PJIT) + pszJitType = "PreJIT"; + else + { + DacpMethodDescData MethodDescData; + if (MethodDescData.Request(g_sos, TO_CDADDR(methodDesc)) == S_OK) + { + // Is it an fcall? + if ((TO_TADDR(MethodDescData.NativeCodeAddr) >= TO_TADDR(moduleInfo[MSCORWKS].baseAddr)) && + ((TO_TADDR(MethodDescData.NativeCodeAddr) < TO_TADDR(moduleInfo[MSCORWKS].baseAddr + moduleInfo[MSCORWKS].size)))) + { + pszJitType = "FCALL"; + } + } + } + + table.WriteColumn(2, pszJitType); + + NameForMD_s(methodDesc,g_mdName,mdNameLen); + table.WriteColumn(3, g_mdName); + } + } + return Status; +} + +extern size_t Align (size_t nbytes); + +HRESULT PrintVC(TADDR taMT, TADDR taObject, BOOL bPrintFields = TRUE) +{ + HRESULT Status; + DacpMethodTableData mtabledata; + if ((Status = mtabledata.Request(g_sos, TO_CDADDR(taMT)))!=S_OK) + return Status; + + size_t size = mtabledata.BaseSize; + if ((Status=g_sos->GetMethodTableName(TO_CDADDR(taMT), mdNameLen, g_mdName, NULL))!=S_OK) + return Status; + + ExtOut("Name: %S\n", g_mdName); + DMLOut("MethodTable: %s\n", DMLMethodTable(taMT)); + DMLOut("EEClass: %s\n", DMLClass(mtabledata.Class)); + ExtOut("Size: %d(0x%x) bytes\n", size, size); + + FileNameForModule(TO_TADDR(mtabledata.Module), g_mdName); + ExtOut("File: %S\n", g_mdName[0] ? g_mdName : W("Unknown Module")); + + if (bPrintFields) + { + DacpMethodTableFieldData vMethodTableFields; + if ((Status = vMethodTableFields.Request(g_sos,TO_CDADDR(taMT)))!=S_OK) + return Status; + + ExtOut("Fields:\n"); + + if (vMethodTableFields.wNumInstanceFields + vMethodTableFields.wNumStaticFields > 0) + DisplayFields(TO_CDADDR(taMT), &mtabledata, &vMethodTableFields, taObject, TRUE, TRUE); + } + + return S_OK; +} + +void PrintRuntimeTypeInfo(TADDR p_rtObject, const DacpObjectData & rtObjectData) +{ + // Get the method table + int iOffset = GetObjFieldOffset(p_rtObject, rtObjectData.MethodTable, W("m_handle")); + if (iOffset > 0) + { + TADDR mtPtr; + if (SUCCEEDED(GetMTOfObject(p_rtObject + iOffset, &mtPtr))) + { + sos::MethodTable mt = mtPtr; + ExtOut("Type Name: %S\n", mt.GetName()); + DMLOut("Type MT: %s\n", DMLMethodTable(mtPtr)); + } + } +} + +HRESULT PrintObj(TADDR taObj, BOOL bPrintFields = TRUE) +{ + if (!sos::IsObject(taObj, true)) + { + ExtOut("<Note: this object has an invalid CLASS field>\n"); + } + + DacpObjectData objData; + HRESULT Status; + if ((Status=objData.Request(g_sos, TO_CDADDR(taObj))) != S_OK) + { + ExtOut("Invalid object\n"); + return Status; + } + + if (objData.ObjectType==OBJ_FREE) + { + ExtOut("Free Object\n"); + DWORD_PTR size = (DWORD_PTR)objData.Size; + ExtOut("Size: %" POINTERSIZE_TYPE "d(0x%" POINTERSIZE_TYPE "x) bytes\n", size, size); + return S_OK; + } + + sos::Object obj = taObj; + ExtOut("Name: %S\n", obj.GetTypeName()); + DMLOut("MethodTable: %s\n", DMLMethodTable(objData.MethodTable)); + + + DacpMethodTableData mtabledata; + if ((Status=mtabledata.Request(g_sos,objData.MethodTable)) == S_OK) + { + DMLOut("EEClass: %s\n", DMLClass(mtabledata.Class)); + } + else + { + ExtOut("Invalid EEClass address\n"); + return Status; + } + + if (objData.RCW != NULL) + { + DMLOut("RCW: %s\n", DMLRCWrapper(objData.RCW)); + } + if (objData.CCW != NULL) + { + DMLOut("CCW: %s\n", DMLCCWrapper(objData.CCW)); + } + + DWORD_PTR size = (DWORD_PTR)objData.Size; + ExtOut("Size: %" POINTERSIZE_TYPE "d(0x%" POINTERSIZE_TYPE "x) bytes\n", size, size); + + if (_wcscmp(obj.GetTypeName(), W("System.RuntimeType")) == 0) + { + PrintRuntimeTypeInfo(taObj, objData); + } + + if (_wcscmp(obj.GetTypeName(), W("System.RuntimeType+RuntimeTypeCache")) == 0) + { + // Get the method table + int iOffset = GetObjFieldOffset (taObj, objData.MethodTable, W("m_runtimeType")); + if (iOffset > 0) + { + TADDR rtPtr; + if (MOVE(rtPtr, taObj + iOffset) == S_OK) + { + DacpObjectData rtObjectData; + if ((Status=rtObjectData.Request(g_sos, TO_CDADDR(rtPtr))) != S_OK) + { + ExtOut("Error when reading RuntimeType field\n"); + return Status; + } + + PrintRuntimeTypeInfo(rtPtr, rtObjectData); + } + } + } + + if (objData.ObjectType==OBJ_ARRAY) + { + ExtOut("Array: Rank %d, Number of elements %" POINTERSIZE_TYPE "d, Type %s", + objData.dwRank, (DWORD_PTR)objData.dwNumComponents, ElementTypeName(objData.ElementType)); + + IfDMLOut(" (<exec cmd=\"!DumpArray /d %p\">Print Array</exec>)", SOS_PTR(taObj)); + ExtOut("\n"); + + if (objData.ElementType == ELEMENT_TYPE_I1 || + objData.ElementType == ELEMENT_TYPE_U1 || + objData.ElementType == ELEMENT_TYPE_CHAR) + { + bool wide = objData.ElementType == ELEMENT_TYPE_CHAR; + + // Get the size of the character array, but clamp it to a reasonable length. + TADDR pos = taObj + (2 * sizeof(DWORD_PTR)); + DWORD_PTR num; + moveN(num, taObj + sizeof(DWORD_PTR)); + + if (IsDMLEnabled()) + DMLOut("<exec cmd=\"%s %x L%x\">Content</exec>: ", (wide) ? "dw" : "db", pos, num); + else + ExtOut("Content: "); + CharArrayContent(pos, (ULONG)(num <= 128 ? num : 128), wide); + ExtOut("\n"); + } + } + else + { + FileNameForModule(TO_TADDR(mtabledata.Module), g_mdName); + ExtOut("File: %S\n", g_mdName[0] ? g_mdName : W("Unknown Module")); + } + + if (objData.ObjectType == OBJ_STRING) + { + ExtOut("String: "); + StringObjectContent(taObj); + ExtOut("\n"); + } + else if (objData.ObjectType == OBJ_OBJECT) + { + ExtOut("Object\n"); + } + + if (bPrintFields) + { + DacpMethodTableFieldData vMethodTableFields; + if ((Status = vMethodTableFields.Request(g_sos,TO_CDADDR(objData.MethodTable)))!=S_OK) + return Status; + + ExtOut("Fields:\n"); + if (vMethodTableFields.wNumInstanceFields + vMethodTableFields.wNumStaticFields > 0) + { + DisplayFields(objData.MethodTable, &mtabledata, &vMethodTableFields, taObj, TRUE, FALSE); + } + else + { + ExtOut("None\n"); + } + } + + sos::ThinLockInfo lockInfo; + if (obj.GetThinLock(lockInfo)) + { + ExtOut("ThinLock owner %x (%p), Recursive %x\n", lockInfo.ThreadId, + SOS_PTR(lockInfo.ThreadPtr), lockInfo.Recursion); + } + + return S_OK; +} + +BOOL IndicesInRange (DWORD * indices, DWORD * lowerBounds, DWORD * bounds, DWORD rank) +{ + int i = 0; + if (!ClrSafeInt<int>::subtraction((int)rank, 1, i)) + { + ExtOut("<integer underflow>\n"); + return FALSE; + } + + for (; i >= 0; i--) + { + if (indices[i] >= bounds[i] + lowerBounds[i]) + { + if (i == 0) + { + return FALSE; + } + + indices[i] = lowerBounds[i]; + indices[i - 1]++; + } + } + + return TRUE; +} + +void ExtOutIndices (DWORD * indices, DWORD rank) +{ + for (DWORD i = 0; i < rank; i++) + { + ExtOut("[%d]", indices[i]); + } +} + +size_t OffsetFromIndices (DWORD * indices, DWORD * lowerBounds, DWORD * bounds, DWORD rank) +{ + _ASSERTE(rank >= 0); + size_t multiplier = 1; + size_t offset = 0; + int i = 0; + if (!ClrSafeInt<int>::subtraction((int)rank, 1, i)) + { + ExtOut("<integer underflow>\n"); + return 0; + } + + for (; i >= 0; i--) + { + DWORD curIndex = indices[i] - lowerBounds[i]; + offset += curIndex * multiplier; + multiplier *= bounds[i]; + } + + return offset; +} +HRESULT PrintArray(DacpObjectData& objData, DumpArrayFlags& flags, BOOL isPermSetPrint); +#ifdef _DEBUG +HRESULT PrintPermissionSet (TADDR p_PermSet) +{ + HRESULT Status = S_OK; + + DacpObjectData PermSetData; + if ((Status=PermSetData.Request(g_sos, TO_CDADDR(p_PermSet))) != S_OK) + { + ExtOut("Invalid object\n"); + return Status; + } + + + sos::MethodTable mt = TO_TADDR(PermSetData.MethodTable); + if (_wcscmp (W("System.Security.PermissionSet"), mt.GetName()) != 0 && _wcscmp(W("System.Security.NamedPermissionSet"), mt.GetName()) != 0) + { + ExtOut("Invalid PermissionSet object\n"); + return S_FALSE; + } + + ExtOut("PermissionSet object: %p\n", SOS_PTR(p_PermSet)); + + // Print basic info + + // Walk the fields, printing some fields in a special way. + + int iOffset = GetObjFieldOffset (p_PermSet, PermSetData.MethodTable, W("m_Unrestricted")); + + if (iOffset > 0) + { + BYTE unrestricted; + MOVE(unrestricted, p_PermSet + iOffset); + if (unrestricted) + ExtOut("Unrestricted: TRUE\n"); + else + ExtOut("Unrestricted: FALSE\n"); + } + + iOffset = GetObjFieldOffset (p_PermSet, PermSetData.MethodTable, W("m_permSet")); + if (iOffset > 0) + { + TADDR tbSetPtr; + MOVE(tbSetPtr, p_PermSet + iOffset); + if (tbSetPtr != NULL) + { + DacpObjectData tbSetData; + if ((Status=tbSetData.Request(g_sos, TO_CDADDR(tbSetPtr))) != S_OK) + { + ExtOut("Invalid object\n"); + return Status; + } + + iOffset = GetObjFieldOffset (tbSetPtr, tbSetData.MethodTable, W("m_Set")); + if (iOffset > 0) + { + DWORD_PTR PermsArrayPtr; + MOVE(PermsArrayPtr, tbSetPtr + iOffset); + if (PermsArrayPtr != NULL) + { + // Print all the permissions in the array + DacpObjectData objData; + if ((Status=objData.Request(g_sos, TO_CDADDR(PermsArrayPtr))) != S_OK) + { + ExtOut("Invalid object\n"); + return Status; + } + DumpArrayFlags flags; + flags.bDetail = TRUE; + return PrintArray(objData, flags, TRUE); + } + } + + iOffset = GetObjFieldOffset (tbSetPtr, tbSetData.MethodTable, W("m_Obj")); + if (iOffset > 0) + { + DWORD_PTR PermObjPtr; + MOVE(PermObjPtr, tbSetPtr + iOffset); + if (PermObjPtr != NULL) + { + // Print the permission object + return PrintObj(PermObjPtr); + } + } + + + } + } + return Status; +} + +#endif // _DEBUG + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to dump the contents of an object from a * +* given address +* * +\**********************************************************************/ +DECLARE_API(DumpArray) +{ + INIT_API(); + + DumpArrayFlags flags; + + MINIDUMP_NOT_SUPPORTED(); + + BOOL dml = FALSE; + + CMDOption option[] = + { // name, vptr, type, hasValue + {"-start", &flags.startIndex, COSIZE_T, TRUE}, + {"-length", &flags.Length, COSIZE_T, TRUE}, + {"-details", &flags.bDetail, COBOOL, FALSE}, + {"-nofields", &flags.bNoFieldsForElement, COBOOL, FALSE}, +#ifndef FEATURE_PAL + {"/d", &dml, COBOOL, FALSE}, +#endif + }; + CMDValue arg[] = + { // vptr, type + {&flags.strObject, COSTRING} + }; + size_t nArg; + if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg)) + { + return Status; + } + + EnableDMLHolder dmlHolder(dml); + DWORD_PTR p_Object = GetExpression (flags.strObject); + if (p_Object == 0) + { + ExtOut("Invalid parameter %s\n", flags.strObject); + return Status; + } + + if (!sos::IsObject(p_Object, true)) + { + ExtOut("<Note: this object has an invalid CLASS field>\n"); + } + + DacpObjectData objData; + if ((Status=objData.Request(g_sos, TO_CDADDR(p_Object))) != S_OK) + { + ExtOut("Invalid object\n"); + return Status; + } + + if (objData.ObjectType != OBJ_ARRAY) + { + ExtOut("Not an array, please use !DumpObj instead\n"); + return S_OK; + } + return PrintArray(objData, flags, FALSE); +} + + +HRESULT PrintArray(DacpObjectData& objData, DumpArrayFlags& flags, BOOL isPermSetPrint) +{ + HRESULT Status = S_OK; + + if (objData.dwRank != 1 && (flags.Length != (DWORD_PTR)-1 ||flags.startIndex != 0)) + { + ExtOut("For multi-dimension array, length and start index are supported\n"); + return S_OK; + } + + if (flags.startIndex > objData.dwNumComponents) + { + ExtOut("Start index out of range\n"); + return S_OK; + } + + if (!flags.bDetail && flags.bNoFieldsForElement) + { + ExtOut("-nofields has no effect unless -details is specified\n"); + } + + DWORD i; + if (!isPermSetPrint) + { + // TODO: don't depend on this being a MethodTable + NameForMT_s(TO_TADDR(objData.ElementTypeHandle), g_mdName, mdNameLen); + + ExtOut("Name: %S[", g_mdName); + for (i = 1; i < objData.dwRank; i++) + ExtOut(","); + ExtOut("]\n"); + + DMLOut("MethodTable: %s\n", DMLMethodTable(objData.MethodTable)); + + { + DacpMethodTableData mtdata; + if (SUCCEEDED(mtdata.Request(g_sos, objData.MethodTable))) + { + DMLOut("EEClass: %s\n", DMLClass(mtdata.Class)); + } + } + + DWORD_PTR size = (DWORD_PTR)objData.Size; + ExtOut("Size: %" POINTERSIZE_TYPE "d(0x%" POINTERSIZE_TYPE "x) bytes\n", size, size); + + ExtOut("Array: Rank %d, Number of elements %" POINTERSIZE_TYPE "d, Type %s\n", + objData.dwRank, (DWORD_PTR)objData.dwNumComponents, ElementTypeName(objData.ElementType)); + DMLOut("Element Methodtable: %s\n", DMLMethodTable(objData.ElementTypeHandle)); + } + + BOOL isElementValueType = IsElementValueType(objData.ElementType); + + DWORD dwRankAllocSize; + if (!ClrSafeInt<DWORD>::multiply(sizeof(DWORD), objData.dwRank, dwRankAllocSize)) + { + ExtOut("Integer overflow on array rank\n"); + return Status; + } + + DWORD *lowerBounds = (DWORD *)alloca(dwRankAllocSize); + if (!SafeReadMemory(objData.ArrayLowerBoundsPtr, lowerBounds, dwRankAllocSize, NULL)) + { + ExtOut("Failed to read lower bounds info from the array\n"); + return S_OK; + } + + DWORD *bounds = (DWORD *)alloca(dwRankAllocSize); + if (!SafeReadMemory (objData.ArrayBoundsPtr, bounds, dwRankAllocSize, NULL)) + { + ExtOut("Failed to read bounds info from the array\n"); + return S_OK; + } + + //length is only supported for single-dimension array + if (objData.dwRank == 1 && flags.Length != (DWORD_PTR)-1) + { + bounds[0] = _min(bounds[0], (DWORD)(flags.Length + flags.startIndex) - lowerBounds[0]); + } + + DWORD *indices = (DWORD *)alloca(dwRankAllocSize); + for (i = 0; i < objData.dwRank; i++) + { + indices[i] = lowerBounds[i]; + } + + //start index is only supported for single-dimension array + if (objData.dwRank == 1) + { + indices[0] = (DWORD)flags.startIndex; + } + + //Offset should be calculated by OffsetFromIndices. However because of the way + //how we grow indices, incrementing offset by one happens to match indices in every iteration + for (size_t offset = OffsetFromIndices (indices, lowerBounds, bounds, objData.dwRank); + IndicesInRange (indices, lowerBounds, bounds, objData.dwRank); + indices[objData.dwRank - 1]++, offset++) + { + if (IsInterrupt()) + { + ExtOut("interrupted by user\n"); + break; + } + + TADDR elementAddress = TO_TADDR(objData.ArrayDataPtr + offset * objData.dwComponentSize); + TADDR p_Element = NULL; + if (isElementValueType) + { + p_Element = elementAddress; + } + else if (!SafeReadMemory (elementAddress, &p_Element, sizeof (p_Element), NULL)) + { + ExtOut("Failed to read element at "); + ExtOutIndices(indices, objData.dwRank); + ExtOut("\n"); + continue; + } + + if (p_Element) + { + ExtOutIndices(indices, objData.dwRank); + + if (isElementValueType) + { + DMLOut( " %s\n", DMLValueClass(objData.MethodTable, p_Element)); + } + else + { + DMLOut(" %s\n", DMLObject(p_Element)); + } + } + else if (!isPermSetPrint) + { + ExtOutIndices(indices, objData.dwRank); + ExtOut(" null\n"); + } + + if (flags.bDetail) + { + IncrementIndent(); + if (isElementValueType) + { + PrintVC(TO_TADDR(objData.ElementTypeHandle), elementAddress, !flags.bNoFieldsForElement); + } + else if (p_Element != NULL) + { + PrintObj(p_Element, !flags.bNoFieldsForElement); + } + DecrementIndent(); + } + } + + return S_OK; +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to dump the contents of an object from a * +* given address +* * +\**********************************************************************/ +DECLARE_API(DumpObj) +{ + INIT_API(); + + MINIDUMP_NOT_SUPPORTED(); + + BOOL dml = FALSE; + BOOL bNoFields = FALSE; + BOOL bRefs = FALSE; + StringHolder str_Object; + CMDOption option[] = + { // name, vptr, type, hasValue + {"-nofields", &bNoFields, COBOOL, FALSE}, + {"-refs", &bRefs, COBOOL, FALSE}, +#ifndef FEATURE_PAL + {"/d", &dml, COBOOL, FALSE}, +#endif + }; + CMDValue arg[] = + { // vptr, type + {&str_Object.data, COSTRING} + }; + size_t nArg; + if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg)) + { + return Status; + } + + DWORD_PTR p_Object = GetExpression(str_Object.data); + EnableDMLHolder dmlHolder(dml); + if (p_Object == 0) + { + ExtOut("Invalid parameter %s\n", args); + return Status; + } + + Status = PrintObj(p_Object, !bNoFields); + + if (SUCCEEDED(Status) && bRefs) + { + ExtOut("GC Refs:\n"); + TableOutput out(2, POINTERSIZE_HEX, AlignRight, 4); + out.WriteRow("offset", "object"); + for (sos::RefIterator itr(TO_TADDR(p_Object)); itr; ++itr) + out.WriteRow(Hex(itr.GetOffset()), ObjectPtr(*itr)); + } + + return Status; +} + +CLRDATA_ADDRESS isExceptionObj(CLRDATA_ADDRESS mtObj) +{ + // We want to follow back until we get the mt for System.Exception + DacpMethodTableData dmtd; + CLRDATA_ADDRESS walkMT = mtObj; + while(walkMT != NULL) + { + if (dmtd.Request(g_sos, walkMT) != S_OK) + { + break; + } + if (walkMT == g_special_usefulGlobals.ExceptionMethodTable) + { + return walkMT; + } + walkMT = dmtd.ParentMethodTable; + } + return NULL; +} + +CLRDATA_ADDRESS isSecurityExceptionObj(CLRDATA_ADDRESS mtObj) +{ + // We want to follow back until we get the mt for System.Exception + DacpMethodTableData dmtd; + CLRDATA_ADDRESS walkMT = mtObj; + while(walkMT != NULL) + { + if (dmtd.Request(g_sos, walkMT) != S_OK) + { + break; + } + NameForMT_s(TO_TADDR(walkMT), g_mdName, mdNameLen); + if (_wcscmp(W("System.Security.SecurityException"), g_mdName) == 0) + { + return walkMT; + } + walkMT = dmtd.ParentMethodTable; + } + return NULL; +} + +// Fill the passed in buffer with a text header for generated exception information. +// Returns the number of characters in the wszBuffer array on exit. +// If NULL is passed for wszBuffer, just returns the number of characters needed. +size_t AddExceptionHeader (__out_ecount_opt(bufferLength) WCHAR *wszBuffer, size_t bufferLength) +{ +#ifdef _TARGET_WIN64_ + const WCHAR *wszHeader = W(" SP IP Function\n"); +#else + const WCHAR *wszHeader = W(" SP IP Function\n"); +#endif // _TARGET_WIN64_ + if (wszBuffer) + { + swprintf_s(wszBuffer, bufferLength, wszHeader); + } + return _wcslen(wszHeader); +} + +struct StackTraceElement +{ + UINT_PTR ip; + UINT_PTR sp; + DWORD_PTR pFunc; // MethodDesc +#if defined(FEATURE_EXCEPTIONDISPATCHINFO) + // TRUE if this element represents the last frame of the foreign + // exception stack trace. + BOOL fIsLastFrameFromForeignStackTrace; +#endif // defined(FEATURE_EXCEPTIONDISPATCHINFO) + +}; + +#include "sos_stacktrace.h" + +class StringOutput +{ +public: + CQuickString cs; + StringOutput() + { + cs.Alloc(1024); + cs.String()[0] = L'\0'; + } + + BOOL Append(__in_z LPCWSTR pszStr) + { + size_t iInputLen = _wcslen (pszStr); + size_t iCurLen = _wcslen (cs.String()); + if ((iCurLen + iInputLen + 1) > cs.Size()) + { + if (cs.ReSize(iCurLen + iInputLen + 1) != S_OK) + { + return FALSE; + } + } + + wcsncat_s (cs.String(), cs.Size(), pszStr, _TRUNCATE); + return TRUE; + } + + size_t Length() + { + return _wcslen(cs.String()); + } + + WCHAR *String() + { + return cs.String(); + } +}; + +static HRESULT DumpMDInfoBuffer(DWORD_PTR dwStartAddr, DWORD Flags, ULONG64 Esp, ULONG64 IPAddr, StringOutput& so); + +// Using heuristics to determine if an exception object represented an async (hardware) or a +// managed exception +// We need to use these heuristics when the System.Exception object is not the active exception +// on some thread, but it's something found somewhere on the managed heap. + +// uses the MapWin32FaultToCOMPlusException to figure out how we map async exceptions +// to managed exceptions and their HRESULTs +static const HRESULT AsyncHResultValues[] = +{ + COR_E_ARITHMETIC, // kArithmeticException + COR_E_OVERFLOW, // kOverflowException + COR_E_DIVIDEBYZERO, // kDivideByZeroException + COR_E_FORMAT, // kFormatException + COR_E_NULLREFERENCE, // kNullReferenceException + E_POINTER, // kAccessViolationException + // the EE is raising the next exceptions more often than the OS will raise an async + // exception for these conditions, so in general treat these as Synchronous + // COR_E_INDEXOUTOFRANGE, // kIndexOutOfRangeException + // COR_E_OUTOFMEMORY, // kOutOfMemoryException + // COR_E_STACKOVERFLOW, // kStackOverflowException + COR_E_DATAMISALIGNED, // kDataMisalignedException + +}; +BOOL IsAsyncException(TADDR taObj, TADDR mtObj) +{ + // by default we'll treat exceptions as synchronous + UINT32 xcode = EXCEPTION_COMPLUS; + int iOffset = GetObjFieldOffset (taObj, mtObj, W("_xcode")); + if (iOffset > 0) + { + HRESULT hr = MOVE(xcode, taObj + iOffset); + if (hr != S_OK) + { + xcode = EXCEPTION_COMPLUS; + goto Done; + } + } + + if (xcode == EXCEPTION_COMPLUS) + { + HRESULT ehr = 0; + iOffset = GetObjFieldOffset (taObj, mtObj, W("_HResult")); + if (iOffset > 0) + { + HRESULT hr = MOVE(ehr, taObj + iOffset); + if (hr != S_OK) + { + xcode = EXCEPTION_COMPLUS; + goto Done; + } + for (size_t idx = 0; idx < _countof(AsyncHResultValues); ++idx) + { + if (ehr == AsyncHResultValues[idx]) + { + xcode = ehr; + break; + } + } + } + } +Done: + return xcode != EXCEPTION_COMPLUS; +} + +// Overload that mirrors the code above when the ExceptionObjectData was already retrieved from LS +BOOL IsAsyncException(const DacpExceptionObjectData & excData) +{ + if (excData.XCode != EXCEPTION_COMPLUS) + return TRUE; + + HRESULT ehr = excData.HResult; + for (size_t idx = 0; idx < _countof(AsyncHResultValues); ++idx) + { + if (ehr == AsyncHResultValues[idx]) + { + return TRUE; + } + } + + return FALSE; +} + + +#define SOS_STACKTRACE_SHOWEXPLICITFRAMES 0x00000002 +size_t FormatGeneratedException (DWORD_PTR dataPtr, + UINT bytes, + __out_ecount_opt(bufferLength) WCHAR *wszBuffer, + size_t bufferLength, + BOOL bAsync, + BOOL bNestedCase = FALSE, + BOOL bLineNumbers = FALSE) +{ + UINT count = bytes / sizeof(StackTraceElement); + size_t Length = 0; + + if (wszBuffer && bufferLength > 0) + { + wszBuffer[0] = L'\0'; + } + + // Buffer is calculated for sprintf below (" %p %p %S\n"); + WCHAR wszLineBuffer[mdNameLen + 8 + sizeof(size_t)*2 + MAX_LONGPATH + 8]; + + if (count == 0) + { + return 0; + } + + if (bNestedCase) + { + // If we are computing the call stack for a nested exception, we + // don't want to print the last frame, because the outer exception + // will have that frame. + count--; + } + + for (UINT i = 0; i < count; i++) + { + StackTraceElement ste; + MOVE (ste, dataPtr + i*sizeof(StackTraceElement)); + + // ste.ip must be adjusted because of an ancient workaround in the exception + // infrastructure. The workaround is that the exception needs to have + // an ip address that will map to the line number where the exception was thrown. + // (It doesn't matter that it's not a valid instruction). (see /vm/excep.cpp) + // + // This "counterhack" is not 100% accurate + // The biggest issue is that !PrintException must work with exception objects + // that may not be currently active; as a consequence we cannot rely on the + // state of some "current thread" to infer whether the IP values stored in + // the exception object have been adjusted or not. If we could, we may examine + // the topmost "Frame" and make the decision based on whether it's a + // FaultingExceptionFrame or not. + // 1. On IA64 the IP values are never adjusted by the EE so there's nothing + // to adjust back. + // 2. On AMD64: + // (a) if the exception was an async (hardware) exception add 1 to all + // IP values in the exception object + // (b) if the exception was a managed exception (either raised by the + // EE or thrown by managed code) do not adjust any IP values + // 3. On X86: + // (a) if the exception was an async (hardware) exception add 1 to + // all but the topmost IP value in the exception object + // (b) if the exception was a managed exception (either raised by + // the EE or thrown by managed code) add 1 to all IP values in + // the exception object +#if defined(_TARGET_AMD64_) + if (bAsync) + { + ste.ip += 1; + } +#elif defined(_TARGET_X86_) + if (IsDbgTargetX86() && (!bAsync || i != 0)) + { + ste.ip += 1; + } +#endif // defined(_TARGET_AMD64_) || defined(_TARGET__X86_) + + StringOutput so; + HRESULT Status = DumpMDInfoBuffer(ste.pFunc, SOS_STACKTRACE_SHOWADDRESSES|SOS_STACKTRACE_SHOWEXPLICITFRAMES, ste.sp, ste.ip, so); + + // If DumpMDInfoBuffer failed (due to out of memory or missing metadata), + // or did not update so (when ste is an explicit frames), do not update wszBuffer + if (Status == S_OK) + { + WCHAR filename[MAX_LONGPATH] = W(""); + ULONG linenum = 0; + if (bLineNumbers && + SUCCEEDED(GetLineByOffset(TO_CDADDR(ste.ip), &linenum, filename, _countof(filename)))) + { + swprintf_s(wszLineBuffer, _countof(wszLineBuffer), W(" %s [%s @ %d]\n"), so.String(), filename, linenum); + } + else + { + swprintf_s(wszLineBuffer, _countof(wszLineBuffer), W(" %s\n"), so.String()); + } + + Length += _wcslen(wszLineBuffer); + + if (wszBuffer) + { + wcsncat_s(wszBuffer, bufferLength, wszLineBuffer, _TRUNCATE); + } + } + } + + return Length; +} + +// ExtOut has an internal limit for the string size +void SosExtOutLargeString(__inout_z __inout_ecount_opt(len) WCHAR * pwszLargeString, size_t len) +{ + const size_t chunkLen = 2048; + + WCHAR *pwsz = pwszLargeString; // beginning of a chunk + size_t count = len/chunkLen; + // write full chunks + for (size_t idx = 0; idx < count; ++idx) + { + WCHAR *pch = pwsz + chunkLen; // after the chunk + // zero terminate the chunk + WCHAR ch = *pch; + *pch = L'\0'; + + ExtOut("%S", pwsz); + + // restore whacked char + *pch = ch; + + // advance to next chunk + pwsz += chunkLen; + } + + // last chunk + ExtOut("%S", pwsz); +} + +HRESULT FormatException(TADDR taObj, BOOL bLineNumbers = FALSE) +{ + HRESULT Status = S_OK; + + DacpObjectData objData; + if ((Status=objData.Request(g_sos, TO_CDADDR(taObj))) != S_OK) + { + ExtOut("Invalid object\n"); + return Status; + } + + // Make sure it is an exception object, and get the MT of Exception + CLRDATA_ADDRESS exceptionMT = isExceptionObj(objData.MethodTable); + if (exceptionMT == NULL) + { + ExtOut("Not a valid exception object\n"); + return Status; + } + + DMLOut("Exception object: %s\n", DMLObject(taObj)); + + if (NameForMT_s(TO_TADDR(objData.MethodTable), g_mdName, mdNameLen)) + { + ExtOut("Exception type: %S\n", g_mdName); + } + else + { + ExtOut("Exception type: <Unknown>\n"); + } + + // Print basic info + + // First try to get exception object data using ISOSDacInterface2 + DacpExceptionObjectData excData; + BOOL bGotExcData = SUCCEEDED(excData.Request(g_sos, TO_CDADDR(taObj))); + + // Walk the fields, printing some fields in a special way. + // HR, InnerException, Message, StackTrace, StackTraceString + + { + TADDR taMsg = 0; + if (bGotExcData) + { + taMsg = TO_TADDR(excData.Message); + } + else + { + int iOffset = GetObjFieldOffset(taObj, objData.MethodTable, W("_message")); + if (iOffset > 0) + { + MOVE (taMsg, taObj + iOffset); + } + } + + ExtOut("Message: "); + + if (taMsg) + StringObjectContent(taMsg); + else + ExtOut("<none>"); + + ExtOut("\n"); + } + + { + TADDR taInnerExc = 0; + if (bGotExcData) + { + taInnerExc = TO_TADDR(excData.InnerException); + } + else + { + int iOffset = GetObjFieldOffset(taObj, objData.MethodTable, W("_innerException")); + if (iOffset > 0) + { + MOVE (taInnerExc, taObj + iOffset); + } + } + + ExtOut("InnerException: "); + if (taInnerExc) + { + TADDR taMT; + if (SUCCEEDED(GetMTOfObject(taInnerExc, &taMT))) + { + NameForMT_s(taMT, g_mdName, mdNameLen); + ExtOut("%S, ", g_mdName); + if (IsDMLEnabled()) + DMLOut("Use <exec cmd=\"!PrintException /d %p\">!PrintException %p</exec> to see more.\n", taInnerExc, taInnerExc); + else + ExtOut("Use !PrintException %p to see more.\n", SOS_PTR(taInnerExc)); + } + else + { + ExtOut("<invalid MethodTable of inner exception>"); + } + } + else + { + ExtOut("<none>\n"); + } + } + + BOOL bAsync = bGotExcData ? IsAsyncException(excData) + : IsAsyncException(taObj, TO_TADDR(objData.MethodTable)); + + { + TADDR taStackTrace = 0; + if (bGotExcData) + { + taStackTrace = TO_TADDR(excData.StackTrace); + } + else + { + int iOffset = GetObjFieldOffset (taObj, objData.MethodTable, W("_stackTrace")); + if (iOffset > 0) + { + MOVE(taStackTrace, taObj + iOffset); + } + } + + ExtOut("StackTrace (generated):\n"); + if (taStackTrace) + { + DWORD arrayLen; + HRESULT hr = MOVE(arrayLen, taStackTrace + sizeof(DWORD_PTR)); + + if (arrayLen != 0 && hr == S_OK) + { +#ifdef _TARGET_WIN64_ + DWORD_PTR dataPtr = taStackTrace + sizeof(DWORD_PTR) + sizeof(DWORD) + sizeof(DWORD); +#else + DWORD_PTR dataPtr = taStackTrace + sizeof(DWORD_PTR) + sizeof(DWORD); +#endif // _TARGET_WIN64_ + size_t stackTraceSize = 0; + MOVE (stackTraceSize, dataPtr); + + DWORD cbStackSize = static_cast<DWORD>(stackTraceSize * sizeof(StackTraceElement)); + dataPtr += sizeof(size_t) + sizeof(size_t); // skip the array header, then goes the data + + if (stackTraceSize == 0) + { + ExtOut("Unable to decipher generated stack trace\n"); + } + else + { + size_t iHeaderLength = AddExceptionHeader (NULL, 0); + size_t iLength = FormatGeneratedException (dataPtr, cbStackSize, NULL, 0, bAsync, FALSE, bLineNumbers); + WCHAR *pwszBuffer = new NOTHROW WCHAR[iHeaderLength + iLength + 1]; + if (pwszBuffer) + { + AddExceptionHeader(pwszBuffer, iHeaderLength + 1); + FormatGeneratedException(dataPtr, cbStackSize, pwszBuffer + iHeaderLength, iLength + 1, bAsync, FALSE, bLineNumbers); + SosExtOutLargeString(pwszBuffer, iHeaderLength + iLength + 1); + delete[] pwszBuffer; + } + ExtOut("\n"); + } + } + else + { + ExtOut("<Not Available>\n"); + } + } + else + { + ExtOut("<none>\n"); + } + } + + { + TADDR taStackString; + if (bGotExcData) + { + taStackString = TO_TADDR(excData.StackTraceString); + } + else + { + int iOffset = GetObjFieldOffset (taObj, objData.MethodTable, W("_stackTraceString")); + MOVE (taStackString, taObj + iOffset); + } + + ExtOut("StackTraceString: "); + if (taStackString) + { + StringObjectContent(taStackString); + ExtOut("\n\n"); // extra newline looks better + } + else + { + ExtOut("<none>\n"); + } + } + + { + DWORD hResult; + if (bGotExcData) + { + hResult = excData.HResult; + } + else + { + int iOffset = GetObjFieldOffset (taObj, objData.MethodTable, W("_HResult")); + MOVE (hResult, taObj + iOffset); + } + + ExtOut("HResult: %lx\n", hResult); + } + + if (isSecurityExceptionObj(objData.MethodTable) != NULL) + { + // We have a SecurityException Object: print out the debugString if present + int iOffset = GetObjFieldOffset (taObj, objData.MethodTable, W("m_debugString")); + if (iOffset > 0) + { + TADDR taDebugString; + MOVE (taDebugString, taObj + iOffset); + + if (taDebugString) + { + ExtOut("SecurityException Message: "); + StringObjectContent(taDebugString); + ExtOut("\n\n"); // extra newline looks better + } + } + } + + return Status; +} + +DECLARE_API(PrintException) +{ + INIT_API(); + + BOOL dml = FALSE; + BOOL bShowNested = FALSE; + BOOL bLineNumbers = FALSE; + BOOL bCCW = FALSE; + StringHolder strObject; + CMDOption option[] = + { // name, vptr, type, hasValue + {"-nested", &bShowNested, COBOOL, FALSE}, + {"-lines", &bLineNumbers, COBOOL, FALSE}, + {"-l", &bLineNumbers, COBOOL, FALSE}, + {"-ccw", &bCCW, COBOOL, FALSE}, +#ifndef FEATURE_PAL + {"/d", &dml, COBOOL, FALSE} +#endif + }; + CMDValue arg[] = + { // vptr, type + {&strObject, COSTRING} + }; + size_t nArg; + if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg)) + { + return Status; + } + if (bLineNumbers) + { + ULONG symlines = 0; + if (SUCCEEDED(g_ExtSymbols->GetSymbolOptions(&symlines))) + { + symlines &= SYMOPT_LOAD_LINES; + } + if (symlines == 0) + { + ExtOut("In order for the option -lines to enable display of source information\n" + "the debugger must be configured to load the line number information from\n" + "the symbol files. Use the \".lines; .reload\" command to achieve this.\n"); + // don't even try + bLineNumbers = FALSE; + } + } + + EnableDMLHolder dmlHolder(dml); + DWORD_PTR p_Object = NULL; + if (nArg == 0) + { + if (bCCW) + { + ExtOut("No CCW pointer specified\n"); + return Status; + } + + // Look at the last exception object on this thread + + CLRDATA_ADDRESS threadAddr = GetCurrentManagedThread(); + DacpThreadData Thread; + + if ((threadAddr == NULL) || (Thread.Request(g_sos, threadAddr) != S_OK)) + { + ExtOut("The current thread is unmanaged\n"); + return Status; + } + + DWORD_PTR dwAddr = NULL; + if ((!SafeReadMemory(TO_TADDR(Thread.lastThrownObjectHandle), + &dwAddr, + sizeof(dwAddr), NULL)) || (dwAddr==NULL)) + { + ExtOut("There is no current managed exception on this thread\n"); + } + else + { + p_Object = dwAddr; + } + } + else + { + p_Object = GetExpression(strObject.data); + if (p_Object == 0) + { + if (bCCW) + { + ExtOut("Invalid CCW pointer %s\n", args); + } + else + { + ExtOut("Invalid exception object %s\n", args); + } + return Status; + } + + if (bCCW) + { + // check if the address is a CCW pointer and then + // get the exception object from it + DacpCCWData ccwData; + if (ccwData.Request(g_sos, p_Object) == S_OK) + { + p_Object = TO_TADDR(ccwData.managedObject); + } + } + } + + if (p_Object) + { + FormatException(p_Object, bLineNumbers); + } + + // Are there nested exceptions? + CLRDATA_ADDRESS threadAddr = GetCurrentManagedThread(); + DacpThreadData Thread; + + if ((threadAddr == NULL) || (Thread.Request(g_sos, threadAddr) != S_OK)) + { + ExtOut("The current thread is unmanaged\n"); + return Status; + } + + if (Thread.firstNestedException) + { + if (!bShowNested) + { + ExtOut("There are nested exceptions on this thread. Run with -nested for details\n"); + return Status; + } + + CLRDATA_ADDRESS currentNested = Thread.firstNestedException; + do + { + CLRDATA_ADDRESS obj = 0, next = 0; + Status = g_sos->GetNestedExceptionData(currentNested, &obj, &next); + + if (Status != S_OK) + { + ExtOut("Error retrieving nested exception info %p\n", SOS_PTR(currentNested)); + return Status; + } + + if (IsInterrupt()) + { + ExtOut("<aborted>\n"); + return Status; + } + + ExtOut("\nNested exception -------------------------------------------------------------\n"); + Status = FormatException((DWORD_PTR) obj, bLineNumbers); + if (Status != S_OK) + { + return Status; + } + + currentNested = next; + } + while(currentNested != NULL); + } + return Status; +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to dump the contents of an object from a * +* given address +* * +\**********************************************************************/ +DECLARE_API(DumpVC) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + DWORD_PTR p_MT = NULL; + DWORD_PTR p_Object = NULL; + BOOL dml = FALSE; + + CMDOption option[] = + { // name, vptr, type, hasValue +#ifndef FEATURE_PAL + {"/d", &dml, COBOOL, FALSE} +#endif + }; + CMDValue arg[] = + { // vptr, type + {&p_MT, COHEX}, + {&p_Object, COHEX}, + }; + size_t nArg; + if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg)) + { + return Status; + } + + EnableDMLHolder dmlHolder(dml); + if (nArg!=2) + { + ExtOut("Usage: !DumpVC <Method Table> <Value object start addr>\n"); + return Status; + } + + if (!IsMethodTable(p_MT)) + { + ExtOut("Not a managed object\n"); + return S_OK; + } + + return PrintVC(p_MT, p_Object); +} + +#ifndef FEATURE_PAL + +#ifdef FEATURE_COMINTEROP + +DECLARE_API(DumpRCW) +{ + INIT_API(); + + BOOL dml = FALSE; + StringHolder strObject; + + CMDOption option[] = + { // name, vptr, type, hasValue + {"/d", &dml, COBOOL, FALSE} + }; + CMDValue arg[] = + { // vptr, type + {&strObject, COSTRING} + }; + size_t nArg; + if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg)) + { + return Status; + } + + EnableDMLHolder dmlHolder(dml); + if (nArg == 0) + { + ExtOut("Missing RCW address\n"); + return Status; + } + else + { + DWORD_PTR p_RCW = GetExpression(strObject.data); + if (p_RCW == 0) + { + ExtOut("Invalid RCW %s\n", args); + } + else + { + DacpRCWData rcwData; + if ((Status = rcwData.Request(g_sos, p_RCW)) != S_OK) + { + ExtOut("Error requesting RCW data\n"); + return Status; + } + BOOL isDCOMProxy; + if (FAILED(rcwData.IsDCOMProxy(g_sos, p_RCW, &isDCOMProxy))) + { + isDCOMProxy = FALSE; + } + + DMLOut("Managed object: %s\n", DMLObject(rcwData.managedObject)); + DMLOut("Creating thread: %p\n", SOS_PTR(rcwData.creatorThread)); + ExtOut("IUnknown pointer: %p\n", SOS_PTR(rcwData.unknownPointer)); + ExtOut("COM Context: %p\n", SOS_PTR(rcwData.ctxCookie)); + ExtOut("Managed ref count: %d\n", rcwData.refCount); + ExtOut("IUnknown V-table pointer : %p (captured at RCW creation time)\n", SOS_PTR(rcwData.vtablePtr)); + + ExtOut("Flags: %s%s%s%s%s%s%s%s\n", + (rcwData.isDisconnected? "IsDisconnected " : ""), + (rcwData.supportsIInspectable? "SupportsIInspectable " : ""), + (rcwData.isAggregated? "IsAggregated " : ""), + (rcwData.isContained? "IsContained " : ""), + (rcwData.isJupiterObject? "IsJupiterObject " : ""), + (rcwData.isFreeThreaded? "IsFreeThreaded " : ""), + (rcwData.identityPointer == TO_CDADDR(p_RCW)? "IsUnique " : ""), + (isDCOMProxy ? "IsDCOMProxy " : "") + ); + + // Jupiter data hidden by default + if (rcwData.isJupiterObject) + { + ExtOut("IJupiterObject: %p\n", SOS_PTR(rcwData.jupiterObject)); + } + + ExtOut("COM interface pointers:\n"); + + ArrayHolder<DacpCOMInterfacePointerData> pArray = new NOTHROW DacpCOMInterfacePointerData[rcwData.interfaceCount]; + if (pArray == NULL) + { + ReportOOM(); + return Status; + } + + if ((Status = g_sos->GetRCWInterfaces(p_RCW, rcwData.interfaceCount, pArray, NULL)) != S_OK) + { + ExtOut("Error requesting COM interface pointers\n"); + return Status; + } + + ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %" POINTERSIZE "s Type\n", "IP", "Context", "MT"); + for (int i = 0; i < rcwData.interfaceCount; i++) + { + // Ignore any NULL MethodTable interface cache. At this point only IJupiterObject + // is saved as NULL MethodTable at first slot, and we've already printed outs its + // value earlier. + if (pArray[i].methodTable == NULL) + continue; + + NameForMT_s(TO_TADDR(pArray[i].methodTable), g_mdName, mdNameLen); + + DMLOut("%p %p %s %S\n", SOS_PTR(pArray[i].interfacePtr), SOS_PTR(pArray[i].comContext), DMLMethodTable(pArray[i].methodTable), g_mdName); + } + } + } + + return Status; +} + +DECLARE_API(DumpCCW) +{ + INIT_API(); + + BOOL dml = FALSE; + StringHolder strObject; + + CMDOption option[] = + { // name, vptr, type, hasValue + {"/d", &dml, COBOOL, FALSE} + }; + CMDValue arg[] = + { // vptr, type + {&strObject, COSTRING} + }; + size_t nArg; + if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg)) + { + return Status; + } + + EnableDMLHolder dmlHolder(dml); + if (nArg == 0) + { + ExtOut("Missing CCW address\n"); + return Status; + } + else + { + DWORD_PTR p_CCW = GetExpression(strObject.data); + if (p_CCW == 0) + { + ExtOut("Invalid CCW %s\n", args); + } + else + { + DacpCCWData ccwData; + if ((Status = ccwData.Request(g_sos, p_CCW)) != S_OK) + { + ExtOut("Error requesting CCW data\n"); + return Status; + } + + if (ccwData.ccwAddress != p_CCW) + ExtOut("CCW: %p\n", SOS_PTR(ccwData.ccwAddress)); + + DMLOut("Managed object: %s\n", DMLObject(ccwData.managedObject)); + ExtOut("Outer IUnknown: %p\n", SOS_PTR(ccwData.outerIUnknown)); + ExtOut("Ref count: %d%s\n", ccwData.refCount, ccwData.isNeutered ? " (NEUTERED)" : ""); + ExtOut("Flags: %s%s\n", + (ccwData.isExtendsCOMObject? "IsExtendsCOMObject " : ""), + (ccwData.isAggregated? "IsAggregated " : "") + ); + + // Jupiter information hidden by default + if (ccwData.jupiterRefCount > 0) + { + ExtOut("Jupiter ref count: %d%s%s%s%s\n", + ccwData.jupiterRefCount, + (ccwData.isPegged || ccwData.isGlobalPegged) ? ", Pegged by" : "", + ccwData.isPegged ? " Jupiter " : "", + (ccwData.isPegged && ccwData.isGlobalPegged) ? "&" : "", + ccwData.isGlobalPegged ? " CLR " : "" + ); + } + + ExtOut("RefCounted Handle: %p%s\n", + SOS_PTR(ccwData.handle), + (ccwData.hasStrongRef ? " (STRONG)" : " (WEAK)")); + + ExtOut("COM interface pointers:\n"); + + ArrayHolder<DacpCOMInterfacePointerData> pArray = new NOTHROW DacpCOMInterfacePointerData[ccwData.interfaceCount]; + if (pArray == NULL) + { + ReportOOM(); + return Status; + } + + if ((Status = g_sos->GetCCWInterfaces(p_CCW, ccwData.interfaceCount, pArray, NULL)) != S_OK) + { + ExtOut("Error requesting COM interface pointers\n"); + return Status; + } + + ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s Type\n", "IP", "MT", "Type"); + for (int i = 0; i < ccwData.interfaceCount; i++) + { + if (pArray[i].methodTable == NULL) + { + wcscpy_s(g_mdName, mdNameLen, W("IDispatch/IUnknown")); + } + else + { + NameForMT_s(TO_TADDR(pArray[i].methodTable), g_mdName, mdNameLen); + } + + DMLOut("%p %s %S\n", pArray[i].interfacePtr, DMLMethodTable(pArray[i].methodTable), g_mdName); + } + } + } + + return Status; +} + +#endif // FEATURE_COMINTEROP + +#ifdef _DEBUG +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to dump the contents of a PermissionSet * +* from a given address. * +* * +\**********************************************************************/ +/* + COMMAND: dumppermissionset. + !DumpPermissionSet <PermissionSet object address> + + This command allows you to examine a PermissionSet object. Note that you can + also use DumpObj such an object in greater detail. DumpPermissionSet attempts + to extract all the relevant information from a PermissionSet that you might be + interested in when performing Code Access Security (CAS) related debugging. + + Here is a simple PermissionSet object: + + 0:000> !DumpPermissionSet 014615f4 + PermissionSet object: 014615f4 + Unrestricted: TRUE + + Note that this is an unrestricted PermissionSet object that does not contain + any individual permissions. + + Here is another example of a PermissionSet object, one that is not unrestricted + and contains a single permission: + + 0:003> !DumpPermissionSet 01469fa8 + PermissionSet object: 01469fa8 + Unrestricted: FALSE + Name: System.Security.Permissions.ReflectionPermission + MethodTable: 5b731308 + EEClass: 5b7e0d78 + Size: 12(0xc) bytes + (C:\WINDOWS\Microsoft.NET\Framework\v2.0.x86chk\assembly\GAC_32\mscorlib\2.0. + 0.0__b77a5c561934e089\mscorlib.dll) + + Fields: + MT Field Offset Type VT Attr Value Name + 5b73125c 4001d66 4 System.Int32 0 instance 2 m_flags + + Here is another example of an unrestricted PermissionSet, one that contains + several permissions. The numbers in parentheses before each Permission object + represents the index of that Permission in the PermissionSet. + + 0:003> !DumpPermissionSet 01467bd8 + PermissionSet object: 01467bd8 + Unrestricted: FALSE + [1] 01467e90 + Name: System.Security.Permissions.FileDialogPermission + MethodTable: 5b73023c + EEClass: 5b7dfb18 + Size: 12(0xc) bytes + (C:\WINDOWS\Microsoft.NET\Framework\v2.0.x86chk\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll) + Fields: + MT Field Offset Type VT Attr Value Name + 5b730190 4001cc2 4 System.Int32 0 instance 1 access + [4] 014682a8 + Name: System.Security.Permissions.ReflectionPermission + MethodTable: 5b731308 + EEClass: 5b7e0d78 + Size: 12(0xc) bytes + (C:\WINDOWS\Microsoft.NET\Framework\v2.0.x86chk\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll) + Fields: + MT Field Offset Type VT Attr Value Name + 5b73125c 4001d66 4 System.Int32 0 instance 0 m_flags + [17] 0146c060 + Name: System.Diagnostics.EventLogPermission + MethodTable: 569841c4 + EEClass: 56a03e5c + Size: 28(0x1c) bytes + (C:\WINDOWS\Microsoft.NET\Framework\v2.0.x86chk\assembly\GAC_MSIL\System\2.0.0.0__b77a5c561934e089\System.dll) + Fields: + MT Field Offset Type VT Attr Value Name + 5b6d65d4 4003078 4 System.Object[] 0 instance 0146c190 tagNames + 5b6c9ed8 4003079 8 System.Type 0 instance 0146c17c permissionAccessType + 5b6cd928 400307a 10 System.Boolean 0 instance 0 isUnrestricted + 5b6c45f8 400307b c ...ections.Hashtable 0 instance 0146c1a4 rootTable + 5b6c090c 4003077 bfc System.String 0 static 00000000 computerName + 56984434 40030e7 14 ...onEntryCollection 0 instance 00000000 innerCollection + [18] 0146ceb4 + Name: System.Net.WebPermission + MethodTable: 5696dfc4 + EEClass: 569e256c + Size: 20(0x14) bytes + (C:\WINDOWS\Microsoft.NET\Framework\v2.0.x86chk\assembly\GAC_MSIL\System\2.0.0.0__b77a5c561934e089\System.dll) + Fields: + MT Field Offset Type VT Attr Value Name + 5b6cd928 400238e c System.Boolean 0 instance 0 m_Unrestricted + 5b6cd928 400238f d System.Boolean 0 instance 0 m_UnrestrictedConnect + 5b6cd928 4002390 e System.Boolean 0 instance 0 m_UnrestrictedAccept + 5b6c639c 4002391 4 ...ections.ArrayList 0 instance 0146cf3c m_connectList + 5b6c639c 4002392 8 ...ections.ArrayList 0 instance 0146cf54 m_acceptList + 569476f8 4002393 8a4 ...Expressions.Regex 0 static 00000000 s_MatchAllRegex + [19] 0146a5fc + Name: System.Net.DnsPermission + MethodTable: 56966408 + EEClass: 569d3c08 + Size: 12(0xc) bytes + (C:\WINDOWS\Microsoft.NET\Framework\v2.0.x86chk\assembly\GAC_MSIL\System\2.0.0.0__b77a5c561934e089\System.dll) + Fields: + MT Field Offset Type VT Attr Value Name + 5b6cd928 4001d2c 4 System.Boolean 0 instance 1 m_noRestriction + [20] 0146d8ec + Name: System.Web.AspNetHostingPermission + MethodTable: 569831bc + EEClass: 56a02ccc + Size: 12(0xc) bytes + (C:\WINDOWS\Microsoft.NET\Framework\v2.0.x86chk\assembly\GAC_MSIL\System\2.0.0.0__b77a5c561934e089\System.dll) + Fields: + MT Field Offset Type VT Attr Value Name + 56983090 4003074 4 System.Int32 0 instance 600 _level + [21] 0146e394 + Name: System.Net.NetworkInformation.NetworkInformationPermission + MethodTable: 5697ac70 + EEClass: 569f7104 + Size: 16(0x10) bytes + (C:\WINDOWS\Microsoft.NET\Framework\v2.0.x86chk\assembly\GAC_MSIL\System\2.0.0.0__b77a5c561934e089\System.dll) + Fields: + MT Field Offset Type VT Attr Value Name + 5697ab38 4002c34 4 System.Int32 0 instance 0 access + 5b6cd928 4002c35 8 System.Boolean 0 instance 0 unrestricted + + + The abbreviation !dps can be used for brevity. + + \\ +*/ +DECLARE_API(DumpPermissionSet) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + DWORD_PTR p_Object = NULL; + + CMDValue arg[] = + { + {&p_Object, COHEX} + }; + size_t nArg; + if (!GetCMDOption(args, NULL, 0, arg, _countof(arg), &nArg)) + { + return Status; + } + if (nArg!=1) + { + ExtOut("Usage: !DumpPermissionSet <PermissionSet object addr>\n"); + return Status; + } + + + return PrintPermissionSet(p_Object); +} + +#endif // _DEBUG + +void GCPrintGenerationInfo(DacpGcHeapDetails &heap); +void GCPrintSegmentInfo(DacpGcHeapDetails &heap, DWORD_PTR &total_size); + +#endif // FEATURE_PAL + +void DisplayInvalidStructuresMessage() +{ + ExtOut("The garbage collector data structures are not in a valid state for traversal.\n"); + ExtOut("It is either in the \"plan phase,\" where objects are being moved around, or\n"); + ExtOut("we are at the initialization or shutdown of the gc heap. Commands related to \n"); + ExtOut("displaying, finding or traversing objects as well as gc heap segments may not \n"); + ExtOut("work properly. !dumpheap and !verifyheap may incorrectly complain of heap \n"); + ExtOut("consistency errors.\n"); +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function dumps GC heap size. * +* * +\**********************************************************************/ +DECLARE_API(EEHeap) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + BOOL dml = FALSE; + BOOL showgc = FALSE; + BOOL showloader = FALSE; + + CMDOption option[] = + { // name, vptr, type, hasValue + {"-gc", &showgc, COBOOL, FALSE}, + {"-loader", &showloader, COBOOL, FALSE}, + {"/d", &dml, COBOOL, FALSE}, + }; + + if (!GetCMDOption(args, option, _countof(option), NULL, 0, NULL)) + { + return Status; + } + + EnableDMLHolder dmlHolder(dml); + if (showloader || !showgc) + { + // Loader heap. + DWORD_PTR allHeapSize = 0; + DWORD_PTR wasted = 0; + DacpAppDomainStoreData adsData; + if ((Status=adsData.Request(g_sos))!=S_OK) + { + ExtOut("Unable to get AppDomain information\n"); + return Status; + } + + // The first one is the system domain. + ExtOut("Loader Heap:\n"); + IfFailRet(PrintDomainHeapInfo("System Domain", adsData.systemDomain, &allHeapSize, &wasted)); + IfFailRet(PrintDomainHeapInfo("Shared Domain", adsData.sharedDomain, &allHeapSize, &wasted)); + + ArrayHolder<CLRDATA_ADDRESS> pArray = new NOTHROW CLRDATA_ADDRESS[adsData.DomainCount]; + + if (pArray==NULL) + { + ReportOOM(); + return Status; + } + + if ((Status=g_sos->GetAppDomainList(adsData.DomainCount, pArray, NULL))!=S_OK) + { + ExtOut("Unable to get the array of all AppDomains.\n"); + return Status; + } + + for (int n=0;n<adsData.DomainCount;n++) + { + if (IsInterrupt()) + break; + + char domain[16]; + sprintf_s(domain, _countof(domain), "Domain %d", n+1); + + IfFailRet(PrintDomainHeapInfo(domain, pArray[n], &allHeapSize, &wasted)); + + } + + // Jit code heap + ExtOut("--------------------------------------\n"); + ExtOut("Jit code heap:\n"); + + if (IsMiniDumpFile()) + { + ExtOut("<no information>\n"); + } + else + { + allHeapSize += JitHeapInfo(); + } + + + // Module Data + { + int numModule; + ArrayHolder<DWORD_PTR> moduleList = ModuleFromName(NULL, &numModule); + if (moduleList == NULL) + { + ExtOut("Failed to request module list.\n"); + } + else + { + // Module Thunk Heaps + ExtOut("--------------------------------------\n"); + ExtOut("Module Thunk heaps:\n"); + allHeapSize += PrintModuleHeapInfo(moduleList, numModule, ModuleHeapType_ThunkHeap, &wasted); + + // Module Lookup Table Heaps + ExtOut("--------------------------------------\n"); + ExtOut("Module Lookup Table heaps:\n"); + allHeapSize += PrintModuleHeapInfo(moduleList, numModule, ModuleHeapType_LookupTableHeap, &wasted); + } + } + + ExtOut("--------------------------------------\n"); + ExtOut("Total LoaderHeap size: "); + PrintHeapSize(allHeapSize, wasted); + ExtOut("=======================================\n"); + } + + if (showgc || !showloader) + { + // GC Heap + DWORD dwNHeaps = 1; + + if (!GetGcStructuresValid()) + { + DisplayInvalidStructuresMessage(); + } + + DacpGcHeapData gcheap; + if (gcheap.Request(g_sos) != S_OK) + { + ExtOut("Error requesting GC Heap data\n"); + return Status; + } + + if (gcheap.bServerMode) + { + dwNHeaps = gcheap.HeapCount; + } + + ExtOut("Number of GC Heaps: %d\n", dwNHeaps); + DWORD_PTR totalSize = 0; + if (!gcheap.bServerMode) + { + DacpGcHeapDetails heapDetails; + if (heapDetails.Request(g_sos) != S_OK) + { + ExtOut("Error requesting details\n"); + return Status; + } + + GCHeapInfo (heapDetails, totalSize); + ExtOut("Total Size: "); + PrintHeapSize(totalSize, 0); + } + else + { + DWORD dwAllocSize; + if (!ClrSafeInt<DWORD>::multiply(sizeof(CLRDATA_ADDRESS), dwNHeaps, dwAllocSize)) + { + ExtOut("Failed to get GCHeaps: integer overflow\n"); + return Status; + } + + CLRDATA_ADDRESS *heapAddrs = (CLRDATA_ADDRESS*)alloca(dwAllocSize); + if (g_sos->GetGCHeapList(dwNHeaps, heapAddrs, NULL) != S_OK) + { + ExtOut("Failed to get GCHeaps\n"); + return Status; + } + + DWORD n; + for (n = 0; n < dwNHeaps; n ++) + { + DacpGcHeapDetails heapDetails; + if (heapDetails.Request(g_sos, heapAddrs[n]) != S_OK) + { + ExtOut("Error requesting details\n"); + return Status; + } + ExtOut("------------------------------\n"); + ExtOut("Heap %d (%p)\n", n, SOS_PTR(heapAddrs[n])); + DWORD_PTR heapSize = 0; + GCHeapInfo (heapDetails, heapSize); + totalSize += heapSize; + ExtOut("Heap Size: " WIN86_8SPACES); + PrintHeapSize(heapSize, 0); + } + } + ExtOut("------------------------------\n"); + ExtOut("GC Heap Size: " WIN86_8SPACES); + PrintHeapSize(totalSize, 0); + } + return Status; +} + +void PrintGCStat(HeapStat *inStat, const char* label=NULL) +{ + if (inStat) + { + bool sorted = false; + try + { + inStat->Sort(); + sorted = true; + inStat->Print(label); + } + catch(...) + { + ExtOut("Exception occurred while trying to %s the GC stats.\n", sorted ? "print" : "sort"); + } + + inStat->Delete(); + } +} + +#ifndef FEATURE_PAL + +DECLARE_API(TraverseHeap) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + BOOL bXmlFormat = FALSE; + BOOL bVerify = FALSE; + StringHolder Filename; + + CMDOption option[] = + { // name, vptr, type, hasValue + {"-xml", &bXmlFormat, COBOOL, FALSE}, + {"-verify", &bVerify, COBOOL, FALSE}, + }; + CMDValue arg[] = + { // vptr, type + {&Filename.data, COSTRING}, + }; + size_t nArg; + if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg)) + { + return Status; + } + + if (nArg != 1) + { + ExtOut("usage: HeapTraverse [-xml] filename\n"); + return Status; + } + + if (!g_snapshot.Build()) + { + ExtOut("Unable to build snapshot of the garbage collector state\n"); + return Status; + } + + FILE* file = NULL; + if (fopen_s(&file, Filename.data, "w") != 0) { + ExtOut("Unable to open file\n"); + return Status; + } + + if (!bVerify) + ExtOut("Assuming a uncorrupted GC heap. If this is a crash dump consider -verify option\n"); + + HeapTraverser traverser(bVerify != FALSE); + + ExtOut("Writing %s format to file %s\n", bXmlFormat ? "Xml" : "CLRProfiler", Filename.data); + ExtOut("Gathering types...\n"); + + // TODO: there may be a canonical list of methodtables in the runtime that we can + // traverse instead of exploring the gc heap for that list. We could then simplify the + // tree structure to a sorted list of methodtables, and the index is the ID. + + // TODO: "Traversing object members" code should be generalized and shared between + // !gcroot and !traverseheap. Also !dumpheap can begin using GCHeapsTraverse. + + if (!traverser.Initialize()) + { + ExtOut("Error initializing heap traversal\n"); + fclose(file); + return Status; + } + + if (!traverser.CreateReport (file, bXmlFormat ? FORMAT_XML : FORMAT_CLRPROFILER)) + { + ExtOut("Unable to write heap report\n"); + fclose(file); + return Status; + } + + fclose(file); + ExtOut("\nfile %s saved\n", Filename.data); + + return Status; +} + +#endif // FEATURE_PAL + +struct PrintRuntimeTypeArgs +{ + DWORD_PTR mtOfRuntimeType; + int handleFieldOffset; + DacpAppDomainStoreData adstore; +}; + +void PrintRuntimeTypes(DWORD_PTR objAddr,size_t Size,DWORD_PTR methodTable,LPVOID token) +{ + PrintRuntimeTypeArgs *pArgs = (PrintRuntimeTypeArgs *)token; + + if (pArgs->mtOfRuntimeType == NULL) + { + NameForMT_s(methodTable, g_mdName, mdNameLen); + + if (_wcscmp(g_mdName, W("System.RuntimeType")) == 0) + { + pArgs->mtOfRuntimeType = methodTable; + pArgs->handleFieldOffset = GetObjFieldOffset(objAddr, methodTable, W("m_handle")); + if (pArgs->handleFieldOffset <= 0) + ExtOut("Error getting System.RuntimeType.m_handle offset\n"); + + pArgs->adstore.Request(g_sos); + } + } + + if ((methodTable == pArgs->mtOfRuntimeType) && (pArgs->handleFieldOffset > 0)) + { + // Get the method table and display the information. + DWORD_PTR mtPtr; + if (MOVE(mtPtr, objAddr + pArgs->handleFieldOffset) == S_OK) + { + DMLOut(DMLObject(objAddr)); + + CLRDATA_ADDRESS appDomain = GetAppDomainForMT(mtPtr); + if (appDomain != NULL) + { + if (appDomain == pArgs->adstore.sharedDomain) + ExtOut(" %" POINTERSIZE "s", "Shared"); + + else if (appDomain == pArgs->adstore.systemDomain) + ExtOut(" %" POINTERSIZE "s", "System"); + else + DMLOut(" %s", DMLDomain(appDomain)); + } + else + { + ExtOut(" %" POINTERSIZE "s", "?"); + } + + NameForMT_s(mtPtr, g_mdName, mdNameLen); + DMLOut(" %s %S\n", DMLMethodTable(mtPtr), g_mdName); + } + } +} + + +DECLARE_API(DumpRuntimeTypes) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + BOOL dml = FALSE; + + CMDOption option[] = + { // name, vptr, type, hasValue + {"/d", &dml, COBOOL, FALSE}, + }; + + if (!GetCMDOption(args, option, _countof(option), NULL, 0, NULL)) + return Status; + + EnableDMLHolder dmlHolder(dml); + + ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %" POINTERSIZE "s Type Name \n", + "Address", "Domain", "MT"); + ExtOut("------------------------------------------------------------------------------\n"); + + PrintRuntimeTypeArgs pargs; + ZeroMemory(&pargs, sizeof(PrintRuntimeTypeArgs)); + + GCHeapsTraverse(PrintRuntimeTypes, (LPVOID)&pargs); + return Status; +} + +#define MIN_FRAGMENTATIONBLOCK_BYTES (1024*512) +namespace sos +{ + class FragmentationBlock + { + public: + FragmentationBlock(TADDR addr, size_t size, TADDR next, TADDR mt) + : mAddress(addr), mSize(size), mNext(next), mNextMT(mt) + { + } + + inline TADDR GetAddress() const + { + return mAddress; + } + inline size_t GetSize() const + { + return mSize; + } + + inline TADDR GetNextObject() const + { + return mNext; + } + + inline TADDR GetNextMT() const + { + return mNextMT; + } + + private: + TADDR mAddress; + size_t mSize; + TADDR mNext; + TADDR mNextMT; + }; +} + +class DumpHeapImpl +{ +public: + DumpHeapImpl(PCSTR args) + : mStart(0), mStop(0), mMT(0), mMinSize(0), mMaxSize(~0), + mStat(FALSE), mStrings(FALSE), mVerify(FALSE), + mThinlock(FALSE), mShort(FALSE), mDML(FALSE), + mLive(FALSE), mDead(FALSE), mType(NULL) + { + ArrayHolder<char> type = NULL; + + TADDR minTemp = 0; + CMDOption option[] = + { // name, vptr, type, hasValue + {"-mt", &mMT, COHEX, TRUE}, // dump objects with a given MethodTable + {"-type", &type, COSTRING, TRUE}, // list objects of specified type + {"-stat", &mStat, COBOOL, FALSE}, // dump a summary of types and the number of instances of each + {"-strings", &mStrings, COBOOL, FALSE}, // dump a summary of string objects + {"-verify", &mVerify, COBOOL, FALSE}, // verify heap objects (!heapverify) + {"-thinlock", &mThinlock, COBOOL, FALSE},// list only thinlocks + {"-short", &mShort, COBOOL, FALSE}, // list only addresses + {"-min", &mMinSize, COHEX, TRUE}, // min size of objects to display + {"-max", &mMaxSize, COHEX, TRUE}, // max size of objects to display + {"-live", &mLive, COHEX, FALSE}, // only print live objects + {"-dead", &mDead, COHEX, FALSE}, // only print dead objects +#ifndef FEATURE_PAL + {"/d", &mDML, COBOOL, FALSE}, // Debugger Markup Language +#endif + }; + + CMDValue arg[] = + { // vptr, type + {&mStart, COHEX}, + {&mStop, COHEX} + }; + + size_t nArgs = 0; + if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArgs)) + sos::Throw<sos::Exception>("Failed to parse command line arguments."); + + if (mStart == 0) + mStart = minTemp; + + if (mStop == 0) + mStop = sos::GCHeap::HeapEnd; + + if (type && mMT) + { + sos::Throw<sos::Exception>("Cannot specify both -mt and -type"); + } + + if (mLive && mDead) + { + sos::Throw<sos::Exception>("Cannot specify both -live and -dead."); + } + + if (mMinSize > mMaxSize) + { + sos::Throw<sos::Exception>("wrong argument"); + } + + // If the user gave us a type, convert it to unicode and clean up "type". + if (type && !mStrings) + { + size_t iLen = strlen(type) + 1; + mType = new WCHAR[iLen]; + MultiByteToWideChar(CP_ACP, 0, type, -1, mType, (int)iLen); + } + } + + ~DumpHeapImpl() + { + if (mType) + delete [] mType; + } + + void Run() + { + // enable Debugger Markup Language + EnableDMLHolder dmlholder(mDML); + sos::GCHeap gcheap; + + if (!gcheap.AreGCStructuresValid()) + DisplayInvalidStructuresMessage(); + + if (IsMiniDumpFile()) + { + ExtOut("In a minidump without full memory, most gc heap structures will not be valid.\n"); + ExtOut("If you need this functionality, get a full memory dump with \".dump /ma mydump.dmp\"\n"); + } + +#ifndef FEATURE_PAL + if (mLive || mDead) + { + GCRootImpl gcroot; + mLiveness = gcroot.GetLiveObjects(); + } +#endif + + // Some of the "specialty" versions of DumpHeap have slightly + // different implementations than the standard version of DumpHeap. + // We seperate them out to not clutter the standard DumpHeap function. + if (mShort) + DumpHeapShort(gcheap); + else if (mThinlock) + DumpHeapThinlock(gcheap); + else if (mStrings) + DumpHeapStrings(gcheap); + else + DumpHeap(gcheap); + + if (mVerify) + ValidateSyncTable(gcheap); + } + + static bool ValidateSyncTable(sos::GCHeap &gcheap) + { + bool succeeded = true; + for (sos::SyncBlkIterator itr; itr; ++itr) + { + sos::CheckInterrupt(); + + if (!itr->IsFree()) + { + if (!sos::IsObject(itr->GetObject(), true)) + { + ExtOut("SyncBlock %d corrupted, points to invalid object %p\n", + itr->GetIndex(), SOS_PTR(itr->GetObject())); + succeeded = false; + } + else + { + // Does the object header point to this syncblock index? + sos::Object obj = itr->GetObject(); + ULONG header = 0; + + if (!obj.TryGetHeader(header)) + { + ExtOut("Failed to get object header for object %p while inspecting syncblock at index %d.\n", + SOS_PTR(itr->GetObject()), itr->GetIndex()); + succeeded = false; + } + else + { + bool valid = false; + if ((header & BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX) != 0 && (header & BIT_SBLK_IS_HASHCODE) == 0) + { + ULONG index = header & MASK_SYNCBLOCKINDEX; + valid = (ULONG)itr->GetIndex() == index; + } + + if (!valid) + { + ExtOut("Object header for %p should have a SyncBlock index of %d.\n", + SOS_PTR(itr->GetObject()), itr->GetIndex()); + succeeded = false; + } + } + } + } + } + + return succeeded; + } +private: + DumpHeapImpl(const DumpHeapImpl &); + + bool Verify(const sos::ObjectIterator &itr) + { + if (mVerify) + { + char buffer[1024]; + if (!itr.Verify(buffer, _countof(buffer))) + { + ExtOut(buffer); + return false; + } + } + + return true; + } + + bool IsCorrectType(const sos::Object &obj) + { + if (mMT != NULL) + return mMT == obj.GetMT(); + + if (mType != NULL) + { + WString name = obj.GetTypeName(); + return _wcsstr(name.c_str(), mType) != NULL; + } + + return true; + } + + bool IsCorrectSize(const sos::Object &obj) + { + size_t size = obj.GetSize(); + return size >= mMinSize && size <= mMaxSize; + } + + bool IsCorrectLiveness(const sos::Object &obj) + { +#ifndef FEATURE_PAL + if (mLive && mLiveness.find(obj.GetAddress()) == mLiveness.end()) + return false; + + if (mDead && (mLiveness.find(obj.GetAddress()) != mLiveness.end() || obj.IsFree())) + return false; +#endif + return true; + } + + + + inline void PrintHeader() + { + ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %8s\n", "Address", "MT", "Size"); + } + + void DumpHeap(sos::GCHeap &gcheap) + { + HeapStat stats; + + // For heap fragmentation tracking. + TADDR lastFreeObj = NULL; + size_t lastFreeSize = 0; + + if (!mStat) + PrintHeader(); + + for (sos::ObjectIterator itr = gcheap.WalkHeap(mStart, mStop); itr; ++itr) + { + if (!Verify(itr)) + return; + + bool onLOH = itr.IsCurrObjectOnLOH(); + + // Check for free objects to report fragmentation + if (lastFreeObj != NULL) + ReportFreeObject(lastFreeObj, lastFreeSize, itr->GetAddress(), itr->GetMT()); + + if (!onLOH && itr->IsFree()) + { + lastFreeObj = *itr; + lastFreeSize = itr->GetSize(); + } + else + { + lastFreeObj = NULL; + } + + if (IsCorrectType(*itr) && IsCorrectSize(*itr) && IsCorrectLiveness(*itr)) + { + stats.Add((DWORD_PTR)itr->GetMT(), (DWORD)itr->GetSize()); + if (!mStat) + DMLOut("%s %s %8d%s\n", DMLObject(itr->GetAddress()), DMLDumpHeapMT(itr->GetMT()), itr->GetSize(), + itr->IsFree() ? " Free":" "); + } + } + + if (!mStat) + ExtOut("\n"); + + stats.Sort(); + stats.Print(); + + PrintFragmentationReport(); + } + + struct StringSetEntry + { + StringSetEntry() : count(0), size(0) + { + str[0] = 0; + } + + StringSetEntry(__in_ecount(64) WCHAR tmp[64], size_t _size) + : count(1), size(_size) + { + memcpy(str, tmp, sizeof(str)); + } + + void Add(size_t _size) const + { + count++; + size += _size; + } + + mutable size_t count; + mutable size_t size; + WCHAR str[64]; + + bool operator<(const StringSetEntry &rhs) const + { + return _wcscmp(str, rhs.str) < 0; + } + }; + + + static bool StringSetCompare(const StringSetEntry &a1, const StringSetEntry &a2) + { + return a1.size < a2.size; + } + + void DumpHeapStrings(sos::GCHeap &gcheap) + { +#ifdef FEATURE_PAL + ExtOut("Not implemented.\n"); +#else + const int offset = sos::Object::GetStringDataOffset(); + typedef std::set<StringSetEntry> Set; + Set set; // A set keyed off of the string's text + + StringSetEntry tmp; // Temp string used to keep track of the set + ULONG fetched = 0; + + TableOutput out(3, POINTERSIZE_HEX, AlignRight); + for (sos::ObjectIterator itr = gcheap.WalkHeap(mStart, mStop); itr; ++itr) + { + if (IsInterrupt()) + break; + + if (itr->IsString() && IsCorrectSize(*itr) && IsCorrectLiveness(*itr)) + { + CLRDATA_ADDRESS addr = itr->GetAddress(); + size_t size = itr->GetSize(); + + if (!mStat) + out.WriteRow(ObjectPtr(addr), Pointer(itr->GetMT()), Decimal(size)); + + // Don't bother calculating the size of the string, just read the full 64 characters of the buffer. The null + // terminator we read will terminate the string. + HRESULT hr = g_ExtData->ReadVirtual(TO_CDADDR(addr+offset), tmp.str, sizeof(WCHAR)*(_countof(tmp.str)-1), &fetched); + if (SUCCEEDED(hr)) + { + // Ensure we null terminate the string. Note that this will not overrun the buffer as we only + // wrote a max of 63 characters into the 64 character buffer. + tmp.str[fetched/sizeof(WCHAR)] = 0; + Set::iterator sitr = set.find(tmp); + if (sitr == set.end()) + { + tmp.size = size; + tmp.count = 1; + set.insert(tmp); + } + else + { + sitr->Add(size); + } + } + } + } + + ExtOut("\n"); + + // Now flatten the set into a vector. This is much faster than keeping two sets, or using a multimap. + typedef std::vector<StringSetEntry> Vect; + Vect v(set.begin(), set.end()); + std::sort(v.begin(), v.end(), &DumpHeapImpl::StringSetCompare); + + // Now print out the data. The call to Flatten ensures that we don't print newlines to break up the + // output in strange ways. + for (Vect::iterator vitr = v.begin(); vitr != v.end(); ++vitr) + { + if (IsInterrupt()) + break; + + Flatten(vitr->str, (unsigned int)_wcslen(vitr->str)); + out.WriteRow(Decimal(vitr->size), Decimal(vitr->count), vitr->str); + } +#endif // FEATURE_PAL + } + + void DumpHeapShort(sos::GCHeap &gcheap) + { + for (sos::ObjectIterator itr = gcheap.WalkHeap(mStart, mStop); itr; ++itr) + { + if (!Verify(itr)) + return; + + if (IsCorrectType(*itr) && IsCorrectSize(*itr) && IsCorrectLiveness(*itr)) + DMLOut("%s\n", DMLObject(itr->GetAddress())); + } + } + + void DumpHeapThinlock(sos::GCHeap &gcheap) + { + int count = 0; + + PrintHeader(); + for (sos::ObjectIterator itr = gcheap.WalkHeap(mStart, mStop); itr; ++itr) + { + if (!Verify(itr)) + return; + + sos::ThinLockInfo lockInfo; + if (IsCorrectType(*itr) && itr->GetThinLock(lockInfo)) + { + DMLOut("%s %s %8d", DMLObject(itr->GetAddress()), DMLDumpHeapMT(itr->GetMT()), itr->GetSize()); + ExtOut(" ThinLock owner %x (%p) Recursive %x\n", lockInfo.ThreadId, + SOS_PTR(lockInfo.ThreadPtr), lockInfo.Recursion); + + count++; + } + } + + ExtOut("Found %d objects.\n", count); + } + +private: + TADDR mStart, + mStop, + mMT, + mMinSize, + mMaxSize; + + BOOL mStat, + mStrings, + mVerify, + mThinlock, + mShort, + mDML, + mLive, + mDead; + + + WCHAR *mType; + +private: +#if !defined(FEATURE_PAL) + // Windows only + std::unordered_set<TADDR> mLiveness; + typedef std::list<sos::FragmentationBlock> FragmentationList; + FragmentationList mFrag; + + void InitFragmentationList() + { + mFrag.clear(); + } + + void ReportFreeObject(TADDR addr, size_t size, TADDR next, TADDR mt) + { + if (size >= MIN_FRAGMENTATIONBLOCK_BYTES) + mFrag.push_back(sos::FragmentationBlock(addr, size, next, mt)); + } + + void PrintFragmentationReport() + { + if (mFrag.size() > 0) + { + ExtOut("Fragmented blocks larger than 0.5 MB:\n"); + ExtOut("%" POINTERSIZE "s %8s %16s\n", "Addr", "Size", "Followed by"); + + for (FragmentationList::const_iterator itr = mFrag.begin(); itr != mFrag.end(); ++itr) + { + sos::MethodTable mt = itr->GetNextMT(); + ExtOut("%p %6.1fMB " WIN64_8SPACES "%p %S\n", + SOS_PTR(itr->GetAddress()), + ((double)itr->GetSize()) / 1024.0 / 1024.0, + SOS_PTR(itr->GetNextObject()), + mt.GetName()); + } + } + } +#else + void InitFragmentationList() {} + void ReportFreeObject(TADDR, TADDR, size_t, TADDR) {} + void PrintFragmentationReport() {} +#endif +}; + +/**********************************************************************\ +* Routine Description: * +* * +* This function dumps all objects on GC heap. It also displays * +* statistics of objects. If GC heap is corrupted, it will stop at +* the bad place. (May not work if GC is in progress.) * +* * +\**********************************************************************/ +DECLARE_API(DumpHeap) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + if (!g_snapshot.Build()) + { + ExtOut("Unable to build snapshot of the garbage collector state\n"); + return E_FAIL; + } + + try + { + DumpHeapImpl dumpHeap(args); + dumpHeap.Run(); + + return S_OK; + } + catch(const sos::Exception &e) + { + ExtOut("%s\n", e.what()); + return E_FAIL; + } +} + +DECLARE_API(VerifyHeap) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + if (!g_snapshot.Build()) + { + ExtOut("Unable to build snapshot of the garbage collector state\n"); + return E_FAIL; + } + + try + { + bool succeeded = true; + char buffer[1024]; + sos::GCHeap gcheap; + sos::ObjectIterator itr = gcheap.WalkHeap(); + + while (itr) + { + if (itr.Verify(buffer, _countof(buffer))) + { + ++itr; + } + else + { + succeeded = false; + ExtOut(buffer); + itr.MoveToNextObjectCarefully(); + } + } + + if (!DumpHeapImpl::ValidateSyncTable(gcheap)) + succeeded = false; + + if (succeeded) + ExtOut("No heap corruption detected.\n"); + + return S_OK; + } + catch(const sos::Exception &e) + { + ExtOut("%s\n", e.what()); + return E_FAIL; + } +} + +#ifndef FEATURE_PAL + +enum failure_get_memory +{ + fgm_no_failure = 0, + fgm_reserve_segment = 1, + fgm_commit_segment_beg = 2, + fgm_commit_eph_segment = 3, + fgm_grow_table = 4, + fgm_commit_table = 5 +}; + +enum oom_reason +{ + oom_no_failure = 0, + oom_budget = 1, + oom_cant_commit = 2, + oom_cant_reserve = 3, + oom_loh = 4, + oom_low_mem = 5, + oom_unproductive_full_gc = 6 +}; + +static const char *const str_oom[] = +{ + "There was no managed OOM due to allocations on the GC heap", // oom_no_failure + "This is likely to be a bug in GC", // oom_budget + "Didn't have enough memory to commit", // oom_cant_commit + "This is likely to be a bug in GC", // oom_cant_reserve + "Didn't have enough memory to allocate an LOH segment", // oom_loh + "Low on memory during GC", // oom_low_mem + "Could not do a full GC" // oom_unproductive_full_gc +}; + +static const char *const str_fgm[] = +{ + "There was no failure to allocate memory", // fgm_no_failure + "Failed to reserve memory", // fgm_reserve_segment + "Didn't have enough memory to commit beginning of the segment", // fgm_commit_segment_beg + "Didn't have enough memory to commit the new ephemeral segment", // fgm_commit_eph_segment + "Didn't have enough memory to grow the internal GC datastructures", // fgm_grow_table + "Didn't have enough memory to commit the internal GC datastructures", // fgm_commit_table +}; + +void PrintOOMInfo(DacpOomData* oomData) +{ + ExtOut("Managed OOM occurred after GC #%d (Requested to allocate %d bytes)\n", + oomData->gc_index, oomData->alloc_size); + + if ((oomData->reason == oom_budget) || + (oomData->reason == oom_cant_reserve)) + { + // TODO: This message needs to be updated with more precious info. + ExtOut("%s, please contact PSS\n", str_oom[oomData->reason]); + } + else + { + ExtOut("Reason: %s\n", str_oom[oomData->reason]); + } + + // Now print out the more detailed memory info if any. + if (oomData->fgm != fgm_no_failure) + { + ExtOut("Detail: %s: %s (%d bytes)", + (oomData->loh_p ? "LOH" : "SOH"), + str_fgm[oomData->fgm], + oomData->size); + + if ((oomData->fgm == fgm_commit_segment_beg) || + (oomData->fgm == fgm_commit_eph_segment) || + (oomData->fgm == fgm_grow_table) || + (oomData->fgm == fgm_commit_table)) + { + // If it's a commit error (fgm_grow_table can indicate a reserve + // or a commit error since we make one VirtualAlloc call to + // reserve and commit), we indicate the available commit + // space if we recorded it. + if (oomData->available_pagefile_mb) + { + ExtOut(" - on GC entry available commit space was %d MB", + oomData->available_pagefile_mb); + } + } + + ExtOut("\n"); + } +} + +DECLARE_API(AnalyzeOOM) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + +#ifndef FEATURE_PAL + + if (!InitializeHeapData ()) + { + ExtOut("GC Heap not initialized yet.\n"); + return S_OK; + } + + BOOL bHasManagedOOM = FALSE; + DacpOomData oomData; + memset (&oomData, 0, sizeof(oomData)); + if (!IsServerBuild()) + { + if (oomData.Request(g_sos) != S_OK) + { + ExtOut("Error requesting OOM data\n"); + return E_FAIL; + } + if (oomData.reason != oom_no_failure) + { + bHasManagedOOM = TRUE; + PrintOOMInfo(&oomData); + } + } + else + { + DWORD dwNHeaps = GetGcHeapCount(); + DWORD dwAllocSize; + if (!ClrSafeInt<DWORD>::multiply(sizeof(CLRDATA_ADDRESS), dwNHeaps, dwAllocSize)) + { + ExtOut("Failed to get GCHeaps: integer overflow\n"); + return Status; + } + + CLRDATA_ADDRESS *heapAddrs = (CLRDATA_ADDRESS*)alloca(dwAllocSize); + if (g_sos->GetGCHeapList(dwNHeaps, heapAddrs, NULL) != S_OK) + { + ExtOut("Failed to get GCHeaps\n"); + return Status; + } + + for (DWORD n = 0; n < dwNHeaps; n ++) + { + if (oomData.Request(g_sos, heapAddrs[n]) != S_OK) + { + ExtOut("Heap %d: Error requesting OOM data\n", n); + return E_FAIL; + } + if (oomData.reason != oom_no_failure) + { + if (!bHasManagedOOM) + { + bHasManagedOOM = TRUE; + } + ExtOut("---------Heap %#-2d---------\n", n); + PrintOOMInfo(&oomData); + } + } + } + + if (!bHasManagedOOM) + { + ExtOut("%s\n", str_oom[oomData.reason]); + } + + return S_OK; +#else + _ASSERTE(false); + return E_FAIL; +#endif // FEATURE_PAL +} + +DECLARE_API(VerifyObj) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + TADDR taddrObj = 0; + TADDR taddrMT; + size_t objSize; + + BOOL bValid = FALSE; + BOOL dml = FALSE; + + CMDOption option[] = + { // name, vptr, type, hasValue + {"/d", &dml, COBOOL, FALSE}, + }; + CMDValue arg[] = + { // vptr, type + {&taddrObj, COHEX} + }; + size_t nArg; + if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg)) + { + return Status; + } + + EnableDMLHolder dmlHolder(dml); + BOOL bContainsPointers; + + if (FAILED(GetMTOfObject(taddrObj, &taddrMT)) || + !GetSizeEfficient(taddrObj, taddrMT, FALSE, objSize, bContainsPointers)) + { + ExtOut("object %#p does not have valid method table\n", SOS_PTR(taddrObj)); + goto Exit; + } + + // we need to build g_snapshot as it is later used in GetGeneration + if (!g_snapshot.Build()) + { + ExtOut("Unable to build snapshot of the garbage collector state\n"); + goto Exit; + } + DacpGcHeapDetails *pheapDetails = g_snapshot.GetHeap(taddrObj); + bValid = VerifyObject(*pheapDetails, taddrObj, taddrMT, objSize, TRUE); + +Exit: + if (bValid) + { + ExtOut("object %#p is a valid object\n", SOS_PTR(taddrObj)); + } + + return Status; +} + +void LNODisplayOutput(LPCWSTR tag, TADDR pMT, TADDR currentObj, size_t size) +{ + sos::Object obj(currentObj, pMT); + DMLOut("%S %s %12d (0x%x)\t%S\n", tag, DMLObject(currentObj), size, size, obj.GetTypeName()); +} + +DECLARE_API(ListNearObj) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + +#if !defined(FEATURE_PAL) + + TADDR taddrArg = 0; + TADDR taddrObj = 0; + // we may want to provide a more exact version of searching for the + // previous object in the heap, using the brick table, instead of + // looking for what may be valid method tables... + //BOOL bExact; + //CMDOption option[] = + //{ + // // name, vptr, type, hasValue + // {"-exact", &bExact, COBOOL, FALSE} + //}; + + BOOL dml = FALSE; + CMDOption option[] = + { // name, vptr, type, hasValue + {"/d", &dml, COBOOL, FALSE}, + }; + CMDValue arg[] = + { + // vptr, type + {&taddrArg, COHEX} + }; + size_t nArg; + if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg) || nArg != 1) + { + ExtOut("Usage: !ListNearObj <obj_address>\n"); + return Status; + } + + EnableDMLHolder dmlHolder(dml); + + if (!g_snapshot.Build()) + { + ExtOut("Unable to build snapshot of the garbage collector state\n"); + return Status; + } + + taddrObj = Align(taddrArg); + + DacpGcHeapDetails *heap = g_snapshot.GetHeap(taddrArg); + if (heap == NULL) + { + ExtOut("Address %p does not lie in the managed heap\n", SOS_PTR(taddrObj)); + return Status; + } + + TADDR_SEGINFO trngSeg = {0, 0, 0}; + TADDR_RANGE allocCtx = {0, 0}; + BOOL bLarge; + int gen; + if (!GCObjInHeap(taddrObj, *heap, trngSeg, gen, allocCtx, bLarge)) + { + ExtOut("Failed to find the segment of the managed heap where the object %p resides\n", + SOS_PTR(taddrObj)); + return Status; + } + + TADDR objMT = NULL; + size_t objSize = 0; + BOOL bObj = FALSE; + TADDR taddrCur; + TADDR curMT = 0; + size_t curSize = 0; + BOOL bCur = FALSE; + TADDR taddrNxt; + TADDR nxtMT = 0; + size_t nxtSize = 0; + BOOL bNxt = FALSE; + BOOL bContainsPointers; + + std::vector<TADDR> candidate; + candidate.reserve(10); + + // since we'll be reading back I'll prime the read cache to a buffer before the current address + MOVE(taddrCur, _max(trngSeg.start, taddrObj-DT_OS_PAGE_SIZE)); + + // ===== Look for a good candidate preceeding taddrObj + + for (taddrCur = taddrObj - sizeof(TADDR); taddrCur >= trngSeg.start; taddrCur -= sizeof(TADDR)) + { + // currently we don't pay attention to allocation contexts. if this + // proves to be an issue we need to reconsider the code below + if (SUCCEEDED(GetMTOfObject(taddrCur, &curMT)) && + GetSizeEfficient(taddrCur, curMT, bLarge, curSize, bContainsPointers)) + { + // remember this as one of the possible "good" objects preceeding taddrObj + candidate.push_back(taddrCur); + + std::vector<TADDR>::iterator it = + std::find(candidate.begin(), candidate.end(), taddrCur+curSize); + if (it != candidate.end()) + { + // We found a chain of two objects preceeding taddrObj. We'll + // trust this is a good indication that the two objects are valid. + // What is not valid is possibly the object following the second + // one... + taddrCur = *it; + GetMTOfObject(taddrCur, &curMT); + GetSizeEfficient(taddrCur, curMT, bLarge, curSize, bContainsPointers); + bCur = TRUE; + break; + } + } + } + + if (!bCur && !candidate.empty()) + { + // pick the closest object to taddrObj + taddrCur = *(candidate.begin()); + GetMTOfObject(taddrCur, &curMT); + GetSizeEfficient(taddrCur, curMT, bLarge, curSize, bContainsPointers); + // we have a candidate, even if not confirmed + bCur = TRUE; + } + + taddrNxt = taddrObj; + if (taddrArg == taddrObj) + { + taddrNxt += sizeof(TADDR); + } + + // ===== Now look at taddrObj + if (taddrObj == taddrArg) + { + // only look at taddrObj if it's the same as what user passed in, meaning it's aligned. + if (SUCCEEDED(GetMTOfObject(taddrObj, &objMT)) && + GetSizeEfficient(taddrObj, objMT, bLarge, objSize, bContainsPointers)) + { + bObj = TRUE; + taddrNxt = taddrObj+objSize; + } + } + + if ((taddrCur + curSize > taddrArg) && taddrCur + curSize < trngSeg.end) + { + if (SUCCEEDED(GetMTOfObject(taddrCur + curSize, &nxtMT)) && + GetSizeEfficient(taddrObj, objMT, bLarge, objSize, bContainsPointers)) + { + taddrNxt = taddrCur+curSize; + } + } + + // ===== And finally move on to elements following taddrObj + + for (; taddrNxt < trngSeg.end; taddrNxt += sizeof(TADDR)) + { + if (SUCCEEDED(GetMTOfObject(taddrNxt, &nxtMT)) && + GetSizeEfficient(taddrNxt, nxtMT, bLarge, nxtSize, bContainsPointers)) + { + bNxt = TRUE; + break; + } + } + + if (bCur) + LNODisplayOutput(W("Before: "), curMT, taddrCur, curSize); + else + ExtOut("Before: couldn't find any object between %#p and %#p\n", + SOS_PTR(trngSeg.start), SOS_PTR(taddrArg)); + + if (bObj) + LNODisplayOutput(W("Current:"), objMT, taddrObj, objSize); + + if (bNxt) + LNODisplayOutput(W("After: "), nxtMT, taddrNxt, nxtSize); + else + ExtOut("After: couldn't find any object between %#p and %#p\n", + SOS_PTR(taddrArg), SOS_PTR(trngSeg.end)); + + if (bCur && bNxt && + (((taddrCur+curSize == taddrObj) && (taddrObj+objSize == taddrNxt)) || (taddrCur+curSize == taddrNxt))) + { + ExtOut("Heap local consistency confirmed.\n"); + } + else + { + ExtOut("Heap local consistency not confirmed.\n"); + } + + return Status; + +#else + + _ASSERTE(false); + return E_FAIL; + +#endif // FEATURE_PAL +} + + +DECLARE_API(GCHeapStat) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + +#ifndef FEATURE_PAL + + BOOL bIncUnreachable = FALSE; + BOOL dml = FALSE; + + CMDOption option[] = { + // name, vptr, type, hasValue + {"-inclUnrooted", &bIncUnreachable, COBOOL, FALSE}, + {"-iu", &bIncUnreachable, COBOOL, FALSE}, + {"/d", &dml, COBOOL, FALSE} + }; + + if (!GetCMDOption(args, option, _countof(option), NULL, 0, NULL)) + { + return Status; + } + + EnableDMLHolder dmlHolder(dml); + ExtOut("%-8s %12s %12s %12s %12s\n", "Heap", "Gen0", "Gen1", "Gen2", "LOH"); + + if (!IsServerBuild()) + { + float tempf; + DacpGcHeapDetails heapDetails; + if (heapDetails.Request(g_sos) != S_OK) + { + ExtErr("Error requesting gc heap details\n"); + return Status; + } + + HeapUsageStat hpUsage; + if (GCHeapUsageStats(heapDetails, bIncUnreachable, &hpUsage)) + { + ExtOut("Heap%-4d %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u\n", 0, + hpUsage.genUsage[0].allocd, hpUsage.genUsage[1].allocd, + hpUsage.genUsage[2].allocd, hpUsage.genUsage[3].allocd); + ExtOut("\nFree space: Percentage\n"); + ExtOut("Heap%-4d %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u", 0, + hpUsage.genUsage[0].freed, hpUsage.genUsage[1].freed, + hpUsage.genUsage[2].freed, hpUsage.genUsage[3].freed); + tempf = ((float)(hpUsage.genUsage[0].freed+hpUsage.genUsage[1].freed+hpUsage.genUsage[2].freed)) / + (hpUsage.genUsage[0].allocd+hpUsage.genUsage[1].allocd+hpUsage.genUsage[2].allocd); + ExtOut("SOH:%3d%% LOH:%3d%%\n", (int)(100 * tempf), + (int)(100*((float)hpUsage.genUsage[3].freed) / (hpUsage.genUsage[3].allocd))); + if (bIncUnreachable) + { + ExtOut("\nUnrooted objects: Percentage\n"); + ExtOut("Heap%-4d %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u", 0, + hpUsage.genUsage[0].unrooted, hpUsage.genUsage[1].unrooted, + hpUsage.genUsage[2].unrooted, hpUsage.genUsage[3].unrooted); + tempf = ((float)(hpUsage.genUsage[0].unrooted+hpUsage.genUsage[1].unrooted+hpUsage.genUsage[2].unrooted)) / + (hpUsage.genUsage[0].allocd+hpUsage.genUsage[1].allocd+hpUsage.genUsage[2].allocd); + ExtOut("SOH:%3d%% LOH:%3d%%\n", (int)(100 * tempf), + (int)(100*((float)hpUsage.genUsage[3].unrooted) / (hpUsage.genUsage[3].allocd))); + } + } + } + else + { + float tempf; + DacpGcHeapData gcheap; + if (gcheap.Request(g_sos) != S_OK) + { + ExtErr("Error requesting GC Heap data\n"); + return Status; + } + + DWORD dwAllocSize; + DWORD dwNHeaps = gcheap.HeapCount; + if (!ClrSafeInt<DWORD>::multiply(sizeof(CLRDATA_ADDRESS), dwNHeaps, dwAllocSize)) + { + ExtErr("Failed to get GCHeaps: integer overflow\n"); + return Status; + } + + CLRDATA_ADDRESS *heapAddrs = (CLRDATA_ADDRESS*)alloca(dwAllocSize); + if (g_sos->GetGCHeapList(dwNHeaps, heapAddrs, NULL) != S_OK) + { + ExtErr("Failed to get GCHeaps\n"); + return Status; + } + + ArrayHolder<HeapUsageStat> hpUsage = new NOTHROW HeapUsageStat[dwNHeaps]; + if (hpUsage == NULL) + { + ReportOOM(); + return Status; + } + + // aggregate stats accross heaps / generation + GenUsageStat genUsageStat[4] = {0, 0, 0, 0}; + + for (DWORD n = 0; n < dwNHeaps; n ++) + { + DacpGcHeapDetails heapDetails; + if (heapDetails.Request(g_sos, heapAddrs[n]) != S_OK) + { + ExtErr("Error requesting gc heap details\n"); + return Status; + } + + if (GCHeapUsageStats(heapDetails, bIncUnreachable, &hpUsage[n])) + { + for (int i = 0; i < 4; ++i) + { + genUsageStat[i].allocd += hpUsage[n].genUsage[i].allocd; + genUsageStat[i].freed += hpUsage[n].genUsage[i].freed; + if (bIncUnreachable) + { + genUsageStat[i].unrooted += hpUsage[n].genUsage[i].unrooted; + } + } + } + } + + for (DWORD n = 0; n < dwNHeaps; n ++) + { + ExtOut("Heap%-4d %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u\n", n, + hpUsage[n].genUsage[0].allocd, hpUsage[n].genUsage[1].allocd, + hpUsage[n].genUsage[2].allocd, hpUsage[n].genUsage[3].allocd); + } + ExtOut("Total %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u\n", + genUsageStat[0].allocd, genUsageStat[1].allocd, + genUsageStat[2].allocd, genUsageStat[3].allocd); + + ExtOut("\nFree space: Percentage\n"); + for (DWORD n = 0; n < dwNHeaps; n ++) + { + ExtOut("Heap%-4d %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u", n, + hpUsage[n].genUsage[0].freed, hpUsage[n].genUsage[1].freed, + hpUsage[n].genUsage[2].freed, hpUsage[n].genUsage[3].freed); + + tempf = ((float)(hpUsage[n].genUsage[0].freed+hpUsage[n].genUsage[1].freed+hpUsage[n].genUsage[2].freed)) / + (hpUsage[n].genUsage[0].allocd+hpUsage[n].genUsage[1].allocd+hpUsage[n].genUsage[2].allocd); + ExtOut("SOH:%3d%% LOH:%3d%%\n", (int)(100 * tempf), + (int)(100*((float)hpUsage[n].genUsage[3].freed) / (hpUsage[n].genUsage[3].allocd)) + ); + } + ExtOut("Total %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u\n", + genUsageStat[0].freed, genUsageStat[1].freed, + genUsageStat[2].freed, genUsageStat[3].freed); + + if (bIncUnreachable) + { + ExtOut("\nUnrooted objects: Percentage\n"); + for (DWORD n = 0; n < dwNHeaps; n ++) + { + ExtOut("Heap%-4d %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u", n, + hpUsage[n].genUsage[0].unrooted, hpUsage[n].genUsage[1].unrooted, + hpUsage[n].genUsage[2].unrooted, hpUsage[n].genUsage[3].unrooted); + + tempf = ((float)(hpUsage[n].genUsage[0].unrooted+hpUsage[n].genUsage[1].unrooted+hpUsage[n].genUsage[2].unrooted)) / + (hpUsage[n].genUsage[0].allocd+hpUsage[n].genUsage[1].allocd+hpUsage[n].genUsage[2].allocd); + ExtOut("SOH:%3d%% LOH:%3d%%\n", (int)(100 * tempf), + (int)(100*((float)hpUsage[n].genUsage[3].unrooted) / (hpUsage[n].genUsage[3].allocd))); + } + ExtOut("Total %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u %12" POINTERSIZE_TYPE "u\n", + genUsageStat[0].unrooted, genUsageStat[1].unrooted, + genUsageStat[2].unrooted, genUsageStat[3].unrooted); + } + + } + + return Status; + +#else + + _ASSERTE(false); + return E_FAIL; + +#endif // FEATURE_PAL +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function dumps what is in the syncblock cache. By default * +* it dumps all active syncblocks. Using -all to dump all syncblocks +* * +\**********************************************************************/ +DECLARE_API(SyncBlk) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + BOOL bDumpAll = FALSE; + size_t nbAsked = 0; + BOOL dml = FALSE; + + CMDOption option[] = + { // name, vptr, type, hasValue + {"-all", &bDumpAll, COBOOL, FALSE}, + {"/d", &dml, COBOOL, FALSE} + }; + CMDValue arg[] = + { // vptr, type + {&nbAsked, COSIZE_T} + }; + size_t nArg; + if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg)) + { + return Status; + } + + EnableDMLHolder dmlHolder(dml); + DacpSyncBlockData syncBlockData; + if (syncBlockData.Request(g_sos,1) != S_OK) + { + ExtOut("Error requesting SyncBlk data\n"); + return Status; + } + + DWORD dwCount = syncBlockData.SyncBlockCount; + + ExtOut("Index" WIN64_8SPACES " SyncBlock MonitorHeld Recursion Owning Thread Info" WIN64_8SPACES " SyncBlock Owner\n"); + ULONG freeCount = 0; + ULONG CCWCount = 0; + ULONG RCWCount = 0; + ULONG CFCount = 0; + for (DWORD nb = 1; nb <= dwCount; nb++) + { + if (IsInterrupt()) + return Status; + + if (nbAsked && nb != nbAsked) + { + continue; + } + + if (syncBlockData.Request(g_sos,nb) != S_OK) + { + ExtOut("SyncBlock %d is invalid%s\n", nb, + (nb != nbAsked) ? ", continuing..." : ""); + continue; + } + + BOOL bPrint = (bDumpAll || nb == nbAsked || (syncBlockData.MonitorHeld > 0 && !syncBlockData.bFree)); + + if (bPrint) + { + ExtOut("%5d ", nb); + if (!syncBlockData.bFree || nb != nbAsked) + { + ExtOut("%p ", syncBlockData.SyncBlockPointer); + ExtOut("%11d ", syncBlockData.MonitorHeld); + ExtOut("%9d ", syncBlockData.Recursion); + ExtOut("%p ", syncBlockData.HoldingThread); + + if (syncBlockData.HoldingThread == ~0ul) + { + ExtOut(" orphaned "); + } + else if (syncBlockData.HoldingThread != NULL) + { + DacpThreadData Thread; + if ((Status = Thread.Request(g_sos, syncBlockData.HoldingThread)) != S_OK) + { + ExtOut("Failed to request Thread at %p\n", syncBlockData.HoldingThread); + return Status; + } + + DMLOut(DMLThreadID(Thread.osThreadId)); + ULONG id; + if (g_ExtSystem->GetThreadIdBySystemId(Thread.osThreadId, &id) == S_OK) + { + ExtOut("%4d ", id); + } + else + { + ExtOut(" XXX "); + } + } + else + { + ExtOut(" none "); + } + + if (syncBlockData.bFree) + { + ExtOut(" %8d", 0); // TODO: do we need to print the free synctable list? + } + else + { + sos::Object obj = TO_TADDR(syncBlockData.Object); + DMLOut(" %s %S", DMLObject(syncBlockData.Object), obj.GetTypeName()); + } + } + } + + if (syncBlockData.bFree) + { + freeCount ++; + if (bPrint) { + ExtOut(" Free"); + } + } + else + { +#ifdef FEATURE_COMINTEROP + if (syncBlockData.COMFlags) { + switch (syncBlockData.COMFlags) { + case SYNCBLOCKDATA_COMFLAGS_CCW: + CCWCount ++; + break; + case SYNCBLOCKDATA_COMFLAGS_RCW: + RCWCount ++; + break; + case SYNCBLOCKDATA_COMFLAGS_CF: + CFCount ++; + break; + } + } +#endif // FEATURE_COMINTEROP + } + + if (syncBlockData.MonitorHeld > 1) + { + // TODO: implement this + /* + ExtOut(" "); + DWORD_PTR pHead = (DWORD_PTR)vSyncBlock.m_Link.m_pNext; + DWORD_PTR pNext = pHead; + Thread vThread; + + while (1) + { + if (IsInterrupt()) + return Status; + DWORD_PTR pWaitEventLink = pNext - offsetLinkSB; + WaitEventLink vWaitEventLink; + vWaitEventLink.Fill(pWaitEventLink); + if (!CallStatus) { + break; + } + DWORD_PTR dwAddr = (DWORD_PTR)vWaitEventLink.m_Thread; + ExtOut("%x ", dwAddr); + vThread.Fill (dwAddr); + if (!CallStatus) { + break; + } + if (bPrint) + DMLOut("%s,", DMLThreadID(vThread.m_OSThreadId)); + pNext = (DWORD_PTR)vWaitEventLink.m_LinkSB.m_pNext; + if (pNext == 0) + break; + } + */ + } + + if (bPrint) + ExtOut("\n"); + } + + ExtOut("-----------------------------\n"); + ExtOut("Total %d\n", dwCount); + ExtOut("CCW %d\n", CCWCount); + ExtOut("RCW %d\n", RCWCount); + ExtOut("ComClassFactory %d\n", CFCount); + ExtOut("Free %d\n", freeCount); + + return Status; +} + +#ifdef FEATURE_COMINTEROP +struct VisitRcwArgs +{ + BOOL bDetail; + UINT MTACount; + UINT STACount; + ULONG FTMCount; +}; + +void VisitRcw(CLRDATA_ADDRESS RCW,CLRDATA_ADDRESS Context,CLRDATA_ADDRESS Thread, BOOL bIsFreeThreaded, LPVOID token) +{ + VisitRcwArgs *pArgs = (VisitRcwArgs *) token; + + if (pArgs->bDetail) + { + if (pArgs->MTACount == 0 && pArgs->STACount == 0 && pArgs->FTMCount == 0) + { + // First time, print a header + ExtOut("RuntimeCallableWrappers (RCW) to be cleaned:\n"); + ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %" POINTERSIZE "s Apartment\n", + "RCW", "CONTEXT", "THREAD"); + } + LPCSTR szThreadApartment; + if (bIsFreeThreaded) + { + szThreadApartment = "(FreeThreaded)"; + pArgs->FTMCount++; + } + else if (Thread == NULL) + { + szThreadApartment = "(MTA)"; + pArgs->MTACount++; + } + else + { + szThreadApartment = "(STA)"; + pArgs->STACount++; + } + + ExtOut("%" POINTERSIZE "p %" POINTERSIZE "p %" POINTERSIZE "p %9s\n", + SOS_PTR(RCW), + SOS_PTR(Context), + SOS_PTR(Thread), + szThreadApartment); + } +} + +DECLARE_API(RCWCleanupList) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + DWORD_PTR p_CleanupList = GetExpression(args); + + VisitRcwArgs travArgs; + ZeroMemory(&travArgs,sizeof(VisitRcwArgs)); + travArgs.bDetail = TRUE; + + // We need to detect when !RCWCleanupList is called with an expression which evaluates to 0 + // (to print out an Invalid parameter message), but at the same time we need to allow an + // empty argument list which would result in p_CleanupList equaling 0. + if (p_CleanupList || strlen(args) == 0) + { + HRESULT hr = g_sos->TraverseRCWCleanupList(p_CleanupList, (VISITRCWFORCLEANUP)VisitRcw, &travArgs); + + if (SUCCEEDED(hr)) + { + ExtOut("Free-Threaded Interfaces to be released: %d\n", travArgs.FTMCount); + ExtOut("MTA Interfaces to be released: %d\n", travArgs.MTACount); + ExtOut("STA Interfaces to be released: %d\n", travArgs.STACount); + } + else + { + ExtOut("An error occurred while traversing the cleanup list.\n"); + } + } + else + { + ExtOut("Invalid parameter %s\n", args); + } + + return Status; +} +#endif // FEATURE_COMINTEROP + + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to dump the contents of the finalizer * +* queue. * +* * +\**********************************************************************/ +DECLARE_API(FinalizeQueue) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + BOOL bDetail = FALSE; + BOOL bAllReady = FALSE; + BOOL bShort = FALSE; + BOOL dml = FALSE; + TADDR taddrMT = 0; + + CMDOption option[] = + { // name, vptr, type, hasValue + {"-detail", &bDetail, COBOOL, FALSE}, + {"-allReady", &bAllReady, COBOOL, FALSE}, + {"-short", &bShort, COBOOL, FALSE}, + {"/d", &dml, COBOOL, FALSE}, + {"-mt", &taddrMT, COHEX, TRUE}, + }; + + if (!GetCMDOption(args, option, _countof(option), NULL, 0, NULL)) + { + return Status; + } + + EnableDMLHolder dmlHolder(dml); + if (!bShort) + { + DacpSyncBlockCleanupData dsbcd; + CLRDATA_ADDRESS sbCurrent = NULL; + ULONG cleanCount = 0; + while ((dsbcd.Request(g_sos,sbCurrent) == S_OK) && dsbcd.SyncBlockPointer) + { + if (bDetail) + { + if (cleanCount == 0) // print first time only + { + ExtOut("SyncBlocks to be cleaned by the finalizer thread:\n"); + ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %" POINTERSIZE "s %" POINTERSIZE "s\n", + "SyncBlock", "RCW", "CCW", "ComClassFactory"); + } + + ExtOut("%" POINTERSIZE "p %" POINTERSIZE "p %" POINTERSIZE "p %" POINTERSIZE "p\n", + (ULONG64) dsbcd.SyncBlockPointer, + (ULONG64) dsbcd.blockRCW, + (ULONG64) dsbcd.blockCCW, + (ULONG64) dsbcd.blockClassFactory); + } + + cleanCount++; + sbCurrent = dsbcd.nextSyncBlock; + if (sbCurrent == NULL) + { + break; + } + } + + ExtOut("SyncBlocks to be cleaned up: %d\n", cleanCount); + +#ifdef FEATURE_COMINTEROP + VisitRcwArgs travArgs; + ZeroMemory(&travArgs,sizeof(VisitRcwArgs)); + travArgs.bDetail = bDetail; + g_sos->TraverseRCWCleanupList(0, (VISITRCWFORCLEANUP) VisitRcw, &travArgs); + ExtOut("Free-Threaded Interfaces to be released: %d\n", travArgs.FTMCount); + ExtOut("MTA Interfaces to be released: %d\n", travArgs.MTACount); + ExtOut("STA Interfaces to be released: %d\n", travArgs.STACount); +#endif // FEATURE_COMINTEROP + +// noRCW: + ExtOut("----------------------------------\n"); + } + + // GC Heap + DWORD dwNHeaps = GetGcHeapCount(); + + HeapStat hpStat; + + if (!IsServerBuild()) + { + DacpGcHeapDetails heapDetails; + if (heapDetails.Request(g_sos) != S_OK) + { + ExtOut("Error requesting details\n"); + return Status; + } + + GatherOneHeapFinalization(heapDetails, &hpStat, bAllReady, bShort); + } + else + { + DWORD dwAllocSize; + if (!ClrSafeInt<DWORD>::multiply(sizeof(CLRDATA_ADDRESS), dwNHeaps, dwAllocSize)) + { + ExtOut("Failed to get GCHeaps: integer overflow\n"); + return Status; + } + + CLRDATA_ADDRESS *heapAddrs = (CLRDATA_ADDRESS*)alloca(dwAllocSize); + if (g_sos->GetGCHeapList(dwNHeaps, heapAddrs, NULL) != S_OK) + { + ExtOut("Failed to get GCHeaps\n"); + return Status; + } + + for (DWORD n = 0; n < dwNHeaps; n ++) + { + DacpGcHeapDetails heapDetails; + if (heapDetails.Request(g_sos, heapAddrs[n]) != S_OK) + { + ExtOut("Error requesting details\n"); + return Status; + } + + ExtOut("------------------------------\n"); + ExtOut("Heap %d\n", n); + GatherOneHeapFinalization(heapDetails, &hpStat, bAllReady, bShort); + } + } + + if (!bShort) + { + if (bAllReady) + { + PrintGCStat(&hpStat, "Statistics for all finalizable objects that are no longer rooted:\n"); + } + else + { + PrintGCStat(&hpStat, "Statistics for all finalizable objects (including all objects ready for finalization):\n"); + } + } + + return Status; +} + +#endif // FEATURE_PAL + +enum { + // These are the values set in m_dwTransientFlags. + // Note that none of these flags survive a prejit save/restore. + + M_CRST_NOTINITIALIZED = 0x00000001, // Used to prevent destruction of garbage m_crst + M_LOOKUPCRST_NOTINITIALIZED = 0x00000002, + + SUPPORTS_UPDATEABLE_METHODS = 0x00000020, + CLASSES_FREED = 0x00000040, + HAS_PHONY_IL_RVAS = 0x00000080, + IS_EDIT_AND_CONTINUE = 0x00000200, +}; + +void ModuleMapTraverse(UINT index, CLRDATA_ADDRESS methodTable, LPVOID token) +{ + ULONG32 rid = (ULONG32)(size_t)token; + NameForMT_s(TO_TADDR(methodTable), g_mdName, mdNameLen); + + DMLOut("%s 0x%08x %S\n", DMLMethodTable(methodTable), (ULONG32)TokenFromRid(rid, index), g_mdName); +} + + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to dump the contents of a Module * +* for a given address * +* * +\**********************************************************************/ +DECLARE_API(DumpModule) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + + DWORD_PTR p_ModuleAddr = NULL; + BOOL bMethodTables = FALSE; + BOOL dml = FALSE; + + CMDOption option[] = + { // name, vptr, type, hasValue + {"-mt", &bMethodTables, COBOOL, FALSE}, +#ifndef FEATURE_PAL + {"/d", &dml, COBOOL, FALSE} +#endif + }; + CMDValue arg[] = + { // vptr, type + {&p_ModuleAddr, COHEX} + }; + + size_t nArg; + if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg)) + { + return Status; + } + if (nArg != 1) + { + ExtOut("Usage: DumpModule [-mt] <Module Address>\n"); + return Status; + } + + EnableDMLHolder dmlHolder(dml); + DacpModuleData module; + if ((Status=module.Request(g_sos, TO_CDADDR(p_ModuleAddr)))!=S_OK) + { + ExtOut("Fail to fill Module %p\n", SOS_PTR(p_ModuleAddr)); + return Status; + } + + WCHAR FileName[MAX_LONGPATH]; + FileNameForModule (&module, FileName); + ExtOut("Name: %S\n", FileName[0] ? FileName : W("Unknown Module")); + + ExtOut("Attributes: "); + if (module.bIsPEFile) + ExtOut("PEFile "); + if (module.bIsReflection) + ExtOut("Reflection "); + if (module.dwTransientFlags & SUPPORTS_UPDATEABLE_METHODS) + ExtOut("SupportsUpdateableMethods"); + ExtOut("\n"); + + DMLOut("Assembly: %s\n", DMLAssembly(module.Assembly)); + + ExtOut("LoaderHeap: %p\n", SOS_PTR(module.pLookupTableHeap)); + ExtOut("TypeDefToMethodTableMap: %p\n", SOS_PTR(module.TypeDefToMethodTableMap)); + ExtOut("TypeRefToMethodTableMap: %p\n", SOS_PTR(module.TypeRefToMethodTableMap)); + ExtOut("MethodDefToDescMap: %p\n", SOS_PTR(module.MethodDefToDescMap)); + ExtOut("FieldDefToDescMap: %p\n", SOS_PTR(module.FieldDefToDescMap)); + ExtOut("MemberRefToDescMap: %p\n", SOS_PTR(module.MemberRefToDescMap)); + ExtOut("FileReferencesMap: %p\n", SOS_PTR(module.FileReferencesMap)); + ExtOut("AssemblyReferencesMap: %p\n", SOS_PTR(module.ManifestModuleReferencesMap)); + + if (module.ilBase && module.metadataStart) + ExtOut("MetaData start address: %p (%d bytes)\n", SOS_PTR(module.metadataStart), module.metadataSize); + + if (bMethodTables) + { + ExtOut("\nTypes defined in this module\n\n"); + ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %s\n", "MT", "TypeDef", "Name"); + + ExtOut("------------------------------------------------------------------------------\n"); + g_sos->TraverseModuleMap(TYPEDEFTOMETHODTABLE, TO_CDADDR(p_ModuleAddr), ModuleMapTraverse, (LPVOID)mdTypeDefNil); + + ExtOut("\nTypes referenced in this module\n\n"); + ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %s\n", "MT", "TypeRef", "Name"); + + ExtOut("------------------------------------------------------------------------------\n"); + g_sos->TraverseModuleMap(TYPEREFTOMETHODTABLE, TO_CDADDR(p_ModuleAddr), ModuleMapTraverse, (LPVOID)mdTypeDefNil); + } + + return Status; +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to dump the contents of a Domain * +* for a given address * +* * +\**********************************************************************/ +DECLARE_API(DumpDomain) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + DWORD_PTR p_DomainAddr = 0; + BOOL dml = FALSE; + + CMDOption option[] = + { // name, vptr, type, hasValue +#ifndef FEATURE_PAL + {"/d", &dml, COBOOL, FALSE}, +#endif + }; + CMDValue arg[] = + { // vptr, type + {&p_DomainAddr, COHEX}, + }; + size_t nArg; + + if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg)) + { + return Status; + } + + EnableDMLHolder dmlHolder(dml); + + DacpAppDomainStoreData adsData; + if ((Status=adsData.Request(g_sos))!=S_OK) + { + ExtOut("Unable to get AppDomain information\n"); + return Status; + } + + if (p_DomainAddr) + { + DacpAppDomainData appDomain1; + if ((Status=appDomain1.Request(g_sos, TO_CDADDR(p_DomainAddr)))!=S_OK) + { + ExtOut("Fail to fill AppDomain\n"); + return Status; + } + + ExtOut("--------------------------------------\n"); + + if (p_DomainAddr == adsData.sharedDomain) + { + DMLOut("Shared Domain: %s\n", DMLDomain(adsData.sharedDomain)); + } + else if (p_DomainAddr == adsData.systemDomain) + { + DMLOut("System Domain: %s\n", DMLDomain(adsData.systemDomain)); + } + else + { + DMLOut("Domain %d:%s %s\n", appDomain1.dwId, (appDomain1.dwId >= 10) ? "" : " ", DMLDomain(p_DomainAddr)); + } + + DomainInfo(&appDomain1); + return Status; + } + + ExtOut("--------------------------------------\n"); + DMLOut("System Domain: %s\n", DMLDomain(adsData.systemDomain)); + DacpAppDomainData appDomain; + if ((Status=appDomain.Request(g_sos,adsData.systemDomain))!=S_OK) + { + ExtOut("Unable to get system domain info.\n"); + return Status; + } + DomainInfo(&appDomain); + + ExtOut("--------------------------------------\n"); + DMLOut("Shared Domain: %s\n", DMLDomain(adsData.sharedDomain)); + if ((Status=appDomain.Request(g_sos, adsData.sharedDomain))!=S_OK) + { + ExtOut("Unable to get shared domain info\n"); + return Status; + } + DomainInfo(&appDomain); + + ArrayHolder<CLRDATA_ADDRESS> pArray = new NOTHROW CLRDATA_ADDRESS[adsData.DomainCount]; + if (pArray==NULL) + { + ReportOOM(); + return Status; + } + + if ((Status=g_sos->GetAppDomainList(adsData.DomainCount, pArray, NULL))!=S_OK) + { + ExtOut("Unable to get array of AppDomains\n"); + return Status; + } + + for (int n=0;n<adsData.DomainCount;n++) + { + if (IsInterrupt()) + break; + + if ((Status=appDomain.Request(g_sos, pArray[n])) != S_OK) + { + ExtOut("Failed to get appdomain %p, error %lx\n", SOS_PTR(pArray[n]), Status); + return Status; + } + + ExtOut("--------------------------------------\n"); + DMLOut("Domain %d:%s %s\n", appDomain.dwId, (appDomain.dwId >= 10) ? "" : " ", DMLDomain(pArray[n])); + DomainInfo(&appDomain); + } + + return Status; +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to dump the contents of a Assembly * +* for a given address * +* * +\**********************************************************************/ +DECLARE_API(DumpAssembly) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + DWORD_PTR p_AssemblyAddr = 0; + BOOL dml = FALSE; + + CMDOption option[] = + { // name, vptr, type, hasValue +#ifndef FEATURE_PAL + {"/d", &dml, COBOOL, FALSE}, +#endif + }; + CMDValue arg[] = + { // vptr, type + {&p_AssemblyAddr, COHEX}, + }; + size_t nArg; + + if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg)) + { + return Status; + } + + EnableDMLHolder dmlHolder(dml); + + if (p_AssemblyAddr == 0) + { + ExtOut("Invalid Assembly %s\n", args); + return Status; + } + + DacpAssemblyData Assembly; + if ((Status=Assembly.Request(g_sos, TO_CDADDR(p_AssemblyAddr)))!=S_OK) + { + ExtOut("Fail to fill Assembly\n"); + return Status; + } + DMLOut("Parent Domain: %s\n", DMLDomain(Assembly.ParentDomain)); + if (g_sos->GetAssemblyName(TO_CDADDR(p_AssemblyAddr), mdNameLen, g_mdName, NULL)==S_OK) + ExtOut("Name: %S\n", g_mdName); + else + ExtOut("Name: Unknown\n"); + + AssemblyInfo(&Assembly); + return Status; +} + + +String GetHostingCapabilities(DWORD hostConfig) +{ + String result; + + bool bAnythingPrinted = false; + +#define CHK_AND_PRINT(hType,hStr) \ + if (hostConfig & (hType)) { \ + if (bAnythingPrinted) result += ", "; \ + result += hStr; \ + bAnythingPrinted = true; \ + } + + CHK_AND_PRINT(CLRMEMORYHOSTED, "Memory"); + CHK_AND_PRINT(CLRTASKHOSTED, "Task"); + CHK_AND_PRINT(CLRSYNCHOSTED, "Sync"); + CHK_AND_PRINT(CLRTHREADPOOLHOSTED, "Threadpool"); + CHK_AND_PRINT(CLRIOCOMPLETIONHOSTED, "IOCompletion"); + CHK_AND_PRINT(CLRASSEMBLYHOSTED, "Assembly"); + CHK_AND_PRINT(CLRGCHOSTED, "GC"); + CHK_AND_PRINT(CLRSECURITYHOSTED, "Security"); + +#undef CHK_AND_PRINT + + return result; +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to dump the managed threads * +* * +\**********************************************************************/ +HRESULT PrintThreadsFromThreadStore(BOOL bMiniDump, BOOL bPrintLiveThreadsOnly) +{ + HRESULT Status; + + DacpThreadStoreData ThreadStore; + if ((Status = ThreadStore.Request(g_sos)) != S_OK) + { + Print("Failed to request ThreadStore\n"); + return Status; + } + + TableOutput table(2, 17); + + table.WriteRow("ThreadCount:", Decimal(ThreadStore.threadCount)); + table.WriteRow("UnstartedThread:", Decimal(ThreadStore.unstartedThreadCount)); + table.WriteRow("BackgroundThread:", Decimal(ThreadStore.backgroundThreadCount)); + table.WriteRow("PendingThread:", Decimal(ThreadStore.pendingThreadCount)); + table.WriteRow("DeadThread:", Decimal(ThreadStore.deadThreadCount)); + + if (ThreadStore.fHostConfig & ~CLRHOSTED) + { + String hosting = "yes"; + + hosting += " ("; + hosting += GetHostingCapabilities(ThreadStore.fHostConfig); + hosting += ")"; + + table.WriteRow("Hosted Runtime:", hosting); + } + else + { + table.WriteRow("Hosted Runtime:", "no"); + } + + const bool hosted = (ThreadStore.fHostConfig & CLRTASKHOSTED) != 0; + table.ReInit(hosted ? 12 : 11, POINTERSIZE_HEX); + table.SetWidths(10, 4, 4, 4, _max(9, POINTERSIZE_HEX), + 8, 11, 1+POINTERSIZE_HEX*2, POINTERSIZE_HEX, + 5, 3, POINTERSIZE_HEX); + + table.SetColAlignment(0, AlignRight); + table.SetColAlignment(1, AlignRight); + table.SetColAlignment(2, AlignRight); + table.SetColAlignment(4, AlignRight); + + table.WriteColumn(8, "Lock"); + table.WriteRow("", "ID", "OSID", "ThreadOBJ", "State", "GC Mode", "GC Alloc Context", "Domain", "Count", "Apt"); + + if (hosted) + table.WriteColumn("Fiber"); + + table.WriteColumn("Exception"); + + DacpThreadData Thread; + CLRDATA_ADDRESS CurThread = ThreadStore.firstThread; + while (CurThread) + { + if (IsInterrupt()) + break; + + if ((Status = Thread.Request(g_sos, CurThread)) != S_OK) + { + PrintLn("Failed to request Thread at ", Pointer(CurThread)); + return Status; + } + + BOOL bSwitchedOutFiber = Thread.osThreadId == SWITCHED_OUT_FIBER_OSID; + if (!IsKernelDebugger()) + { + ULONG id = 0; + + if (bSwitchedOutFiber) + { + table.WriteColumn(0, "<<<< "); + } + else if (g_ExtSystem->GetThreadIdBySystemId(Thread.osThreadId, &id) == S_OK) + { + table.WriteColumn(0, Decimal(id)); + } + else if (bPrintLiveThreadsOnly) + { + CurThread = Thread.nextThread; + continue; + } + else + { + table.WriteColumn(0, "XXXX "); + } + } + + table.WriteColumn(1, Decimal(Thread.corThreadId)); + table.WriteColumn(2, ThreadID(bSwitchedOutFiber ? 0 : Thread.osThreadId)); + table.WriteColumn(3, Pointer(CurThread)); + table.WriteColumn(4, ThreadState(Thread.state)); + table.WriteColumn(5, Thread.preemptiveGCDisabled == 1 ? "Cooperative" : "Preemptive"); + table.WriteColumnFormat(6, "%p:%p", Thread.allocContextPtr, Thread.allocContextLimit); + + if (Thread.domain) + { + table.WriteColumn(7, AppDomainPtr(Thread.domain)); + } + else + { + CLRDATA_ADDRESS domain = 0; + if (FAILED(g_sos->GetDomainFromContext(Thread.context, &domain))) + table.WriteColumn(7, "<error>"); + else + table.WriteColumn(7, AppDomainPtr(domain)); + } + + table.WriteColumn(8, Decimal(Thread.lockCount)); + + // Apartment state +#ifndef FEATURE_PAL + DWORD_PTR OleTlsDataAddr; + if (!bSwitchedOutFiber + && SafeReadMemory(Thread.teb + offsetof(TEB, ReservedForOle), + &OleTlsDataAddr, + sizeof(OleTlsDataAddr), NULL) && OleTlsDataAddr != 0) + { + DWORD AptState; + if (SafeReadMemory(OleTlsDataAddr+offsetof(SOleTlsData,dwFlags), + &AptState, + sizeof(AptState), NULL)) + { + if (AptState & OLETLS_APARTMENTTHREADED) + table.WriteColumn(9, "STA"); + else if (AptState & OLETLS_MULTITHREADED) + table.WriteColumn(9, "MTA"); + else if (AptState & OLETLS_INNEUTRALAPT) + table.WriteColumn(9, "NTA"); + else + table.WriteColumn(9, "Ukn"); + } + else + { + table.WriteColumn(9, "Ukn"); + } + } + else +#endif // FEATURE_PAL + table.WriteColumn(9, "Ukn"); + + if (hosted) + table.WriteColumn(10, Thread.fiberData); + + WString lastCol; + if (CurThread == ThreadStore.finalizerThread) + lastCol += W("(Finalizer) "); + if (CurThread == ThreadStore.gcThread) + lastCol += W("(GC) "); + + const int TS_TPWorkerThread = 0x01000000; // is this a threadpool worker thread? + const int TS_CompletionPortThread = 0x08000000; // is this is a completion port thread? + + if (Thread.state & TS_TPWorkerThread) + lastCol += W("(Threadpool Worker) "); + else if (Thread.state & TS_CompletionPortThread) + lastCol += W("(Threadpool Completion Port) "); + + + TADDR taLTOH; + if (Thread.lastThrownObjectHandle && SafeReadMemory(TO_TADDR(Thread.lastThrownObjectHandle), + &taLTOH, sizeof(taLTOH), NULL) && taLTOH) + { + TADDR taMT; + if (SafeReadMemory(taLTOH, &taMT, sizeof(taMT), NULL)) + { + if (NameForMT_s(taMT, g_mdName, mdNameLen)) + lastCol += WString(g_mdName) + W(" ") + ExceptionPtr(taLTOH); + else + lastCol += WString(W("<Invalid Object> (")) + Pointer(taLTOH) + W(")"); + + // Print something if there are nested exceptions on the thread + if (Thread.firstNestedException) + lastCol += W(" (nested exceptions)"); + } + } + + table.WriteColumn(lastCol); + CurThread = Thread.nextThread; + } + + return Status; +} + +#ifndef FEATURE_PAL +HRESULT PrintSpecialThreads() +{ + Print("\n"); + + DWORD dwCLRTLSDataIndex = 0; + HRESULT Status = g_sos->GetTLSIndex(&dwCLRTLSDataIndex); + + if (!SUCCEEDED (Status)) + { + Print("Failed to retrieve Tls Data index\n"); + return Status; + } + + + ULONG ulOriginalThreadID = 0; + Status = g_ExtSystem->GetCurrentThreadId (&ulOriginalThreadID); + if (!SUCCEEDED (Status)) + { + Print("Failed to require current Thread ID\n"); + return Status; + } + + ULONG ulTotalThreads = 0; + Status = g_ExtSystem->GetNumberThreads (&ulTotalThreads); + if (!SUCCEEDED (Status)) + { + Print("Failed to require total thread number\n"); + return Status; + } + + TableOutput table(3, 4, AlignRight, 5); + table.WriteRow("", "OSID", "Special thread type"); + + for (ULONG ulThread = 0; ulThread < ulTotalThreads; ulThread++) + { + ULONG Id = 0; + ULONG SysId = 0; + HRESULT threadStatus = g_ExtSystem->GetThreadIdsByIndex(ulThread, 1, &Id, &SysId); + if (!SUCCEEDED (threadStatus)) + { + PrintLn("Failed to get thread ID for thread ", Decimal(ulThread)); + continue; + } + + threadStatus = g_ExtSystem->SetCurrentThreadId(Id); + if (!SUCCEEDED (threadStatus)) + { + PrintLn("Failed to switch to thread ", ThreadID(SysId)); + continue; + } + + CLRDATA_ADDRESS cdaTeb = 0; + threadStatus = g_ExtSystem->GetCurrentThreadTeb(&cdaTeb); + if (!SUCCEEDED (threadStatus)) + { + PrintLn("Failed to get Teb for Thread ", ThreadID(SysId)); + continue; + } + + TADDR CLRTLSDataAddr = 0; + +#ifdef FEATURE_IMPLICIT_TLS + TADDR tlsArrayAddr = NULL; + if (!SafeReadMemory (TO_TADDR(cdaTeb) + WINNT_OFFSETOF__TEB__ThreadLocalStoragePointer , &tlsArrayAddr, sizeof (void**), NULL)) + { + PrintLn("Failed to get Tls expansion slots for thread ", ThreadID(SysId)); + continue; + } + + TADDR moduleTlsDataAddr = 0; + + if (!SafeReadMemory (tlsArrayAddr + sizeof (void*) * dwCLRTLSDataIndex, &moduleTlsDataAddr, sizeof (void**), NULL)) + { + PrintLn("Failed to get Tls expansion slots for thread ", ThreadID(SysId)); + continue; + } + + CLRTLSDataAddr = moduleTlsDataAddr + OFFSETOF__TLS__tls_EETlsData; +#else + if (dwCLRTLSDataIndex < TLS_MINIMUM_AVAILABLE) + { + CLRTLSDataAddr = TO_TADDR(cdaTeb) + offsetof(TEB, TlsSlots) + sizeof (void*) * dwCLRTLSDataIndex; + } + else + { + //if TLS index is bigger than TLS_MINIMUM_AVAILABLE, the TLS slot lives in ExpansionSlots + TADDR TebExpsionAddr = NULL; + if (!SafeReadMemory (TO_TADDR(cdaTeb) + offsetof(TEB, TlsExpansionSlots) , &TebExpsionAddr, sizeof (void**), NULL)) + { + PrintLn("Failed to get Tls expansion slots for thread ", ThreadID(SysId)); + continue; + } + + if (TebExpsionAddr == NULL) + { + continue; + } + + CLRTLSDataAddr = TebExpsionAddr + sizeof (void*) * (dwCLRTLSDataIndex - TLS_MINIMUM_AVAILABLE); + } +#endif // FEATURE_IMPLICIT_TLS + + TADDR CLRTLSData = NULL; + if (!SafeReadMemory (CLRTLSDataAddr, &CLRTLSData, sizeof (TADDR), NULL)) + { + PrintLn("Failed to get CLR Tls data for thread ", ThreadID(SysId)); + continue; + } + + if (CLRTLSData == NULL) + { + continue; + } + + size_t ThreadType = 0; + if (!SafeReadMemory (CLRTLSData + sizeof (TADDR) * TlsIdx_ThreadType, &ThreadType, sizeof (size_t), NULL)) + { + PrintLn("Failed to get thread type info not found for thread ", ThreadID(SysId)); + continue; + } + + if (ThreadType == 0) + { + continue; + } + + table.WriteColumn(0, Decimal(Id)); + table.WriteColumn(1, ThreadID(SysId)); + + String type; + if (ThreadType & ThreadType_GC) + { + type += "GC "; + } + if (ThreadType & ThreadType_Timer) + { + type += "Timer "; + } + if (ThreadType & ThreadType_Gate) + { + type += "Gate "; + } + if (ThreadType & ThreadType_DbgHelper) + { + type += "DbgHelper "; + } + if (ThreadType & ThreadType_Shutdown) + { + type += "Shutdown "; + } + if (ThreadType & ThreadType_DynamicSuspendEE) + { + type += "SuspendEE "; + } + if (ThreadType & ThreadType_Finalizer) + { + type += "Finalizer "; + } + if (ThreadType & ThreadType_ADUnloadHelper) + { + type += "ADUnloadHelper "; + } + if (ThreadType & ThreadType_ShutdownHelper) + { + type += "ShutdownHelper "; + } + if (ThreadType & ThreadType_Threadpool_IOCompletion) + { + type += "IOCompletion "; + } + if (ThreadType & ThreadType_Threadpool_Worker) + { + type += "ThreadpoolWorker "; + } + if (ThreadType & ThreadType_Wait) + { + type += "Wait "; + } + if (ThreadType & ThreadType_ProfAPI_Attach) + { + type += "ProfilingAPIAttach "; + } + if (ThreadType & ThreadType_ProfAPI_Detach) + { + type += "ProfilingAPIDetach "; + } + + table.WriteColumn(2, type); + } + + Status = g_ExtSystem->SetCurrentThreadId (ulOriginalThreadID); + if (!SUCCEEDED (Status)) + { + ExtOut("Failed to switch to original thread\n"); + return Status; + } + + return Status; +} +#endif //FEATURE_PAL + +struct ThreadStateTable +{ + unsigned int State; + const char * Name; +}; +static const struct ThreadStateTable ThreadStates[] = +{ + {0x1, "Thread Abort Requested"}, + {0x2, "GC Suspend Pending"}, + {0x4, "User Suspend Pending"}, + {0x8, "Debug Suspend Pending"}, + {0x10, "GC On Transitions"}, + {0x20, "Legal to Join"}, + {0x40, "Yield Requested"}, + {0x80, "Hijacked by the GC"}, + {0x100, "Blocking GC for Stack Overflow"}, + {0x200, "Background"}, + {0x400, "Unstarted"}, + {0x800, "Dead"}, + {0x1000, "CLR Owns"}, + {0x2000, "CoInitialized"}, + {0x4000, "In Single Threaded Apartment"}, + {0x8000, "In Multi Threaded Apartment"}, + {0x10000, "Reported Dead"}, + {0x20000, "Fully initialized"}, + {0x40000, "Task Reset"}, + {0x80000, "Sync Suspended"}, + {0x100000, "Debug Will Sync"}, + {0x200000, "Stack Crawl Needed"}, + {0x400000, "Suspend Unstarted"}, + {0x800000, "Aborted"}, + {0x1000000, "Thread Pool Worker Thread"}, + {0x2000000, "Interruptible"}, + {0x4000000, "Interrupted"}, + {0x8000000, "Completion Port Thread"}, + {0x10000000, "Abort Initiated"}, + {0x20000000, "Finalized"}, + {0x40000000, "Failed to Start"}, + {0x80000000, "Detached"}, +}; + +DECLARE_API(ThreadState) +{ + INIT_API_NODAC(); + + size_t state = GetExpression(args); + int count = 0; + + if (state) + { + + for (unsigned int i = 0; i < _countof(ThreadStates); ++i) + if (state & ThreadStates[i].State) + { + ExtOut(" %s\n", ThreadStates[i].Name); + count++; + } + } + + // If we did not find any thread states, print out a message to let the user + // know that the function is working correctly. + if (count == 0) + ExtOut(" No thread states for '%s'\n", args); + + return Status; +} + +DECLARE_API(Threads) +{ + INIT_API(); + + BOOL bPrintSpecialThreads = FALSE; + BOOL bPrintLiveThreadsOnly = FALSE; + BOOL dml = FALSE; + + CMDOption option[] = + { // name, vptr, type, hasValue + {"-special", &bPrintSpecialThreads, COBOOL, FALSE}, + {"-live", &bPrintLiveThreadsOnly, COBOOL, FALSE}, +#ifndef FEATURE_PAL + {"/d", &dml, COBOOL, FALSE}, +#endif + }; + if (!GetCMDOption(args, option, _countof(option), NULL, 0, NULL)) + { + return Status; + } + + // We need to support minidumps for this command. + BOOL bMiniDump = IsMiniDumpFile(); + + if (bMiniDump && bPrintSpecialThreads) + { + Print("Special thread information is not available in mini dumps.\n"); + } + + EnableDMLHolder dmlHolder(dml); + + try + { + Status = PrintThreadsFromThreadStore(bMiniDump, bPrintLiveThreadsOnly); + if (!bMiniDump && bPrintSpecialThreads) + { +#ifdef FEATURE_PAL + Print("\n-special not supported.\n"); +#else //FEATURE_PAL + HRESULT Status2 = PrintSpecialThreads(); + if (!SUCCEEDED(Status2)) + Status = Status2; +#endif //FEATURE_PAL + } + } + catch (sos::Exception &e) + { + ExtOut("%s\n", e.what()); + } + + return Status; +} + +#ifndef FEATURE_PAL +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to dump the Watson Buckets. * +* * +\**********************************************************************/ +DECLARE_API(WatsonBuckets) +{ + INIT_API(); + + // We don't need to support minidumps for this command. + if (IsMiniDumpFile()) + { + ExtOut("Not supported on mini dumps.\n"); + } + + // Get the current managed thread. + CLRDATA_ADDRESS threadAddr = GetCurrentManagedThread(); + DacpThreadData Thread; + + if ((threadAddr == NULL) || ((Status = Thread.Request(g_sos, threadAddr)) != S_OK)) + { + ExtOut("The current thread is unmanaged\n"); + return Status; + } + + // Get the definition of GenericModeBlock. +#include <msodw.h> + GenericModeBlock gmb; + + if ((Status = g_sos->GetClrWatsonBuckets(threadAddr, &gmb)) != S_OK) + { + ExtOut("Can't get Watson Buckets\n"); + return Status; + } + + ExtOut("Watson Bucket parameters:\n"); + ExtOut("b1: %S\n", gmb.wzP1); + ExtOut("b2: %S\n", gmb.wzP2); + ExtOut("b3: %S\n", gmb.wzP3); + ExtOut("b4: %S\n", gmb.wzP4); + ExtOut("b5: %S\n", gmb.wzP5); + ExtOut("b6: %S\n", gmb.wzP6); + ExtOut("b7: %S\n", gmb.wzP7); + ExtOut("b8: %S\n", gmb.wzP8); + ExtOut("b9: %S\n", gmb.wzP9); + + return Status; +} // WatsonBuckets() +#endif // FEATURE_PAL + +struct PendingBreakpoint +{ + WCHAR szModuleName[MAX_LONGPATH]; + WCHAR szFunctionName[mdNameLen]; + WCHAR szFilename[MAX_LONGPATH]; + DWORD lineNumber; + TADDR pModule; + DWORD ilOffset; + mdMethodDef methodToken; + void SetModule(TADDR module) + { + pModule = module; + } + + bool ModuleMatches(TADDR compare) + { + return (compare == pModule); + } + + PendingBreakpoint *pNext; + PendingBreakpoint() : lineNumber(0), ilOffset(0), methodToken(0), pNext(NULL) + { + szModuleName[0] = L'\0'; + szFunctionName[0] = L'\0'; + szFilename[0] = L'\0'; + } +}; + +void IssueDebuggerBPCommand ( CLRDATA_ADDRESS addr ) +{ + const int MaxBPsCached = 1024; + static CLRDATA_ADDRESS alreadyPlacedBPs[MaxBPsCached]; + static int curLimit = 0; + + // on ARM the debugger requires breakpoint addresses to be sanitized + if (IsDbgTargetArm()) +#ifndef FEATURE_PAL + addr &= ~THUMB_CODE; +#else + addr |= THUMB_CODE; // lldb expects thumb code bit set +#endif + + // if we overflowed our cache consider all new BPs unique... + BOOL bUnique = curLimit >= MaxBPsCached; + if (!bUnique) + { + bUnique = TRUE; + for (int i = 0; i < curLimit; ++i) + { + if (alreadyPlacedBPs[i] == addr) + { + bUnique = FALSE; + break; + } + } + } + if (bUnique) + { + char buffer[64]; // sufficient for "bp <pointersize>" + static WCHAR wszNameBuffer[1024]; // should be large enough + + // get the MethodDesc name + CLRDATA_ADDRESS pMD; + if (g_sos->GetMethodDescPtrFromIP(addr, &pMD) != S_OK + || g_sos->GetMethodDescName(pMD, 1024, wszNameBuffer, NULL) != S_OK) + { + wcscpy_s(wszNameBuffer, _countof(wszNameBuffer), W("UNKNOWN")); + } + +#ifndef FEATURE_PAL + sprintf_s(buffer, _countof(buffer), "bp %p", (void*) (size_t) addr); +#else + sprintf_s(buffer, _countof(buffer), "breakpoint set --address 0x%p", (void*) (size_t) addr); +#endif + ExtOut("Setting breakpoint: %s [%S]\n", buffer, wszNameBuffer); + g_ExtControl->Execute(DEBUG_EXECUTE_NOT_LOGGED, buffer, 0); + + if (curLimit < MaxBPsCached) + { + alreadyPlacedBPs[curLimit++] = addr; + } + } +} + +class Breakpoints +{ + PendingBreakpoint* m_breakpoints; +public: + Breakpoints() + { + m_breakpoints = NULL; + } + ~Breakpoints() + { + PendingBreakpoint *pCur = m_breakpoints; + while(pCur) + { + PendingBreakpoint *pNext = pCur->pNext; + delete pCur; + pCur = pNext; + } + m_breakpoints = NULL; + } + + void Add(__in_z LPWSTR szModule, __in_z LPWSTR szName, TADDR mod, DWORD ilOffset) + { + if (!IsIn(szModule, szName, mod)) + { + PendingBreakpoint *pNew = new PendingBreakpoint(); + wcscpy_s(pNew->szModuleName, MAX_LONGPATH, szModule); + wcscpy_s(pNew->szFunctionName, mdNameLen, szName); + pNew->SetModule(mod); + pNew->ilOffset = ilOffset; + pNew->pNext = m_breakpoints; + m_breakpoints = pNew; + } + } + + void Add(__in_z LPWSTR szModule, __in_z LPWSTR szName, mdMethodDef methodToken, TADDR mod, DWORD ilOffset) + { + if (!IsIn(methodToken, mod, ilOffset)) + { + PendingBreakpoint *pNew = new PendingBreakpoint(); + wcscpy_s(pNew->szModuleName, MAX_LONGPATH, szModule); + wcscpy_s(pNew->szFunctionName, mdNameLen, szName); + pNew->methodToken = methodToken; + pNew->SetModule(mod); + pNew->ilOffset = ilOffset; + pNew->pNext = m_breakpoints; + m_breakpoints = pNew; + } + } + + void Add(__in_z LPWSTR szFilename, DWORD lineNumber, TADDR mod) + { + if (!IsIn(szFilename, lineNumber, mod)) + { + PendingBreakpoint *pNew = new PendingBreakpoint(); + wcscpy_s(pNew->szFilename, MAX_LONGPATH, szFilename); + pNew->lineNumber = lineNumber; + pNew->SetModule(mod); + pNew->pNext = m_breakpoints; + m_breakpoints = pNew; + } + } + + void Add(__in_z LPWSTR szFilename, DWORD lineNumber, mdMethodDef methodToken, TADDR mod, DWORD ilOffset) + { + if (!IsIn(methodToken, mod, ilOffset)) + { + PendingBreakpoint *pNew = new PendingBreakpoint(); + wcscpy_s(pNew->szFilename, MAX_LONGPATH, szFilename); + pNew->lineNumber = lineNumber; + pNew->methodToken = methodToken; + pNew->SetModule(mod); + pNew->ilOffset = ilOffset; + pNew->pNext = m_breakpoints; + m_breakpoints = pNew; + } + } + + //returns true if updates are still needed for this module, FALSE if all BPs are now bound + BOOL Update(TADDR mod, BOOL isNewModule) + { + BOOL bNeedUpdates = FALSE; + PendingBreakpoint *pCur = NULL; + + if(isNewModule) + { + SymbolReader symbolReader; + SymbolReader* pSymReader = &symbolReader; + if(LoadSymbolsForModule(mod, &symbolReader) != S_OK) + pSymReader = NULL; + + // Get tokens for any modules that match. If there was a change, + // update notifications. + pCur = m_breakpoints; + while(pCur) + { + PendingBreakpoint *pNext = pCur->pNext; + ResolvePendingNonModuleBoundBreakpoint(mod, pCur, pSymReader); + pCur = pNext; + } + } + + pCur = m_breakpoints; + while(pCur) + { + PendingBreakpoint *pNext = pCur->pNext; + if (ResolvePendingBreakpoint(mod, pCur)) + { + bNeedUpdates = TRUE; + } + pCur = pNext; + } + return bNeedUpdates; + } + + void RemovePendingForModule(TADDR mod) + { + PendingBreakpoint *pCur = m_breakpoints; + while(pCur) + { + PendingBreakpoint *pNext = pCur->pNext; + if (pCur->ModuleMatches(mod)) + { + // Delete the current node, and keep going + Delete(pCur); + } + + pCur = pNext; + } + } + + void ListBreakpoints() + { + PendingBreakpoint *pCur = m_breakpoints; + size_t iBreakpointIndex = 1; + ExtOut(SOSPrefix "bpmd pending breakpoint list\n Breakpoint index - Location, ModuleID, Method Token\n"); + while(pCur) + { + //windbg likes to format %p as always being 64 bits + ULONG64 modulePtr = (ULONG64)pCur->pModule; + + if(pCur->szModuleName[0] != L'\0') + ExtOut("%d - %ws!%ws+%d, 0x%p, 0x%08x\n", iBreakpointIndex, pCur->szModuleName, pCur->szFunctionName, pCur->ilOffset, modulePtr, pCur->methodToken); + else + ExtOut("%d - %ws:%d, 0x%p, 0x%08x\n", iBreakpointIndex, pCur->szFilename, pCur->lineNumber, modulePtr, pCur->methodToken); + iBreakpointIndex++; + pCur = pCur->pNext; + } + } + +#ifndef FEATURE_PAL + void SaveBreakpoints(FILE* pFile) + { + PendingBreakpoint *pCur = m_breakpoints; + while(pCur) + { + if(pCur->szModuleName[0] != L'\0') + fprintf_s(pFile, "!bpmd %ws %ws %d\n", pCur->szModuleName, pCur->szFunctionName, pCur->ilOffset); + else + fprintf_s(pFile, "!bpmd %ws:%d\n", pCur->szFilename, pCur->lineNumber); + pCur = pCur->pNext; + } + } +#endif + + void CleanupNotifications() + { +#ifdef FEATURE_PAL + if (m_breakpoints == NULL) + { + g_ExtServices->ClearExceptionCallback(); + } +#endif + } + + void ClearBreakpoint(size_t breakPointToClear) + { + PendingBreakpoint *pCur = m_breakpoints; + size_t iBreakpointIndex = 1; + while(pCur) + { + if (breakPointToClear == iBreakpointIndex) + { + ExtOut("%d - %ws, %ws, %p\n", iBreakpointIndex, pCur->szModuleName, pCur->szFunctionName, pCur->pModule); + ExtOut("Cleared\n"); + Delete(pCur); + break; + } + iBreakpointIndex++; + pCur = pCur->pNext; + } + + if (pCur == NULL) + { + ExtOut("Invalid pending breakpoint index.\n"); + } + CleanupNotifications(); + } + + void ClearAllBreakpoints() + { + size_t iBreakpointIndex = 1; + for (PendingBreakpoint *pCur = m_breakpoints; pCur != NULL; ) + { + PendingBreakpoint* pNext = pCur->pNext; + Delete(pCur); + iBreakpointIndex++; + pCur = pNext; + } + CleanupNotifications(); + + ExtOut("All pending breakpoints cleared.\n"); + } + + HRESULT LoadSymbolsForModule(TADDR mod, SymbolReader* pSymbolReader) + { + HRESULT Status = S_OK; + ToRelease<IXCLRDataModule> pModule; + IfFailRet(g_sos->GetModule(mod, &pModule)); + + ToRelease<IMetaDataImport> pMDImport = NULL; + IfFailRet(pModule->QueryInterface(IID_IMetaDataImport, (LPVOID *) &pMDImport)); + + IfFailRet(pSymbolReader->LoadSymbols(pMDImport, pModule)); + + return S_OK; + } + + HRESULT ResolvePendingNonModuleBoundBreakpoint(__in_z WCHAR* pFilename, DWORD lineNumber, TADDR mod, SymbolReader* pSymbolReader) + { + HRESULT Status = S_OK; + if(pSymbolReader == NULL) + return S_FALSE; // no symbols, can't bind here + + mdMethodDef methodDef; + ULONG32 ilOffset; + if(FAILED(Status = pSymbolReader->ResolveSequencePoint(pFilename, lineNumber, mod, &methodDef, &ilOffset))) + { + return S_FALSE; // not binding in a module is typical + } + + Add(pFilename, lineNumber, methodDef, mod, ilOffset); + return Status; + } + + HRESULT ResolvePendingNonModuleBoundBreakpoint(__in_z WCHAR* pModuleName, __in_z WCHAR* pMethodName, TADDR mod, DWORD ilOffset) + { + HRESULT Status = S_OK; + char szName[mdNameLen]; + int numModule; + + ToRelease<IXCLRDataModule> module; + IfFailRet(g_sos->GetModule(mod, &module)); + + WideCharToMultiByte(CP_ACP, 0, pModuleName, (int)(_wcslen(pModuleName) + 1), szName, mdNameLen, NULL, NULL); + + ArrayHolder<DWORD_PTR> moduleList = ModuleFromName(szName, &numModule); + if (moduleList == NULL) + { + ExtOut("Failed to request module list.\n"); + return E_FAIL; + } + + for (int i = 0; i < numModule; i++) + { + // If any one entry in moduleList matches, then the current PendingBreakpoint + // is the right one. + if(moduleList[i] != TO_TADDR(mod)) + continue; + + CLRDATA_ENUM h; + if (module->StartEnumMethodDefinitionsByName(pMethodName, 0, &h) == S_OK) + { + IXCLRDataMethodDefinition *pMeth = NULL; + while (module->EnumMethodDefinitionByName(&h, &pMeth) == S_OK) + { + mdMethodDef methodToken; + ToRelease<IXCLRDataModule> pUnusedModule; + IfFailRet(pMeth->GetTokenAndScope(&methodToken, &pUnusedModule)); + + Add(pModuleName, pMethodName, methodToken, mod, ilOffset); + pMeth->Release(); + } + module->EndEnumMethodDefinitionsByName(h); + } + } + return S_OK; + } + + // Return TRUE if there might be more instances that will be JITTED later + static BOOL ResolveMethodInstances(IXCLRDataMethodDefinition *pMeth, DWORD ilOffset) + { + BOOL bFoundCode = FALSE; + BOOL bNeedDefer = FALSE; + CLRDATA_ENUM h1; + + if (pMeth->StartEnumInstances (NULL, &h1) == S_OK) + { + IXCLRDataMethodInstance *inst = NULL; + while (pMeth->EnumInstance (&h1, &inst) == S_OK) + { + BOOL foundByIlOffset = FALSE; + ULONG32 rangesNeeded = 0; + if(inst->GetAddressRangesByILOffset(ilOffset, 0, &rangesNeeded, NULL) == S_OK) + { + ArrayHolder<CLRDATA_ADDRESS_RANGE> ranges = new NOTHROW CLRDATA_ADDRESS_RANGE[rangesNeeded]; + if (ranges != NULL) + { + if (inst->GetAddressRangesByILOffset(ilOffset, rangesNeeded, NULL, ranges) == S_OK) + { + for (DWORD i = 0; i < rangesNeeded; i++) + { + IssueDebuggerBPCommand(ranges[i].startAddress); + bFoundCode = TRUE; + foundByIlOffset = TRUE; + } + } + } + } + + if (!foundByIlOffset && ilOffset == 0) + { + CLRDATA_ADDRESS addr = 0; + if (inst->GetRepresentativeEntryAddress(&addr) == S_OK) + { + IssueDebuggerBPCommand(addr); + bFoundCode = TRUE; + } + } + } + pMeth->EndEnumInstances (h1); + } + + // if this is a generic method we need to add a defered bp + BOOL bGeneric = FALSE; + pMeth->HasClassOrMethodInstantiation(&bGeneric); + + bNeedDefer = !bFoundCode || bGeneric; + // This is down here because we only need to call SetCodeNofiication once. + if (bNeedDefer) + { + if (pMeth->SetCodeNotification (CLRDATA_METHNOTIFY_GENERATED) != S_OK) + { + bNeedDefer = FALSE; + ExtOut("Failed to set code notification\n"); + } + } + return bNeedDefer; + } + +private: + BOOL IsIn(__in_z LPWSTR szModule, __in_z LPWSTR szName, TADDR mod) + { + PendingBreakpoint *pCur = m_breakpoints; + while(pCur) + { + if (pCur->ModuleMatches(mod) && + _wcsicmp(pCur->szModuleName, szModule) == 0 && + _wcscmp(pCur->szFunctionName, szName) == 0) + { + return TRUE; + } + pCur = pCur->pNext; + } + return FALSE; + } + + BOOL IsIn(__in_z LPWSTR szFilename, DWORD lineNumber, TADDR mod) + { + PendingBreakpoint *pCur = m_breakpoints; + while(pCur) + { + if (pCur->ModuleMatches(mod) && + _wcsicmp(pCur->szFilename, szFilename) == 0 && + pCur->lineNumber == lineNumber) + { + return TRUE; + } + pCur = pCur->pNext; + } + return FALSE; + } + + BOOL IsIn(mdMethodDef token, TADDR mod, DWORD ilOffset) + { + PendingBreakpoint *pCur = m_breakpoints; + while(pCur) + { + if (pCur->ModuleMatches(mod) && + pCur->methodToken == token && + pCur->ilOffset == ilOffset) + { + return TRUE; + } + pCur = pCur->pNext; + } + return FALSE; + } + + void Delete(PendingBreakpoint *pDelete) + { + PendingBreakpoint *pCur = m_breakpoints; + PendingBreakpoint *pPrev = NULL; + while(pCur) + { + if (pCur == pDelete) + { + if (pPrev == NULL) + { + m_breakpoints = pCur->pNext; + } + else + { + pPrev->pNext = pCur->pNext; + } + delete pCur; + return; + } + pPrev = pCur; + pCur = pCur->pNext; + } + } + + + + HRESULT ResolvePendingNonModuleBoundBreakpoint(TADDR mod, PendingBreakpoint *pCur, SymbolReader* pSymbolReader) + { + // This function only works with pending breakpoints that are not module bound. + if (pCur->pModule == NULL) + { + if(pCur->szModuleName[0] != L'\0') + { + return ResolvePendingNonModuleBoundBreakpoint(pCur->szModuleName, pCur->szFunctionName, mod, pCur->ilOffset); + } + else + { + return ResolvePendingNonModuleBoundBreakpoint(pCur->szFilename, pCur->lineNumber, mod, pSymbolReader); + } + } + else + { + return S_OK; + } + } + + // Returns TRUE if further instances may be jitted, FALSE if all instances are now resolved + BOOL ResolvePendingBreakpoint(TADDR addr, PendingBreakpoint *pCur) + { + // Only go forward if the module matches the current PendingBreakpoint + if (!pCur->ModuleMatches(addr)) + { + return FALSE; + } + + ToRelease<IXCLRDataModule> mod; + if (FAILED(g_sos->GetModule(addr, &mod))) + { + return FALSE; + } + + if(pCur->methodToken == 0) + { + return FALSE; + } + + ToRelease<IXCLRDataMethodDefinition> pMeth = NULL; + mod->GetMethodDefinitionByToken(pCur->methodToken, &pMeth); + + // We may not need the code notification. Maybe it was ngen'd and we + // already have the method? + // We can delete the current entry if ResolveMethodInstances() set all BPs + return ResolveMethodInstances(pMeth, pCur->ilOffset); + } +}; + +Breakpoints g_bpoints; + +// Controls whether optimizations are disabled on module load and whether NGEN can be used +BOOL g_fAllowJitOptimization = TRUE; + +// Controls whether a one-shot breakpoint should be inserted the next time +// execution is about to enter a catch clause +BOOL g_stopOnNextCatch = FALSE; + +// According to the latest debuggers these callbacks will not get called +// unless the user (or an extension, like SOS :-)) had previously enabled +// clrn with "sxe clrn". +class CNotification : public IXCLRDataExceptionNotification4 +{ + static int s_condemnedGen; + + int m_count; + int m_dbgStatus; +public: + CNotification() + : m_count(0) + , m_dbgStatus(DEBUG_STATUS_NO_CHANGE) + {} + + int GetDebugStatus() + { + return m_dbgStatus; + } + + STDMETHODIMP QueryInterface (REFIID iid, void **ppvObject) + { + if (ppvObject == NULL) + return E_INVALIDARG; + + if (IsEqualIID(iid, IID_IUnknown) + || IsEqualIID(iid, IID_IXCLRDataExceptionNotification) + || IsEqualIID(iid, IID_IXCLRDataExceptionNotification2) + || IsEqualIID(iid, IID_IXCLRDataExceptionNotification3) + || IsEqualIID(iid, IID_IXCLRDataExceptionNotification4)) + { + *ppvObject = static_cast<IXCLRDataExceptionNotification4*>(this); + AddRef(); + return S_OK; + } + else + return E_NOINTERFACE; + + } + + STDMETHODIMP_(ULONG) AddRef(void) { return ++m_count; } + STDMETHODIMP_(ULONG) Release(void) + { + m_count--; + if (m_count < 0) + { + m_count = 0; + } + return m_count; + } + + + /* + * New code was generated or discarded for a method.: + */ + STDMETHODIMP OnCodeGenerated(IXCLRDataMethodInstance* method) + { + // Some method has been generated, make a breakpoint and remove it. + ULONG32 len = mdNameLen; + LPWSTR szModuleName = (LPWSTR)alloca(mdNameLen * sizeof(WCHAR)); + if (method->GetName(0, mdNameLen, &len, g_mdName) == S_OK) + { + ToRelease<IXCLRDataModule> pMod; + HRESULT hr = method->GetTokenAndScope(NULL, &pMod); + if (SUCCEEDED(hr)) + { + len = mdNameLen; + if (pMod->GetName(mdNameLen, &len, szModuleName) == S_OK) + { + ExtOut("JITTED %S!%S\n", szModuleName, g_mdName); + + // Add breakpoint, perhaps delete pending breakpoint + DacpGetModuleAddress dgma; + if (SUCCEEDED(dgma.Request(pMod))) + { + g_bpoints.Update(TO_TADDR(dgma.ModulePtr), FALSE); + } + else + { + ExtOut("Failed to request module address.\n"); + } + } + } + } + + m_dbgStatus = DEBUG_STATUS_GO_HANDLED; + return S_OK; + } + + STDMETHODIMP OnCodeDiscarded(IXCLRDataMethodInstance* method) + { + return E_NOTIMPL; + } + + /* + * The process or task reached the desired execution state. + */ + STDMETHODIMP OnProcessExecution(ULONG32 state) { return E_NOTIMPL; } + STDMETHODIMP OnTaskExecution(IXCLRDataTask* task, + ULONG32 state) { return E_NOTIMPL; } + + /* + * The given module was loaded or unloaded. + */ + STDMETHODIMP OnModuleLoaded(IXCLRDataModule* mod) + { + DacpGetModuleAddress dgma; + if (SUCCEEDED(dgma.Request(mod))) + { + g_bpoints.Update(TO_TADDR(dgma.ModulePtr), TRUE); + } + + if(!g_fAllowJitOptimization) + { + HRESULT hr; + ToRelease<IXCLRDataModule2> mod2; + if(FAILED(mod->QueryInterface(__uuidof(IXCLRDataModule2), (void**) &mod2))) + { + ExtOut("SOS: warning, optimizations for this module could not be suppressed because this CLR version doesn't support the functionality\n"); + } + else if(FAILED(hr = mod2->SetJITCompilerFlags(CORDEBUG_JIT_DISABLE_OPTIMIZATION))) + { + if(hr == CORDBG_E_CANT_CHANGE_JIT_SETTING_FOR_ZAP_MODULE) + ExtOut("SOS: warning, optimizations for this module could not be surpressed because an optimized prejitted image was loaded\n"); + else + ExtOut("SOS: warning, optimizations for this module could not be surpressed hr=0x%x\n", hr); + } + } + + m_dbgStatus = DEBUG_STATUS_GO_HANDLED; + return S_OK; + } + + STDMETHODIMP OnModuleUnloaded(IXCLRDataModule* mod) + { + DacpGetModuleAddress dgma; + if (SUCCEEDED(dgma.Request(mod))) + { + g_bpoints.RemovePendingForModule(TO_TADDR(dgma.ModulePtr)); + } + + m_dbgStatus = DEBUG_STATUS_GO_HANDLED; + return S_OK; + } + + /* + * The given type was loaded or unloaded. + */ + STDMETHODIMP OnTypeLoaded(IXCLRDataTypeInstance* typeInst) + { return E_NOTIMPL; } + STDMETHODIMP OnTypeUnloaded(IXCLRDataTypeInstance* typeInst) + { return E_NOTIMPL; } + + STDMETHODIMP OnAppDomainLoaded(IXCLRDataAppDomain* domain) + { return E_NOTIMPL; } + STDMETHODIMP OnAppDomainUnloaded(IXCLRDataAppDomain* domain) + { return E_NOTIMPL; } + STDMETHODIMP OnException(IXCLRDataExceptionState* exception) + { return E_NOTIMPL; } + + STDMETHODIMP OnGcEvent(GcEvtArgs gcEvtArgs) +{ + // by default don't stop on these notifications... + m_dbgStatus = DEBUG_STATUS_GO_HANDLED; + + IXCLRDataProcess2* idp2 = NULL; + if (SUCCEEDED(g_clrData->QueryInterface(IID_IXCLRDataProcess2, (void**) &idp2))) + { + if (gcEvtArgs.typ == GC_MARK_END) + { + // erase notification request + GcEvtArgs gea = { GC_MARK_END, { 0 } }; + idp2->SetGcNotification(gea); + + s_condemnedGen = bitidx(gcEvtArgs.condemnedGeneration); + + ExtOut("CLR notification: GC - Performing a gen %d collection. Determined surviving objects...\n", s_condemnedGen); + + // GC_MARK_END notification means: give the user a chance to examine the debuggee + m_dbgStatus = DEBUG_STATUS_BREAK; + } + } + + return S_OK; + } + + /* + * Catch is about to be entered + */ + STDMETHODIMP ExceptionCatcherEnter(IXCLRDataMethodInstance* method, DWORD catcherNativeOffset) + { + if(g_stopOnNextCatch) + { + CLRDATA_ADDRESS startAddr; + if(method->GetRepresentativeEntryAddress(&startAddr) == S_OK) + { + CHAR buffer[100]; +#ifndef FEATURE_PAL + sprintf_s(buffer, _countof(buffer), "bp /1 %p", (void*) (size_t) (startAddr+catcherNativeOffset)); +#else + sprintf_s(buffer, _countof(buffer), "breakpoint set --one-shot --address 0x%p", (void*) (size_t) (startAddr+catcherNativeOffset)); +#endif + g_ExtControl->Execute(DEBUG_EXECUTE_NOT_LOGGED, buffer, 0); + } + g_stopOnNextCatch = FALSE; + } + + m_dbgStatus = DEBUG_STATUS_GO_HANDLED; + return S_OK; + } + + static int GetCondemnedGen() + { + return s_condemnedGen; + } + +}; + +int CNotification::s_condemnedGen = -1; + +BOOL CheckCLRNotificationEvent(DEBUG_LAST_EVENT_INFO_EXCEPTION* pdle) +{ + ISOSDacInterface4 *psos4 = NULL; + CLRDATA_ADDRESS arguments[3]; + HRESULT Status; + + if (SUCCEEDED(Status = g_sos->QueryInterface(__uuidof(ISOSDacInterface4), (void**) &psos4))) + { + int count = _countof(arguments); + int countNeeded = 0; + + Status = psos4->GetClrNotification(arguments, count, &countNeeded); + psos4->Release(); + + if (SUCCEEDED(Status)) + { + memset(&pdle->ExceptionRecord, 0, sizeof(pdle->ExceptionRecord)); + pdle->FirstChance = TRUE; + pdle->ExceptionRecord.ExceptionCode = CLRDATA_NOTIFY_EXCEPTION; + + _ASSERTE(count <= EXCEPTION_MAXIMUM_PARAMETERS); + for (int i = 0; i < count; i++) + { + pdle->ExceptionRecord.ExceptionInformation[i] = arguments[i]; + } + // The rest of the ExceptionRecord isn't used by TranslateExceptionRecordToNotification + return TRUE; + } + // No pending exception notification + return FALSE; + } + + // The new DAC based interface doesn't exists so ask the debugger for the last exception + // information. NOTE: this function doesn't work on xplat version when the coreclr symbols + // have been stripped. + + ULONG Type, ProcessId, ThreadId; + ULONG ExtraInformationUsed; + Status = g_ExtControl->GetLastEventInformation( + &Type, + &ProcessId, + &ThreadId, + pdle, + sizeof(DEBUG_LAST_EVENT_INFO_EXCEPTION), + &ExtraInformationUsed, + NULL, + 0, + NULL); + + if (Status != S_OK || Type != DEBUG_EVENT_EXCEPTION) + { + return FALSE; + } + + if (!pdle->FirstChance || pdle->ExceptionRecord.ExceptionCode != CLRDATA_NOTIFY_EXCEPTION) + { + return FALSE; + } + + return TRUE; +} + +HRESULT HandleCLRNotificationEvent() +{ + /* + * Did we get module load notification? If so, check if any in our pending list + * need to be registered for jit notification. + * + * Did we get a jit notification? If so, check if any can be removed and + * real breakpoints be set. + */ + DEBUG_LAST_EVENT_INFO_EXCEPTION dle; + CNotification Notification; + + if (!CheckCLRNotificationEvent(&dle)) + { +#ifndef FEATURE_PAL + ExtOut("Expecting first chance CLRN exception\n"); + return E_FAIL; +#else + g_ExtControl->Execute(DEBUG_EXECUTE_NOT_LOGGED, "process continue", 0); + return S_OK; +#endif + } + + // Notification only needs to live for the lifetime of the call below, so it's a non-static + // local. + HRESULT Status = g_clrData->TranslateExceptionRecordToNotification(&dle.ExceptionRecord, &Notification); + if (Status != S_OK) + { + ExtErr("Error processing exception notification\n"); + return Status; + } + else + { + switch (Notification.GetDebugStatus()) + { + case DEBUG_STATUS_GO: + case DEBUG_STATUS_GO_HANDLED: + case DEBUG_STATUS_GO_NOT_HANDLED: +#ifndef FEATURE_PAL + g_ExtControl->Execute(DEBUG_EXECUTE_NOT_LOGGED, "g", 0); +#else + g_ExtControl->Execute(DEBUG_EXECUTE_NOT_LOGGED, "process continue", 0); +#endif + break; + default: + break; + } + } + + return S_OK; +} + +#ifndef FEATURE_PAL + +DECLARE_API(HandleCLRN) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + return HandleCLRNotificationEvent(); +} + +#else // FEATURE_PAL + +HRESULT HandleExceptionNotification(ILLDBServices *client) +{ + INIT_API(); + return HandleCLRNotificationEvent(); +} + +#endif // FEATURE_PAL + +DECLARE_API(bpmd) +{ + INIT_API_NOEE(); + MINIDUMP_NOT_SUPPORTED(); + int i; + char buffer[1024]; + + if (IsDumpFile()) + { + ExtOut(SOSPrefix "bpmd is not supported on a dump file.\n"); + return Status; + } + + + // We keep a list of managed breakpoints the user wants to set, and display pending bps + // bpmd. If you call bpmd <module name> <method> we will set or update an existing bp. + // bpmd acts as a feeder of breakpoints to bp when the time is right. + // + + StringHolder DllName,TypeName; + int lineNumber = 0; + size_t Offset = 0; + + DWORD_PTR pMD = NULL; + BOOL fNoFutureModule = FALSE; + BOOL fList = FALSE; + size_t clearItem = 0; + BOOL fClearAll = FALSE; + CMDOption option[] = + { // name, vptr, type, hasValue + {"-md", &pMD, COHEX, TRUE}, + {"-nofuturemodule", &fNoFutureModule, COBOOL, FALSE}, + {"-list", &fList, COBOOL, FALSE}, + {"-clear", &clearItem, COSIZE_T, TRUE}, + {"-clearall", &fClearAll, COBOOL, FALSE}, + }; + CMDValue arg[] = + { // vptr, type + {&DllName.data, COSTRING}, + {&TypeName.data, COSTRING}, + {&Offset, COSIZE_T}, + }; + size_t nArg; + if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg)) + { + return Status; + } + + bool fBadParam = false; + bool fIsFilename = false; + int commandsParsed = 0; + + if (pMD != NULL) + { + if (nArg != 0) + { + fBadParam = true; + } + commandsParsed++; + } + if (fList) + { + commandsParsed++; + if (nArg != 0) + { + fBadParam = true; + } + } + if (fClearAll) + { + commandsParsed++; + if (nArg != 0) + { + fBadParam = true; + } + } + if (clearItem != 0) + { + commandsParsed++; + if (nArg != 0) + { + fBadParam = true; + } + } + if (1 <= nArg && nArg <= 3) + { + commandsParsed++; + // did we get dll and type name or file:line#? Search for a colon in the first arg + // to see if it is in fact a file:line# + CHAR* pColon = strchr(DllName.data, ':'); +#ifndef FEATURE_PAL + if (FAILED(g_ExtSymbols->GetModuleByModuleName(MAIN_CLR_MODULE_NAME_A, 0, NULL, NULL))) { +#else + if (FAILED(g_ExtSymbols->GetModuleByModuleName(MAIN_CLR_DLL_NAME_A, 0, NULL, NULL))) { +#endif + ExtOut("%s not loaded yet\n", MAIN_CLR_DLL_NAME_A); + return Status; + } + + if(NULL != pColon) + { + fIsFilename = true; + *pColon = '\0'; + pColon++; + if(1 != sscanf_s(pColon, "%d", &lineNumber)) + { + ExtOut("Unable to parse line number\n"); + fBadParam = true; + } + else if(lineNumber < 0) + { + ExtOut("Line number must be positive\n"); + fBadParam = true; + } + if(nArg != 1) fBadParam = 1; + } + } + + if (fBadParam || (commandsParsed != 1)) + { + ExtOut("Usage: " SOSPrefix "bpmd -md <MethodDesc pointer>\n"); + ExtOut("Usage: " SOSPrefix "bpmd [-nofuturemodule] <module name> <managed function name> [<il offset>]\n"); + ExtOut("Usage: " SOSPrefix "bpmd <filename>:<line number>\n"); + ExtOut("Usage: " SOSPrefix "bpmd -list\n"); + ExtOut("Usage: " SOSPrefix "bpmd -clear <pending breakpoint number>\n"); + ExtOut("Usage: " SOSPrefix "bpmd -clearall\n"); +#ifdef FEATURE_PAL + ExtOut("See \"soshelp bpmd\" for more details.\n"); +#else + ExtOut("See \"!help bpmd\" for more details.\n"); +#endif + return Status; + } + + if (fList) + { + g_bpoints.ListBreakpoints(); + return Status; + } + if (clearItem != 0) + { + g_bpoints.ClearBreakpoint(clearItem); + return Status; + } + if (fClearAll) + { + g_bpoints.ClearAllBreakpoints(); + return Status; + } + // Add a breakpoint + // Do we already have this breakpoint? + // Or, before setting it, is the module perhaps already loaded and code + // is available? If so, don't add to our pending list, just go ahead and + // set the real breakpoint. + + LPWSTR ModuleName = (LPWSTR)alloca(mdNameLen * sizeof(WCHAR)); + LPWSTR FunctionName = (LPWSTR)alloca(mdNameLen * sizeof(WCHAR)); + LPWSTR Filename = (LPWSTR)alloca(MAX_LONGPATH * sizeof(WCHAR)); + + BOOL bNeedNotificationExceptions = FALSE; + + if (pMD == NULL) + { + int numModule = 0; + int numMethods = 0; + + ArrayHolder<DWORD_PTR> moduleList = NULL; + + if(!fIsFilename) + { + MultiByteToWideChar(CP_ACP, 0, DllName.data, -1, ModuleName, mdNameLen); + MultiByteToWideChar(CP_ACP, 0, TypeName.data, -1, FunctionName, mdNameLen); + } + else + { + MultiByteToWideChar(CP_ACP, 0, DllName.data, -1, Filename, MAX_LONGPATH); + } + + // Get modules that may need a breakpoint bound + if ((Status = CheckEEDll()) == S_OK) + { + if ((Status = LoadClrDebugDll()) != S_OK) + { + // if the EE is loaded but DAC isn't we should stop. + DACMessage(Status); + return Status; + } + g_bDacBroken = FALSE; \ + + // Get the module list + moduleList = ModuleFromName(fIsFilename ? NULL : DllName.data, &numModule); + + // Its OK if moduleList is NULL + // There is a very normal case when checking for modules after clr is loaded + // but before any AppDomains or assemblies are created + // for example: + // >sxe ld:clr + // >g + // ... + // ModLoad: clr.dll + // >!bpmd Foo.dll Foo.Bar + } + // If LoadClrDebugDll() succeeded make sure we release g_clrData + ToRelease<IXCLRDataProcess> spIDP(g_clrData); + ToRelease<ISOSDacInterface> spISD(g_sos); + ResetGlobals(); + + // we can get here with EE not loaded => 0 modules + // EE is loaded => 0 or more modules + ArrayHolder<DWORD_PTR> pMDs = NULL; + for (int iModule = 0; iModule < numModule; iModule++) + { + ToRelease<IXCLRDataModule> ModDef; + if (g_sos->GetModule(moduleList[iModule], &ModDef) != S_OK) + { + continue; + } + + HRESULT symbolsLoaded = S_FALSE; + if(!fIsFilename) + { + g_bpoints.ResolvePendingNonModuleBoundBreakpoint(ModuleName, FunctionName, moduleList[iModule], (DWORD)Offset); + } + else + { + SymbolReader symbolReader; + symbolsLoaded = g_bpoints.LoadSymbolsForModule(moduleList[iModule], &symbolReader); + if(symbolsLoaded == S_OK && + g_bpoints.ResolvePendingNonModuleBoundBreakpoint(Filename, lineNumber, moduleList[iModule], &symbolReader) == S_OK) + { + // if we have symbols then get the function name so we can lookup the MethodDescs + mdMethodDef methodDefToken; + ULONG32 ilOffset; + if(SUCCEEDED(symbolReader.ResolveSequencePoint(Filename, lineNumber, moduleList[iModule], &methodDefToken, &ilOffset))) + { + ToRelease<IXCLRDataMethodDefinition> pMethodDef = NULL; + if (SUCCEEDED(ModDef->GetMethodDefinitionByToken(methodDefToken, &pMethodDef))) + { + ULONG32 nameLen = 0; + pMethodDef->GetName(0, mdNameLen, &nameLen, FunctionName); + + // get the size of the required buffer + int buffSize = WideCharToMultiByte(CP_ACP, 0, FunctionName, -1, TypeName.data, 0, NULL, NULL); + + TypeName.data = new NOTHROW char[buffSize]; + if (TypeName.data != NULL) + { + int bytesWritten = WideCharToMultiByte(CP_ACP, 0, FunctionName, -1, TypeName.data, buffSize, NULL, NULL); + _ASSERTE(bytesWritten == buffSize); + } + } + } + } + } + + HRESULT gotMethodDescs = GetMethodDescsFromName(moduleList[iModule], ModDef, TypeName.data, &pMDs, &numMethods); + if (FAILED(gotMethodDescs) && (!fIsFilename)) + { + // BPs via file name will enumerate through modules so there will be legitimate failures. + // for module/type name we already found a match so this shouldn't fail (this is the original behavior). + ExtOut("Error getting MethodDescs for module %p\n", moduleList[iModule]); + return Status; + } + + // for filename+line number only print extra info if symbols for this module are loaded (it can get quite noisy otherwise). + if ((!fIsFilename) || (fIsFilename && symbolsLoaded == S_OK)) + { + for (int i = 0; i < numMethods; i++) + { + if (pMDs[i] == MD_NOT_YET_LOADED) + { + continue; + } + ExtOut("MethodDesc = %p\n", SOS_PTR(pMDs[i])); + } + } + + if (g_bpoints.Update(moduleList[iModule], FALSE)) + { + bNeedNotificationExceptions = TRUE; + } + } + + if (!fNoFutureModule) + { + // add a pending breakpoint that will find future loaded modules, and + // wait for the module load notification. + if (!fIsFilename) + { + g_bpoints.Add(ModuleName, FunctionName, NULL, (DWORD)Offset); + } + else + { + g_bpoints.Add(Filename, lineNumber, NULL); + } + bNeedNotificationExceptions = TRUE; + + ULONG32 flags = 0; + g_clrData->GetOtherNotificationFlags(&flags); + flags |= (CLRDATA_NOTIFY_ON_MODULE_LOAD | CLRDATA_NOTIFY_ON_MODULE_UNLOAD); + g_clrData->SetOtherNotificationFlags(flags); + } + } + else /* We were given a MethodDesc already */ + { + // if we've got an explicit MD, then we better have CLR and mscordacwks loaded + INIT_API_EE() + INIT_API_DAC(); + + DacpMethodDescData MethodDescData; + ExtOut("MethodDesc = %p\n", SOS_PTR(pMD)); + if (MethodDescData.Request(g_sos, TO_CDADDR(pMD)) != S_OK) + { + ExtOut("%p is not a valid MethodDesc\n", SOS_PTR(pMD)); + return Status; + } + + if (MethodDescData.bHasNativeCode) + { + IssueDebuggerBPCommand((size_t) MethodDescData.NativeCodeAddr); + } + else if (MethodDescData.bIsDynamic) + { +#ifndef FEATURE_PAL + // Dynamic methods don't have JIT notifications. This is something we must + // fix in the next release. Until then, you have a cumbersome user experience. + ExtOut("This DynamicMethodDesc is not yet JITTED. Placing memory breakpoint at %p\n", + MethodDescData.AddressOfNativeCodeSlot); + + sprintf_s(buffer, _countof(buffer), +#ifdef _TARGET_WIN64_ + "ba w8" +#else + "ba w4" +#endif // _TARGET_WIN64_ + + " /1 %p \"bp poi(%p); g\"", + (void*) (size_t) MethodDescData.AddressOfNativeCodeSlot, + (void*) (size_t) MethodDescData.AddressOfNativeCodeSlot); + + Status = g_ExtControl->Execute(DEBUG_EXECUTE_NOT_LOGGED, buffer, 0); + if (FAILED(Status)) + { + ExtOut("Unable to set breakpoint with IDebugControl::Execute: %x\n",Status); + ExtOut("Attempted to run: %s\n", buffer); + } +#else + ExtErr("This DynamicMethodDesc is not yet JITTED %p\n", MethodDescData.AddressOfNativeCodeSlot); +#endif // FEATURE_PAL + } + else + { + // Must issue a pending breakpoint. + if (g_sos->GetMethodDescName(pMD, mdNameLen, FunctionName, NULL) != S_OK) + { + ExtOut("Unable to get method name for MethodDesc %p\n", SOS_PTR(pMD)); + return Status; + } + + FileNameForModule ((DWORD_PTR) MethodDescData.ModulePtr, ModuleName); + + // We didn't find code, add a breakpoint. + g_bpoints.ResolvePendingNonModuleBoundBreakpoint(ModuleName, FunctionName, TO_TADDR(MethodDescData.ModulePtr), 0); + g_bpoints.Update(TO_TADDR(MethodDescData.ModulePtr), FALSE); + bNeedNotificationExceptions = TRUE; + } + } + + if (bNeedNotificationExceptions) + { + ExtOut("Adding pending breakpoints...\n"); +#ifndef FEATURE_PAL + sprintf_s(buffer, _countof(buffer), "sxe -c \"!HandleCLRN\" clrn"); + Status = g_ExtControl->Execute(DEBUG_EXECUTE_NOT_LOGGED, buffer, 0); +#else + Status = g_ExtServices->SetExceptionCallback(HandleExceptionNotification); +#endif // FEATURE_PAL + } + + return Status; +} + +#ifndef FEATURE_PAL + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to dump the managed threadpool * +* * +\**********************************************************************/ +DECLARE_API(ThreadPool) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + DacpThreadpoolData threadpool; + + if ((Status = threadpool.Request(g_sos)) == S_OK) + { + BOOL doHCDump = FALSE; + + CMDOption option[] = + { // name, vptr, type, hasValue + {"-ti", &doHCDump, COBOOL, FALSE} + }; + + if (!GetCMDOption(args, option, _countof(option), NULL, 0, NULL)) + { + return Status; + } + + ExtOut ("CPU utilization: %d%%\n", threadpool.cpuUtilization); + ExtOut ("Worker Thread:"); + ExtOut (" Total: %d", threadpool.NumWorkingWorkerThreads + threadpool.NumIdleWorkerThreads + threadpool.NumRetiredWorkerThreads); + ExtOut (" Running: %d", threadpool.NumWorkingWorkerThreads); + ExtOut (" Idle: %d", threadpool.NumIdleWorkerThreads); + ExtOut (" MaxLimit: %d", threadpool.MaxLimitTotalWorkerThreads); + ExtOut (" MinLimit: %d", threadpool.MinLimitTotalWorkerThreads); + ExtOut ("\n"); + + int numWorkRequests = 0; + CLRDATA_ADDRESS workRequestPtr = threadpool.FirstUnmanagedWorkRequest; + DacpWorkRequestData workRequestData; + while (workRequestPtr) + { + if ((Status = workRequestData.Request(g_sos,workRequestPtr))!=S_OK) + { + ExtOut(" Failed to examine a WorkRequest\n"); + return Status; + } + numWorkRequests++; + workRequestPtr = workRequestData.NextWorkRequest; + } + + ExtOut ("Work Request in Queue: %d\n", numWorkRequests); + workRequestPtr = threadpool.FirstUnmanagedWorkRequest; + while (workRequestPtr) + { + if ((Status = workRequestData.Request(g_sos,workRequestPtr))!=S_OK) + { + ExtOut(" Failed to examine a WorkRequest\n"); + return Status; + } + + if (workRequestData.Function == threadpool.AsyncTimerCallbackCompletionFPtr) + ExtOut (" AsyncTimerCallbackCompletion TimerInfo@%p\n", SOS_PTR(workRequestData.Context)); + else + ExtOut (" Unknown Function: %p Context: %p\n", SOS_PTR(workRequestData.Function), + SOS_PTR(workRequestData.Context)); + + workRequestPtr = workRequestData.NextWorkRequest; + } + + if (doHCDump) + { + ExtOut ("--------------------------------------\n"); + ExtOut ("\nThread Injection History\n"); + if (threadpool.HillClimbingLogSize > 0) + { + static char const * const TransitionNames[] = + { + "Warmup", + "Initializing", + "RandomMove", + "ClimbingMove", + "ChangePoint", + "Stabilizing", + "Starvation", + "ThreadTimedOut", + "Undefined" + }; + + ExtOut("\n Time Transition New #Threads #Samples Throughput\n"); + DacpHillClimbingLogEntry entry; + + // get the most recent entry first, so we can calculate time offsets + + int index = (threadpool.HillClimbingLogFirstIndex + threadpool.HillClimbingLogSize-1) % HillClimbingLogCapacity; + CLRDATA_ADDRESS entryPtr = threadpool.HillClimbingLog + (index * sizeof(HillClimbingLogEntry)); + if ((Status = entry.Request(g_sos,entryPtr))!=S_OK) + { + ExtOut(" Failed to examine a HillClimbing log entry\n"); + return Status; + } + DWORD endTime = entry.TickCount; + + for (int i = 0; i < threadpool.HillClimbingLogSize; i++) + { + index = (i + threadpool.HillClimbingLogFirstIndex) % HillClimbingLogCapacity; + entryPtr = threadpool.HillClimbingLog + (index * sizeof(HillClimbingLogEntry)); + + if ((Status = entry.Request(g_sos,entryPtr))!=S_OK) + { + ExtOut(" Failed to examine a HillClimbing log entry\n"); + return Status; + } + + ExtOut("%8.2lf %-14s %12d %12d %11.2lf\n", + (double)(int)(entry.TickCount - endTime) / 1000.0, + TransitionNames[entry.Transition], + entry.NewControlSetting, + entry.LastHistoryCount, + entry.LastHistoryMean); + } + } + } + + ExtOut ("--------------------------------------\n"); + ExtOut ("Number of Timers: %d\n", threadpool.NumTimers); + ExtOut ("--------------------------------------\n"); + + ExtOut ("Completion Port Thread:"); + ExtOut ("Total: %d", threadpool.NumCPThreads); + ExtOut (" Free: %d", threadpool.NumFreeCPThreads); + ExtOut (" MaxFree: %d", threadpool.MaxFreeCPThreads); + ExtOut (" CurrentLimit: %d", threadpool.CurrentLimitTotalCPThreads); + ExtOut (" MaxLimit: %d", threadpool.MaxLimitTotalCPThreads); + ExtOut (" MinLimit: %d", threadpool.MinLimitTotalCPThreads); + ExtOut ("\n"); + } + else + { + ExtOut("Failed to request ThreadpoolMgr information\n"); + } + return Status; +} + +#endif // FEATURE_PAL + +DECLARE_API(FindAppDomain) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + DWORD_PTR p_Object = NULL; + BOOL dml = FALSE; + + CMDOption option[] = + { // name, vptr, type, hasValue +#ifndef FEATURE_PAL + {"/d", &dml, COBOOL, FALSE}, +#endif + }; + CMDValue arg[] = + { // vptr, type + {&p_Object, COHEX}, + }; + size_t nArg; + + if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg)) + { + return Status; + } + + EnableDMLHolder dmlHolder(dml); + + if ((p_Object == 0) || !sos::IsObject(p_Object)) + { + ExtOut("%p is not a valid object\n", SOS_PTR(p_Object)); + return Status; + } + + DacpAppDomainStoreData adstore; + if (adstore.Request(g_sos) != S_OK) + { + ExtOut("Error getting AppDomain information\n"); + return Status; + } + + CLRDATA_ADDRESS appDomain = GetAppDomain (TO_CDADDR(p_Object)); + + if (appDomain != NULL) + { + DMLOut("AppDomain: %s\n", DMLDomain(appDomain)); + if (appDomain == adstore.sharedDomain) + { + ExtOut("Name: Shared Domain\n"); + ExtOut("ID: (shared domain)\n"); + } + else if (appDomain == adstore.systemDomain) + { + ExtOut("Name: System Domain\n"); + ExtOut("ID: (system domain)\n"); + } + else + { + DacpAppDomainData domain; + if ((domain.Request(g_sos, appDomain) != S_OK) || + (g_sos->GetAppDomainName(appDomain,mdNameLen,g_mdName, NULL)!=S_OK)) + { + ExtOut("Error getting AppDomain %p.\n", SOS_PTR(appDomain)); + return Status; + } + + ExtOut("Name: %S\n", (g_mdName[0]!=L'\0') ? g_mdName : W("None")); + ExtOut("ID: %d\n", domain.dwId); + } + } + else + { + ExtOut("The type is declared in the shared domain and other\n"); + ExtOut("methods of finding the AppDomain failed. Try running\n"); + if (IsDMLEnabled()) + DMLOut("<exec cmd=\"!gcroot /d %p\">!gcroot %p</exec>, and if you find a root on a\n", p_Object, p_Object); + else + ExtOut("!gcroot %p, and if you find a root on a\n", p_Object); + ExtOut("stack, check the AppDomain of that stack with !threads.\n"); + ExtOut("Note that the Thread could have transitioned between\n"); + ExtOut("multiple AppDomains.\n"); + } + + return Status; +} + +#ifndef FEATURE_PAL + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to get the COM state (e.g. APT,contexe * +* activity. * +* * +\**********************************************************************/ +#ifdef FEATURE_COMINTEROP +DECLARE_API(COMState) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + + ULONG numThread; + ULONG maxId; + g_ExtSystem->GetTotalNumberThreads(&numThread,&maxId); + + ULONG curId; + g_ExtSystem->GetCurrentThreadId(&curId); + + SIZE_T AllocSize; + if (!ClrSafeInt<SIZE_T>::multiply(sizeof(ULONG), numThread, AllocSize)) + { + ExtOut(" Error! integer overflow on numThread 0x%08x\n", numThread); + return Status; + } + ULONG *ids = (ULONG*)alloca(AllocSize); + ULONG *sysIds = (ULONG*)alloca(AllocSize); + g_ExtSystem->GetThreadIdsByIndex(0,numThread,ids,sysIds); +#if defined(_TARGET_WIN64_) + ExtOut(" ID TEB APT APTId CallerTID Context\n"); +#else + ExtOut(" ID TEB APT APTId CallerTID Context\n"); +#endif + for (ULONG i = 0; i < numThread; i ++) { + g_ExtSystem->SetCurrentThreadId(ids[i]); + CLRDATA_ADDRESS cdaTeb; + g_ExtSystem->GetCurrentThreadTeb(&cdaTeb); + ExtOut("%3d %4x %p", ids[i], sysIds[i], SOS_PTR(CDA_TO_UL64(cdaTeb))); + // Apartment state + TADDR OleTlsDataAddr; + if (SafeReadMemory(TO_TADDR(cdaTeb) + offsetof(TEB,ReservedForOle), + &OleTlsDataAddr, + sizeof(OleTlsDataAddr), NULL) && OleTlsDataAddr != 0) { + DWORD AptState; + if (SafeReadMemory(OleTlsDataAddr+offsetof(SOleTlsData,dwFlags), + &AptState, + sizeof(AptState), NULL)) { + if (AptState & OLETLS_APARTMENTTHREADED) { + ExtOut(" STA"); + } + else if (AptState & OLETLS_MULTITHREADED) { + ExtOut(" MTA"); + } + else if (AptState & OLETLS_INNEUTRALAPT) { + ExtOut(" NTA"); + } + else { + ExtOut(" Ukn"); + } + + // Read these fields only if we were able to read anything of the SOleTlsData structure + DWORD dwApartmentID; + if (SafeReadMemory(OleTlsDataAddr+offsetof(SOleTlsData,dwApartmentID), + &dwApartmentID, + sizeof(dwApartmentID), NULL)) { + ExtOut(" %8x", dwApartmentID); + } + else + ExtOut(" %8x", 0); + + DWORD dwTIDCaller; + if (SafeReadMemory(OleTlsDataAddr+offsetof(SOleTlsData,dwTIDCaller), + &dwTIDCaller, + sizeof(dwTIDCaller), NULL)) { + ExtOut(" %8x", dwTIDCaller); + } + else + ExtOut(" %8x", 0); + + size_t Context; + if (SafeReadMemory(OleTlsDataAddr+offsetof(SOleTlsData,pCurrentCtx), + &Context, + sizeof(Context), NULL)) { + ExtOut(" %p", SOS_PTR(Context)); + } + else + ExtOut(" %p", SOS_PTR(0)); + + } + else + ExtOut(" Ukn"); + } + else + ExtOut(" Ukn"); + ExtOut("\n"); + } + + g_ExtSystem->SetCurrentThreadId(curId); + return Status; +} +#endif // FEATURE_COMINTEROP + +#endif // FEATURE_PAL + +BOOL traverseEh(UINT clauseIndex,UINT totalClauses,DACEHInfo *pEHInfo,LPVOID token) +{ + size_t methodStart = (size_t) token; + + if (IsInterrupt()) + { + return FALSE; + } + + ExtOut("EHHandler %d: %s ", clauseIndex, EHTypeName(pEHInfo->clauseType)); + + LPCWSTR typeName = EHTypedClauseTypeName(pEHInfo); + if (typeName != NULL) + { + ExtOut("catch(%S) ", typeName); + } + + if (IsClonedFinally(pEHInfo)) + ExtOut("(cloned finally)"); + else if (pEHInfo->isDuplicateClause) + ExtOut("(duplicate)"); + + ExtOut("\n"); + ExtOut("Clause: "); + + ULONG64 addrStart = pEHInfo->tryStartOffset + methodStart; + ULONG64 addrEnd = pEHInfo->tryEndOffset + methodStart; + +#ifdef _WIN64 + ExtOut("[%08x`%08x, %08x`%08x]", + (ULONG)(addrStart >> 32), (ULONG)addrStart, + (ULONG)(addrEnd >> 32), (ULONG)addrEnd); +#else + ExtOut("[%08x, %08x]", (ULONG)addrStart, (ULONG)addrEnd); +#endif + + ExtOut(" [%x, %x]\n", + (UINT32) pEHInfo->tryStartOffset, + (UINT32) pEHInfo->tryEndOffset); + + ExtOut("Handler: "); + + addrStart = pEHInfo->handlerStartOffset + methodStart; + addrEnd = pEHInfo->handlerEndOffset + methodStart; + +#ifdef _WIN64 + ExtOut("[%08x`%08x, %08x`%08x]", + (ULONG)(addrStart >> 32), (ULONG)addrStart, + (ULONG)(addrEnd >> 32), (ULONG)addrEnd); +#else + ExtOut("[%08x, %08x]", (ULONG)addrStart, (ULONG)addrEnd); +#endif + + ExtOut(" [%x, %x]\n", + (UINT32) pEHInfo->handlerStartOffset, + (UINT32) pEHInfo->handlerEndOffset); + + if (pEHInfo->clauseType == EHFilter) + { + ExtOut("Filter: "); + + addrStart = pEHInfo->filterOffset + methodStart; + +#ifdef _WIN64 + ExtOut("[%08x`%08x]", (ULONG)(addrStart >> 32), (ULONG)addrStart); +#else + ExtOut("[%08x]", (ULONG)addrStart); +#endif + + ExtOut(" [%x]\n", + (UINT32) pEHInfo->filterOffset); + } + + ExtOut("\n"); + return TRUE; +} + +DECLARE_API(EHInfo) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + DWORD_PTR dwStartAddr = NULL; + BOOL dml = FALSE; + + CMDOption option[] = + { // name, vptr, type, hasValue + {"/d", &dml, COBOOL, FALSE}, + }; + + CMDValue arg[] = + { // vptr, type + {&dwStartAddr, COHEX}, + }; + + size_t nArg; + if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg) || (0 == nArg)) + { + return Status; + } + + EnableDMLHolder dmlHolder(dml); + DWORD_PTR tmpAddr = dwStartAddr; + + if (!IsMethodDesc(dwStartAddr)) + { + JITTypes jitType; + DWORD_PTR methodDesc; + DWORD_PTR gcinfoAddr; + IP2MethodDesc (dwStartAddr, methodDesc, jitType, gcinfoAddr); + tmpAddr = methodDesc; + } + + DacpMethodDescData MD; + if ((tmpAddr == 0) || (MD.Request(g_sos, TO_CDADDR(tmpAddr)) != S_OK)) + { + ExtOut("%p is not a MethodDesc\n", SOS_PTR(tmpAddr)); + return Status; + } + + if (1 == nArg && !MD.bHasNativeCode) + { + ExtOut("No EH info available\n"); + return Status; + } + + DacpCodeHeaderData codeHeaderData; + if (codeHeaderData.Request(g_sos, TO_CDADDR(MD.NativeCodeAddr)) != S_OK) + { + ExtOut("Unable to get codeHeader information\n"); + return Status; + } + + DMLOut("MethodDesc: %s\n", DMLMethodDesc(MD.MethodDescPtr)); + DumpMDInfo(TO_TADDR(MD.MethodDescPtr)); + + ExtOut("\n"); + Status = g_sos->TraverseEHInfo(TO_CDADDR(MD.NativeCodeAddr), traverseEh, (LPVOID)MD.NativeCodeAddr); + + if (Status == E_ABORT) + { + ExtOut("<user aborted>\n"); + } + else if (Status != S_OK) + { + ExtOut("Failed to perform EHInfo traverse\n"); + } + + return Status; +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to dump the GC encoding of a managed * +* function. * +* * +\**********************************************************************/ +DECLARE_API(GCInfo) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + + TADDR taStartAddr = NULL; + TADDR taGCInfoAddr; + BOOL dml = FALSE; + + CMDOption option[] = + { // name, vptr, type, hasValue + {"/d", &dml, COBOOL, FALSE}, + }; + CMDValue arg[] = + { // vptr, type + {&taStartAddr, COHEX}, + }; + size_t nArg; + if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg) || (0 == nArg)) + { + return Status; + } + + EnableDMLHolder dmlHolder(dml); + TADDR tmpAddr = taStartAddr; + + if (!IsMethodDesc(taStartAddr)) + { + JITTypes jitType; + TADDR methodDesc; + TADDR gcinfoAddr; + IP2MethodDesc(taStartAddr, methodDesc, jitType, gcinfoAddr); + tmpAddr = methodDesc; + } + + DacpMethodDescData MD; + if ((tmpAddr == 0) || (MD.Request(g_sos, TO_CDADDR(tmpAddr)) != S_OK)) + { + ExtOut("%p is not a valid MethodDesc\n", SOS_PTR(taStartAddr)); + return Status; + } + + if (1 == nArg && !MD.bHasNativeCode) + { + ExtOut("No GC info available\n"); + return Status; + } + + DacpCodeHeaderData codeHeaderData; + + if ( + // Try to get code header data from taStartAddr. This will get the code + // header corresponding to the IP address, even if the function was rejitted + (codeHeaderData.Request(g_sos, TO_CDADDR(taStartAddr)) != S_OK) && + + // If that didn't work, just try to use the code address that the MD + // points to. If the function was rejitted, this will only give you the + // original JITted code, but that's better than nothing + (codeHeaderData.Request(g_sos, TO_CDADDR(MD.NativeCodeAddr)) != S_OK) + ) + { + // We always used to emit this (before rejit support), even if we couldn't get + // the code header, so keep on doing so. + ExtOut("entry point %p\n", SOS_PTR(MD.NativeCodeAddr)); + + // And now the error.... + ExtOut("Unable to get codeHeader information\n"); + return Status; + } + + // We have the code header, so use it to determine the method start + + ExtOut("entry point %p\n", SOS_PTR(codeHeaderData.MethodStart)); + + if (codeHeaderData.JITType == TYPE_UNKNOWN) + { + ExtOut("unknown Jit\n"); + return Status; + } + else if (codeHeaderData.JITType == TYPE_JIT) + { + ExtOut("Normal JIT generated code\n"); + } + else if (codeHeaderData.JITType == TYPE_PJIT) + { + ExtOut("preJIT generated code\n"); + } + + taGCInfoAddr = TO_TADDR(codeHeaderData.GCInfo); + + ExtOut("GC info %p\n", SOS_PTR(taGCInfoAddr)); + + // assume that GC encoding table is never more than + // 40 + methodSize * 2 + int tableSize = 0; + if (!ClrSafeInt<int>::multiply(codeHeaderData.MethodSize, 2, tableSize) || + !ClrSafeInt<int>::addition(tableSize, 40, tableSize)) + { + ExtOut("<integer overflow>\n"); + return E_FAIL; + } + ArrayHolder<BYTE> table = new NOTHROW BYTE[tableSize]; + if (table == NULL) + { + ExtOut("Could not allocate memory to read the gc info.\n"); + return E_OUTOFMEMORY; + } + + memset(table, 0, tableSize); + // We avoid using move here, because we do not want to return + if (!SafeReadMemory(taGCInfoAddr, table, tableSize, NULL)) + { + ExtOut("Could not read memory %p\n", SOS_PTR(taGCInfoAddr)); + return Status; + } + + // Mutable table pointer since we need to pass the appropriate + // offset into the table to DumpGCTable. + GCInfoToken gcInfoToken = { table, GCINFO_VERSION }; + unsigned int methodSize = (unsigned int)codeHeaderData.MethodSize; + + g_targetMachine->DumpGCInfo(gcInfoToken, methodSize, ExtOut, true /*encBytes*/, true /*bPrintHeader*/); + + return Status; +} + +#if !defined(FEATURE_PAL) + +void DecodeGCTableEntry (const char *fmt, ...) +{ + GCEncodingInfo *pInfo = (GCEncodingInfo*)GetFiberData(); + va_list va; + + // + // Append the new data to the buffer + // + + va_start(va, fmt); + + int cch = _vsnprintf_s(&pInfo->buf[pInfo->cch], _countof(pInfo->buf) - pInfo->cch, _countof(pInfo->buf) - pInfo->cch - 1, fmt, va); + if (cch >= 0) + pInfo->cch += cch; + + va_end(va); + + pInfo->buf[pInfo->cch] = '\0'; + + // + // If there are complete lines in the buffer, decode them. + // + + for (;;) + { + char *pNewLine = strchr(pInfo->buf, '\n'); + + if (!pNewLine) + break; + + // + // The line should start with a 16-bit (x86) or 32-bit (non-x86) hex + // offset. strtoul returns ULONG_MAX or 0 on failure. 0 is a valid + // offset for the first encoding, or while the last offset was 0. + // + + if (isxdigit(pInfo->buf[0])) + { + char *pEnd; + ULONG ofs = strtoul(pInfo->buf, &pEnd, 16); + + if ( isspace(*pEnd) + && -1 != ofs + && ( -1 == pInfo->ofs + || 0 == pInfo->ofs + || ofs > 0)) + { + pInfo->ofs = ofs; + *pNewLine = '\0'; + + SwitchToFiber(pInfo->pvMainFiber); + } + } + else if (0 == strncmp(pInfo->buf, "Untracked:", 10)) + { + pInfo->ofs = 0; + *pNewLine = '\0'; + + SwitchToFiber(pInfo->pvMainFiber); + } + + // + // Shift the remaining data to the start of the buffer + // + + strcpy_s(pInfo->buf, _countof(pInfo->buf), pNewLine+1); + pInfo->cch = (int)strlen(pInfo->buf); + } +} + + +VOID CALLBACK DumpGCTableFiberEntry (LPVOID pvGCEncodingInfo) +{ + GCEncodingInfo *pInfo = (GCEncodingInfo*)pvGCEncodingInfo; + GCInfoToken gcInfoToken = { pInfo->table, GCINFO_VERSION }; + g_targetMachine->DumpGCInfo(gcInfoToken, pInfo->methodSize, DecodeGCTableEntry, false /*encBytes*/, false /*bPrintHeader*/); + + pInfo->fDoneDecoding = true; + SwitchToFiber(pInfo->pvMainFiber); +} +#endif // !FEATURE_PAL + +BOOL gatherEh(UINT clauseIndex,UINT totalClauses,DACEHInfo *pEHInfo,LPVOID token) +{ + SOSEHInfo *pInfo = (SOSEHInfo *) token; + + if (pInfo == NULL) + { + return FALSE; + } + + if (pInfo->m_pInfos == NULL) + { + // First time, initialize structure + pInfo->EHCount = totalClauses; + pInfo->m_pInfos = new NOTHROW DACEHInfo[totalClauses]; + if (pInfo->m_pInfos == NULL) + { + ReportOOM(); + return FALSE; + } + } + + pInfo->m_pInfos[clauseIndex] = *((DACEHInfo*)pEHInfo); + return TRUE; +} + + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to unassembly a managed function. * +* It tries to print symbolic info for function call, contants... * +* * +\**********************************************************************/ +DECLARE_API(u) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + + DWORD_PTR dwStartAddr = NULL; + BOOL fWithGCInfo = FALSE; + BOOL fWithEHInfo = FALSE; + BOOL bSuppressLines = FALSE; + BOOL bDisplayOffsets = FALSE; + BOOL dml = FALSE; + size_t nArg; + + CMDOption option[] = + { // name, vptr, type, hasValue +#ifndef FEATURE_PAL + {"-gcinfo", &fWithGCInfo, COBOOL, FALSE}, +#endif + {"-ehinfo", &fWithEHInfo, COBOOL, FALSE}, + {"-n", &bSuppressLines, COBOOL, FALSE}, + {"-o", &bDisplayOffsets, COBOOL, FALSE}, +#ifndef FEATURE_PAL + {"/d", &dml, COBOOL, FALSE}, +#endif + }; + CMDValue arg[] = + { // vptr, type + {&dwStartAddr, COHEX}, + }; + if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg) || (nArg < 1)) + { + return Status; + } + // symlines will be non-zero only if SYMOPT_LOAD_LINES was set in the symbol options + ULONG symlines = 0; + if (!bSuppressLines && SUCCEEDED(g_ExtSymbols->GetSymbolOptions(&symlines))) + { + symlines &= SYMOPT_LOAD_LINES; + } + bSuppressLines = bSuppressLines || (symlines == 0); + + EnableDMLHolder dmlHolder(dml); + // dwStartAddr is either some IP address or a MethodDesc. Start off assuming it's a + // MethodDesc. + DWORD_PTR methodDesc = dwStartAddr; + if (!IsMethodDesc(methodDesc)) + { + // Not a methodDesc, so gotta find it ourselves + DWORD_PTR tmpAddr = dwStartAddr; + JITTypes jt; + DWORD_PTR gcinfoAddr; + IP2MethodDesc (tmpAddr, methodDesc, jt, + gcinfoAddr); + if (!methodDesc || jt == TYPE_UNKNOWN) + { + // It is not managed code. + ExtOut("Unmanaged code\n"); + UnassemblyUnmanaged(dwStartAddr, bSuppressLines); + return Status; + } + } + + DacpMethodDescData MethodDescData; + if ((Status=MethodDescData.Request(g_sos, TO_CDADDR(methodDesc))) != S_OK) + { + ExtOut("Failed to get method desc for %p.\n", SOS_PTR(dwStartAddr)); + return Status; + } + + if (!MethodDescData.bHasNativeCode) + { + ExtOut("Not jitted yet\n"); + return Status; + } + + // Get the appropriate code header. If we were passed an MD, then use + // MethodDescData.NativeCodeAddr to find the code header; if we were passed an IP, use + // that IP to find the code header. This ensures that, for rejitted functions, we + // disassemble the rejit version that the user explicitly specified with their IP. + DacpCodeHeaderData codeHeaderData; + if (codeHeaderData.Request( + g_sos, + TO_CDADDR( + (dwStartAddr == methodDesc) ? MethodDescData.NativeCodeAddr : dwStartAddr) + ) != S_OK) + + { + ExtOut("Unable to get codeHeader information\n"); + return Status; + } + + if (codeHeaderData.MethodStart == 0) + { + ExtOut("not a valid MethodDesc\n"); + return Status; + } + + if (codeHeaderData.JITType == TYPE_UNKNOWN) + { + ExtOut("unknown Jit\n"); + return Status; + } + else if (codeHeaderData.JITType == TYPE_JIT) + { + ExtOut("Normal JIT generated code\n"); + } + else if (codeHeaderData.JITType == TYPE_PJIT) + { + ExtOut("preJIT generated code\n"); + } + + NameForMD_s(methodDesc, g_mdName, mdNameLen); + ExtOut("%S\n", g_mdName); + if (codeHeaderData.ColdRegionStart != NULL) + { + ExtOut("Begin %p, size %x. Cold region begin %p, size %x\n", + SOS_PTR(codeHeaderData.MethodStart), codeHeaderData.HotRegionSize, + SOS_PTR(codeHeaderData.ColdRegionStart), codeHeaderData.ColdRegionSize); + } + else + { + ExtOut("Begin %p, size %x\n", SOS_PTR(codeHeaderData.MethodStart), codeHeaderData.MethodSize); + } + +#if !defined(FEATURE_PAL) + // + // Set up to mix gc info with the code if requested + // + + GCEncodingInfo gcEncodingInfo = {0}; + + // The actual GC Encoding Table, this is updated during the course of the function. + gcEncodingInfo.table = NULL; + + // The holder to make sure we clean up the memory for the table + ArrayHolder<BYTE> table = NULL; + + if (fWithGCInfo) + { + // assume that GC encoding table is never more than 40 + methodSize * 2 + int tableSize = 0; + if (!ClrSafeInt<int>::multiply(codeHeaderData.MethodSize, 2, tableSize) || + !ClrSafeInt<int>::addition(tableSize, 40, tableSize)) + { + ExtOut("<integer overflow>\n"); + return E_FAIL; + } + + + // Assign the new array to the mutable gcEncodingInfo table and to the + // table ArrayHolder to clean this up when the function exits. + table = gcEncodingInfo.table = new NOTHROW BYTE[tableSize]; + + if (gcEncodingInfo.table == NULL) + { + ExtOut("Could not allocate memory to read the gc info.\n"); + return E_OUTOFMEMORY; + } + + memset (gcEncodingInfo.table, 0, tableSize); + // We avoid using move here, because we do not want to return + if (!SafeReadMemory(TO_TADDR(codeHeaderData.GCInfo), gcEncodingInfo.table, tableSize, NULL)) + { + ExtOut("Could not read memory %p\n", SOS_PTR(codeHeaderData.GCInfo)); + return Status; + } + + // + // Skip the info header + // + gcEncodingInfo.methodSize = (unsigned int)codeHeaderData.MethodSize; + + // + // DumpGCTable will call gcPrintf for each encoding. We'd like a "give + // me the next encoding" interface, but we're stuck with the callback. + // To reconcile this without messing up too much code, we'll create a + // fiber to dump the gc table. When we need the next gc encoding, + // we'll switch to this fiber. The callback will note the next offset, + // and switch back to the main fiber. + // + + gcEncodingInfo.ofs = -1; + gcEncodingInfo.hotSizeToAdd = 0; + + gcEncodingInfo.pvMainFiber = ConvertThreadToFiber(NULL); + if (!gcEncodingInfo.pvMainFiber && ERROR_ALREADY_FIBER == GetLastError()) + gcEncodingInfo.pvMainFiber = GetCurrentFiber(); + + if (!gcEncodingInfo.pvMainFiber) + return Status; + + gcEncodingInfo.pvGCTableFiber = CreateFiber(0, DumpGCTableFiberEntry, &gcEncodingInfo); + if (!gcEncodingInfo.pvGCTableFiber) + return Status; + + SwitchToFiber(gcEncodingInfo.pvGCTableFiber); + } +#endif + + SOSEHInfo *pInfo = NULL; + if (fWithEHInfo) + { + pInfo = new NOTHROW SOSEHInfo; + if (pInfo == NULL) + { + ReportOOM(); + } + else if (g_sos->TraverseEHInfo(MethodDescData.NativeCodeAddr, gatherEh, (LPVOID)pInfo) != S_OK) + { + ExtOut("Failed to gather EHInfo data\n"); + delete pInfo; + pInfo = NULL; + } + } + + if (codeHeaderData.ColdRegionStart == NULL) + { + g_targetMachine->Unassembly ( + (DWORD_PTR) codeHeaderData.MethodStart, + ((DWORD_PTR)codeHeaderData.MethodStart) + codeHeaderData.MethodSize, + dwStartAddr, + (DWORD_PTR) MethodDescData.GCStressCodeCopy, +#if !defined(FEATURE_PAL) + fWithGCInfo ? &gcEncodingInfo : +#endif + NULL, + pInfo, + bSuppressLines, + bDisplayOffsets + ); + } + else + { + ExtOut("Hot region:\n"); + g_targetMachine->Unassembly ( + (DWORD_PTR) codeHeaderData.MethodStart, + ((DWORD_PTR)codeHeaderData.MethodStart) + codeHeaderData.HotRegionSize, + dwStartAddr, + (DWORD_PTR) MethodDescData.GCStressCodeCopy, +#if !defined(FEATURE_PAL) + fWithGCInfo ? &gcEncodingInfo : +#endif + NULL, + pInfo, + bSuppressLines, + bDisplayOffsets + ); + + ExtOut("Cold region:\n"); + +#if !defined(FEATURE_PAL) + // Displaying gcinfo for a cold region requires knowing the size of + // the hot region preceeding. + gcEncodingInfo.hotSizeToAdd = codeHeaderData.HotRegionSize; +#endif + g_targetMachine->Unassembly ( + (DWORD_PTR) codeHeaderData.ColdRegionStart, + ((DWORD_PTR)codeHeaderData.ColdRegionStart) + codeHeaderData.ColdRegionSize, + dwStartAddr, + ((DWORD_PTR) MethodDescData.GCStressCodeCopy) + codeHeaderData.HotRegionSize, +#if !defined(FEATURE_PAL) + fWithGCInfo ? &gcEncodingInfo : +#endif + NULL, + pInfo, + bSuppressLines, + bDisplayOffsets + ); + + } + + if (pInfo) + { + delete pInfo; + pInfo = NULL; + } + +#if !defined(FEATURE_PAL) + if (fWithGCInfo) + DeleteFiber(gcEncodingInfo.pvGCTableFiber); +#endif + + return Status; +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to dump the in-memory stress log * +* !DumpLog [filename] * +* will dump the stress log corresponding to the clr.dll * +* loaded in the debuggee's VAS * +* !DumpLog -addr <addr_of_StressLog::theLog> [filename] * +* will dump the stress log associated with any DLL linked * +* against utilcode.lib, most commonly mscordbi.dll * +* (e.g. !DumpLog -addr mscordbi!StressLog::theLog) * +* * +\**********************************************************************/ +DECLARE_API(DumpLog) +{ + INIT_API_NO_RET_ON_FAILURE(); + + MINIDUMP_NOT_SUPPORTED(); + + const char* fileName = "StressLog.txt"; + + CLRDATA_ADDRESS StressLogAddress = NULL; + + StringHolder sFileName, sLogAddr; + CMDOption option[] = + { // name, vptr, type, hasValue + {"-addr", &sLogAddr.data, COSTRING, TRUE} + }; + CMDValue arg[] = + { // vptr, type + {&sFileName.data, COSTRING} + }; + size_t nArg; + if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg)) + { + return Status; + } + if (nArg > 0 && sFileName.data != NULL) + { + fileName = sFileName.data; + } + + // allow users to specify -addr mscordbdi!StressLog::theLog, for example. + if (sLogAddr.data != NULL) + { + StressLogAddress = GetExpression(sLogAddr.data); + } + + if (StressLogAddress == NULL) + { + if (g_bDacBroken) + { +#ifdef FEATURE_PAL + ExtOut("No stress log address. DAC is broken; can't get it\n"); + return E_FAIL; +#else + // Try to find stress log symbols + DWORD_PTR dwAddr = GetValueFromExpression(MAIN_CLR_MODULE_NAME_A "!StressLog::theLog"); + StressLogAddress = dwAddr; +#endif + } + else if (g_sos->GetStressLogAddress(&StressLogAddress) != S_OK) + { + ExtOut("Unable to find stress log via DAC\n"); + return E_FAIL; + } + } + + if (StressLogAddress == NULL) + { + ExtOut("Please provide the -addr argument for the address of the stress log, since no recognized runtime is loaded.\n"); + return E_FAIL; + } + + ExtOut("Attempting to dump Stress log to file '%s'\n", fileName); + + + + Status = StressLog::Dump(StressLogAddress, fileName, g_ExtData); + + if (Status == S_OK) + ExtOut("SUCCESS: Stress log dumped\n"); + else if (Status == S_FALSE) + ExtOut("No Stress log in the image, no file written\n"); + else + ExtOut("FAILURE: Stress log not dumped\n"); + + return Status; +} + +#ifdef TRACE_GC + +DECLARE_API (DumpGCLog) +{ + INIT_API_NODAC(); + MINIDUMP_NOT_SUPPORTED(); + + if (GetEEFlavor() == UNKNOWNEE) + { + ExtOut("CLR not loaded\n"); + return Status; + } + + const char* fileName = "GCLog.txt"; + + while (isspace (*args)) + args ++; + + if (*args != 0) + fileName = args; + + DWORD_PTR dwAddr = GetValueFromExpression(MAIN_CLR_MODULE_NAME_A "!SVR::gc_log_buffer"); + moveN (dwAddr, dwAddr); + + if (dwAddr == 0) + { + dwAddr = GetValueFromExpression(MAIN_CLR_MODULE_NAME_A "!WKS::gc_log_buffer"); + moveN (dwAddr, dwAddr); + if (dwAddr == 0) + { + ExtOut("Can't get either WKS or SVR GC's log file"); + return E_FAIL; + } + } + + ExtOut("Dumping GC log at %08x\n", dwAddr); + + g_bDacBroken = FALSE; + + ExtOut("Attempting to dump GC log to file '%s'\n", fileName); + + Status = E_FAIL; + + HANDLE hGCLog = CreateFileA( + fileName, + GENERIC_WRITE, + FILE_SHARE_READ, + NULL, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL); + + if (hGCLog == INVALID_HANDLE_VALUE) + { + ExtOut("failed to create file: %d\n", GetLastError()); + goto exit; + } + + int iLogSize = 1024*1024; + BYTE* bGCLog = new NOTHROW BYTE[iLogSize]; + if (bGCLog == NULL) + { + ReportOOM(); + goto exit; + } + + memset (bGCLog, 0, iLogSize); + if (!SafeReadMemory(dwAddr, bGCLog, iLogSize, NULL)) + { + ExtOut("failed to read memory from %08x\n", dwAddr); + } + + int iRealLogSize = iLogSize - 1; + while (iRealLogSize >= 0) + { + if (bGCLog[iRealLogSize] != '*') + { + break; + } + + iRealLogSize--; + } + + DWORD dwWritten = 0; + WriteFile (hGCLog, bGCLog, iRealLogSize + 1, &dwWritten, NULL); + + Status = S_OK; + +exit: + + if (hGCLog != INVALID_HANDLE_VALUE) + { + CloseHandle (hGCLog); + } + + if (Status == S_OK) + ExtOut("SUCCESS: Stress log dumped\n"); + else if (Status == S_FALSE) + ExtOut("No Stress log in the image, no file written\n"); + else + ExtOut("FAILURE: Stress log not dumped\n"); + + return Status; +} +#endif //TRACE_GC + +#ifndef FEATURE_PAL +DECLARE_API (DumpGCConfigLog) +{ + INIT_API(); +#ifdef GC_CONFIG_DRIVEN + MINIDUMP_NOT_SUPPORTED(); + + if (GetEEFlavor() == UNKNOWNEE) + { + ExtOut("CLR not loaded\n"); + return Status; + } + + const char* fileName = "GCConfigLog.txt"; + + while (isspace (*args)) + args ++; + + if (*args != 0) + fileName = args; + + if (!InitializeHeapData ()) + { + ExtOut("GC Heap not initialized yet.\n"); + return S_OK; + } + + BOOL fIsServerGC = IsServerBuild(); + + DWORD_PTR dwAddr = 0; + DWORD_PTR dwAddrOffset = 0; + + if (fIsServerGC) + { + dwAddr = GetValueFromExpression(MAIN_CLR_MODULE_NAME_A "!SVR::gc_config_log_buffer"); + dwAddrOffset = GetValueFromExpression(MAIN_CLR_MODULE_NAME_A "!SVR::gc_config_log_buffer_offset"); + } + else + { + dwAddr = GetValueFromExpression(MAIN_CLR_MODULE_NAME_A "!WKS::gc_config_log_buffer"); + dwAddrOffset = GetValueFromExpression(MAIN_CLR_MODULE_NAME_A "!WKS::gc_config_log_buffer_offset"); + } + + moveN (dwAddr, dwAddr); + moveN (dwAddrOffset, dwAddrOffset); + + if (dwAddr == 0) + { + ExtOut("Can't get either WKS or SVR GC's config log buffer"); + return E_FAIL; + } + + ExtOut("Dumping GC log at %08x\n", dwAddr); + + g_bDacBroken = FALSE; + + ExtOut("Attempting to dump GC log to file '%s'\n", fileName); + + Status = E_FAIL; + + HANDLE hGCLog = CreateFileA( + fileName, + GENERIC_WRITE, + FILE_SHARE_READ, + NULL, + OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL); + + if (hGCLog == INVALID_HANDLE_VALUE) + { + ExtOut("failed to create file: %d\n", GetLastError()); + goto exit; + } + + { + int iLogSize = (int)dwAddrOffset; + + ArrayHolder<BYTE> bGCLog = new NOTHROW BYTE[iLogSize]; + if (bGCLog == NULL) + { + ReportOOM(); + goto exit; + } + + memset (bGCLog, 0, iLogSize); + if (!SafeReadMemory(dwAddr, bGCLog, iLogSize, NULL)) + { + ExtOut("failed to read memory from %08x\n", dwAddr); + } + + SetFilePointer (hGCLog, 0, 0, FILE_END); + DWORD dwWritten; + WriteFile (hGCLog, bGCLog, iLogSize, &dwWritten, NULL); + } + + Status = S_OK; + +exit: + + if (hGCLog != INVALID_HANDLE_VALUE) + { + CloseHandle (hGCLog); + } + + if (Status == S_OK) + ExtOut("SUCCESS: Stress log dumped\n"); + else if (Status == S_FALSE) + ExtOut("No Stress log in the image, no file written\n"); + else + ExtOut("FAILURE: Stress log not dumped\n"); + + return Status; +#else + ExtOut("Not implemented\n"); + return S_OK; +#endif //GC_CONFIG_DRIVEN +} +#endif // FEATURE_PAL + +#ifdef GC_CONFIG_DRIVEN +static const char * const str_interesting_data_points[] = +{ + "pre short", // 0 + "post short", // 1 + "merged pins", // 2 + "converted pins", // 3 + "pre pin", // 4 + "post pin", // 5 + "pre and post pin", // 6 + "pre short padded", // 7 + "post short padded", // 7 +}; + +static const char * const str_heap_compact_reasons[] = +{ + "low on ephemeral space", + "high fragmetation", + "couldn't allocate gaps", + "user specfied compact LOH", + "last GC before OOM", + "induced compacting GC", + "fragmented gen0 (ephemeral GC)", + "high memory load (ephemeral GC)", + "high memory load and frag", + "very high memory load and frag", + "no gc mode" +}; + +static BOOL gc_heap_compact_reason_mandatory_p[] = +{ + TRUE, //compact_low_ephemeral = 0, + FALSE, //compact_high_frag = 1, + TRUE, //compact_no_gaps = 2, + TRUE, //compact_loh_forced = 3, + TRUE, //compact_last_gc = 4 + TRUE, //compact_induced_compacting = 5, + FALSE, //compact_fragmented_gen0 = 6, + FALSE, //compact_high_mem_load = 7, + TRUE, //compact_high_mem_frag = 8, + TRUE, //compact_vhigh_mem_frag = 9, + TRUE //compact_no_gc_mode = 10 +}; + +static const char * const str_heap_expand_mechanisms[] = +{ + "reused seg with normal fit", + "reused seg with best fit", + "expand promoting eph", + "expand with a new seg", + "no memory for a new seg", + "expand in next full GC" +}; + +static const char * const str_bit_mechanisms[] = +{ + "using mark list", + "demotion" +}; + +static const char * const str_gc_global_mechanisms[] = +{ + "concurrent GCs", + "compacting GCs", + "promoting GCs", + "GCs that did demotion", + "card bundles", + "elevation logic" +}; + +void PrintInterestingGCInfo(DacpGCInterestingInfoData* dataPerHeap) +{ + ExtOut("Interesting data points\n"); + size_t* data = dataPerHeap->interestingDataPoints; + for (int i = 0; i < NUM_GC_DATA_POINTS; i++) + { + ExtOut("%20s: %d\n", str_interesting_data_points[i], data[i]); + } + + ExtOut("\nCompacting reasons\n"); + data = dataPerHeap->compactReasons; + for (int i = 0; i < MAX_COMPACT_REASONS_COUNT; i++) + { + ExtOut("[%s]%35s: %d\n", (gc_heap_compact_reason_mandatory_p[i] ? "M" : "W"), str_heap_compact_reasons[i], data[i]); + } + + ExtOut("\nExpansion mechanisms\n"); + data = dataPerHeap->expandMechanisms; + for (int i = 0; i < MAX_EXPAND_MECHANISMS_COUNT; i++) + { + ExtOut("%30s: %d\n", str_heap_expand_mechanisms[i], data[i]); + } + + ExtOut("\nOther mechanisms enabled\n"); + data = dataPerHeap->bitMechanisms; + for (int i = 0; i < MAX_GC_MECHANISM_BITS_COUNT; i++) + { + ExtOut("%20s: %d\n", str_bit_mechanisms[i], data[i]); + } +} +#endif //GC_CONFIG_DRIVEN + +DECLARE_API(DumpGCData) +{ + INIT_API(); + +#ifdef GC_CONFIG_DRIVEN + MINIDUMP_NOT_SUPPORTED(); + + if (!InitializeHeapData ()) + { + ExtOut("GC Heap not initialized yet.\n"); + return S_OK; + } + + DacpGCInterestingInfoData interestingInfo; + interestingInfo.RequestGlobal(g_sos); + for (int i = 0; i < MAX_GLOBAL_GC_MECHANISMS_COUNT; i++) + { + ExtOut("%-30s: %d\n", str_gc_global_mechanisms[i], interestingInfo.globalMechanisms[i]); + } + + ExtOut("\n[info per heap]\n"); + + if (!IsServerBuild()) + { + if (interestingInfo.Request(g_sos) != S_OK) + { + ExtOut("Error requesting interesting GC info\n"); + return E_FAIL; + } + + PrintInterestingGCInfo(&interestingInfo); + } + else + { + DWORD dwNHeaps = GetGcHeapCount(); + DWORD dwAllocSize; + if (!ClrSafeInt<DWORD>::multiply(sizeof(CLRDATA_ADDRESS), dwNHeaps, dwAllocSize)) + { + ExtOut("Failed to get GCHeaps: integer overflow\n"); + return Status; + } + + CLRDATA_ADDRESS *heapAddrs = (CLRDATA_ADDRESS*)alloca(dwAllocSize); + if (g_sos->GetGCHeapList(dwNHeaps, heapAddrs, NULL) != S_OK) + { + ExtOut("Failed to get GCHeaps\n"); + return Status; + } + + for (DWORD n = 0; n < dwNHeaps; n ++) + { + if (interestingInfo.Request(g_sos, heapAddrs[n]) != S_OK) + { + ExtOut("Heap %d: Error requesting interesting GC info\n", n); + return E_FAIL; + } + + ExtOut("--------info for heap %d--------\n", n); + PrintInterestingGCInfo(&interestingInfo); + ExtOut("\n"); + } + } + + return S_OK; +#else + ExtOut("Not implemented\n"); + return S_OK; +#endif //GC_CONFIG_DRIVEN +} + +#ifndef FEATURE_PAL +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to dump the build number and type of the * +* mscoree.dll * +* * +\**********************************************************************/ +DECLARE_API (EEVersion) +{ + INIT_API(); + + EEFLAVOR eef = GetEEFlavor(); + if (eef == UNKNOWNEE) { + ExtOut("CLR not loaded\n"); + return Status; + } + + if (g_ExtSymbols2) { + VS_FIXEDFILEINFO version; + + BOOL ret = GetEEVersion(&version); + + if (ret) + { + if (version.dwFileVersionMS != (DWORD)-1) + { + ExtOut("%u.%u.%u.%u", + HIWORD(version.dwFileVersionMS), + LOWORD(version.dwFileVersionMS), + HIWORD(version.dwFileVersionLS), + LOWORD(version.dwFileVersionLS)); + if (version.dwFileFlags & VS_FF_DEBUG) + { + ExtOut(" Checked or debug build"); + } + else + { + BOOL fRet = IsRetailBuild ((size_t)moduleInfo[eef].baseAddr); + + if (fRet) + ExtOut(" retail"); + else + ExtOut(" free"); + } + + ExtOut("\n"); + } + } + } + + if (!InitializeHeapData ()) + ExtOut("GC Heap not initialized, so GC mode is not determined yet.\n"); + else if (IsServerBuild()) + ExtOut("Server mode with %d gc heaps\n", GetGcHeapCount()); + else + ExtOut("Workstation mode\n"); + + if (!GetGcStructuresValid()) + { + ExtOut("In plan phase of garbage collection\n"); + } + + // Print SOS version + VS_FIXEDFILEINFO sosVersion; + if (GetSOSVersion(&sosVersion)) + { + if (sosVersion.dwFileVersionMS != (DWORD)-1) + { + ExtOut("SOS Version: %u.%u.%u.%u", + HIWORD(sosVersion.dwFileVersionMS), + LOWORD(sosVersion.dwFileVersionMS), + HIWORD(sosVersion.dwFileVersionLS), + LOWORD(sosVersion.dwFileVersionLS)); + if (sosVersion.dwFileFlags & VS_FF_DEBUG) + { + ExtOut(" Checked or debug build"); + } + else + { + ExtOut(" retail build"); + } + + ExtOut("\n"); + } + } + return Status; +} +#endif // FEATURE_PAL + +#ifndef FEATURE_PAL +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to print the environment setting for * +* the current process. * +* * +\**********************************************************************/ +DECLARE_API (ProcInfo) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + if (IsDumpFile()) + { + ExtOut("!ProcInfo is not supported on a dump file.\n"); + return Status; + } + +#define INFO_ENV 0x00000001 +#define INFO_TIME 0x00000002 +#define INFO_MEM 0x00000004 +#define INFO_ALL 0xFFFFFFFF + + DWORD fProcInfo = INFO_ALL; + + if (_stricmp (args, "-env") == 0) { + fProcInfo = INFO_ENV; + } + + if (_stricmp (args, "-time") == 0) { + fProcInfo = INFO_TIME; + } + + if (_stricmp (args, "-mem") == 0) { + fProcInfo = INFO_MEM; + } + + if (fProcInfo & INFO_ENV) { + ExtOut("---------------------------------------\n"); + ExtOut("Environment\n"); + ULONG64 pPeb; + g_ExtSystem->GetCurrentProcessPeb(&pPeb); + + static ULONG Offset_ProcessParam = -1; + static ULONG Offset_Environment = -1; + if (Offset_ProcessParam == -1) + { + ULONG TypeId; + ULONG64 NtDllBase; + if (SUCCEEDED(g_ExtSymbols->GetModuleByModuleName ("ntdll",0,NULL, + &NtDllBase))) + { + if (SUCCEEDED(g_ExtSymbols->GetTypeId (NtDllBase, "PEB", &TypeId))) + { + if (FAILED (g_ExtSymbols->GetFieldOffset(NtDllBase, TypeId, + "ProcessParameters", &Offset_ProcessParam))) + Offset_ProcessParam = -1; + } + if (SUCCEEDED(g_ExtSymbols->GetTypeId (NtDllBase, "_RTL_USER_PROCESS_PARAMETERS", &TypeId))) + { + if (FAILED (g_ExtSymbols->GetFieldOffset(NtDllBase, TypeId, + "Environment", &Offset_Environment))) + Offset_Environment = -1; + } + } + } + // We can not get it from PDB. Use the fixed one. + if (Offset_ProcessParam == -1) + Offset_ProcessParam = offsetof (DT_PEB, ProcessParameters); + + if (Offset_Environment == -1) + Offset_Environment = offsetof (DT_RTL_USER_PROCESS_PARAMETERS, Environment); + + + ULONG64 addr = pPeb + Offset_ProcessParam; + DWORD_PTR value; + g_ExtData->ReadVirtual(UL64_TO_CDA(addr), &value, sizeof(PVOID), NULL); + addr = value + Offset_Environment; + g_ExtData->ReadVirtual(UL64_TO_CDA(addr), &value, sizeof(PVOID), NULL); + + static WCHAR buffer[DT_OS_PAGE_SIZE/2]; + ULONG readBytes = DT_OS_PAGE_SIZE; + ULONG64 Page; + if ((g_ExtData->ReadDebuggerData( DEBUG_DATA_MmPageSize, &Page, sizeof(Page), NULL)) == S_OK + && Page > 0) + { + ULONG uPageSize = (ULONG)(ULONG_PTR)Page; + if (readBytes > uPageSize) { + readBytes = uPageSize; + } + } + addr = value; + while (1) { + if (IsInterrupt()) + return Status; + if (FAILED(g_ExtData->ReadVirtual(UL64_TO_CDA(addr), &buffer, readBytes, NULL))) + break; + addr += readBytes; + WCHAR *pt = buffer; + WCHAR *end = pt; + while (pt < &buffer[DT_OS_PAGE_SIZE/2]) { + end = _wcschr (pt, L'\0'); + if (end == NULL) { + char format[20]; + sprintf_s (format,_countof (format), "%dS", &buffer[DT_OS_PAGE_SIZE/2] - pt); + ExtOut(format, pt); + break; + } + else if (end == pt) { + break; + } + ExtOut("%S\n", pt); + pt = end + 1; + } + if (end == pt) { + break; + } + } + } + + HANDLE hProcess = INVALID_HANDLE_VALUE; + if (fProcInfo & (INFO_TIME | INFO_MEM)) { + ULONG64 handle; + g_ExtSystem->GetCurrentProcessHandle(&handle); + hProcess = (HANDLE)handle; + } + + if (!IsDumpFile() && fProcInfo & INFO_TIME) { + FILETIME CreationTime; + FILETIME ExitTime; + FILETIME KernelTime; + FILETIME UserTime; + + typedef BOOL (WINAPI *FntGetProcessTimes)(HANDLE, LPFILETIME, LPFILETIME, LPFILETIME, LPFILETIME); + static FntGetProcessTimes pFntGetProcessTimes = (FntGetProcessTimes)-1; + if (pFntGetProcessTimes == (FntGetProcessTimes)-1) { + HINSTANCE hstat = LoadLibrary ("Kernel32.dll"); + if (hstat != 0) + { + pFntGetProcessTimes = (FntGetProcessTimes)GetProcAddress (hstat, "GetProcessTimes"); + FreeLibrary (hstat); + } + else + pFntGetProcessTimes = NULL; + } + + if (pFntGetProcessTimes && pFntGetProcessTimes (hProcess,&CreationTime,&ExitTime,&KernelTime,&UserTime)) { + ExtOut("---------------------------------------\n"); + ExtOut("Process Times\n"); + static char *Month[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", + "Oct", "Nov", "Dec"}; + SYSTEMTIME SystemTime; + FILETIME LocalFileTime; + if (FileTimeToLocalFileTime (&CreationTime,&LocalFileTime) + && FileTimeToSystemTime (&LocalFileTime,&SystemTime)) { + ExtOut("Process Started at: %4d %s %2d %d:%d:%d.%02d\n", + SystemTime.wYear, Month[SystemTime.wMonth-1], SystemTime.wDay, + SystemTime.wHour, SystemTime.wMinute, + SystemTime.wSecond, SystemTime.wMilliseconds/10); + } + + DWORD nDay = 0; + DWORD nHour = 0; + DWORD nMin = 0; + DWORD nSec = 0; + DWORD nHundred = 0; + + ULONG64 totalTime; + + totalTime = KernelTime.dwLowDateTime + (((ULONG64)KernelTime.dwHighDateTime) << 32); + nDay = (DWORD)(totalTime/(24*3600*10000000ui64)); + totalTime %= 24*3600*10000000ui64; + nHour = (DWORD)(totalTime/(3600*10000000ui64)); + totalTime %= 3600*10000000ui64; + nMin = (DWORD)(totalTime/(60*10000000)); + totalTime %= 60*10000000; + nSec = (DWORD)(totalTime/10000000); + totalTime %= 10000000; + nHundred = (DWORD)(totalTime/100000); + ExtOut("Kernel CPU time : %d days %02d:%02d:%02d.%02d\n", + nDay, nHour, nMin, nSec, nHundred); + + DWORD sDay = nDay; + DWORD sHour = nHour; + DWORD sMin = nMin; + DWORD sSec = nSec; + DWORD sHundred = nHundred; + + totalTime = UserTime.dwLowDateTime + (((ULONG64)UserTime.dwHighDateTime) << 32); + nDay = (DWORD)(totalTime/(24*3600*10000000ui64)); + totalTime %= 24*3600*10000000ui64; + nHour = (DWORD)(totalTime/(3600*10000000ui64)); + totalTime %= 3600*10000000ui64; + nMin = (DWORD)(totalTime/(60*10000000)); + totalTime %= 60*10000000; + nSec = (DWORD)(totalTime/10000000); + totalTime %= 10000000; + nHundred = (DWORD)(totalTime/100000); + ExtOut("User CPU time : %d days %02d:%02d:%02d.%02d\n", + nDay, nHour, nMin, nSec, nHundred); + + sDay += nDay; + sHour += nHour; + sMin += nMin; + sSec += nSec; + sHundred += nHundred; + if (sHundred >= 100) { + sSec += sHundred/100; + sHundred %= 100; + } + if (sSec >= 60) { + sMin += sSec/60; + sSec %= 60; + } + if (sMin >= 60) { + sHour += sMin/60; + sMin %= 60; + } + if (sHour >= 24) { + sDay += sHour/24; + sHour %= 24; + } + ExtOut("Total CPU time : %d days %02d:%02d:%02d.%02d\n", + sDay, sHour, sMin, sSec, sHundred); + } + } + + if (!IsDumpFile() && fProcInfo & INFO_MEM) { + typedef + NTSTATUS + (NTAPI + *FntNtQueryInformationProcess)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG); + + static FntNtQueryInformationProcess pFntNtQueryInformationProcess = (FntNtQueryInformationProcess)-1; + if (pFntNtQueryInformationProcess == (FntNtQueryInformationProcess)-1) { + HINSTANCE hstat = LoadLibrary ("ntdll.dll"); + if (hstat != 0) + { + pFntNtQueryInformationProcess = (FntNtQueryInformationProcess)GetProcAddress (hstat, "NtQueryInformationProcess"); + FreeLibrary (hstat); + } + else + pFntNtQueryInformationProcess = NULL; + } + VM_COUNTERS memory; + if (pFntNtQueryInformationProcess && + NT_SUCCESS (pFntNtQueryInformationProcess (hProcess,ProcessVmCounters,&memory,sizeof(memory),NULL))) { + ExtOut("---------------------------------------\n"); + ExtOut("Process Memory\n"); + ExtOut("WorkingSetSize: %8d KB PeakWorkingSetSize: %8d KB\n", + memory.WorkingSetSize/1024, memory.PeakWorkingSetSize/1024); + ExtOut("VirtualSize: %8d KB PeakVirtualSize: %8d KB\n", + memory.VirtualSize/1024, memory.PeakVirtualSize/1024); + ExtOut("PagefileUsage: %8d KB PeakPagefileUsage: %8d KB\n", + memory.PagefileUsage/1024, memory.PeakPagefileUsage/1024); + } + + MEMORYSTATUS memstat; + GlobalMemoryStatus (&memstat); + ExtOut("---------------------------------------\n"); + ExtOut("%ld percent of memory is in use.\n\n", + memstat.dwMemoryLoad); + ExtOut("Memory Availability (Numbers in MB)\n\n"); + ExtOut(" %8s %8s\n", "Total", "Avail"); + ExtOut("Physical Memory %8d %8d\n", memstat.dwTotalPhys/1024/1024, memstat.dwAvailPhys/1024/1024); + ExtOut("Page File %8d %8d\n", memstat.dwTotalPageFile/1024/1024, memstat.dwAvailPageFile/1024/1024); + ExtOut("Virtual Memory %8d %8d\n", memstat.dwTotalVirtual/1024/1024, memstat.dwAvailVirtual/1024/1024); + } + + return Status; +} +#endif // FEATURE_PAL + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to find the address of EE data for a * +* metadata token. * +* * +\**********************************************************************/ +DECLARE_API(Token2EE) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + StringHolder DllName; + ULONG64 token = 0; + BOOL dml = FALSE; + + CMDOption option[] = + { // name, vptr, type, hasValue +#ifndef FEATURE_PAL + {"/d", &dml, COBOOL, FALSE}, +#endif + }; + + CMDValue arg[] = + { // vptr, type + {&DllName.data, COSTRING}, + {&token, COHEX} + }; + + size_t nArg; + if (!GetCMDOption(args,option, _countof(option), arg, _countof(arg), &nArg)) + { + return Status; + } + if (nArg!=2) + { + ExtOut("Usage: !Token2EE module_name mdToken\n"); + ExtOut(" You can pass * for module_name to search all modules.\n"); + return Status; + } + + EnableDMLHolder dmlHolder(dml); + int numModule; + ArrayHolder<DWORD_PTR> moduleList = NULL; + + if (strcmp(DllName.data, "*") == 0) + { + moduleList = ModuleFromName(NULL, &numModule); + } + else + { + moduleList = ModuleFromName(DllName.data, &numModule); + } + + if (moduleList == NULL) + { + ExtOut("Failed to request module list.\n"); + } + else + { + for (int i = 0; i < numModule; i ++) + { + if (IsInterrupt()) + break; + + if (i > 0) + { + ExtOut("--------------------------------------\n"); + } + + DWORD_PTR dwAddr = moduleList[i]; + WCHAR FileName[MAX_LONGPATH]; + FileNameForModule(dwAddr, FileName); + + // We'd like a short form for this output + LPWSTR pszFilename = _wcsrchr (FileName, DIRECTORY_SEPARATOR_CHAR_W); + if (pszFilename == NULL) + { + pszFilename = FileName; + } + else + { + pszFilename++; // skip past the last "\" character + } + + DMLOut("Module: %s\n", DMLModule(dwAddr)); + ExtOut("Assembly: %S\n", pszFilename); + + GetInfoFromModule(dwAddr, (ULONG)token); + } + } + + return Status; +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to find the address of EE data for a * +* metadata token. * +* * +\**********************************************************************/ +DECLARE_API(Name2EE) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + StringHolder DllName, TypeName; + BOOL dml = FALSE; + + CMDOption option[] = + { // name, vptr, type, hasValue +#ifndef FEATURE_PAL + {"/d", &dml, COBOOL, FALSE}, +#endif + }; + + CMDValue arg[] = + { // vptr, type + {&DllName.data, COSTRING}, + {&TypeName.data, COSTRING} + }; + size_t nArg; + + if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg)) + { + return Status; + } + + EnableDMLHolder dmlHolder(dml); + + if (nArg == 1) + { + // The input may be in the form <modulename>!<type> + // If so, do some surgery on the input params. + + // There should be only 1 ! character + LPSTR pszSeperator = strchr (DllName.data, '!'); + if (pszSeperator != NULL) + { + if (strchr (pszSeperator + 1, '!') == NULL) + { + size_t capacity_TypeName_data = strlen(pszSeperator + 1) + 1; + TypeName.data = new NOTHROW char[capacity_TypeName_data]; + if (TypeName.data) + { + // get the type name, + strcpy_s (TypeName.data, capacity_TypeName_data, pszSeperator + 1); + // and truncate DllName + *pszSeperator = '\0'; + + // Do some extra validation + if (strlen (DllName.data) >= 1 && + strlen (TypeName.data) > 1) + { + nArg = 2; + } + } + } + } + } + + if (nArg != 2) + { + ExtOut("Usage: " SOSPrefix "name2ee module_name item_name\n"); + ExtOut(" or " SOSPrefix "name2ee module_name!item_name\n"); + ExtOut(" use * for module_name to search all loaded modules\n"); + ExtOut("Examples: " SOSPrefix "name2ee mscorlib.dll System.String.ToString\n"); + ExtOut(" " SOSPrefix "name2ee *!System.String\n"); + return Status; + } + + int numModule; + ArrayHolder<DWORD_PTR> moduleList = NULL; + if (strcmp(DllName.data, "*") == 0) + { + moduleList = ModuleFromName(NULL, &numModule); + } + else + { + moduleList = ModuleFromName(DllName.data, &numModule); + } + + + if (moduleList == NULL) + { + ExtOut("Failed to request module list.\n", DllName.data); + } + else + { + for (int i = 0; i < numModule; i ++) + { + if (IsInterrupt()) + break; + + if (i > 0) + { + ExtOut("--------------------------------------\n"); + } + + DWORD_PTR dwAddr = moduleList[i]; + WCHAR FileName[MAX_LONGPATH]; + FileNameForModule (dwAddr, FileName); + + // We'd like a short form for this output + LPWSTR pszFilename = _wcsrchr (FileName, DIRECTORY_SEPARATOR_CHAR_W); + if (pszFilename == NULL) + { + pszFilename = FileName; + } + else + { + pszFilename++; // skip past the last "\" character + } + + DMLOut("Module: %s\n", DMLModule(dwAddr)); + ExtOut("Assembly: %S\n", pszFilename); + GetInfoFromName(dwAddr, TypeName.data); + } + } + + return Status; +} + + +#ifndef FEATURE_PAL +DECLARE_API(PathTo) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + DWORD_PTR root = NULL; + DWORD_PTR target = NULL; + BOOL dml = FALSE; + size_t nArg; + + CMDOption option[] = + { // name, vptr, type, hasValue + {"/d", &dml, COBOOL, FALSE}, + }; + CMDValue arg[] = + { // vptr, type + {&root, COHEX}, + {&target, COHEX}, + }; + if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg)) + { + return Status; + } + + if (root == 0 || target == 0) + { + ExtOut("Invalid argument %s\n", args); + return Status; + } + + GCRootImpl gcroot; + bool result = gcroot.PrintPathToObject(root, target); + + if (!result) + ExtOut("Did not find a path from %p to %p.\n", SOS_PTR(root), SOS_PTR(target)); + + return Status; +} +#endif + + + +/**********************************************************************\ +* Routine Description: * +* * +* This function finds all roots (on stack or in handles) for a * +* given object. * +* * +\**********************************************************************/ +DECLARE_API(GCRoot) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + BOOL bNoStacks = FALSE; + DWORD_PTR obj = NULL; + BOOL dml = FALSE; + BOOL all = FALSE; + size_t nArg; + + CMDOption option[] = + { // name, vptr, type, hasValue + {"-nostacks", &bNoStacks, COBOOL, FALSE}, + {"-all", &all, COBOOL, FALSE}, +#ifndef FEATURE_PAL + {"/d", &dml, COBOOL, FALSE}, +#endif + }; + CMDValue arg[] = + + { // vptr, type + {&obj, COHEX} + }; + if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg)) + { + return Status; + } + if (obj == 0) + { + ExtOut("Invalid argument %s\n", args); + return Status; + } + + EnableDMLHolder dmlHolder(dml); + GCRootImpl gcroot; + int i = gcroot.PrintRootsForObject(obj, all == TRUE, bNoStacks == TRUE); + + if (IsInterrupt()) + ExtOut("Interrupted, data may be incomplete.\n"); + + if (all) + ExtOut("Found %d roots.\n", i); + else + ExtOut("Found %d unique roots (run '!GCRoot -all' to see all roots).\n", i); + + return Status; +} + +DECLARE_API(GCWhere) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + BOOL dml = FALSE; + BOOL bGetBrick; + BOOL bGetCard; + TADDR taddrObj = 0; + size_t nArg; + + CMDOption option[] = + { // name, vptr, type, hasValue + {"-brick", &bGetBrick, COBOOL, FALSE}, + {"-card", &bGetCard, COBOOL, FALSE}, + {"/d", &dml, COBOOL, FALSE}, + }; + CMDValue arg[] = + { // vptr, type + {&taddrObj, COHEX} + }; + if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg)) + { + return Status; + } + + EnableDMLHolder dmlHolder(dml); + // Obtain allocation context for each managed thread. + AllocInfo allocInfo; + allocInfo.Init(); + + TADDR_SEGINFO trngSeg = { 0, 0, 0 }; + TADDR_RANGE allocCtx = { 0, 0 }; + int gen = -1; + BOOL bLarge = FALSE; + BOOL bFound = FALSE; + + size_t size = 0; + if (sos::IsObject(taddrObj)) + { + TADDR taddrMT; + BOOL bContainsPointers; + if(FAILED(GetMTOfObject(taddrObj, &taddrMT)) || + !GetSizeEfficient(taddrObj, taddrMT, FALSE, size, bContainsPointers)) + { + ExtWarn("Couldn't get size for object %#p: possible heap corruption.\n", + SOS_PTR(taddrObj)); + } + } + + if (!IsServerBuild()) + { + DacpGcHeapDetails heapDetails; + if (heapDetails.Request(g_sos) != S_OK) + { + ExtOut("Error requesting gc heap details\n"); + return Status; + } + + if (GCObjInHeap(taddrObj, heapDetails, trngSeg, gen, allocCtx, bLarge)) + { + ExtOut("Address " WIN64_8SPACES " Gen Heap segment " WIN64_8SPACES " begin " WIN64_8SPACES " allocated " WIN64_8SPACES " size\n"); + ExtOut("%p %d %2d %p %p %p 0x%x(%d)\n", + SOS_PTR(taddrObj), gen, 0, SOS_PTR(trngSeg.segAddr), SOS_PTR(trngSeg.start), SOS_PTR(trngSeg.end), size, size); + bFound = TRUE; + } + } + else + { + DacpGcHeapData gcheap; + if (gcheap.Request(g_sos) != S_OK) + { + ExtOut("Error requesting GC Heap data\n"); + return Status; + } + + DWORD dwAllocSize; + DWORD dwNHeaps = gcheap.HeapCount; + if (!ClrSafeInt<DWORD>::multiply(sizeof(CLRDATA_ADDRESS), dwNHeaps, dwAllocSize)) + { + ExtOut("Failed to get GCHeaps: integer overflow\n"); + return Status; + } + + CLRDATA_ADDRESS *heapAddrs = (CLRDATA_ADDRESS*)alloca(dwAllocSize); + if (g_sos->GetGCHeapList(dwNHeaps, heapAddrs, NULL) != S_OK) + { + ExtOut("Failed to get GCHeaps\n"); + return Status; + } + + for (DWORD n = 0; n < dwNHeaps; n ++) + { + DacpGcHeapDetails heapDetails; + if (heapDetails.Request(g_sos, heapAddrs[n]) != S_OK) + { + ExtOut("Error requesting details\n"); + return Status; + } + + if (GCObjInHeap(taddrObj, heapDetails, trngSeg, gen, allocCtx, bLarge)) + { + ExtOut("Address " WIN64_8SPACES " Gen Heap segment " WIN64_8SPACES " begin " WIN64_8SPACES " allocated" WIN64_8SPACES " size\n"); + ExtOut("%p %d %2d %p %p %p 0x%x(%d)\n", + SOS_PTR(taddrObj), gen, n, SOS_PTR(trngSeg.segAddr), SOS_PTR(trngSeg.start), SOS_PTR(trngSeg.end), size, size); + bFound = TRUE; + break; + } + } + } + + if (!bFound) + { + ExtOut("Address %#p not found in the managed heap.\n", SOS_PTR(taddrObj)); + } + + return Status; +} + +#ifndef FEATURE_PAL + +DECLARE_API(FindRoots) +{ +#ifndef FEATURE_PAL + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + if (IsDumpFile()) + { + ExtOut("!FindRoots is not supported on a dump file.\n"); + return Status; + } + + LONG_PTR gen = -100; // initialized outside the legal range: [-1, 2] + StringHolder sgen; + TADDR taObj = NULL; + BOOL dml = FALSE; + size_t nArg; + + CMDOption option[] = + { // name, vptr, type, hasValue + {"-gen", &sgen.data, COSTRING, TRUE}, + {"/d", &dml, COBOOL, FALSE}, + }; + CMDValue arg[] = + { // vptr, type + {&taObj, COHEX} + }; + if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg)) + { + return Status; + } + + EnableDMLHolder dmlHolder(dml); + if (sgen.data != NULL) + { + if (_stricmp(sgen.data, "any") == 0) + { + gen = -1; + } + else + { + gen = GetExpression(sgen.data); + } + } + if ((gen < -1 || gen > 2) && (taObj == 0)) + { + ExtOut("Incorrect options. Usage:\n\t!FindRoots -gen <N>\n\t\twhere N is 0, 1, 2, or \"any\". OR\n\t!FindRoots <obj>\n"); + return Status; + } + + if (gen >= -1 && gen <= 2) + { + IXCLRDataProcess2* idp2 = NULL; + if (FAILED(g_clrData->QueryInterface(IID_IXCLRDataProcess2, (void**) &idp2))) + { + ExtOut("Your version of the runtime/DAC do not support this command.\n"); + return Status; + } + + // Request GC_MARK_END notifications from debuggee + GcEvtArgs gea = { GC_MARK_END, { ((gen == -1) ? 7 : (1 << gen)) } }; + idp2->SetGcNotification(gea); + // ... and register the notification handler + g_ExtControl->Execute(DEBUG_EXECUTE_NOT_LOGGED, "sxe -c \"!HandleCLRN\" clrn", 0); + // the above notification is removed in CNotification::OnGcEvent() + } + else + { + // verify that the last event in the debugger was indeed a CLRN exception + DEBUG_LAST_EVENT_INFO_EXCEPTION dle; + CNotification Notification; + + if (!CheckCLRNotificationEvent(&dle)) + { + ExtOut("The command !FindRoots can only be used after the debugger stopped on a CLRN GC notification.\n"); + ExtOut("At this time !GCRoot should be used instead.\n"); + return Status; + } + // validate argument + if (!g_snapshot.Build()) + { + ExtOut("Unable to build snapshot of the garbage collector state\n"); + return Status; + } + + if (g_snapshot.GetHeap(taObj) == NULL) + { + ExtOut("Address %#p is not in the managed heap.\n", SOS_PTR(taObj)); + return Status; + } + + int ogen = g_snapshot.GetGeneration(taObj); + if (ogen > CNotification::GetCondemnedGen()) + { + DMLOut("Object %s will survive this collection:\n\tgen(%#p) = %d > %d = condemned generation.\n", + DMLObject(taObj), SOS_PTR(taObj), ogen, CNotification::GetCondemnedGen()); + return Status; + } + + GCRootImpl gcroot; + int roots = gcroot.FindRoots(CNotification::GetCondemnedGen(), taObj); + + ExtOut("Found %d roots.\n", roots); + } + + return Status; +#else + return E_NOTIMPL; +#endif +} + +class GCHandleStatsForDomains +{ +public: + const static int SHARED_DOMAIN_INDEX = 0; + const static int SYSTEM_DOMAIN_INDEX = 1; + + GCHandleStatsForDomains() + : m_singleDomainMode(FALSE), m_numDomains(0), m_pStatistics(NULL), m_pDomainPointers(NULL) + { + } + + ~GCHandleStatsForDomains() + { + if (m_pStatistics) + { + if (m_singleDomainMode) + delete m_pStatistics; + else + delete [] m_pStatistics; + } + + if (m_pDomainPointers) + delete [] m_pDomainPointers; + } + + BOOL Init(BOOL singleDomainMode) + { + m_singleDomainMode = singleDomainMode; + if (m_singleDomainMode) + { + m_numDomains = 1; + m_pStatistics = new NOTHROW GCHandleStatistics(); + if (m_pStatistics == NULL) + return FALSE; + } + else + { + DacpAppDomainStoreData adsData; + if (adsData.Request(g_sos) != S_OK) + return FALSE; + + m_numDomains = adsData.DomainCount + 2; + ArrayHolder<CLRDATA_ADDRESS> pArray = new NOTHROW CLRDATA_ADDRESS[adsData.DomainCount + 2]; + if (pArray == NULL) + return FALSE; + + pArray[SHARED_DOMAIN_INDEX] = adsData.sharedDomain; + pArray[SYSTEM_DOMAIN_INDEX] = adsData.systemDomain; + + if (g_sos->GetAppDomainList(adsData.DomainCount, pArray+2, NULL) != S_OK) + return FALSE; + + m_pDomainPointers = pArray.Detach(); + m_pStatistics = new NOTHROW GCHandleStatistics[adsData.DomainCount + 2]; + if (m_pStatistics == NULL) + return FALSE; + } + + return TRUE; + } + + GCHandleStatistics *LookupStatistics(CLRDATA_ADDRESS appDomainPtr) const + { + if (m_singleDomainMode) + { + // You can pass NULL appDomainPtr if you are in singleDomainMode + return m_pStatistics; + } + else + { + for (int i=0; i < m_numDomains; i++) + if (m_pDomainPointers[i] == appDomainPtr) + return m_pStatistics + i; + } + + return NULL; + } + + + GCHandleStatistics *GetStatistics(int appDomainIndex) const + { + SOS_Assert(appDomainIndex >= 0); + SOS_Assert(appDomainIndex < m_numDomains); + + return m_singleDomainMode ? m_pStatistics : m_pStatistics + appDomainIndex; + } + + int GetNumDomains() const + { + return m_numDomains; + } + + CLRDATA_ADDRESS GetDomain(int index) const + { + SOS_Assert(index >= 0); + SOS_Assert(index < m_numDomains); + return m_pDomainPointers[index]; + } + +private: + BOOL m_singleDomainMode; + int m_numDomains; + GCHandleStatistics *m_pStatistics; + CLRDATA_ADDRESS *m_pDomainPointers; +}; + +class GCHandlesImpl +{ +public: + GCHandlesImpl(PCSTR args) + : mPerDomain(FALSE), mStat(FALSE), mDML(FALSE), mType((int)~0) + { + ArrayHolder<char> type = NULL; + CMDOption option[] = + { + {"-perdomain", &mPerDomain, COBOOL, FALSE}, + {"-stat", &mStat, COBOOL, FALSE}, + {"-type", &type, COSTRING, TRUE}, + {"/d", &mDML, COBOOL, FALSE}, + }; + + if (!GetCMDOption(args,option,_countof(option),NULL,0,NULL)) + sos::Throw<sos::Exception>("Failed to parse command line arguments."); + + if (type != NULL) + if (_stricmp(type, "Pinned") == 0) + mType = HNDTYPE_PINNED; + else if (_stricmp(type, "RefCounted") == 0) + mType = HNDTYPE_REFCOUNTED; + else if (_stricmp(type, "WeakShort") == 0) + mType = HNDTYPE_WEAK_SHORT; + else if (_stricmp(type, "WeakLong") == 0) + mType = HNDTYPE_WEAK_LONG; + else if (_stricmp(type, "Strong") == 0) + mType = HNDTYPE_STRONG; + else if (_stricmp(type, "Variable") == 0) + mType = HNDTYPE_VARIABLE; + else if (_stricmp(type, "AsyncPinned") == 0) + mType = HNDTYPE_ASYNCPINNED; + else if (_stricmp(type, "SizedRef") == 0) + mType = HNDTYPE_SIZEDREF; + else if (_stricmp(type, "Dependent") == 0) + mType = HNDTYPE_DEPENDENT; + else if (_stricmp(type, "WeakWinRT") == 0) + mType = HNDTYPE_WEAK_WINRT; + else + sos::Throw<sos::Exception>("Unknown handle type '%s'.", type.GetPtr()); + } + + void Run() + { + EnableDMLHolder dmlHolder(mDML); + + mOut.ReInit(6, POINTERSIZE_HEX, AlignRight); + mOut.SetWidths(5, POINTERSIZE_HEX, 11, POINTERSIZE_HEX, 8, POINTERSIZE_HEX); + mOut.SetColAlignment(1, AlignLeft); + + if (mHandleStat.Init(!mPerDomain) == FALSE) + sos::Throw<sos::Exception>("Error getting per-appdomain handle information"); + + if (!mStat) + mOut.WriteRow("Handle", "Type", "Object", "Size", "Data", "Type"); + + WalkHandles(); + + for (int i=0; (i < mHandleStat.GetNumDomains()) && !IsInterrupt(); i++) + { + GCHandleStatistics *pStats = mHandleStat.GetStatistics(i); + + if (mPerDomain) + { + Print( "------------------------------------------------------------------------------\n"); + Print("GC Handle Statistics for AppDomain ", AppDomainPtr(mHandleStat.GetDomain(i))); + + if (i == GCHandleStatsForDomains::SHARED_DOMAIN_INDEX) + Print(" (Shared Domain)\n"); + else if (i == GCHandleStatsForDomains::SYSTEM_DOMAIN_INDEX) + Print(" (System Domain)\n"); + else + Print("\n"); + } + + if (!mStat) + Print("\n"); + PrintGCStat(&pStats->hs); + + // Don't print handle stats if the user has filtered by type. All handles will be the same + // type, and the total count will be displayed by PrintGCStat. + if (mType == (unsigned int)~0) + { + Print("\n"); + PrintGCHandleStats(pStats); + } + } + } + +private: + void WalkHandles() + { + ToRelease<ISOSHandleEnum> handles; + if (FAILED(g_sos->GetHandleEnum(&handles))) + { + if (IsMiniDumpFile()) + sos::Throw<sos::Exception>("Unable to display GC handles.\nA minidump without full memory may not have this information."); + else + sos::Throw<sos::Exception>("Failed to walk the handle table."); + } + + // GCC can't handle stacks which are too large. +#ifndef FEATURE_PAL + SOSHandleData data[256]; +#else + SOSHandleData data[4]; +#endif + + unsigned int fetched = 0; + HRESULT hr = S_OK; + do + { + if (FAILED(hr = handles->Next(_countof(data), data, &fetched))) + { + ExtOut("Error %x while walking the handle table.\n", hr); + break; + } + + WalkHandles(data, fetched); + } while (_countof(data) == fetched); + } + + void WalkHandles(SOSHandleData data[], unsigned int count) + { + for (unsigned int i = 0; i < count; ++i) + { + sos::CheckInterrupt(); + + if (mType != (unsigned int)~0 && mType != data[i].Type) + continue; + + GCHandleStatistics *pStats = mHandleStat.LookupStatistics(data[i].AppDomain); + TADDR objAddr = 0; + TADDR mtAddr = 0; + size_t size = 0; + const WCHAR *mtName = 0; + const char *type = 0; + + if (FAILED(MOVE(objAddr, data[i].Handle))) + { + objAddr = 0; + mtName = W("<error>"); + } + else + { + sos::Object obj(TO_TADDR(objAddr)); + mtAddr = obj.GetMT(); + if (sos::MethodTable::IsFreeMT(mtAddr)) + { + mtName = W("<free>"); + } + else if (!sos::MethodTable::IsValid(mtAddr)) + { + mtName = W("<error>"); + } + else + { + size = obj.GetSize(); + if (mType == (unsigned int)~0 || mType == data[i].Type) + pStats->hs.Add(obj.GetMT(), (DWORD)size); + } + } + + switch(data[i].Type) + { + case HNDTYPE_PINNED: + type = "Pinned"; + if (pStats) pStats->pinnedHandleCount++; + break; + case HNDTYPE_REFCOUNTED: + type = "RefCounted"; + if (pStats) pStats->refCntHandleCount++; + break; + case HNDTYPE_STRONG: + type = "Strong"; + if (pStats) pStats->strongHandleCount++; + break; + case HNDTYPE_WEAK_SHORT: + type = "WeakShort"; + if (pStats) pStats->weakShortHandleCount++; + break; + case HNDTYPE_WEAK_LONG: + type = "WeakLong"; + if (pStats) pStats->weakLongHandleCount++; + break; + case HNDTYPE_ASYNCPINNED: + type = "AsyncPinned"; + if (pStats) pStats->asyncPinnedHandleCount++; + break; + case HNDTYPE_VARIABLE: + type = "Variable"; + if (pStats) pStats->variableCount++; + break; + case HNDTYPE_SIZEDREF: + type = "SizedRef"; + if (pStats) pStats->sizedRefCount++; + break; + case HNDTYPE_DEPENDENT: + type = "Dependent"; + if (pStats) pStats->dependentCount++; + break; + case HNDTYPE_WEAK_WINRT: + type = "WeakWinRT"; + if (pStats) pStats->weakWinRTHandleCount++; + break; + default: + DebugBreak(); + type = "Unknown"; + pStats->unknownHandleCount++; + break; + } + + if (type && !mStat) + { + sos::MethodTable mt = mtAddr; + if (mtName == 0) + mtName = mt.GetName(); + + if (data[i].Type == HNDTYPE_REFCOUNTED) + mOut.WriteRow(data[i].Handle, type, ObjectPtr(objAddr), Decimal(size), Decimal(data[i].RefCount), mtName); + else if (data[i].Type == HNDTYPE_DEPENDENT) + mOut.WriteRow(data[i].Handle, type, ObjectPtr(objAddr), Decimal(size), ObjectPtr(data[i].Secondary), mtName); + else if (data[i].Type == HNDTYPE_WEAK_WINRT) + mOut.WriteRow(data[i].Handle, type, ObjectPtr(objAddr), Decimal(size), Pointer(data[i].Secondary), mtName); + else + mOut.WriteRow(data[i].Handle, type, ObjectPtr(objAddr), Decimal(size), "", mtName); + } + } + } + + inline void PrintHandleRow(const char *text, int count) + { + if (count) + mOut.WriteRow(text, Decimal(count)); + } + + void PrintGCHandleStats(GCHandleStatistics *pStats) + { + Print("Handles:\n"); + mOut.ReInit(2, 21, AlignLeft, 4); + + PrintHandleRow("Strong Handles:", pStats->strongHandleCount); + PrintHandleRow("Pinned Handles:", pStats->pinnedHandleCount); + PrintHandleRow("Async Pinned Handles:", pStats->asyncPinnedHandleCount); + PrintHandleRow("Ref Count Handles:", pStats->refCntHandleCount); + PrintHandleRow("Weak Long Handles:", pStats->weakLongHandleCount); + PrintHandleRow("Weak Short Handles:", pStats->weakShortHandleCount); + PrintHandleRow("Weak WinRT Handles:", pStats->weakWinRTHandleCount); + PrintHandleRow("Variable Handles:", pStats->variableCount); + PrintHandleRow("SizedRef Handles:", pStats->sizedRefCount); + PrintHandleRow("Dependent Handles:", pStats->dependentCount); + PrintHandleRow("Other Handles:", pStats->unknownHandleCount); + } + +private: + BOOL mPerDomain, mStat, mDML; + unsigned int mType; + TableOutput mOut; + GCHandleStatsForDomains mHandleStat; +}; + +/**********************************************************************\ +* Routine Description: * +* * +* This function dumps GC Handle statistics * +* * +\**********************************************************************/ +DECLARE_API(GCHandles) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + try + { + GCHandlesImpl gchandles(args); + gchandles.Run(); + } + catch(const sos::Exception &e) + { + Print(e.what()); + } + + return Status; +} + +BOOL derivedFrom(CLRDATA_ADDRESS mtObj, __in_z LPWSTR baseString) +{ + // We want to follow back until we get the mt for System.Exception + DacpMethodTableData dmtd; + CLRDATA_ADDRESS walkMT = mtObj; + while(walkMT != NULL) + { + if (dmtd.Request(g_sos, walkMT) != S_OK) + { + break; + } + NameForMT_s (TO_TADDR(walkMT), g_mdName, mdNameLen); + if (_wcscmp (baseString, g_mdName) == 0) + { + return TRUE; + } + walkMT = dmtd.ParentMethodTable; + } + return FALSE; +} + +// This is an experimental and undocumented SOS API that attempts to step through code +// stopping once jitted code is reached. It currently has some issues - it can take arbitrarily long +// to reach jitted code and canceling it is terrible. At best it doesn't cancel, at worst it +// kills the debugger. IsInterrupt() doesn't work nearly as nicely as one would hope :/ +#ifndef FEATURE_PAL +DECLARE_API(TraceToCode) +{ + INIT_API_NOEE(); + + static ULONG64 g_clrBaseAddr = 0; + + + while(true) + { + if (IsInterrupt()) + { + ExtOut("Interrupted\n"); + return S_FALSE; + } + + ULONG64 Offset; + g_ExtRegisters->GetInstructionOffset(&Offset); + + DWORD codeType = 0; + ULONG64 base = 0; + CLRDATA_ADDRESS cdaStart = TO_CDADDR(Offset); + DacpMethodDescData MethodDescData; + if(g_ExtSymbols->GetModuleByOffset(Offset, 0, NULL, &base) == S_OK) + { + if(g_clrBaseAddr == 0) + { + g_ExtSymbols->GetModuleByModuleName (MAIN_CLR_MODULE_NAME_A,0,NULL, + &g_clrBaseAddr); + } + if(g_clrBaseAddr == base) + { + ExtOut("Compiled code in CLR\n"); + codeType = 4; + } + else + { + ExtOut("Compiled code in module @ 0x%I64x\n", base); + codeType = 8; + } + } + else if (g_sos != NULL || LoadClrDebugDll()==S_OK) + { + CLRDATA_ADDRESS addr; + if(g_sos->GetMethodDescPtrFromIP(cdaStart, &addr) == S_OK) + { + WCHAR wszNameBuffer[1024]; // should be large enough + + // get the MethodDesc name + if ((g_sos->GetMethodDescName(addr, 1024, wszNameBuffer, NULL) == S_OK) && + _wcsncmp(W("DomainBoundILStubClass"), wszNameBuffer, 22)==0) + { + ExtOut("ILStub\n"); + codeType = 2; + } + else + { + ExtOut("Jitted code\n"); + codeType = 1; + } + } + else + { + ExtOut("Not compiled or jitted, assuming stub\n"); + codeType = 16; + } + } + else + { + // not compiled but CLR isn't loaded... some other code generator? + return E_FAIL; + } + + if(codeType == 1) + { + return S_OK; + } + else + { + Status = g_ExtControl->Execute(DEBUG_EXECUTE_NOT_LOGGED, "thr; .echo wait" ,0); + if (FAILED(Status)) + { + ExtOut("Error tracing instruction\n"); + return Status; + } + } + } + + return Status; + +} +#endif // FEATURE_PAL + +// This is an experimental and undocumented API that sets a debugger pseudo-register based +// on the type of code at the given IP. It can be used in scripts to keep stepping until certain +// kinds of code have been reached. Presumbably its slower than !TraceToCode but at least it +// cancels much better +#ifndef FEATURE_PAL +DECLARE_API(GetCodeTypeFlags) +{ + INIT_API(); + + + char buffer[100+mdNameLen]; + size_t ip; + StringHolder PReg; + + CMDValue arg[] = { + // vptr, type + {&ip, COSIZE_T}, + {&PReg.data, COSTRING} + }; + size_t nArg; + if (!GetCMDOption(args, NULL, 0, arg, _countof(arg), &nArg)) + { + return Status; + } + + size_t preg = 1; // by default + if (nArg == 2) + { + preg = GetExpression(PReg.data); + if (preg > 19) + { + ExtOut("Pseudo-register number must be between 0 and 19\n"); + return Status; + } + } + + sprintf_s(buffer,_countof (buffer), + "r$t%d=0", + preg); + Status = g_ExtControl->Execute(DEBUG_EXECUTE_NOT_LOGGED, buffer ,0); + if (FAILED(Status)) + { + ExtOut("Error initialized register $t%d to zero\n", preg); + return Status; + } + + ULONG64 base = 0; + CLRDATA_ADDRESS cdaStart = TO_CDADDR(ip); + DWORD codeType = 0; + CLRDATA_ADDRESS addr; + if(g_sos->GetMethodDescPtrFromIP(cdaStart, &addr) == S_OK) + { + WCHAR wszNameBuffer[1024]; // should be large enough + + // get the MethodDesc name + if (g_sos->GetMethodDescName(addr, 1024, wszNameBuffer, NULL) == S_OK && + _wcsncmp(W("DomainBoundILStubClass"), wszNameBuffer, 22)==0) + { + ExtOut("ILStub\n"); + codeType = 2; + } + else + { + ExtOut("Jitted code"); + codeType = 1; + } + } + else if(g_ExtSymbols->GetModuleByOffset (ip, 0, NULL, &base) == S_OK) + { + ULONG64 clrBaseAddr = 0; + if(SUCCEEDED(g_ExtSymbols->GetModuleByModuleName (MAIN_CLR_MODULE_NAME_A,0,NULL, &clrBaseAddr)) && base==clrBaseAddr) + { + ExtOut("Compiled code in CLR"); + codeType = 4; + } + else + { + ExtOut("Compiled code in module @ 0x%I64x\n", base); + codeType = 8; + } + } + else + { + ExtOut("Not compiled or jitted, assuming stub\n"); + codeType = 16; + } + + sprintf_s(buffer,_countof (buffer), + "r$t%d=%x", + preg, codeType); + Status = g_ExtControl->Execute(DEBUG_EXECUTE_NOT_LOGGED, buffer, 0); + if (FAILED(Status)) + { + ExtOut("Error setting register $t%d\n", preg); + return Status; + } + return Status; + +} +#endif // FEATURE_PAL + +DECLARE_API(StopOnException) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + + char buffer[100+mdNameLen]; + + BOOL fDerived = FALSE; + BOOL fCreate1 = FALSE; + BOOL fCreate2 = FALSE; + + CMDOption option[] = { + // name, vptr, type, hasValue + {"-derived", &fDerived, COBOOL, FALSE}, // catch derived exceptions + {"-create", &fCreate1, COBOOL, FALSE}, // create 1st chance handler + {"-create2", &fCreate2, COBOOL, FALSE}, // create 2nd chance handler + }; + + StringHolder TypeName,PReg; + + CMDValue arg[] = { + // vptr, type + {&TypeName.data, COSTRING}, + {&PReg.data, COSTRING} + }; + size_t nArg; + if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg)) + { + return Status; + } + if (IsDumpFile()) + { + ExtOut("Live debugging session required\n"); + return Status; + } + if (nArg < 1 || nArg > 2) + { + ExtOut("usage: StopOnException [-derived] [-create | -create2] <type name>\n"); + ExtOut(" [<pseudo-register number for result>]\n"); + ExtOut("ex: StopOnException -create System.OutOfMemoryException 1\n"); + return Status; + } + + size_t preg = 1; // by default + if (nArg == 2) + { + preg = GetExpression(PReg.data); + if (preg > 19) + { + ExtOut("Pseudo-register number must be between 0 and 19\n"); + return Status; + } + } + + sprintf_s(buffer,_countof (buffer), + "r$t%d=0", + preg); + Status = g_ExtControl->Execute(DEBUG_EXECUTE_NOT_LOGGED, buffer, 0); + if (FAILED(Status)) + { + ExtOut("Error initialized register $t%d to zero\n", preg); + return Status; + } + + if (fCreate1 || fCreate2) + { + sprintf_s(buffer,_countof (buffer), + "sxe %s \"!soe %s %s %d;.if(@$t%d==0) {g} .else {.echo '%s hit'}\" %x", + fCreate1 ? "-c" : "-c2", + fDerived ? "-derived" : "", + TypeName.data, + preg, + preg, + TypeName.data, + EXCEPTION_COMPLUS + ); + + Status = g_ExtControl->Execute(DEBUG_EXECUTE_NOT_LOGGED, buffer, 0); + if (FAILED(Status)) + { + ExtOut("Error setting breakpoint: %s\n", buffer); + return Status; + } + + ExtOut("Breakpoint set\n"); + return Status; + } + + // Find the last thrown exception on this thread. + // Does it match? If so, set the register. + CLRDATA_ADDRESS threadAddr = GetCurrentManagedThread(); + DacpThreadData Thread; + + if ((threadAddr == NULL) || (Thread.Request(g_sos, threadAddr) != S_OK)) + { + ExtOut("The current thread is unmanaged\n"); + return Status; + } + + TADDR taLTOH; + if (!SafeReadMemory(Thread.lastThrownObjectHandle, + &taLTOH, + sizeof(taLTOH), NULL)) + { + ExtOut("There is no current managed exception on this thread\n"); + return Status; + } + + if (taLTOH) + { + LPWSTR typeNameWide = (LPWSTR)alloca(mdNameLen * sizeof(WCHAR)); + MultiByteToWideChar(CP_ACP,0,TypeName.data,-1,typeNameWide,mdNameLen); + + TADDR taMT; + if (SafeReadMemory(taLTOH, &taMT, sizeof(taMT), NULL)) + { + NameForMT_s (taMT, g_mdName, mdNameLen); + if ((_wcscmp(g_mdName,typeNameWide) == 0) || + (fDerived && derivedFrom(taMT, typeNameWide))) + { + sprintf_s(buffer,_countof (buffer), + "r$t%d=1", + preg); + Status = g_ExtControl->Execute(DEBUG_EXECUTE_NOT_LOGGED, buffer, 0); + if (FAILED(Status)) + { + ExtOut("Failed to execute the following command: %s\n", buffer); + } + } + } + } + + return Status; +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function finds the size of an object or all roots. * +* * +\**********************************************************************/ +DECLARE_API(ObjSize) +{ +#ifndef FEATURE_PAL + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + BOOL dml = FALSE; + StringHolder str_Object; + + + CMDOption option[] = + { // name, vptr, type, hasValue + {"/d", &dml, COBOOL, FALSE}, + }; + CMDValue arg[] = + { // vptr, type + {&str_Object.data, COSTRING} + }; + size_t nArg; + if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg)) + { + return Status; + } + + EnableDMLHolder dmlHolder(dml); + TADDR obj = GetExpression(str_Object.data); + + GCRootImpl gcroot; + if (obj == 0) + { + gcroot.ObjSize(); + } + else + { + if(!sos::IsObject(obj)) + { + ExtOut("%p is not a valid object.\n", SOS_PTR(obj)); + return Status; + } + + size_t size = gcroot.ObjSize(obj); + TADDR mt = 0; + MOVE(mt, obj); + sos::MethodTable methodTable = mt; + ExtOut("sizeof(%p) = %d (0x%x) bytes (%S)\n", SOS_PTR(obj), size, size, methodTable.GetName()); + } + return Status; +#else + return E_NOTIMPL; +#endif + +} + +#ifndef FEATURE_PAL +// For FEATURE_PAL, MEMORY_BASIC_INFORMATION64 doesn't exist yet. TODO? +DECLARE_API(GCHandleLeaks) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + ExtOut("-------------------------------------------------------------------------------\n"); + ExtOut("GCHandleLeaks will report any GCHandles that couldn't be found in memory. \n"); + ExtOut("Strong and Pinned GCHandles are reported at this time. You can safely abort the\n"); + ExtOut("memory scan with Control-C or Control-Break. \n"); + ExtOut("-------------------------------------------------------------------------------\n"); + + static DWORD_PTR array[2000]; + UINT i; + BOOL dml = FALSE; + + CMDOption option[] = + { // name, vptr, type, hasValue + {"/d", &dml, COBOOL, FALSE}, + }; + + if (!GetCMDOption(args, option, _countof(option), NULL, 0, NULL)) + { + return Status; + } + + EnableDMLHolder dmlHolder(dml); + + UINT iFinal = FindAllPinnedAndStrong(array,sizeof(array)/sizeof(DWORD_PTR)); + ExtOut("Found %d handles:\n",iFinal); + for (i=1;i<=iFinal;i++) + { + ExtOut("%p\t", SOS_PTR(array[i-1])); + if ((i % 4) == 0) + ExtOut("\n"); + } + + ExtOut("\nSearching memory\n"); + // Now search memory for this: + DWORD_PTR buffer[1024]; + ULONG64 memCur = 0x0; + BOOL bAbort = FALSE; + + //find out memory used by stress log + StressLogMem stressLog; + CLRDATA_ADDRESS StressLogAddress = NULL; + if (LoadClrDebugDll() != S_OK) + { + // Try to find stress log symbols + DWORD_PTR dwAddr = GetValueFromExpression(MAIN_CLR_MODULE_NAME_A "!StressLog::theLog"); + StressLogAddress = dwAddr; + g_bDacBroken = TRUE; + } + else + { + if (g_sos->GetStressLogAddress(&StressLogAddress) != S_OK) + { + ExtOut("Unable to find stress log via DAC\n"); + } + g_bDacBroken = FALSE; + } + + if (stressLog.Init (StressLogAddress, g_ExtData)) + { + ExtOut("Reference found in stress log will be ignored\n"); + } + else + { + ExtOut("Failed to read whole or part of stress log, some references may come from stress log\n"); + } + + + while (!bAbort) + { + NTSTATUS status; + MEMORY_BASIC_INFORMATION64 memInfo; + + status = g_ExtData2->QueryVirtual(UL64_TO_CDA(memCur), &memInfo); + + if( !NT_SUCCESS(status) ) + { + break; + } + + if (memInfo.State == MEM_COMMIT) + { + for (ULONG64 memIter = memCur; memIter < (memCur + memInfo.RegionSize); memIter+=sizeof(buffer)) + { + if (IsInterrupt()) + { + ExtOut("Quitting at %p due to user abort\n", SOS_PTR(memIter)); + bAbort = TRUE; + break; + } + + if ((memIter % 0x10000000)==0x0) + { + ExtOut("Searching %p...\n", SOS_PTR(memIter)); + } + + ULONG size = 0; + HRESULT ret; + ret = g_ExtData->ReadVirtual(UL64_TO_CDA(memIter), buffer, sizeof(buffer), &size); + if (ret == S_OK) + { + for (UINT x=0;x<1024;x++) + { + DWORD_PTR value = buffer[x]; + // We don't care about the low bit. Also, the GCHandle class turns on the + // low bit for pinned handles, so without the statement below, we wouldn't + // notice pinned handles. + value = value & ~1; + for (i=0;i<iFinal;i++) + { + ULONG64 addrInDebugee = (ULONG64)memIter+(x*sizeof(DWORD_PTR)); + if ((array[i] & ~1) == value) + { + if (stressLog.IsInStressLog (addrInDebugee)) + { + ExtOut("Found %p in stress log at location %p, reference not counted\n", SOS_PTR(value), addrInDebugee); + } + else + { + ExtOut("Found %p at location %p\n", SOS_PTR(value), addrInDebugee); + array[i] |= 0x1; + } + } + } + } + } + else + { + if (size > 0) + { + ExtOut("only read %x bytes at %p\n", size, SOS_PTR(memIter)); + } + } + } + } + + memCur += memInfo.RegionSize; + } + + int numNotFound = 0; + for (i=0;i<iFinal;i++) + { + if ((array[i] & 0x1) == 0) + { + numNotFound++; + // ExtOut("WARNING: %p not found\n", SOS_PTR(array[i])); + } + } + + if (numNotFound > 0) + { + ExtOut("------------------------------------------------------------------------------\n"); + ExtOut("Some handles were not found. If the number of not-found handles grows over the\n"); + ExtOut("lifetime of your application, you may have a GCHandle leak. This will cause \n"); + ExtOut("the GC Heap to grow larger as objects are being kept alive, referenced only \n"); + ExtOut("by the orphaned handle. If the number doesn't grow over time, note that there \n"); + ExtOut("may be some noise in this output, as an unmanaged application may be storing \n"); + ExtOut("the handle in a non-standard way, perhaps with some bits flipped. The memory \n"); + ExtOut("scan wouldn't be able to find those. \n"); + ExtOut("------------------------------------------------------------------------------\n"); + + ExtOut("Didn't find %d handles:\n", numNotFound); + int numPrinted=0; + for (i=0;i<iFinal;i++) + { + if ((array[i] & 0x1) == 0) + { + numPrinted++; + ExtOut("%p\t", SOS_PTR(array[i])); + if ((numPrinted % 4) == 0) + ExtOut("\n"); + } + } + ExtOut("\n"); + } + else + { + ExtOut("------------------------------------------------------------------------------\n"); + ExtOut("All handles found"); + if (bAbort) + ExtOut(" even though you aborted.\n"); + else + ExtOut(".\n"); + ExtOut("A leak may still exist because in a general scan of process memory SOS can't \n"); + ExtOut("differentiate between garbage and valid structures, so you may have false \n"); + ExtOut("positives. If you still suspect a leak, use this function over time to \n"); + ExtOut("identify a possible trend. \n"); + ExtOut("------------------------------------------------------------------------------\n"); + } + + return Status; +} +#endif // FEATURE_PAL + +#endif // FEATURE_PAL + +class ClrStackImplWithICorDebug +{ +private: + static HRESULT DereferenceAndUnboxValue(ICorDebugValue * pValue, ICorDebugValue** ppOutputValue, BOOL * pIsNull = NULL) + { + HRESULT Status = S_OK; + *ppOutputValue = NULL; + if(pIsNull != NULL) *pIsNull = FALSE; + + ToRelease<ICorDebugReferenceValue> pReferenceValue; + Status = pValue->QueryInterface(IID_ICorDebugReferenceValue, (LPVOID*) &pReferenceValue); + if (SUCCEEDED(Status)) + { + BOOL isNull = FALSE; + IfFailRet(pReferenceValue->IsNull(&isNull)); + if(!isNull) + { + ToRelease<ICorDebugValue> pDereferencedValue; + IfFailRet(pReferenceValue->Dereference(&pDereferencedValue)); + return DereferenceAndUnboxValue(pDereferencedValue, ppOutputValue); + } + else + { + if(pIsNull != NULL) *pIsNull = TRUE; + *ppOutputValue = pValue; + (*ppOutputValue)->AddRef(); + return S_OK; + } + } + + ToRelease<ICorDebugBoxValue> pBoxedValue; + Status = pValue->QueryInterface(IID_ICorDebugBoxValue, (LPVOID*) &pBoxedValue); + if (SUCCEEDED(Status)) + { + ToRelease<ICorDebugObjectValue> pUnboxedValue; + IfFailRet(pBoxedValue->GetObject(&pUnboxedValue)); + return DereferenceAndUnboxValue(pUnboxedValue, ppOutputValue); + } + *ppOutputValue = pValue; + (*ppOutputValue)->AddRef(); + return S_OK; + } + + static BOOL ShouldExpandVariable(__in_z WCHAR* varToExpand, __in_z WCHAR* currentExpansion) + { + if(currentExpansion == NULL || varToExpand == NULL) return FALSE; + + size_t varToExpandLen = _wcslen(varToExpand); + size_t currentExpansionLen = _wcslen(currentExpansion); + if(currentExpansionLen > varToExpandLen) return FALSE; + if(currentExpansionLen < varToExpandLen && varToExpand[currentExpansionLen] != L'.') return FALSE; + if(_wcsncmp(currentExpansion, varToExpand, currentExpansionLen) != 0) return FALSE; + + return TRUE; + } + + static BOOL IsEnum(ICorDebugValue * pInputValue) + { + ToRelease<ICorDebugValue> pValue; + if(FAILED(DereferenceAndUnboxValue(pInputValue, &pValue, NULL))) return FALSE; + + WCHAR baseTypeName[mdNameLen]; + ToRelease<ICorDebugValue2> pValue2; + ToRelease<ICorDebugType> pType; + ToRelease<ICorDebugType> pBaseType; + + if(FAILED(pValue->QueryInterface(IID_ICorDebugValue2, (LPVOID *) &pValue2))) return FALSE; + if(FAILED(pValue2->GetExactType(&pType))) return FALSE; + if(FAILED(pType->GetBase(&pBaseType)) || pBaseType == NULL) return FALSE; + if(FAILED(GetTypeOfValue(pBaseType, baseTypeName, mdNameLen))) return FALSE; + + return (_wcsncmp(baseTypeName, W("System.Enum"), 11) == 0); + } + + static HRESULT AddGenericArgs(ICorDebugType * pType, __inout_ecount(typeNameLen) WCHAR* typeName, ULONG typeNameLen) + { + bool isFirst = true; + ToRelease<ICorDebugTypeEnum> pTypeEnum; + if(SUCCEEDED(pType->EnumerateTypeParameters(&pTypeEnum))) + { + ULONG numTypes = 0; + ToRelease<ICorDebugType> pCurrentTypeParam; + + while(SUCCEEDED(pTypeEnum->Next(1, &pCurrentTypeParam, &numTypes))) + { + if(numTypes == 0) break; + + if(isFirst) + { + isFirst = false; + wcsncat_s(typeName, typeNameLen, W("<"), typeNameLen); + } + else wcsncat_s(typeName, typeNameLen, W(","), typeNameLen); + + WCHAR typeParamName[mdNameLen]; + typeParamName[0] = L'\0'; + GetTypeOfValue(pCurrentTypeParam, typeParamName, mdNameLen); + wcsncat_s(typeName, typeNameLen, typeParamName, typeNameLen); + } + if(!isFirst) + wcsncat_s(typeName, typeNameLen, W(">"), typeNameLen); + } + + return S_OK; + } + + static HRESULT GetTypeOfValue(ICorDebugType * pType, __inout_ecount(typeNameLen) WCHAR* typeName, ULONG typeNameLen) + { + HRESULT Status = S_OK; + + CorElementType corElemType; + IfFailRet(pType->GetType(&corElemType)); + + switch (corElemType) + { + //List of unsupported CorElementTypes: + //ELEMENT_TYPE_END = 0x0, + //ELEMENT_TYPE_VAR = 0x13, // a class type variable VAR <U1> + //ELEMENT_TYPE_GENERICINST = 0x15, // GENERICINST <generic type> <argCnt> <arg1> ... <argn> + //ELEMENT_TYPE_TYPEDBYREF = 0x16, // TYPEDREF (it takes no args) a typed referece to some other type + //ELEMENT_TYPE_MVAR = 0x1e, // a method type variable MVAR <U1> + //ELEMENT_TYPE_CMOD_REQD = 0x1F, // required C modifier : E_T_CMOD_REQD <mdTypeRef/mdTypeDef> + //ELEMENT_TYPE_CMOD_OPT = 0x20, // optional C modifier : E_T_CMOD_OPT <mdTypeRef/mdTypeDef> + //ELEMENT_TYPE_INTERNAL = 0x21, // INTERNAL <typehandle> + //ELEMENT_TYPE_MAX = 0x22, // first invalid element type + //ELEMENT_TYPE_MODIFIER = 0x40, + //ELEMENT_TYPE_SENTINEL = 0x01 | ELEMENT_TYPE_MODIFIER, // sentinel for varargs + //ELEMENT_TYPE_PINNED = 0x05 | ELEMENT_TYPE_MODIFIER, + //ELEMENT_TYPE_R4_HFA = 0x06 | ELEMENT_TYPE_MODIFIER, // used only internally for R4 HFA types + //ELEMENT_TYPE_R8_HFA = 0x07 | ELEMENT_TYPE_MODIFIER, // used only internally for R8 HFA types + default: + swprintf_s(typeName, typeNameLen, W("(Unhandled CorElementType: 0x%x)\0"), corElemType); + break; + + case ELEMENT_TYPE_VALUETYPE: + case ELEMENT_TYPE_CLASS: + { + //Defaults in case we fail... + if(corElemType == ELEMENT_TYPE_VALUETYPE) swprintf_s(typeName, typeNameLen, W("struct\0")); + else swprintf_s(typeName, typeNameLen, W("class\0")); + + mdTypeDef typeDef; + ToRelease<ICorDebugClass> pClass; + if(SUCCEEDED(pType->GetClass(&pClass)) && SUCCEEDED(pClass->GetToken(&typeDef))) + { + ToRelease<ICorDebugModule> pModule; + IfFailRet(pClass->GetModule(&pModule)); + + ToRelease<IUnknown> pMDUnknown; + ToRelease<IMetaDataImport> pMD; + IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown)); + IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD)); + + if(SUCCEEDED(NameForToken_s(TokenFromRid(typeDef, mdtTypeDef), pMD, g_mdName, mdNameLen, false))) + swprintf_s(typeName, typeNameLen, W("%s\0"), g_mdName); + } + AddGenericArgs(pType, typeName, typeNameLen); + } + break; + case ELEMENT_TYPE_VOID: + swprintf_s(typeName, typeNameLen, W("void\0")); + break; + case ELEMENT_TYPE_BOOLEAN: + swprintf_s(typeName, typeNameLen, W("bool\0")); + break; + case ELEMENT_TYPE_CHAR: + swprintf_s(typeName, typeNameLen, W("char\0")); + break; + case ELEMENT_TYPE_I1: + swprintf_s(typeName, typeNameLen, W("signed byte\0")); + break; + case ELEMENT_TYPE_U1: + swprintf_s(typeName, typeNameLen, W("byte\0")); + break; + case ELEMENT_TYPE_I2: + swprintf_s(typeName, typeNameLen, W("short\0")); + break; + case ELEMENT_TYPE_U2: + swprintf_s(typeName, typeNameLen, W("unsigned short\0")); + break; + case ELEMENT_TYPE_I4: + swprintf_s(typeName, typeNameLen, W("int\0")); + break; + case ELEMENT_TYPE_U4: + swprintf_s(typeName, typeNameLen, W("unsigned int\0")); + break; + case ELEMENT_TYPE_I8: + swprintf_s(typeName, typeNameLen, W("long\0")); + break; + case ELEMENT_TYPE_U8: + swprintf_s(typeName, typeNameLen, W("unsigned long\0")); + break; + case ELEMENT_TYPE_R4: + swprintf_s(typeName, typeNameLen, W("float\0")); + break; + case ELEMENT_TYPE_R8: + swprintf_s(typeName, typeNameLen, W("double\0")); + break; + case ELEMENT_TYPE_OBJECT: + swprintf_s(typeName, typeNameLen, W("object\0")); + break; + case ELEMENT_TYPE_STRING: + swprintf_s(typeName, typeNameLen, W("string\0")); + break; + case ELEMENT_TYPE_I: + swprintf_s(typeName, typeNameLen, W("IntPtr\0")); + break; + case ELEMENT_TYPE_U: + swprintf_s(typeName, typeNameLen, W("UIntPtr\0")); + break; + case ELEMENT_TYPE_SZARRAY: + case ELEMENT_TYPE_ARRAY: + case ELEMENT_TYPE_BYREF: + case ELEMENT_TYPE_PTR: + { + ToRelease<ICorDebugType> pFirstParameter; + if(SUCCEEDED(pType->GetFirstTypeParameter(&pFirstParameter))) + GetTypeOfValue(pFirstParameter, typeName, typeNameLen); + else + swprintf_s(typeName, typeNameLen, W("<unknown>\0")); + + switch(corElemType) + { + case ELEMENT_TYPE_SZARRAY: + wcsncat_s(typeName, typeNameLen, W("[]\0"), typeNameLen); + return S_OK; + case ELEMENT_TYPE_ARRAY: + { + ULONG32 rank = 0; + pType->GetRank(&rank); + wcsncat_s(typeName, typeNameLen, W("["), typeNameLen); + for(ULONG32 i = 0; i < rank - 1; i++) + { + // + wcsncat_s(typeName, typeNameLen, W(","), typeNameLen); + } + wcsncat_s(typeName, typeNameLen, W("]\0"), typeNameLen); + } + return S_OK; + case ELEMENT_TYPE_BYREF: + wcsncat_s(typeName, typeNameLen, W("&\0"), typeNameLen); + return S_OK; + case ELEMENT_TYPE_PTR: + wcsncat_s(typeName, typeNameLen, W("*\0"), typeNameLen); + return S_OK; + default: + // note we can never reach here as this is a nested switch + // and corElemType can only be one of the values above + break; + } + } + break; + case ELEMENT_TYPE_FNPTR: + swprintf_s(typeName, typeNameLen, W("*(...)\0")); + break; + case ELEMENT_TYPE_TYPEDBYREF: + swprintf_s(typeName, typeNameLen, W("typedbyref\0")); + break; + } + return S_OK; + } + + static HRESULT GetTypeOfValue(ICorDebugValue * pValue, __inout_ecount(typeNameLen) WCHAR* typeName, ULONG typeNameLen) + { + HRESULT Status = S_OK; + + CorElementType corElemType; + IfFailRet(pValue->GetType(&corElemType)); + + ToRelease<ICorDebugType> pType; + ToRelease<ICorDebugValue2> pValue2; + if(SUCCEEDED(pValue->QueryInterface(IID_ICorDebugValue2, (void**) &pValue2)) && SUCCEEDED(pValue2->GetExactType(&pType))) + return GetTypeOfValue(pType, typeName, typeNameLen); + else + swprintf_s(typeName, typeNameLen, W("<unknown>\0")); + + return S_OK; + } + + static HRESULT PrintEnumValue(ICorDebugValue* pInputValue, BYTE* enumValue) + { + HRESULT Status = S_OK; + + ToRelease<ICorDebugValue> pValue; + IfFailRet(DereferenceAndUnboxValue(pInputValue, &pValue, NULL)); + + mdTypeDef currentTypeDef; + ToRelease<ICorDebugClass> pClass; + ToRelease<ICorDebugValue2> pValue2; + ToRelease<ICorDebugType> pType; + ToRelease<ICorDebugModule> pModule; + IfFailRet(pValue->QueryInterface(IID_ICorDebugValue2, (LPVOID *) &pValue2)); + IfFailRet(pValue2->GetExactType(&pType)); + IfFailRet(pType->GetClass(&pClass)); + IfFailRet(pClass->GetModule(&pModule)); + IfFailRet(pClass->GetToken(¤tTypeDef)); + + ToRelease<IUnknown> pMDUnknown; + ToRelease<IMetaDataImport> pMD; + IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown)); + IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD)); + + + //First, we need to figure out the underlying enum type so that we can correctly type cast the raw values of each enum constant + //We get that from the non-static field of the enum variable (I think the field is called __value or something similar) + ULONG numFields = 0; + HCORENUM fEnum = NULL; + mdFieldDef fieldDef; + CorElementType enumUnderlyingType = ELEMENT_TYPE_END; + while(SUCCEEDED(pMD->EnumFields(&fEnum, currentTypeDef, &fieldDef, 1, &numFields)) && numFields != 0) + { + DWORD fieldAttr = 0; + PCCOR_SIGNATURE pSignatureBlob = NULL; + ULONG sigBlobLength = 0; + if(SUCCEEDED(pMD->GetFieldProps(fieldDef, NULL, NULL, 0, NULL, &fieldAttr, &pSignatureBlob, &sigBlobLength, NULL, NULL, NULL))) + { + if((fieldAttr & fdStatic) == 0) + { + CorSigUncompressCallingConv(pSignatureBlob); + enumUnderlyingType = CorSigUncompressElementType(pSignatureBlob); + break; + } + } + } + pMD->CloseEnum(fEnum); + + + //Now that we know the underlying enum type, let's decode the enum variable into OR-ed, human readable enum contants + fEnum = NULL; + bool isFirst = true; + ULONG64 remainingValue = *((ULONG64*)enumValue); + while(SUCCEEDED(pMD->EnumFields(&fEnum, currentTypeDef, &fieldDef, 1, &numFields)) && numFields != 0) + { + ULONG nameLen = 0; + DWORD fieldAttr = 0; + WCHAR mdName[mdNameLen]; + WCHAR typeName[mdNameLen]; + UVCP_CONSTANT pRawValue = NULL; + ULONG rawValueLength = 0; + if(SUCCEEDED(pMD->GetFieldProps(fieldDef, NULL, mdName, mdNameLen, &nameLen, &fieldAttr, NULL, NULL, NULL, &pRawValue, &rawValueLength))) + { + DWORD enumValueRequiredAttributes = fdPublic | fdStatic | fdLiteral | fdHasDefault; + if((fieldAttr & enumValueRequiredAttributes) != enumValueRequiredAttributes) + continue; + + ULONG64 currentConstValue = 0; + switch (enumUnderlyingType) + { + case ELEMENT_TYPE_CHAR: + case ELEMENT_TYPE_I1: + currentConstValue = (ULONG64)(*((CHAR*)pRawValue)); + break; + case ELEMENT_TYPE_U1: + currentConstValue = (ULONG64)(*((BYTE*)pRawValue)); + break; + case ELEMENT_TYPE_I2: + currentConstValue = (ULONG64)(*((SHORT*)pRawValue)); + break; + case ELEMENT_TYPE_U2: + currentConstValue = (ULONG64)(*((USHORT*)pRawValue)); + break; + case ELEMENT_TYPE_I4: + currentConstValue = (ULONG64)(*((INT32*)pRawValue)); + break; + case ELEMENT_TYPE_U4: + currentConstValue = (ULONG64)(*((UINT32*)pRawValue)); + break; + case ELEMENT_TYPE_I8: + currentConstValue = (ULONG64)(*((LONG*)pRawValue)); + break; + case ELEMENT_TYPE_U8: + currentConstValue = (ULONG64)(*((ULONG*)pRawValue)); + break; + case ELEMENT_TYPE_I: + currentConstValue = (ULONG64)(*((int*)pRawValue)); + break; + case ELEMENT_TYPE_U: + case ELEMENT_TYPE_R4: + case ELEMENT_TYPE_R8: + // Technically U and the floating-point ones are options in the CLI, but not in the CLS or C#, so these are NYI + default: + currentConstValue = 0; + } + + if((currentConstValue == remainingValue) || ((currentConstValue != 0) && ((currentConstValue & remainingValue) == currentConstValue))) + { + remainingValue &= ~currentConstValue; + if(isFirst) + { + ExtOut(" = %S", mdName); + isFirst = false; + } + else ExtOut(" | %S", mdName); + } + } + } + pMD->CloseEnum(fEnum); + + return S_OK; + } + + static HRESULT PrintStringValue(ICorDebugValue * pValue) + { + HRESULT Status; + + ToRelease<ICorDebugStringValue> pStringValue; + IfFailRet(pValue->QueryInterface(IID_ICorDebugStringValue, (LPVOID*) &pStringValue)); + + ULONG32 cchValue; + IfFailRet(pStringValue->GetLength(&cchValue)); + cchValue++; // Allocate one more for null terminator + + CQuickString quickString; + quickString.Alloc(cchValue); + + ULONG32 cchValueReturned; + IfFailRet(pStringValue->GetString( + cchValue, + &cchValueReturned, + quickString.String())); + + ExtOut(" = \"%S\"\n", quickString.String()); + + return S_OK; + } + + static HRESULT PrintSzArrayValue(ICorDebugValue * pValue, ICorDebugILFrame * pILFrame, IMetaDataImport * pMD, int indent, __in_z WCHAR* varToExpand, __inout_ecount(currentExpansionSize) WCHAR* currentExpansion, DWORD currentExpansionSize, int currentFrame) + { + HRESULT Status = S_OK; + + ToRelease<ICorDebugArrayValue> pArrayValue; + IfFailRet(pValue->QueryInterface(IID_ICorDebugArrayValue, (LPVOID*) &pArrayValue)); + + ULONG32 nRank; + IfFailRet(pArrayValue->GetRank(&nRank)); + if (nRank != 1) + { + return E_UNEXPECTED; + } + + ULONG32 cElements; + IfFailRet(pArrayValue->GetCount(&cElements)); + + if (cElements == 0) ExtOut(" (empty)\n"); + else if (cElements == 1) ExtOut(" (1 element)\n"); + else ExtOut(" (%d elements)\n", cElements); + + if(!ShouldExpandVariable(varToExpand, currentExpansion)) return S_OK; + size_t currentExpansionLen = _wcslen(currentExpansion); + + for (ULONG32 i=0; i < cElements; i++) + { + for(int j = 0; j <= indent; j++) ExtOut(" "); + currentExpansion[currentExpansionLen] = L'\0'; + swprintf_s(currentExpansion, mdNameLen, W("%s.[%d]\0"), currentExpansion, i); + + bool printed = false; + CorElementType corElemType; + ToRelease<ICorDebugType> pFirstParameter; + ToRelease<ICorDebugValue2> pValue2; + ToRelease<ICorDebugType> pType; + if(SUCCEEDED(pArrayValue->QueryInterface(IID_ICorDebugValue2, (LPVOID *) &pValue2)) && SUCCEEDED(pValue2->GetExactType(&pType))) + { + if(SUCCEEDED(pType->GetFirstTypeParameter(&pFirstParameter)) && SUCCEEDED(pFirstParameter->GetType(&corElemType))) + { + switch(corElemType) + { + //If the array element is something that we can expand with !clrstack, show information about the type of this element + case ELEMENT_TYPE_VALUETYPE: + case ELEMENT_TYPE_CLASS: + case ELEMENT_TYPE_SZARRAY: + { + WCHAR typeOfElement[mdNameLen]; + GetTypeOfValue(pFirstParameter, typeOfElement, mdNameLen); + DMLOut(" |- %s = %S", DMLManagedVar(currentExpansion, currentFrame, i), typeOfElement); + printed = true; + } + break; + default: + break; + } + } + } + if(!printed) DMLOut(" |- %s", DMLManagedVar(currentExpansion, currentFrame, i)); + + ToRelease<ICorDebugValue> pElementValue; + IfFailRet(pArrayValue->GetElementAtPosition(i, &pElementValue)); + IfFailRet(PrintValue(pElementValue, pILFrame, pMD, indent + 1, varToExpand, currentExpansion, currentExpansionSize, currentFrame)); + } + + return S_OK; + } + + static HRESULT PrintValue(ICorDebugValue * pInputValue, ICorDebugILFrame * pILFrame, IMetaDataImport * pMD, int indent, __in_z WCHAR* varToExpand, __inout_ecount(currentExpansionSize) WCHAR* currentExpansion, DWORD currentExpansionSize, int currentFrame) + { + HRESULT Status = S_OK; + + BOOL isNull = TRUE; + ToRelease<ICorDebugValue> pValue; + IfFailRet(DereferenceAndUnboxValue(pInputValue, &pValue, &isNull)); + + if(isNull) + { + ExtOut(" = null\n"); + return S_OK; + } + + ULONG32 cbSize; + IfFailRet(pValue->GetSize(&cbSize)); + ArrayHolder<BYTE> rgbValue = new NOTHROW BYTE[cbSize]; + if (rgbValue == NULL) + { + ReportOOM(); + return E_OUTOFMEMORY; + } + + memset(rgbValue.GetPtr(), 0, cbSize * sizeof(BYTE)); + + CorElementType corElemType; + IfFailRet(pValue->GetType(&corElemType)); + if (corElemType == ELEMENT_TYPE_STRING) + { + return PrintStringValue(pValue); + } + + if (corElemType == ELEMENT_TYPE_SZARRAY) + { + return PrintSzArrayValue(pValue, pILFrame, pMD, indent, varToExpand, currentExpansion, currentExpansionSize, currentFrame); + } + + ToRelease<ICorDebugGenericValue> pGenericValue; + IfFailRet(pValue->QueryInterface(IID_ICorDebugGenericValue, (LPVOID*) &pGenericValue)); + IfFailRet(pGenericValue->GetValue((LPVOID) &(rgbValue[0]))); + + if(IsEnum(pValue)) + { + Status = PrintEnumValue(pValue, rgbValue); + ExtOut("\n"); + return Status; + } + + switch (corElemType) + { + default: + ExtOut(" (Unhandled CorElementType: 0x%x)\n", corElemType); + break; + + case ELEMENT_TYPE_PTR: + ExtOut(" = <pointer>\n"); + break; + + case ELEMENT_TYPE_FNPTR: + { + CORDB_ADDRESS addr = 0; + ToRelease<ICorDebugReferenceValue> pReferenceValue = NULL; + if(SUCCEEDED(pValue->QueryInterface(IID_ICorDebugReferenceValue, (LPVOID*) &pReferenceValue))) + pReferenceValue->GetValue(&addr); + ExtOut(" = <function pointer 0x%x>\n", addr); + } + break; + + case ELEMENT_TYPE_VALUETYPE: + case ELEMENT_TYPE_CLASS: + CORDB_ADDRESS addr; + if(SUCCEEDED(pValue->GetAddress(&addr))) + { + ExtOut(" @ 0x%I64x\n", addr); + } + else + { + ExtOut("\n"); + } + ProcessFields(pValue, NULL, pILFrame, indent + 1, varToExpand, currentExpansion, currentExpansionSize, currentFrame); + break; + + case ELEMENT_TYPE_BOOLEAN: + ExtOut(" = %s\n", rgbValue[0] == 0 ? "false" : "true"); + break; + + case ELEMENT_TYPE_CHAR: + ExtOut(" = '%C'\n", *(WCHAR *) &(rgbValue[0])); + break; + + case ELEMENT_TYPE_I1: + ExtOut(" = %d\n", *(char*) &(rgbValue[0])); + break; + + case ELEMENT_TYPE_U1: + ExtOut(" = %d\n", *(unsigned char*) &(rgbValue[0])); + break; + + case ELEMENT_TYPE_I2: + ExtOut(" = %hd\n", *(short*) &(rgbValue[0])); + break; + + case ELEMENT_TYPE_U2: + ExtOut(" = %hu\n", *(unsigned short*) &(rgbValue[0])); + break; + + case ELEMENT_TYPE_I: + ExtOut(" = %d\n", *(int*) &(rgbValue[0])); + break; + + case ELEMENT_TYPE_U: + ExtOut(" = %u\n", *(unsigned int*) &(rgbValue[0])); + break; + + case ELEMENT_TYPE_I4: + ExtOut(" = %d\n", *(int*) &(rgbValue[0])); + break; + + case ELEMENT_TYPE_U4: + ExtOut(" = %u\n", *(unsigned int*) &(rgbValue[0])); + break; + + case ELEMENT_TYPE_I8: + ExtOut(" = %I64d\n", *(__int64*) &(rgbValue[0])); + break; + + case ELEMENT_TYPE_U8: + ExtOut(" = %I64u\n", *(unsigned __int64*) &(rgbValue[0])); + break; + + case ELEMENT_TYPE_R4: + ExtOut(" = %f\n", (double) *(float*) &(rgbValue[0])); + break; + + case ELEMENT_TYPE_R8: + ExtOut(" = %f\n", *(double*) &(rgbValue[0])); + break; + + case ELEMENT_TYPE_OBJECT: + ExtOut(" = object\n"); + break; + + // TODO: The following corElementTypes are not yet implemented here. Array + // might be interesting to add, though the others may be of rather limited use: + // ELEMENT_TYPE_ARRAY = 0x14, // MDARRAY <type> <rank> <bcount> <bound1> ... <lbcount> <lb1> ... + // + // ELEMENT_TYPE_GENERICINST = 0x15, // GENERICINST <generic type> <argCnt> <arg1> ... <argn> + } + + return S_OK; + } + + static HRESULT PrintParameters(BOOL bParams, BOOL bLocals, IMetaDataImport * pMD, mdTypeDef typeDef, mdMethodDef methodDef, ICorDebugILFrame * pILFrame, ICorDebugModule * pModule, __in_z WCHAR* varToExpand, int currentFrame) + { + HRESULT Status = S_OK; + + ULONG cParams = 0; + ToRelease<ICorDebugValueEnum> pParamEnum; + IfFailRet(pILFrame->EnumerateArguments(&pParamEnum)); + IfFailRet(pParamEnum->GetCount(&cParams)); + if (cParams > 0 && bParams) + { + DWORD methAttr = 0; + IfFailRet(pMD->GetMethodProps(methodDef, NULL, NULL, 0, NULL, &methAttr, NULL, NULL, NULL, NULL)); + + ExtOut("\nPARAMETERS:\n"); + for (ULONG i=0; i < cParams; i++) + { + ULONG paramNameLen = 0; + mdParamDef paramDef; + WCHAR paramName[mdNameLen] = W("\0"); + + if(i == 0 && (methAttr & mdStatic) == 0) + swprintf_s(paramName, mdNameLen, W("this\0")); + else + { + int idx = ((methAttr & mdStatic) == 0)? i : (i + 1); + if(SUCCEEDED(pMD->GetParamForMethodIndex(methodDef, idx, ¶mDef))) + pMD->GetParamProps(paramDef, NULL, NULL, paramName, mdNameLen, ¶mNameLen, NULL, NULL, NULL, NULL); + } + if(_wcslen(paramName) == 0) + swprintf_s(paramName, mdNameLen, W("param_%d\0"), i); + + ToRelease<ICorDebugValue> pValue; + ULONG cArgsFetched; + Status = pParamEnum->Next(1, &pValue, &cArgsFetched); + + if (FAILED(Status)) + { + ExtOut(" + (Error 0x%x retrieving parameter '%S')\n", Status, paramName); + continue; + } + + if (Status == S_FALSE) + { + break; + } + + WCHAR typeName[mdNameLen] = W("\0"); + GetTypeOfValue(pValue, typeName, mdNameLen); + DMLOut(" + %S %s", typeName, DMLManagedVar(paramName, currentFrame, paramName)); + + ToRelease<ICorDebugReferenceValue> pRefValue; + if(SUCCEEDED(pValue->QueryInterface(IID_ICorDebugReferenceValue, (void**)&pRefValue)) && pRefValue != NULL) + { + BOOL bIsNull = TRUE; + pRefValue->IsNull(&bIsNull); + if(bIsNull) + { + ExtOut(" = null\n"); + continue; + } + } + + WCHAR currentExpansion[mdNameLen]; + swprintf_s(currentExpansion, mdNameLen, W("%s\0"), paramName); + if((Status=PrintValue(pValue, pILFrame, pMD, 0, varToExpand, currentExpansion, mdNameLen, currentFrame)) != S_OK) + ExtOut(" + (Error 0x%x printing parameter %d)\n", Status, i); + } + } + else if (cParams == 0 && bParams) + ExtOut("\nPARAMETERS: (none)\n"); + + ULONG cLocals = 0; + ToRelease<ICorDebugValueEnum> pLocalsEnum; + IfFailRet(pILFrame->EnumerateLocalVariables(&pLocalsEnum)); + IfFailRet(pLocalsEnum->GetCount(&cLocals)); + if (cLocals > 0 && bLocals) + { + bool symbolsAvailable = false; + SymbolReader symReader; + if(SUCCEEDED(symReader.LoadSymbols(pMD, pModule))) + symbolsAvailable = true; + ExtOut("\nLOCALS:\n"); + for (ULONG i=0; i < cLocals; i++) + { + ULONG paramNameLen = 0; + WCHAR paramName[mdNameLen] = W("\0"); + + ToRelease<ICorDebugValue> pValue; + if(symbolsAvailable) + { + Status = symReader.GetNamedLocalVariable(pILFrame, i, paramName, mdNameLen, &pValue); + } + else + { + ULONG cArgsFetched; + Status = pLocalsEnum->Next(1, &pValue, &cArgsFetched); + } + if(_wcslen(paramName) == 0) + swprintf_s(paramName, mdNameLen, W("local_%d\0"), i); + + if (FAILED(Status)) + { + ExtOut(" + (Error 0x%x retrieving local variable '%S')\n", Status, paramName); + continue; + } + + if (Status == S_FALSE) + { + break; + } + + WCHAR typeName[mdNameLen] = W("\0"); + GetTypeOfValue(pValue, typeName, mdNameLen); + DMLOut(" + %S %s", typeName, DMLManagedVar(paramName, currentFrame, paramName)); + + ToRelease<ICorDebugReferenceValue> pRefValue = NULL; + if(SUCCEEDED(pValue->QueryInterface(IID_ICorDebugReferenceValue, (void**)&pRefValue)) && pRefValue != NULL) + { + BOOL bIsNull = TRUE; + pRefValue->IsNull(&bIsNull); + if(bIsNull) + { + ExtOut(" = null\n"); + continue; + } + } + + WCHAR currentExpansion[mdNameLen]; + swprintf_s(currentExpansion, mdNameLen, W("%s\0"), paramName); + if((Status=PrintValue(pValue, pILFrame, pMD, 0, varToExpand, currentExpansion, mdNameLen, currentFrame)) != S_OK) + ExtOut(" + (Error 0x%x printing local variable %d)\n", Status, i); + } + } + else if (cLocals == 0 && bLocals) + ExtOut("\nLOCALS: (none)\n"); + + if(bParams || bLocals) + ExtOut("\n"); + + return S_OK; + } + + static HRESULT ProcessFields(ICorDebugValue* pInputValue, ICorDebugType* pTypeCast, ICorDebugILFrame * pILFrame, int indent, __in_z WCHAR* varToExpand, __inout_ecount(currentExpansionSize) WCHAR* currentExpansion, DWORD currentExpansionSize, int currentFrame) + { + if(!ShouldExpandVariable(varToExpand, currentExpansion)) return S_OK; + size_t currentExpansionLen = _wcslen(currentExpansion); + + HRESULT Status = S_OK; + + BOOL isNull = FALSE; + ToRelease<ICorDebugValue> pValue; + IfFailRet(DereferenceAndUnboxValue(pInputValue, &pValue, &isNull)); + + if(isNull) return S_OK; + + mdTypeDef currentTypeDef; + ToRelease<ICorDebugClass> pClass; + ToRelease<ICorDebugValue2> pValue2; + ToRelease<ICorDebugType> pType; + ToRelease<ICorDebugModule> pModule; + IfFailRet(pValue->QueryInterface(IID_ICorDebugValue2, (LPVOID *) &pValue2)); + if(pTypeCast == NULL) + IfFailRet(pValue2->GetExactType(&pType)); + else + { + pType = pTypeCast; + pType->AddRef(); + } + IfFailRet(pType->GetClass(&pClass)); + IfFailRet(pClass->GetModule(&pModule)); + IfFailRet(pClass->GetToken(¤tTypeDef)); + + ToRelease<IUnknown> pMDUnknown; + ToRelease<IMetaDataImport> pMD; + IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown)); + IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD)); + + WCHAR baseTypeName[mdNameLen] = W("\0"); + ToRelease<ICorDebugType> pBaseType; + if(SUCCEEDED(pType->GetBase(&pBaseType)) && pBaseType != NULL && SUCCEEDED(GetTypeOfValue(pBaseType, baseTypeName, mdNameLen))) + { + if(_wcsncmp(baseTypeName, W("System.Enum"), 11) == 0) + return S_OK; + else if(_wcsncmp(baseTypeName, W("System.Object"), 13) != 0 && _wcsncmp(baseTypeName, W("System.ValueType"), 16) != 0) + { + currentExpansion[currentExpansionLen] = W('\0'); + wcscat_s(currentExpansion, currentExpansionSize, W(".\0")); + wcscat_s(currentExpansion, currentExpansionSize, W("[basetype]")); + for(int i = 0; i < indent; i++) ExtOut(" "); + DMLOut(" |- %S %s\n", baseTypeName, DMLManagedVar(currentExpansion, currentFrame, W("[basetype]"))); + + if(ShouldExpandVariable(varToExpand, currentExpansion)) + ProcessFields(pInputValue, pBaseType, pILFrame, indent + 1, varToExpand, currentExpansion, currentExpansionSize, currentFrame); + } + } + + + ULONG numFields = 0; + HCORENUM fEnum = NULL; + mdFieldDef fieldDef; + while(SUCCEEDED(pMD->EnumFields(&fEnum, currentTypeDef, &fieldDef, 1, &numFields)) && numFields != 0) + { + ULONG nameLen = 0; + DWORD fieldAttr = 0; + WCHAR mdName[mdNameLen]; + WCHAR typeName[mdNameLen]; + if(SUCCEEDED(pMD->GetFieldProps(fieldDef, NULL, mdName, mdNameLen, &nameLen, &fieldAttr, NULL, NULL, NULL, NULL, NULL))) + { + currentExpansion[currentExpansionLen] = W('\0'); + wcscat_s(currentExpansion, currentExpansionSize, W(".\0")); + wcscat_s(currentExpansion, currentExpansionSize, mdName); + + ToRelease<ICorDebugValue> pFieldVal; + if(fieldAttr & fdLiteral) + { + //TODO: Is it worth it?? + //ExtOut(" |- const %S", mdName); + } + else + { + for(int i = 0; i < indent; i++) ExtOut(" "); + + if (fieldAttr & fdStatic) + pType->GetStaticFieldValue(fieldDef, pILFrame, &pFieldVal); + else + { + ToRelease<ICorDebugObjectValue> pObjValue; + if (SUCCEEDED(pValue->QueryInterface(IID_ICorDebugObjectValue, (LPVOID*) &pObjValue))) + pObjValue->GetFieldValue(pClass, fieldDef, &pFieldVal); + } + + if(pFieldVal != NULL) + { + typeName[0] = L'\0'; + GetTypeOfValue(pFieldVal, typeName, mdNameLen); + DMLOut(" |- %S %s", typeName, DMLManagedVar(currentExpansion, currentFrame, mdName)); + PrintValue(pFieldVal, pILFrame, pMD, indent, varToExpand, currentExpansion, currentExpansionSize, currentFrame); + } + else if(!(fieldAttr & fdLiteral)) + ExtOut(" |- < unknown type > %S\n", mdName); + } + } + } + pMD->CloseEnum(fEnum); + return S_OK; + } + +public: + + // This is the main worker function used if !clrstack is called with "-i" to indicate + // that the public ICorDebug* should be used instead of the private DAC interface. NOTE: + // Currently only bParams is supported. NOTE: This is a work in progress and the + // following would be good to do: + // * More thorough testing with interesting stacks, especially with transitions into + // and out of managed code. + // * Consider interleaving this code back into the main body of !clrstack if it turns + // out that there's a lot of duplication of code between these two functions. + // (Still unclear how things will look once locals is implemented.) + static HRESULT ClrStackFromPublicInterface(BOOL bParams, BOOL bLocals, BOOL bSuppressLines, __in_z WCHAR* varToExpand = NULL, int onlyShowFrame = -1) + { + HRESULT Status; + + IfFailRet(InitCorDebugInterface()); + + ExtOut("\n\n\nDumping managed stack and managed variables using ICorDebug.\n"); + ExtOut("=============================================================================\n"); + + ToRelease<ICorDebugThread> pThread; + ToRelease<ICorDebugThread3> pThread3; + ToRelease<ICorDebugStackWalk> pStackWalk; + ULONG ulThreadID = 0; + g_ExtSystem->GetCurrentThreadSystemId(&ulThreadID); + + IfFailRet(g_pCorDebugProcess->GetThread(ulThreadID, &pThread)); + IfFailRet(pThread->QueryInterface(IID_ICorDebugThread3, (LPVOID *) &pThread3)); + IfFailRet(pThread3->CreateStackWalk(&pStackWalk)); + + InternalFrameManager internalFrameManager; + IfFailRet(internalFrameManager.Init(pThread3)); + + #if defined(_AMD64_) + ExtOut("%-16s %-16s %s\n", "Child SP", "IP", "Call Site"); + #elif defined(_X86_) + ExtOut("%-8s %-8s %s\n", "Child SP", "IP", "Call Site"); + #endif + + int currentFrame = -1; + + for (Status = S_OK; ; Status = pStackWalk->Next()) + { + currentFrame++; + + if (Status == CORDBG_S_AT_END_OF_STACK) + { + ExtOut("Stack walk complete.\n"); + break; + } + IfFailRet(Status); + + if (IsInterrupt()) + { + ExtOut("<interrupted>\n"); + break; + } + + CROSS_PLATFORM_CONTEXT context; + ULONG32 cbContextActual; + if ((Status=pStackWalk->GetContext( + DT_CONTEXT_FULL, + sizeof(context), + &cbContextActual, + (BYTE *)&context))!=S_OK) + { + ExtOut("GetFrameContext failed: %lx\n",Status); + break; + } + + // First find the info for the Frame object, if the current frame has an associated clr!Frame. + CLRDATA_ADDRESS sp = GetSP(context); + CLRDATA_ADDRESS ip = GetIP(context); + + ToRelease<ICorDebugFrame> pFrame; + IfFailRet(pStackWalk->GetFrame(&pFrame)); + if (Status == S_FALSE) + { + DMLOut("%p %s [NativeStackFrame]\n", SOS_PTR(sp), DMLIP(ip)); + continue; + } + + // TODO: What about internal frames preceding the above native stack frame? + // Should I just exclude the above native stack frame from the output? + // TODO: Compare caller frame (instead of current frame) against internal frame, + // to deal with issues of current frame's current SP being closer to leaf than + // EE Frames it pushes. By "caller" I mean not just managed caller, but the + // very next non-internal frame dbi would return (native or managed). OR... + // perhaps I should use GetStackRange() instead, to see if the internal frame + // appears leafier than the base-part of the range of the currently iterated + // stack frame? I think I like that better. + _ASSERTE(pFrame != NULL); + IfFailRet(internalFrameManager.PrintPrecedingInternalFrames(pFrame)); + + // Print the stack and instruction pointers. + DMLOut("%p %s ", SOS_PTR(sp), DMLIP(ip)); + + ToRelease<ICorDebugRuntimeUnwindableFrame> pRuntimeUnwindableFrame; + Status = pFrame->QueryInterface(IID_ICorDebugRuntimeUnwindableFrame, (LPVOID *) &pRuntimeUnwindableFrame); + if (SUCCEEDED(Status)) + { + ExtOut("[RuntimeUnwindableFrame]\n"); + continue; + } + + // Print the method/Frame info + + // TODO: IS THE FOLLOWING NECESSARY, OR AM I GUARANTEED THAT ALL INTERNAL FRAMES + // CAN BE FOUND VIA GetActiveInternalFrames? + ToRelease<ICorDebugInternalFrame> pInternalFrame; + Status = pFrame->QueryInterface(IID_ICorDebugInternalFrame, (LPVOID *) &pInternalFrame); + if (SUCCEEDED(Status)) + { + // This is a clr!Frame. + LPCWSTR pwszFrameName = W("TODO: Implement GetFrameName"); + ExtOut("[%S: p] ", pwszFrameName); + } + + // Print the frame's associated function info, if it has any. + ToRelease<ICorDebugILFrame> pILFrame; + HRESULT hrILFrame = pFrame->QueryInterface(IID_ICorDebugILFrame, (LPVOID*) &pILFrame); + + if (SUCCEEDED(hrILFrame)) + { + ToRelease<ICorDebugFunction> pFunction; + Status = pFrame->GetFunction(&pFunction); + if (FAILED(Status)) + { + // We're on a JITted frame, but there's no Function for it. So it must + // be... + ExtOut("[IL Stub or LCG]\n"); + continue; + } + + ToRelease<ICorDebugClass> pClass; + ToRelease<ICorDebugModule> pModule; + mdMethodDef methodDef; + IfFailRet(pFunction->GetClass(&pClass)); + IfFailRet(pFunction->GetModule(&pModule)); + IfFailRet(pFunction->GetToken(&methodDef)); + + WCHAR wszModuleName[100]; + ULONG32 cchModuleNameActual; + IfFailRet(pModule->GetName(_countof(wszModuleName), &cchModuleNameActual, wszModuleName)); + + ToRelease<IUnknown> pMDUnknown; + ToRelease<IMetaDataImport> pMD; + ToRelease<IMDInternalImport> pMDInternal; + IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown)); + IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD)); + IfFailRet(GetMDInternalFromImport(pMD, &pMDInternal)); + + mdTypeDef typeDef; + IfFailRet(pClass->GetToken(&typeDef)); + + // Note that we don't need to pretty print the class, as class name is + // already printed from GetMethodName below + + CQuickBytes functionName; + // TODO: WARNING: GetMethodName() appears to include lots of unexercised + // code, as evidenced by some fundamental bugs I found. It should either be + // thoroughly reviewed, or some other more exercised code path to grab the + // name should be used. + // TODO: If we do stay with GetMethodName, it should be updated to print + // generics properly. Today, it does not show generic type parameters, and + // if any arguments have a generic type, those arguments are just shown as + // "__Canon", even when they're value types. + GetMethodName(methodDef, pMD, &functionName); + + DMLOut(DMLManagedVar(W("-a"), currentFrame, (LPWSTR)functionName.Ptr())); + ExtOut(" (%S)\n", wszModuleName); + + if (SUCCEEDED(hrILFrame) && (bParams || bLocals)) + { + if(onlyShowFrame == -1 || (onlyShowFrame >= 0 && currentFrame == onlyShowFrame)) + IfFailRet(PrintParameters(bParams, bLocals, pMD, typeDef, methodDef, pILFrame, pModule, varToExpand, currentFrame)); + } + } + } + ExtOut("=============================================================================\n"); + +#ifdef FEATURE_PAL + // Temporary until we get a process exit notification plumbed from lldb + UninitCorDebugInterface(); +#endif + return S_OK; + } +}; + +WString BuildRegisterOutput(const SOSStackRefData &ref, bool printObj) +{ + WString res; + + if (ref.HasRegisterInformation) + { + WCHAR reg[32]; + HRESULT hr = g_sos->GetRegisterName(ref.Register, _countof(reg), reg, NULL); + if (SUCCEEDED(hr)) + res = reg; + else + res = W("<unknown register>"); + + if (ref.Offset) + { + int offset = ref.Offset; + if (offset > 0) + { + res += W("+"); + } + else + { + res += W("-"); + offset = -offset; + } + + res += Hex(offset); + } + + res += W(": "); + } + + if (ref.Address) + res += WString(Pointer(ref.Address)); + + if (printObj) + { + if (ref.Address) + res += W(" -> "); + + res += WString(ObjectPtr(ref.Object)); + } + + if (ref.Flags & SOSRefPinned) + { + res += W(" (pinned)"); + } + + if (ref.Flags & SOSRefInterior) + { + res += W(" (interior)"); + } + + return res; +} + +void PrintRef(const SOSStackRefData &ref, TableOutput &out) +{ + WString res = BuildRegisterOutput(ref); + + if (ref.Object && (ref.Flags & SOSRefInterior) == 0) + { + WCHAR type[128]; + sos::BuildTypeWithExtraInfo(TO_TADDR(ref.Object), _countof(type), type); + + res += WString(W(" - ")) + type; + } + + out.WriteColumn(2, res); +} + + +class ClrStackImpl +{ +public: + static void PrintThread(ULONG osID, BOOL bParams, BOOL bLocals, BOOL bSuppressLines, BOOL bGC, BOOL bFull, BOOL bDisplayRegVals) + { + // Symbols variables + ULONG symlines = 0; // symlines will be non-zero only if SYMOPT_LOAD_LINES was set in the symbol options + if (!bSuppressLines && SUCCEEDED(g_ExtSymbols->GetSymbolOptions(&symlines))) + { + symlines &= SYMOPT_LOAD_LINES; + } + + if (symlines == 0) + bSuppressLines = TRUE; + + ToRelease<IXCLRDataStackWalk> pStackWalk; + + HRESULT hr = CreateStackWalk(osID, &pStackWalk); + if (FAILED(hr) || pStackWalk == NULL) + { + ExtOut("Failed to start stack walk: %lx\n", hr); + return; + } + +#ifdef _TARGET_WIN64_ + PDEBUG_STACK_FRAME currentNativeFrame = NULL; + ULONG numNativeFrames = 0; + if (bFull) + { + hr = GetContextStackTrace(&numNativeFrames); + if (FAILED(hr)) + { + ExtOut("Failed to get native stack frames: %lx\n", hr); + return; + } + currentNativeFrame = &g_Frames[0]; + } +#endif // _TARGET_WIN64_ + + unsigned int refCount = 0, errCount = 0; + ArrayHolder<SOSStackRefData> pRefs = NULL; + ArrayHolder<SOSStackRefError> pErrs = NULL; + if (bGC && FAILED(GetGCRefs(osID, &pRefs, &refCount, &pErrs, &errCount))) + refCount = 0; + + TableOutput out(3, POINTERSIZE_HEX, AlignRight); + out.WriteRow("Child SP", "IP", "Call Site"); + + do + { + if (IsInterrupt()) + { + ExtOut("<interrupted>\n"); + break; + } + CLRDATA_ADDRESS ip = 0, sp = 0; + hr = GetFrameLocation(pStackWalk, &ip, &sp); + + DacpFrameData FrameData; + HRESULT frameDataResult = FrameData.Request(pStackWalk); + if (SUCCEEDED(frameDataResult) && FrameData.frameAddr) + sp = FrameData.frameAddr; + +#ifdef _TARGET_WIN64_ + while ((numNativeFrames > 0) && (currentNativeFrame->StackOffset <= sp)) + { + if (currentNativeFrame->StackOffset != sp) + { + PrintNativeStackFrame(out, currentNativeFrame, bSuppressLines); + } + currentNativeFrame++; + numNativeFrames--; + } +#endif // _TARGET_WIN64_ + + // Print the stack pointer. + out.WriteColumn(0, sp); + + // Print the method/Frame info + if (SUCCEEDED(frameDataResult) && FrameData.frameAddr) + { + // Skip the instruction pointer because it doesn't really mean anything for method frames + out.WriteColumn(1, bFull ? String("") : NativePtr(ip)); + + // This is a clr!Frame. + out.WriteColumn(2, GetFrameFromAddress(TO_TADDR(FrameData.frameAddr), pStackWalk, bFull)); + + // Print out gc references for the Frame. + for (unsigned int i = 0; i < refCount; ++i) + if (pRefs[i].Source == sp) + PrintRef(pRefs[i], out); + + // Print out an error message if we got one. + for (unsigned int i = 0; i < errCount; ++i) + if (pErrs[i].Source == sp) + out.WriteColumn(2, "Failed to enumerate GC references."); + } + else + { + out.WriteColumn(1, InstructionPtr(ip)); + out.WriteColumn(2, MethodNameFromIP(ip, bSuppressLines, bFull, bFull)); + + // Print out gc references. refCount will be zero if bGC is false (or if we + // failed to fetch gc reference information). + for (unsigned int i = 0; i < refCount; ++i) + if (pRefs[i].Source == ip && pRefs[i].StackPointer == sp) + PrintRef(pRefs[i], out); + + // Print out an error message if we got one. + for (unsigned int i = 0; i < errCount; ++i) + if (pErrs[i].Source == sp) + out.WriteColumn(2, "Failed to enumerate GC references."); + + if (bParams || bLocals) + PrintArgsAndLocals(pStackWalk, bParams, bLocals); + } + + if (bDisplayRegVals) + PrintManagedFrameContext(pStackWalk); + + } while (pStackWalk->Next() == S_OK); + +#ifdef _TARGET_WIN64_ + while (numNativeFrames > 0) + { + PrintNativeStackFrame(out, currentNativeFrame, bSuppressLines); + currentNativeFrame++; + numNativeFrames--; + } +#endif // _TARGET_WIN64_ + } + + static HRESULT PrintManagedFrameContext(IXCLRDataStackWalk *pStackWalk) + { + CROSS_PLATFORM_CONTEXT context; + HRESULT hr = pStackWalk->GetContext(DT_CONTEXT_FULL, g_targetMachine->GetContextSize(), NULL, (BYTE *)&context); + if (FAILED(hr) || hr == S_FALSE) + { + // GetFrameContext returns S_FALSE if the frame iterator is invalid. That's basically an error for us. + ExtOut("GetFrameContext failed: %lx\n", hr); + return E_FAIL; + } + +#if defined(SOS_TARGET_AMD64) + String outputFormat3 = " %3s=%016x %3s=%016x %3s=%016x\n"; + String outputFormat2 = " %3s=%016x %3s=%016x\n"; + ExtOut(outputFormat3, "rsp", context.Amd64Context.Rsp, "rbp", context.Amd64Context.Rbp, "rip", context.Amd64Context.Rip); + ExtOut(outputFormat3, "rax", context.Amd64Context.Rax, "rbx", context.Amd64Context.Rbx, "rcx", context.Amd64Context.Rcx); + ExtOut(outputFormat3, "rdx", context.Amd64Context.Rdx, "rsi", context.Amd64Context.Rsi, "rdi", context.Amd64Context.Rdi); + ExtOut(outputFormat3, "r8", context.Amd64Context.R8, "r9", context.Amd64Context.R9, "r10", context.Amd64Context.R10); + ExtOut(outputFormat3, "r11", context.Amd64Context.R11, "r12", context.Amd64Context.R12, "r13", context.Amd64Context.R13); + ExtOut(outputFormat2, "r14", context.Amd64Context.R14, "r15", context.Amd64Context.R15); +#elif defined(SOS_TARGET_X86) + String outputFormat3 = " %3s=%08x %3s=%08x %3s=%08x\n"; + String outputFormat2 = " %3s=%08x %3s=%08x\n"; + ExtOut(outputFormat3, "esp", context.X86Context.Esp, "ebp", context.X86Context.Ebp, "eip", context.X86Context.Eip); + ExtOut(outputFormat3, "eax", context.X86Context.Eax, "ebx", context.X86Context.Ebx, "ecx", context.X86Context.Ecx); + ExtOut(outputFormat3, "edx", context.X86Context.Edx, "esi", context.X86Context.Esi, "edi", context.X86Context.Edi); +#elif defined(SOS_TARGET_ARM) + String outputFormat3 = " %3s=%08x %3s=%08x %3s=%08x\n"; + String outputFormat2 = " %s=%08x %s=%08x\n"; + String outputFormat1 = " %s=%08x\n"; + ExtOut(outputFormat3, "r0", context.ArmContext.R0, "r1", context.ArmContext.R1, "r2", context.ArmContext.R2); + ExtOut(outputFormat3, "r3", context.ArmContext.R3, "r4", context.ArmContext.R4, "r5", context.ArmContext.R5); + ExtOut(outputFormat3, "r6", context.ArmContext.R6, "r7", context.ArmContext.R7, "r8", context.ArmContext.R8); + ExtOut(outputFormat3, "r9", context.ArmContext.R9, "r10", context.ArmContext.R10, "r11", context.ArmContext.R11); + ExtOut(outputFormat1, "r12", context.ArmContext.R12); + ExtOut(outputFormat3, "sp", context.ArmContext.Sp, "lr", context.ArmContext.Lr, "pc", context.ArmContext.Pc); + ExtOut(outputFormat2, "cpsr", context.ArmContext.Cpsr, "fpsr", context.ArmContext.Fpscr); +#elif defined(SOS_TARGET_ARM64) + String outputXRegFormat3 = " x%d=%016x x%d=%016x x%d=%016x\n"; + String outputXRegFormat1 = " x%d=%016x\n"; + String outputFormat3 = " %s=%016x %s=%016x %s=%016x\n"; + String outputFormat2 = " %s=%08x %s=%08x\n"; + DWORD64 *X = context.Arm64Context.X; + for (int i = 0; i < 9; i++) + { + ExtOut(outputXRegFormat3, i + 0, X[i + 0], i + 1, X[i + 1], i + 2, X[i + 2]); + } + ExtOut(outputXRegFormat1, 28, X[28]); + ExtOut(outputFormat3, "sp", context.ArmContext.Sp, "lr", context.ArmContext.Lr, "pc", context.ArmContext.Pc); + ExtOut(outputFormat2, "cpsr", context.ArmContext.Cpsr, "fpsr", context.ArmContext.Fpscr); +#else + ExtOut("Can't display register values for this platform\n"); +#endif + return S_OK; + + } + + static HRESULT GetFrameLocation(IXCLRDataStackWalk *pStackWalk, CLRDATA_ADDRESS *ip, CLRDATA_ADDRESS *sp) + { + CROSS_PLATFORM_CONTEXT context; + HRESULT hr = pStackWalk->GetContext(DT_CONTEXT_FULL, g_targetMachine->GetContextSize(), NULL, (BYTE *)&context); + if (FAILED(hr) || hr == S_FALSE) + { + // GetFrameContext returns S_FALSE if the frame iterator is invalid. That's basically an error for us. + ExtOut("GetFrameContext failed: %lx\n", hr); + return E_FAIL; + } + + // First find the info for the Frame object, if the current frame has an associated clr!Frame. + *ip = GetIP(context); + *sp = GetSP(context); + + if (IsDbgTargetArm()) + *ip = *ip & ~THUMB_CODE; + + return S_OK; + } + + static void PrintNativeStackFrame(TableOutput out, PDEBUG_STACK_FRAME frame, BOOL bSuppressLines) + { + char filename[MAX_LONGPATH + 1]; + char symbol[1024]; + ULONG64 displacement; + + ULONG64 ip = frame->InstructionOffset; + + out.WriteColumn(0, frame->StackOffset); + out.WriteColumn(1, NativePtr(ip)); + + HRESULT hr = g_ExtSymbols->GetNameByOffset(TO_CDADDR(ip), symbol, _countof(symbol), NULL, &displacement); + if (SUCCEEDED(hr) && symbol[0] != '\0') + { + String frameOutput; + frameOutput += symbol; + + if (displacement) + { + frameOutput += " + "; + frameOutput += Decimal(displacement); + } + + if (!bSuppressLines) + { + ULONG line; + hr = g_ExtSymbols->GetLineByOffset(TO_CDADDR(ip), &line, filename, _countof(filename), NULL, NULL); + if (SUCCEEDED(hr)) + { + frameOutput += " at "; + frameOutput += filename; + frameOutput += ":"; + frameOutput += Decimal(line); + } + } + + out.WriteColumn(2, frameOutput); + } + else + { + out.WriteColumn(2, ""); + } + } + + static void PrintCurrentThread(BOOL bParams, BOOL bLocals, BOOL bSuppressLines, BOOL bGC, BOOL bNative, BOOL bDisplayRegVals) + { + ULONG id = 0; + ULONG osid = 0; + + g_ExtSystem->GetCurrentThreadSystemId(&osid); + ExtOut("OS Thread Id: 0x%x ", osid); + g_ExtSystem->GetCurrentThreadId(&id); + ExtOut("(%d)\n", id); + + PrintThread(osid, bParams, bLocals, bSuppressLines, bGC, bNative, bDisplayRegVals); + } +private: + + static HRESULT CreateStackWalk(ULONG osID, IXCLRDataStackWalk **ppStackwalk) + { + HRESULT hr = S_OK; + ToRelease<IXCLRDataTask> pTask; + + if ((hr = g_ExtSystem->GetCurrentThreadSystemId(&osID)) != S_OK || + (hr = g_clrData->GetTaskByOSThreadID(osID, &pTask)) != S_OK) + { + ExtOut("Unable to walk the managed stack. The current thread is likely not a \n"); + ExtOut("managed thread. You can run !threads to get a list of managed threads in\n"); + ExtOut("the process\n"); + return hr; + } + + return pTask->CreateStackWalk(CLRDATA_SIMPFRAME_UNRECOGNIZED | + CLRDATA_SIMPFRAME_MANAGED_METHOD | + CLRDATA_SIMPFRAME_RUNTIME_MANAGED_CODE | + CLRDATA_SIMPFRAME_RUNTIME_UNMANAGED_CODE, + ppStackwalk); + } + + /* Prints the args and locals of for a thread's stack. + * Params: + * pStackWalk - the stack we are printing + * bArgs - whether to print args + * bLocals - whether to print locals + */ + static void PrintArgsAndLocals(IXCLRDataStackWalk *pStackWalk, BOOL bArgs, BOOL bLocals) + { + ToRelease<IXCLRDataFrame> pFrame; + ToRelease<IXCLRDataValue> pVal; + ULONG32 argCount = 0; + ULONG32 localCount = 0; + HRESULT hr = S_OK; + + hr = pStackWalk->GetFrame(&pFrame); + + // Print arguments + if (SUCCEEDED(hr) && bArgs) + hr = pFrame->GetNumArguments(&argCount); + + if (SUCCEEDED(hr) && bArgs) + hr = ShowArgs(argCount, pFrame, pVal); + + // Print locals + if (SUCCEEDED(hr) && bLocals) + hr = pFrame->GetNumLocalVariables(&localCount); + + if (SUCCEEDED(hr) && bLocals) + ShowLocals(localCount, pFrame, pVal); + + ExtOut("\n"); + } + + + + /* Displays the arguments to a function + * Params: + * argy - the number of arguments the function has + * pFramey - the frame we are inspecting + * pVal - a pointer to the CLRDataValue we use to query for info about the args + */ + static HRESULT ShowArgs(ULONG32 argy, IXCLRDataFrame *pFramey, IXCLRDataValue *pVal) + { + CLRDATA_ADDRESS addr = 0; + BOOL fPrintedLocation = FALSE; + ULONG64 outVar = 0; + ULONG32 tmp; + HRESULT hr = S_OK; + + ArrayHolder<WCHAR> argName = new NOTHROW WCHAR[mdNameLen]; + if (!argName) + { + ReportOOM(); + return E_FAIL; + } + + for (ULONG32 i=0; i < argy; i++) + { + if (i == 0) + { + ExtOut(" PARAMETERS:\n"); + } + + hr = pFramey->GetArgumentByIndex(i, + &pVal, + mdNameLen, + &tmp, + argName); + + if (FAILED(hr)) + return hr; + + ExtOut(" "); + + if (argName[0] != L'\0') + { + ExtOut("%S ", argName.GetPtr()); + } + + // At times we cannot print the value of a parameter (most + // common case being a non-primitive value type). In these + // cases we need to print the location of the parameter, + // so that we can later examine it (e.g. using !dumpvc) + { + bool result = SUCCEEDED(pVal->GetNumLocations(&tmp)) && tmp == 1; + if (result) + result = SUCCEEDED(pVal->GetLocationByIndex(0, &tmp, &addr)); + + if (result) + { + if (tmp == CLRDATA_VLOC_REGISTER) + { + ExtOut("(<CLR reg>) "); + } + else + { + ExtOut("(0x%p) ", SOS_PTR(CDA_TO_UL64(addr))); + } + fPrintedLocation = TRUE; + } + } + + if (argName[0] != L'\0' || fPrintedLocation) + { + ExtOut("= "); + } + + if (HRESULT_CODE(pVal->GetBytes(0,&tmp,NULL)) == ERROR_BUFFER_OVERFLOW) + { + ArrayHolder<BYTE> pByte = new NOTHROW BYTE[tmp + 1]; + if (pByte == NULL) + { + ReportOOM(); + return E_FAIL; + } + + hr = pVal->GetBytes(tmp, &tmp, pByte); + + if (FAILED(hr)) + { + ExtOut("<unable to retrieve data>\n"); + } + else + { + switch(tmp) + { + case 1: outVar = *((BYTE *)pByte.GetPtr()); break; + case 2: outVar = *((short *)pByte.GetPtr()); break; + case 4: outVar = *((DWORD *)pByte.GetPtr()); break; + case 8: outVar = *((ULONG64 *)pByte.GetPtr()); break; + default: outVar = 0; + } + + if (outVar) + DMLOut("0x%s\n", DMLObject(outVar)); + else + ExtOut("0x%p\n", SOS_PTR(outVar)); + } + + } + else + { + ExtOut("<no data>\n"); + } + + pVal->Release(); + } + + return S_OK; + } + + + /* Prints the locals of a frame. + * Params: + * localy - the number of locals in the frame + * pFramey - the frame we are inspecting + * pVal - a pointer to the CLRDataValue we use to query for info about the args + */ + static HRESULT ShowLocals(ULONG32 localy, IXCLRDataFrame *pFramey, IXCLRDataValue *pVal) + { + for (ULONG32 i=0; i < localy; i++) + { + if (i == 0) + ExtOut(" LOCALS:\n"); + + HRESULT hr; + ExtOut(" "); + + // local names don't work in Whidbey. + hr = pFramey->GetLocalVariableByIndex(i, &pVal, mdNameLen, NULL, g_mdName); + if (FAILED(hr)) + { + return hr; + } + + ULONG32 numLocations; + if (SUCCEEDED(pVal->GetNumLocations(&numLocations)) && + numLocations == 1) + { + ULONG32 flags; + CLRDATA_ADDRESS addr; + if (SUCCEEDED(pVal->GetLocationByIndex(0, &flags, &addr))) + { + if (flags == CLRDATA_VLOC_REGISTER) + { + ExtOut("<CLR reg> "); + } + else + { + ExtOut("0x%p ", SOS_PTR(CDA_TO_UL64(addr))); + } + } + + // Can I get a name for the item? + + ExtOut("= "); + } + ULONG32 dwSize = 0; + hr = pVal->GetBytes(0, &dwSize, NULL); + + if (HRESULT_CODE(hr) == ERROR_BUFFER_OVERFLOW) + { + ArrayHolder<BYTE> pByte = new NOTHROW BYTE[dwSize + 1]; + if (pByte == NULL) + { + ReportOOM(); + return E_FAIL; + } + + hr = pVal->GetBytes(dwSize,&dwSize,pByte); + + if (FAILED(hr)) + { + ExtOut("<unable to retrieve data>\n"); + } + else + { + ULONG64 outVar = 0; + switch(dwSize) + { + case 1: outVar = *((BYTE *) pByte.GetPtr()); break; + case 2: outVar = *((short *) pByte.GetPtr()); break; + case 4: outVar = *((DWORD *) pByte.GetPtr()); break; + case 8: outVar = *((ULONG64 *) pByte.GetPtr()); break; + default: outVar = 0; + } + + if (outVar) + DMLOut("0x%s\n", DMLObject(outVar)); + else + ExtOut("0x%p\n", SOS_PTR(outVar)); + } + } + else + { + ExtOut("<no data>\n"); + } + + pVal->Release(); + } + + return S_OK; + } + +}; + +#ifndef FEATURE_PAL + +WatchCmd g_watchCmd; + +// The grand new !Watch command, private to Apollo for now +DECLARE_API(Watch) +{ + INIT_API_NOEE(); + BOOL bExpression = FALSE; + StringHolder addExpression; + StringHolder aExpression; + StringHolder saveName; + StringHolder sName; + StringHolder expression; + StringHolder filterName; + StringHolder renameOldName; + size_t expandIndex = -1; + size_t removeIndex = -1; + BOOL clear = FALSE; + + size_t nArg = 0; + CMDOption option[] = + { // name, vptr, type, hasValue + {"-add", &addExpression.data, COSTRING, TRUE}, + {"-a", &aExpression.data, COSTRING, TRUE}, + {"-save", &saveName.data, COSTRING, TRUE}, + {"-s", &sName.data, COSTRING, TRUE}, + {"-clear", &clear, COBOOL, FALSE}, + {"-c", &clear, COBOOL, FALSE}, + {"-expand", &expandIndex, COSIZE_T, TRUE}, + {"-filter", &filterName.data, COSTRING, TRUE}, + {"-r", &removeIndex, COSIZE_T, TRUE}, + {"-remove", &removeIndex, COSIZE_T, TRUE}, + {"-rename", &renameOldName.data, COSTRING, TRUE}, + }; + + CMDValue arg[] = + { // vptr, type + {&expression.data, COSTRING} + }; + if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg)) + { + return Status; + } + + if(addExpression.data != NULL || aExpression.data != NULL) + { + WCHAR pAddExpression[MAX_EXPRESSION]; + swprintf_s(pAddExpression, MAX_EXPRESSION, W("%S"), addExpression.data != NULL ? addExpression.data : aExpression.data); + Status = g_watchCmd.Add(pAddExpression); + } + else if(removeIndex != -1) + { + if(removeIndex <= 0) + { + ExtOut("Index must be a postive decimal number\n"); + } + else + { + Status = g_watchCmd.Remove((int)removeIndex); + if(Status == S_OK) + ExtOut("Watch expression #%d has been removed\n", removeIndex); + else if(Status == S_FALSE) + ExtOut("There is no watch expression with index %d\n", removeIndex); + else + ExtOut("Unknown failure 0x%x removing watch expression\n", Status); + } + } + else if(saveName.data != NULL || sName.data != NULL) + { + WCHAR pSaveName[MAX_EXPRESSION]; + swprintf_s(pSaveName, MAX_EXPRESSION, W("%S"), saveName.data != NULL ? saveName.data : sName.data); + Status = g_watchCmd.SaveList(pSaveName); + } + else if(clear) + { + g_watchCmd.Clear(); + } + else if(renameOldName.data != NULL) + { + if(nArg != 1) + { + ExtOut("Must provide an old and new name. Usage: !watch -rename <old_name> <new_name>.\n"); + return S_FALSE; + } + WCHAR pOldName[MAX_EXPRESSION]; + swprintf_s(pOldName, MAX_EXPRESSION, W("%S"), renameOldName.data); + WCHAR pNewName[MAX_EXPRESSION]; + swprintf_s(pNewName, MAX_EXPRESSION, W("%S"), expression.data); + g_watchCmd.RenameList(pOldName, pNewName); + } + // print the tree, possibly with filtering and/or expansion + else if(expandIndex != -1 || expression.data == NULL) + { + WCHAR pExpression[MAX_EXPRESSION]; + pExpression[0] = '\0'; + + if(expandIndex != -1) + { + if(expression.data != NULL) + { + swprintf_s(pExpression, MAX_EXPRESSION, W("%S"), expression.data); + } + else + { + ExtOut("No expression was provided. Usage !watch -expand <index> <expression>\n"); + return S_FALSE; + } + } + WCHAR pFilterName[MAX_EXPRESSION]; + pFilterName[0] = '\0'; + + if(filterName.data != NULL) + { + swprintf_s(pFilterName, MAX_EXPRESSION, W("%S"), filterName.data); + } + + g_watchCmd.Print((int)expandIndex, pExpression, pFilterName); + } + else + { + ExtOut("Unrecognized argument: %s\n", expression.data); + } + + return Status; +} + +#endif // FEATURE_PAL + +DECLARE_API(ClrStack) +{ + INIT_API(); + + BOOL bAll = FALSE; + BOOL bParams = FALSE; + BOOL bLocals = FALSE; + BOOL bSuppressLines = FALSE; + BOOL bICorDebug = FALSE; + BOOL bGC = FALSE; + BOOL dml = FALSE; + BOOL bFull = FALSE; + BOOL bDisplayRegVals = FALSE; + DWORD frameToDumpVariablesFor = -1; + StringHolder cvariableName; + ArrayHolder<WCHAR> wvariableName = new NOTHROW WCHAR[mdNameLen]; + if (wvariableName == NULL) + { + ReportOOM(); + return E_OUTOFMEMORY; + } + + memset(wvariableName, 0, sizeof(wvariableName)); + + size_t nArg = 0; + CMDOption option[] = + { // name, vptr, type, hasValue + {"-a", &bAll, COBOOL, FALSE}, + {"-p", &bParams, COBOOL, FALSE}, + {"-l", &bLocals, COBOOL, FALSE}, + {"-n", &bSuppressLines, COBOOL, FALSE}, + {"-i", &bICorDebug, COBOOL, FALSE}, + {"-gc", &bGC, COBOOL, FALSE}, + {"-f", &bFull, COBOOL, FALSE}, + {"-r", &bDisplayRegVals, COBOOL, FALSE }, +#ifndef FEATURE_PAL + {"/d", &dml, COBOOL, FALSE}, +#endif + }; + CMDValue arg[] = + { // vptr, type + {&cvariableName.data, COSTRING}, + {&frameToDumpVariablesFor, COSIZE_T}, + }; + if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg)) + { + return Status; + } + + EnableDMLHolder dmlHolder(dml); + if (bAll || bParams || bLocals) + { + // No parameter or local supports for minidump case! + MINIDUMP_NOT_SUPPORTED(); + } + + if (bAll) + { + bParams = bLocals = TRUE; + } + + if (bICorDebug) + { + if(nArg > 0) + { + bool firstParamIsNumber = true; + for(DWORD i = 0; i < strlen(cvariableName.data); i++) + firstParamIsNumber = firstParamIsNumber && isdigit(cvariableName.data[i]); + + if(firstParamIsNumber && nArg == 1) + { + frameToDumpVariablesFor = (DWORD)GetExpression(cvariableName.data); + cvariableName.data[0] = '\0'; + } + } + if(cvariableName.data != NULL && strlen(cvariableName.data) > 0) + swprintf_s(wvariableName, mdNameLen, W("%S\0"), cvariableName.data); + + if(_wcslen(wvariableName) > 0) + bParams = bLocals = TRUE; + + EnableDMLHolder dmlHolder(TRUE); + return ClrStackImplWithICorDebug::ClrStackFromPublicInterface(bParams, bLocals, FALSE, wvariableName, frameToDumpVariablesFor); + } + + ClrStackImpl::PrintCurrentThread(bParams, bLocals, bSuppressLines, bGC, bFull, bDisplayRegVals); + + return S_OK; +} + +#ifndef FEATURE_PAL + +BOOL IsMemoryInfoAvailable() +{ + ULONG Class; + ULONG Qualifier; + g_ExtControl->GetDebuggeeType(&Class,&Qualifier); + if (Qualifier == DEBUG_DUMP_SMALL) + { + g_ExtControl->GetDumpFormatFlags(&Qualifier); + if ((Qualifier & DEBUG_FORMAT_USER_SMALL_FULL_MEMORY) == 0) + { + if ((Qualifier & DEBUG_FORMAT_USER_SMALL_FULL_MEMORY_INFO) == 0) + { + return FALSE; + } + } + } + return TRUE; +} + +DECLARE_API( VMMap ) +{ + INIT_API(); + + if (IsMiniDumpFile() || !IsMemoryInfoAvailable()) + { + ExtOut("!VMMap requires a full memory dump (.dump /ma) or a live process.\n"); + } + else + { + vmmap(); + } + + return Status; +} // DECLARE_API( vmmap ) + +DECLARE_API( SOSFlush ) +{ + INIT_API(); + + g_clrData->Flush(); + + return Status; +} // DECLARE_API( SOSFlush ) + +DECLARE_API( VMStat ) +{ + INIT_API(); + + if (IsMiniDumpFile() || !IsMemoryInfoAvailable()) + { + ExtOut("!VMStat requires a full memory dump (.dump /ma) or a live process.\n"); + } + else + { + vmstat(); + } + + return Status; +} // DECLARE_API( vmmap ) + +/**********************************************************************\ +* Routine Description: * +* * +* This function saves a dll to a file. * +* * +\**********************************************************************/ +DECLARE_API(SaveModule) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + StringHolder Location; + DWORD_PTR moduleAddr = NULL; + BOOL bIsImage; + + CMDValue arg[] = + { // vptr, type + {&moduleAddr, COHEX}, + {&Location.data, COSTRING} + }; + size_t nArg; + if (!GetCMDOption(args, NULL, 0, arg, _countof(arg), &nArg)) + { + return Status; + } + if (nArg != 2) + { + ExtOut("Usage: SaveModule <address> <file to save>\n"); + return Status; + } + if (moduleAddr == 0) { + ExtOut ("Invalid arg\n"); + return Status; + } + + char* ptr = Location.data; + + DWORD_PTR dllBase = 0; + ULONG64 base; + if (g_ExtSymbols->GetModuleByOffset(TO_CDADDR(moduleAddr),0,NULL,&base) == S_OK) + { + dllBase = TO_TADDR(base); + } + else if (IsModule(moduleAddr)) + { + DacpModuleData module; + module.Request(g_sos, TO_CDADDR(moduleAddr)); + dllBase = TO_TADDR(module.ilBase); + if (dllBase == 0) + { + ExtOut ("Module does not have base address\n"); + return Status; + } + } + else + { + ExtOut ("%p is not a Module or base address\n", SOS_PTR(moduleAddr)); + return Status; + } + + MEMORY_BASIC_INFORMATION64 mbi; + if (FAILED(g_ExtData2->QueryVirtual(TO_CDADDR(dllBase), &mbi))) + { + ExtOut("Failed to retrieve information about segment %p", SOS_PTR(dllBase)); + return Status; + } + + // module loaded as an image or mapped as a flat file? + bIsImage = (mbi.Type == MEM_IMAGE); + + IMAGE_DOS_HEADER DosHeader; + if (g_ExtData->ReadVirtual(TO_CDADDR(dllBase), &DosHeader, sizeof(DosHeader), NULL) != S_OK) + return S_FALSE; + + IMAGE_NT_HEADERS Header; + if (g_ExtData->ReadVirtual(TO_CDADDR(dllBase + DosHeader.e_lfanew), &Header, sizeof(Header), NULL) != S_OK) + return S_FALSE; + + DWORD_PTR sectionAddr = dllBase + DosHeader.e_lfanew + offsetof(IMAGE_NT_HEADERS,OptionalHeader) + + Header.FileHeader.SizeOfOptionalHeader; + + IMAGE_SECTION_HEADER section; + struct MemLocation + { + DWORD_PTR VAAddr; + DWORD_PTR VASize; + DWORD_PTR FileAddr; + DWORD_PTR FileSize; + }; + + int nSection = Header.FileHeader.NumberOfSections; + ExtOut("%u sections in file\n",nSection); + MemLocation *memLoc = (MemLocation*)_alloca(nSection*sizeof(MemLocation)); + int indxSec = -1; + int slot; + for (int n = 0; n < nSection; n++) + { + if (g_ExtData->ReadVirtual(TO_CDADDR(sectionAddr), §ion, sizeof(section), NULL) == S_OK) + { + for (slot = 0; slot <= indxSec; slot ++) + if (section.PointerToRawData < memLoc[slot].FileAddr) + break; + + for (int k = indxSec; k >= slot; k --) + memcpy(&memLoc[k+1], &memLoc[k], sizeof(MemLocation)); + + memLoc[slot].VAAddr = section.VirtualAddress; + memLoc[slot].VASize = section.Misc.VirtualSize; + memLoc[slot].FileAddr = section.PointerToRawData; + memLoc[slot].FileSize = section.SizeOfRawData; + ExtOut("section %d - VA=%x, VASize=%x, FileAddr=%x, FileSize=%x\n", + n, memLoc[slot].VAAddr,memLoc[slot]. VASize,memLoc[slot].FileAddr, + memLoc[slot].FileSize); + indxSec ++; + } + else + { + ExtOut("Fail to read PE section info\n"); + return Status; + } + sectionAddr += sizeof(section); + } + + if (ptr[0] == '\0') + { + ExtOut ("File not specified\n"); + return Status; + } + + PCSTR file = ptr; + ptr += strlen(ptr)-1; + while (isspace(*ptr)) + { + *ptr = '\0'; + ptr --; + } + + HANDLE hFile = CreateFileA(file,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,0,NULL); + if (hFile == INVALID_HANDLE_VALUE) + { + ExtOut ("Fail to create file %s\n", file); + return Status; + } + + ULONG pageSize = OSPageSize(); + char *buffer = (char *)_alloca(pageSize); + DWORD nRead; + DWORD nWrite; + + // NT PE Headers + TADDR dwAddr = dllBase; + TADDR dwEnd = dllBase + Header.OptionalHeader.SizeOfHeaders; + while (dwAddr < dwEnd) + { + nRead = pageSize; + if (dwEnd - dwAddr < nRead) + nRead = (ULONG)(dwEnd - dwAddr); + + if (g_ExtData->ReadVirtual(TO_CDADDR(dwAddr), buffer, nRead, &nRead) == S_OK) + { + WriteFile(hFile,buffer,nRead,&nWrite,NULL); + } + else + { + ExtOut ("Fail to read memory\n"); + goto end; + } + dwAddr += nRead; + } + + for (slot = 0; slot <= indxSec; slot ++) + { + dwAddr = dllBase + (bIsImage ? memLoc[slot].VAAddr : memLoc[slot].FileAddr); + dwEnd = memLoc[slot].FileSize + dwAddr - 1; + + while (dwAddr <= dwEnd) + { + nRead = pageSize; + if (dwEnd - dwAddr + 1 < pageSize) + nRead = (ULONG)(dwEnd - dwAddr + 1); + + if (g_ExtData->ReadVirtual(TO_CDADDR(dwAddr), buffer, nRead, &nRead) == S_OK) + { + WriteFile(hFile,buffer,nRead,&nWrite,NULL); + } + else + { + ExtOut ("Fail to read memory\n"); + goto end; + } + dwAddr += pageSize; + } + } +end: + CloseHandle (hFile); + return Status; +} + +#ifdef _DEBUG +DECLARE_API(dbgout) +{ + INIT_API(); + + BOOL bOff = FALSE; + + CMDOption option[] = + { // name, vptr, type, hasValue + {"-off", &bOff, COBOOL, FALSE}, + }; + + if (!GetCMDOption(args, option, _countof(option), NULL, 0, NULL)) + { + return Status; + } + + Output::SetDebugOutputEnabled(!bOff); + return Status; +} +DECLARE_API(filthint) +{ + INIT_API(); + + BOOL bOff = FALSE; + DWORD_PTR filter = 0; + + CMDOption option[] = + { // name, vptr, type, hasValue + {"-off", &bOff, COBOOL, FALSE}, + }; + CMDValue arg[] = + { // vptr, type + {&filter, COHEX} + }; + size_t nArg; + if (!GetCMDOption(args, option, _countof(option), + arg, _countof(arg), &nArg)) + { + return Status; + } + if (bOff) + { + g_filterHint = 0; + return Status; + } + + g_filterHint = filter; + return Status; +} +#endif // _DEBUG + +#endif // FEATURE_PAL + +static HRESULT DumpMDInfoBuffer(DWORD_PTR dwStartAddr, DWORD Flags, ULONG64 Esp, + ULONG64 IPAddr, StringOutput& so) +{ +#define DOAPPEND(str) \ + do { \ + if (!so.Append((str))) { \ + return E_OUTOFMEMORY; \ + }} while (0) + + // Should we skip explicit frames? They are characterized by Esp = 0, && Eip = 0 or 1. + // See comment in FormatGeneratedException() for explanation why on non_IA64 Eip is 1, and not 0 + if (!(Flags & SOS_STACKTRACE_SHOWEXPLICITFRAMES) && (Esp == 0) && (IPAddr == 1)) + { + return S_FALSE; + } + + DacpMethodDescData MethodDescData; + if (MethodDescData.Request(g_sos, TO_CDADDR(dwStartAddr)) != S_OK) + { + return E_FAIL; + } + + ArrayHolder<WCHAR> wszNameBuffer = new WCHAR[MAX_LONGPATH+1]; + + if (Flags & SOS_STACKTRACE_SHOWADDRESSES) + { + _snwprintf_s(wszNameBuffer, MAX_LONGPATH, MAX_LONGPATH, W("%p %p "), (void*)(size_t) Esp, (void*)(size_t) IPAddr); // _TRUNCATE + DOAPPEND(wszNameBuffer); + } + + DacpModuleData dmd; + BOOL bModuleNameWorked = FALSE; + ULONG64 addrInModule = IPAddr; + if (dmd.Request(g_sos, MethodDescData.ModulePtr) == S_OK) + { + CLRDATA_ADDRESS base = 0; + if (g_sos->GetPEFileBase(dmd.File, &base) == S_OK) + { + if (base) + { + addrInModule = base; + } + } + } + ULONG Index; + ULONG64 base; + if (g_ExtSymbols->GetModuleByOffset(UL64_TO_CDA(addrInModule), 0, &Index, &base) == S_OK) + { + ArrayHolder<char> szModuleName = new char[MAX_LONGPATH+1]; + if (g_ExtSymbols->GetModuleNames(Index, base, NULL, 0, NULL, szModuleName, MAX_LONGPATH, NULL, NULL, 0, NULL) == S_OK) + { + MultiByteToWideChar (CP_ACP, 0, szModuleName, MAX_LONGPATH, wszNameBuffer, MAX_LONGPATH); + DOAPPEND (wszNameBuffer); + bModuleNameWorked = TRUE; + } + } +#ifdef FEATURE_PAL + else + { + if (g_sos->GetPEFileName(dmd.File, MAX_LONGPATH, wszNameBuffer, NULL) == S_OK) + { + if (wszNameBuffer[0] != W('\0')) + { + WCHAR *pJustName = _wcsrchr(wszNameBuffer, DIRECTORY_SEPARATOR_CHAR_W); + if (pJustName == NULL) + pJustName = wszNameBuffer - 1; + + DOAPPEND(pJustName + 1); + bModuleNameWorked = TRUE; + } + } + } +#endif // FEATURE_PAL + + // Under certain circumstances DacpMethodDescData::GetMethodDescName() + // returns a module qualified method name + HRESULT hr = g_sos->GetMethodDescName(dwStartAddr, MAX_LONGPATH, wszNameBuffer, NULL); + + WCHAR* pwszMethNameBegin = (hr != S_OK ? NULL : _wcschr(wszNameBuffer, L'!')); + if (!bModuleNameWorked && hr == S_OK && pwszMethNameBegin != NULL) + { + // if we weren't able to get the module name, but GetMethodDescName returned + // the module as part of the returned method name, use this data + DOAPPEND(wszNameBuffer); + } + else + { + if (!bModuleNameWorked) + { + DOAPPEND (W("UNKNOWN")); + } + DOAPPEND(W("!")); + if (hr == S_OK) + { + // the module name we retrieved above from debugger will take + // precedence over the name possibly returned by GetMethodDescName() + DOAPPEND(pwszMethNameBegin != NULL ? (pwszMethNameBegin+1) : (WCHAR *)wszNameBuffer); + } + else + { + DOAPPEND(W("UNKNOWN")); + } + } + + ULONG64 Displacement = (IPAddr - MethodDescData.NativeCodeAddr); + if (Displacement) + { + _snwprintf_s(wszNameBuffer, MAX_LONGPATH, MAX_LONGPATH, W("+%#x"), Displacement); // _TRUNCATE + DOAPPEND (wszNameBuffer); + } + + return S_OK; +#undef DOAPPEND +} + +BOOL AppendContext(LPVOID pTransitionContexts, size_t maxCount, size_t *pcurCount, size_t uiSizeOfContext, + CROSS_PLATFORM_CONTEXT *context) +{ + if (pTransitionContexts == NULL || *pcurCount >= maxCount) + { + ++(*pcurCount); + return FALSE; + } + if (uiSizeOfContext == sizeof(StackTrace_SimpleContext)) + { + StackTrace_SimpleContext *pSimple = (StackTrace_SimpleContext *) pTransitionContexts; + g_targetMachine->FillSimpleContext(&pSimple[*pcurCount], context); + } + else if (uiSizeOfContext == g_targetMachine->GetContextSize()) + { + // FillTargetContext ensures we only write uiSizeOfContext bytes in pTransitionContexts + // and not sizeof(CROSS_PLATFORM_CONTEXT) bytes (which would overrun). + g_targetMachine->FillTargetContext(pTransitionContexts, context, (int)(*pcurCount)); + } + else + { + return FALSE; + } + ++(*pcurCount); + return TRUE; +} + +HRESULT CALLBACK ImplementEFNStackTrace( + PDEBUG_CLIENT client, + __out_ecount_opt(*puiTextLength) WCHAR wszTextOut[], + size_t *puiTextLength, + LPVOID pTransitionContexts, + size_t *puiTransitionContextCount, + size_t uiSizeOfContext, + DWORD Flags) +{ + +#define DOAPPEND(str) if (!so.Append((str))) { \ + Status = E_OUTOFMEMORY; \ + goto Exit; \ +} + + HRESULT Status = E_FAIL; + StringOutput so; + size_t transitionContextCount = 0; + + if (puiTextLength == NULL) + { + return E_INVALIDARG; + } + + if (pTransitionContexts) + { + if (puiTransitionContextCount == NULL) + { + return E_INVALIDARG; + } + + // Do error checking on context size + if ((uiSizeOfContext != g_targetMachine->GetContextSize()) && + (uiSizeOfContext != sizeof(StackTrace_SimpleContext))) + { + return E_INVALIDARG; + } + } + + IXCLRDataStackWalk *pStackWalk = NULL; + IXCLRDataTask* Task; + ULONG ThreadId; + + if ((Status = g_ExtSystem->GetCurrentThreadSystemId(&ThreadId)) != S_OK || + (Status = g_clrData->GetTaskByOSThreadID(ThreadId, &Task)) != S_OK) + { + // Not a managed thread. + return SOS_E_NOMANAGEDCODE; + } + + Status = Task->CreateStackWalk(CLRDATA_SIMPFRAME_UNRECOGNIZED | + CLRDATA_SIMPFRAME_MANAGED_METHOD | + CLRDATA_SIMPFRAME_RUNTIME_MANAGED_CODE | + CLRDATA_SIMPFRAME_RUNTIME_UNMANAGED_CODE, + &pStackWalk); + + Task->Release(); + + if (Status != S_OK) + { + if (Status == E_FAIL) + { + return SOS_E_NOMANAGEDCODE; + } + return Status; + } + +#ifdef _TARGET_WIN64_ + ULONG numFrames = 0; + BOOL bInNative = TRUE; + + Status = GetContextStackTrace(&numFrames); + if (FAILED(Status)) + { + goto Exit; + } + + for (ULONG i = 0; i < numFrames; i++) + { + PDEBUG_STACK_FRAME pCur = g_Frames + i; + + CLRDATA_ADDRESS pMD; + if (g_sos->GetMethodDescPtrFromIP(pCur->InstructionOffset, &pMD) == S_OK) + { + if (bInNative || transitionContextCount==0) + { + // We only want to list one transition frame if there are multiple frames. + bInNative = FALSE; + + DOAPPEND (W("(TransitionMU)\n")); + // For each transition, we need to store the context information + if (puiTransitionContextCount) + { + // below we cast the i-th AMD64_CONTEXT to CROSS_PLATFORM_CONTEXT + AppendContext (pTransitionContexts, *puiTransitionContextCount, + &transitionContextCount, uiSizeOfContext, (CROSS_PLATFORM_CONTEXT*)(&(g_X64FrameContexts[i]))); + } + else + { + transitionContextCount++; + } + } + + Status = DumpMDInfoBuffer((DWORD_PTR) pMD, Flags, + pCur->StackOffset, pCur->InstructionOffset, so); + if (FAILED(Status)) + { + goto Exit; + } + else if (Status == S_OK) + { + DOAPPEND (W("\n")); + } + // for S_FALSE do not append anything + + } + else + { + if (!bInNative) + { + // We only want to list one transition frame if there are multiple frames. + bInNative = TRUE; + + DOAPPEND (W("(TransitionUM)\n")); + // For each transition, we need to store the context information + if (puiTransitionContextCount) + { + AppendContext (pTransitionContexts, *puiTransitionContextCount, + &transitionContextCount, uiSizeOfContext, (CROSS_PLATFORM_CONTEXT*)(&(g_X64FrameContexts[i]))); + } + else + { + transitionContextCount++; + } + } + } + } + +Exit: +#else // _TARGET_WIN64_ + +#ifdef _DEBUG + size_t prevLength = 0; + static WCHAR wszNameBuffer[1024]; // should be large enough + wcscpy_s(wszNameBuffer, 1024, W("Frame")); // default value +#endif + + BOOL bInNative = TRUE; + + UINT frameCount = 0; + do + { + DacpFrameData FrameData; + if ((Status = FrameData.Request(pStackWalk)) != S_OK) + { + goto Exit; + } + + CROSS_PLATFORM_CONTEXT context; + if ((Status=pStackWalk->GetContext(DT_CONTEXT_FULL, g_targetMachine->GetContextSize(), + NULL, (BYTE *)&context))!=S_OK) + { + goto Exit; + } + + ExtDbgOut ( " * Ctx[BSI]: %08x %08x %08x ", GetBP(context), GetSP(context), GetIP(context) ); + + CLRDATA_ADDRESS pMD; + if (!FrameData.frameAddr) + { + if (bInNative || transitionContextCount==0) + { + // We only want to list one transition frame if there are multiple frames. + bInNative = FALSE; + + DOAPPEND (W("(TransitionMU)\n")); + // For each transition, we need to store the context information + if (puiTransitionContextCount) + { + AppendContext (pTransitionContexts, *puiTransitionContextCount, + &transitionContextCount, uiSizeOfContext, &context); + } + else + { + transitionContextCount++; + } + } + + // we may have a method, try to get the methoddesc + if (g_sos->GetMethodDescPtrFromIP(GetIP(context), &pMD)==S_OK) + { + Status = DumpMDInfoBuffer((DWORD_PTR) pMD, Flags, + GetSP(context), GetIP(context), so); + if (FAILED(Status)) + { + goto Exit; + } + else if (Status == S_OK) + { + DOAPPEND (W("\n")); + } + // for S_FALSE do not append anything + } + } + else + { +#ifdef _DEBUG + if (Output::IsDebugOutputEnabled()) + { + DWORD_PTR vtAddr; + MOVE(vtAddr, TO_TADDR(FrameData.frameAddr)); + if (g_sos->GetFrameName(TO_CDADDR(vtAddr), 1024, wszNameBuffer, NULL) == S_OK) + ExtDbgOut("[%ls: %08x] ", wszNameBuffer, FrameData.frameAddr); + else + ExtDbgOut("[Frame: %08x] ", FrameData.frameAddr); + } +#endif + if (!bInNative) + { + // We only want to list one transition frame if there are multiple frames. + bInNative = TRUE; + + DOAPPEND (W("(TransitionUM)\n")); + // For each transition, we need to store the context information + if (puiTransitionContextCount) + { + AppendContext (pTransitionContexts, *puiTransitionContextCount, + &transitionContextCount, uiSizeOfContext, &context); + } + else + { + transitionContextCount++; + } + } + } + +#ifdef _DEBUG + if (so.Length() > prevLength) + { + ExtDbgOut ( "%ls", so.String()+prevLength ); + prevLength = so.Length(); + } + else + ExtDbgOut ( "\n" ); +#endif + + } + while ((frameCount++) < MAX_STACK_FRAMES && pStackWalk->Next()==S_OK); + + Status = S_OK; + +Exit: +#endif // _TARGET_WIN64_ + + if (pStackWalk) + { + pStackWalk->Release(); + pStackWalk = NULL; + } + + // We have finished. Does the user want to copy this data to a buffer? + if (Status == S_OK) + { + if(wszTextOut) + { + // They want at least partial output + wcsncpy_s (wszTextOut, *puiTextLength, so.String(), *puiTextLength-1); // _TRUNCATE + } + else + { + *puiTextLength = _wcslen (so.String()) + 1; + } + + if (puiTransitionContextCount) + { + *puiTransitionContextCount = transitionContextCount; + } + } + + return Status; +} + +#ifdef FEATURE_PAL +#define PAL_TRY_NAKED PAL_CPP_TRY +#define PAL_EXCEPT_NAKED(disp) PAL_CPP_CATCH_ALL +#define PAL_ENDTRY_NAKED PAL_CPP_ENDTRY +#endif + +// TODO: Convert PAL_TRY_NAKED to something that works on the Mac. +HRESULT CALLBACK ImplementEFNStackTraceTry( + PDEBUG_CLIENT client, + __out_ecount_opt(*puiTextLength) WCHAR wszTextOut[], + size_t *puiTextLength, + LPVOID pTransitionContexts, + size_t *puiTransitionContextCount, + size_t uiSizeOfContext, + DWORD Flags) +{ + HRESULT Status = E_FAIL; + + PAL_TRY_NAKED + { + Status = ImplementEFNStackTrace(client, wszTextOut, puiTextLength, + pTransitionContexts, puiTransitionContextCount, + uiSizeOfContext, Flags); + } + PAL_EXCEPT_NAKED (EXCEPTION_EXECUTE_HANDLER) + { + } + PAL_ENDTRY_NAKED + + return Status; +} + +// See sos_stacktrace.h for the contract with the callers regarding the LPVOID arguments. +HRESULT CALLBACK _EFN_StackTrace( + PDEBUG_CLIENT client, + __out_ecount_opt(*puiTextLength) WCHAR wszTextOut[], + size_t *puiTextLength, + __out_bcount_opt(uiSizeOfContext*(*puiTransitionContextCount)) LPVOID pTransitionContexts, + size_t *puiTransitionContextCount, + size_t uiSizeOfContext, + DWORD Flags) +{ + INIT_API(); + + Status = ImplementEFNStackTraceTry(client, wszTextOut, puiTextLength, + pTransitionContexts, puiTransitionContextCount, + uiSizeOfContext, Flags); + + return Status; +} + + +BOOL FormatFromRemoteString(DWORD_PTR strObjPointer, __out_ecount(cchString) PWSTR wszBuffer, ULONG cchString) +{ + BOOL bRet = FALSE; + + wszBuffer[0] = L'\0'; + + DacpObjectData objData; + if (objData.Request(g_sos, TO_CDADDR(strObjPointer))!=S_OK) + { + return bRet; + } + + strobjInfo stInfo; + + if (MOVE(stInfo, strObjPointer) != S_OK) + { + return bRet; + } + + DWORD dwBufLength = 0; + if (!ClrSafeInt<DWORD>::addition(stInfo.m_StringLength, 1, dwBufLength)) + { + ExtOut("<integer overflow>\n"); + return bRet; + } + + LPWSTR pwszBuf = new NOTHROW WCHAR[dwBufLength]; + if (pwszBuf == NULL) + { + return bRet; + } + + if (g_sos->GetObjectStringData(TO_CDADDR(strObjPointer), stInfo.m_StringLength+1, pwszBuf, NULL)!=S_OK) + { + delete [] pwszBuf; + return bRet; + } + + // String is in format + // <SP><SP><SP>at <function name>(args,...)\n + // ... + // Parse and copy just <function name>(args,...) + + LPWSTR pwszPointer = pwszBuf; + + WCHAR PSZSEP[] = W(" at "); + + UINT Length = 0; + while(1) + { + if (_wcsncmp(pwszPointer, PSZSEP, _countof(PSZSEP)-1) != 0) + { + delete [] pwszBuf; + return bRet; + } + + pwszPointer += _wcslen(PSZSEP); + LPWSTR nextPos = _wcsstr(pwszPointer, PSZSEP); + if (nextPos == NULL) + { + // Done! Note that we are leaving the function before we add the last + // line of stack trace to the output string. This is on purpose because + // this string needs to be merged with a real trace, and the last line + // of the trace will be common to the real trace. + break; + } + WCHAR c = *nextPos; + *nextPos = L'\0'; + + // Buffer is calculated for sprintf below (" %p %p %S\n"); + WCHAR wszLineBuffer[mdNameLen + 8 + sizeof(size_t)*2]; + + // Note that we don't add a newline because we have this embedded in wszLineBuffer + swprintf_s(wszLineBuffer, _countof(wszLineBuffer), W(" %p %p %s"), (void*)(size_t)-1, (void*)(size_t)-1, pwszPointer); + Length += (UINT)_wcslen(wszLineBuffer); + + if (wszBuffer) + { + wcsncat_s(wszBuffer, cchString, wszLineBuffer, _TRUNCATE); + } + + *nextPos = c; + // Move to the next line. + pwszPointer = nextPos; + } + + delete [] pwszBuf; + + // Return TRUE only if the stack string had any information that was successfully parsed. + // (Length > 0) is a good indicator of that. + bRet = (Length > 0); + return bRet; +} + +HRESULT AppendExceptionInfo(CLRDATA_ADDRESS cdaObj, + __out_ecount(cchString) PWSTR wszStackString, + ULONG cchString, + BOOL bNestedCase) // If bNestedCase is TRUE, the last frame of the computed stack is left off +{ + DacpObjectData objData; + if (objData.Request(g_sos, cdaObj) != S_OK) + { + return E_FAIL; + } + + // Make sure it is an exception object, and get the MT of Exception + CLRDATA_ADDRESS exceptionMT = isExceptionObj(objData.MethodTable); + if (exceptionMT == NULL) + { + return E_INVALIDARG; + } + + // First try to get exception object data using ISOSDacInterface2 + DacpExceptionObjectData excData; + BOOL bGotExcData = SUCCEEDED(excData.Request(g_sos, cdaObj)); + + int iOffset; + // Is there a _remoteStackTraceString? We'll want to prepend that data. + // We only have string data, so IP/SP info has to be set to -1. + DWORD_PTR strPointer; + if (bGotExcData) + { + strPointer = TO_TADDR(excData.RemoteStackTraceString); + } + else + { + iOffset = GetObjFieldOffset (cdaObj, objData.MethodTable, W("_remoteStackTraceString")); + MOVE (strPointer, TO_TADDR(cdaObj) + iOffset); + } + if (strPointer) + { + WCHAR *pwszBuffer = new NOTHROW WCHAR[cchString]; + if (pwszBuffer == NULL) + { + return E_OUTOFMEMORY; + } + + if (FormatFromRemoteString(strPointer, pwszBuffer, cchString)) + { + // Prepend this stuff to the string for the user + wcsncat_s(wszStackString, cchString, pwszBuffer, _TRUNCATE); + } + delete[] pwszBuffer; + } + + BOOL bAsync = bGotExcData ? IsAsyncException(excData) + : IsAsyncException(TO_TADDR(cdaObj), TO_TADDR(objData.MethodTable)); + + DWORD_PTR arrayPtr; + if (bGotExcData) + { + arrayPtr = TO_TADDR(excData.StackTrace); + } + else + { + iOffset = GetObjFieldOffset (cdaObj, objData.MethodTable, W("_stackTrace")); + MOVE (arrayPtr, TO_TADDR(cdaObj) + iOffset); + } + + if (arrayPtr) + { + DWORD arrayLen; + MOVE (arrayLen, arrayPtr + sizeof(DWORD_PTR)); + + if (arrayLen) + { +#ifdef _TARGET_WIN64_ + DWORD_PTR dataPtr = arrayPtr + sizeof(DWORD_PTR) + sizeof(DWORD) + sizeof(DWORD); +#else + DWORD_PTR dataPtr = arrayPtr + sizeof(DWORD_PTR) + sizeof(DWORD); +#endif // _TARGET_WIN64_ + size_t stackTraceSize = 0; + MOVE (stackTraceSize, dataPtr); // data length is stored at the beginning of the array in this case + + DWORD cbStackSize = static_cast<DWORD>(stackTraceSize * sizeof(StackTraceElement)); + dataPtr += sizeof(size_t) + sizeof(size_t); // skip the array header, then goes the data + + if (stackTraceSize != 0) + { + size_t iLength = FormatGeneratedException (dataPtr, cbStackSize, NULL, 0, bAsync, bNestedCase); + WCHAR *pwszBuffer = new NOTHROW WCHAR[iLength + 1]; + if (pwszBuffer) + { + FormatGeneratedException(dataPtr, cbStackSize, pwszBuffer, iLength + 1, bAsync, bNestedCase); + wcsncat_s(wszStackString, cchString, pwszBuffer, _TRUNCATE); + delete[] pwszBuffer; + } + else + { + return E_OUTOFMEMORY; + } + } + } + } + return S_OK; +} + +HRESULT ImplementEFNGetManagedExcepStack( + CLRDATA_ADDRESS cdaStackObj, + __out_ecount(cchString) PWSTR wszStackString, + ULONG cchString) +{ + HRESULT Status = E_FAIL; + + if (wszStackString == NULL || cchString == 0) + { + return E_INVALIDARG; + } + + CLRDATA_ADDRESS threadAddr = GetCurrentManagedThread(); + DacpThreadData Thread; + BOOL bCanUseThreadContext = TRUE; + + ZeroMemory(&Thread, sizeof(DacpThreadData)); + + if ((threadAddr == NULL) || (Thread.Request(g_sos, threadAddr) != S_OK)) + { + // The current thread is unmanaged + bCanUseThreadContext = FALSE; + } + + if (cdaStackObj == NULL) + { + if (!bCanUseThreadContext) + { + return E_INVALIDARG; + } + + TADDR taLTOH = NULL; + if ((!SafeReadMemory(TO_TADDR(Thread.lastThrownObjectHandle), + &taLTOH, + sizeof(taLTOH), NULL)) || (taLTOH==NULL)) + { + return Status; + } + else + { + cdaStackObj = TO_CDADDR(taLTOH); + } + } + + // Put the stack trace header on + AddExceptionHeader(wszStackString, cchString); + + // First is there a nested exception? + if (bCanUseThreadContext && Thread.firstNestedException) + { + CLRDATA_ADDRESS obj = 0, next = 0; + CLRDATA_ADDRESS currentNested = Thread.firstNestedException; + do + { + Status = g_sos->GetNestedExceptionData(currentNested, &obj, &next); + + // deal with the inability to read a nested exception gracefully + if (Status != S_OK) + { + break; + } + + Status = AppendExceptionInfo(obj, wszStackString, cchString, TRUE); + currentNested = next; + } + while(currentNested != NULL); + } + + Status = AppendExceptionInfo(cdaStackObj, wszStackString, cchString, FALSE); + + return Status; +} + +// TODO: Enable this when ImplementEFNStackTraceTry is fixed. +// This function, like VerifyDAC, exists for the purpose of testing +// hard-to-get-to SOS APIs. +DECLARE_API(VerifyStackTrace) +{ + INIT_API(); + + BOOL bVerifyManagedExcepStack = FALSE; + CMDOption option[] = + { // name, vptr, type, hasValue + {"-ManagedExcepStack", &bVerifyManagedExcepStack, COBOOL, FALSE}, + }; + + if (!GetCMDOption(args, option, _countof(option), NULL,0,NULL)) + { + return Status; + } + + if (bVerifyManagedExcepStack) + { + CLRDATA_ADDRESS threadAddr = GetCurrentManagedThread(); + DacpThreadData Thread; + + TADDR taExc = NULL; + if ((threadAddr == NULL) || (Thread.Request(g_sos, threadAddr) != S_OK)) + { + ExtOut("The current thread is unmanaged\n"); + return Status; + } + + TADDR taLTOH = NULL; + if ((!SafeReadMemory(TO_TADDR(Thread.lastThrownObjectHandle), + &taLTOH, + sizeof(taLTOH), NULL)) || (taLTOH == NULL)) + { + ExtOut("There is no current managed exception on this thread\n"); + return Status; + } + else + { + taExc = taLTOH; + } + + const SIZE_T cchStr = 4096; + WCHAR *wszStr = (WCHAR *)alloca(cchStr * sizeof(WCHAR)); + if (ImplementEFNGetManagedExcepStack(TO_CDADDR(taExc), wszStr, cchStr) != S_OK) + { + ExtOut("Error!\n"); + return Status; + } + + ExtOut("_EFN_GetManagedExcepStack(%P, wszStr, sizeof(wszStr)) returned:\n", SOS_PTR(taExc)); + ExtOut("%S\n", wszStr); + + if (ImplementEFNGetManagedExcepStack((ULONG64)NULL, wszStr, cchStr) != S_OK) + { + ExtOut("Error!\n"); + return Status; + } + + ExtOut("_EFN_GetManagedExcepStack(NULL, wszStr, sizeof(wszStr)) returned:\n"); + ExtOut("%S\n", wszStr); + } + else + { + size_t textLength = 0; + size_t contextLength = 0; + Status = ImplementEFNStackTraceTry(client, + NULL, + &textLength, + NULL, + &contextLength, + 0, + 0); + + if (Status != S_OK) + { + ExtOut("Error: %lx\n", Status); + return Status; + } + + ExtOut("Number of characters requested: %d\n", textLength); + WCHAR *wszBuffer = new NOTHROW WCHAR[textLength + 1]; + if (wszBuffer == NULL) + { + ReportOOM(); + return Status; + } + + // For the transition contexts buffer the callers are expected to allocate + // contextLength * sizeof(TARGET_CONTEXT), and not + // contextLength * sizeof(CROSS_PLATFORM_CONTEXT). See sos_stacktrace.h for + // details. + LPBYTE pContexts = new NOTHROW BYTE[contextLength * g_targetMachine->GetContextSize()]; + + if (pContexts == NULL) + { + ReportOOM(); + delete[] wszBuffer; + return Status; + } + + Status = ImplementEFNStackTrace(client, + wszBuffer, + &textLength, + pContexts, + &contextLength, + g_targetMachine->GetContextSize(), + 0); + + if (Status != S_OK) + { + ExtOut("Error: %lx\n", Status); + delete[] wszBuffer; + delete [] pContexts; + return Status; + } + + ExtOut("%S\n", wszBuffer); + + ExtOut("Context information:\n"); + if (IsDbgTargetX86()) + { + ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %" POINTERSIZE "s\n", + "Ebp", "Esp", "Eip"); + } + else if (IsDbgTargetAmd64()) + { + ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %" POINTERSIZE "s\n", + "Rbp", "Rsp", "Rip"); + } + else if (IsDbgTargetArm()) + { + ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %" POINTERSIZE "s\n", + "FP", "SP", "PC"); + } + else + { + ExtOut("Unsupported platform"); + delete [] pContexts; + delete[] wszBuffer; + return S_FALSE; + } + + for (size_t j=0; j < contextLength; j++) + { + CROSS_PLATFORM_CONTEXT *pCtx = (CROSS_PLATFORM_CONTEXT*)(pContexts + j*g_targetMachine->GetContextSize()); + ExtOut("%p %p %p\n", GetBP(*pCtx), GetSP(*pCtx), GetIP(*pCtx)); + } + + delete [] pContexts; + + StackTrace_SimpleContext *pSimple = new NOTHROW StackTrace_SimpleContext[contextLength]; + if (pSimple == NULL) + { + ReportOOM(); + delete[] wszBuffer; + return Status; + } + + Status = ImplementEFNStackTrace(client, + wszBuffer, + &textLength, + pSimple, + &contextLength, + sizeof(StackTrace_SimpleContext), + 0); + + if (Status != S_OK) + { + ExtOut("Error: %lx\n", Status); + delete[] wszBuffer; + delete [] pSimple; + return Status; + } + + ExtOut("Simple Context information:\n"); + if (IsDbgTargetX86()) + ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %" POINTERSIZE "s\n", + "Ebp", "Esp", "Eip"); + else if (IsDbgTargetAmd64()) + ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %" POINTERSIZE "s\n", + "Rbp", "Rsp", "Rip"); + else if (IsDbgTargetArm()) + ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %" POINTERSIZE "s\n", + "FP", "SP", "PC"); + else + { + ExtOut("Unsupported platform"); + delete[] wszBuffer; + delete [] pSimple; + return S_FALSE; + } + for (size_t j=0; j < contextLength; j++) + { + ExtOut("%p %p %p\n", SOS_PTR(pSimple[j].FrameOffset), + SOS_PTR(pSimple[j].StackOffset), + SOS_PTR(pSimple[j].InstructionOffset)); + } + delete [] pSimple; + delete[] wszBuffer; + } + + return Status; +} + +#ifndef FEATURE_PAL + +// This is an internal-only Apollo extension to de-optimize the code +DECLARE_API(SuppressJitOptimization) +{ + INIT_API_NOEE(); + MINIDUMP_NOT_SUPPORTED(); + + StringHolder onOff; + CMDValue arg[] = + { // vptr, type + {&onOff.data, COSTRING}, + }; + size_t nArg; + if (!GetCMDOption(args, NULL, 0, arg, _countof(arg), &nArg)) + { + return E_FAIL; + } + + if(nArg == 1 && (_stricmp(onOff.data, "On") == 0)) + { + // if CLR is already loaded, try to change the flags now + if(CheckEEDll() == S_OK) + { + SetNGENCompilerFlags(CORDEBUG_JIT_DISABLE_OPTIMIZATION); + } + + if(!g_fAllowJitOptimization) + ExtOut("JIT optimization is already suppressed\n"); + else + { + g_fAllowJitOptimization = FALSE; + g_ExtControl->Execute(DEBUG_EXECUTE_NOT_LOGGED, "sxe -c \"!HandleCLRN\" clrn", 0); + ExtOut("JIT optimization will be suppressed\n"); + } + + + } + else if(nArg == 1 && (_stricmp(onOff.data, "Off") == 0)) + { + // if CLR is already loaded, try to change the flags now + if(CheckEEDll() == S_OK) + { + SetNGENCompilerFlags(CORDEBUG_JIT_DEFAULT); + } + + if(g_fAllowJitOptimization) + ExtOut("JIT optimization is already permitted\n"); + else + { + g_fAllowJitOptimization = TRUE; + ExtOut("JIT optimization will be permitted\n"); + } + } + else + { + ExtOut("Usage: !SuppressJitOptimization <on|off>\n"); + } + + return S_OK; +} + +// Uses ICorDebug to set the state of desired NGEN compiler flags. This can suppress pre-jitted optimized +// code +HRESULT SetNGENCompilerFlags(DWORD flags) +{ + HRESULT hr; + + ToRelease<ICorDebugProcess2> proc2; + if(FAILED(hr = InitCorDebugInterface())) + { + ExtOut("SOS: warning, prejitted code optimizations could not be changed. Failed to load ICorDebug HR = 0x%x\n", hr); + } + else if(FAILED(g_pCorDebugProcess->QueryInterface(__uuidof(ICorDebugProcess2), (void**) &proc2))) + { + if(flags != CORDEBUG_JIT_DEFAULT) + { + ExtOut("SOS: warning, prejitted code optimizations could not be changed. This CLR version doesn't support the functionality\n"); + } + else + { + hr = S_OK; + } + } + else if(FAILED(hr = proc2->SetDesiredNGENCompilerFlags(flags))) + { + // Versions of CLR that don't have SetDesiredNGENCompilerFlags DAC-ized will return E_FAIL. + // This was first supported in the clr_triton branch around 4/1/12, Apollo release + // It will likely be supported in desktop CLR during Dev12 + if(hr == E_FAIL) + { + if(flags != CORDEBUG_JIT_DEFAULT) + { + ExtOut("SOS: warning, prejitted code optimizations could not be changed. This CLR version doesn't support the functionality\n"); + } + else + { + hr = S_OK; + } + } + else if(hr == CORDBG_E_NGEN_NOT_SUPPORTED) + { + if(flags != CORDEBUG_JIT_DEFAULT) + { + ExtOut("SOS: warning, prejitted code optimizations could not be changed. This CLR version doesn't support NGEN\n"); + } + else + { + hr = S_OK; + } + } + else if(hr == CORDBG_E_MUST_BE_IN_CREATE_PROCESS) + { + DWORD currentFlags = 0; + if(FAILED(hr = proc2->GetDesiredNGENCompilerFlags(¤tFlags))) + { + ExtOut("SOS: warning, prejitted code optimizations could not be changed. GetDesiredNGENCompilerFlags failed hr=0x%x\n", hr); + } + else if(currentFlags != flags) + { + ExtOut("SOS: warning, prejitted code optimizations could not be changed at this time. This setting is fixed once CLR starts\n"); + } + else + { + hr = S_OK; + } + } + else + { + ExtOut("SOS: warning, prejitted code optimizations could not be changed at this time. SetDesiredNGENCompilerFlags hr = 0x%x\n", hr); + } + } + + return hr; +} + + +// This is an internal-only Apollo extension to save breakpoint/watch state +DECLARE_API(SaveState) +{ + INIT_API_NOEE(); + MINIDUMP_NOT_SUPPORTED(); + + StringHolder filePath; + CMDValue arg[] = + { // vptr, type + {&filePath.data, COSTRING}, + }; + size_t nArg; + if (!GetCMDOption(args, NULL, 0, arg, _countof(arg), &nArg)) + { + return E_FAIL; + } + + if(nArg == 0) + { + ExtOut("Usage: !SaveState <file_path>\n"); + } + + FILE* pFile; + errno_t error = fopen_s(&pFile, filePath.data, "w"); + if(error != 0) + { + ExtOut("Failed to open file %s, error=0x%x\n", filePath.data, error); + return E_FAIL; + } + + g_bpoints.SaveBreakpoints(pFile); + g_watchCmd.SaveListToFile(pFile); + + fclose(pFile); + ExtOut("Session breakpoints and watch expressions saved to %s\n", filePath.data); + return S_OK; +} + +#endif // FEATURE_PAL + +DECLARE_API(StopOnCatch) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + + g_stopOnNextCatch = TRUE; + ULONG32 flags = 0; + g_clrData->GetOtherNotificationFlags(&flags); + flags |= CLRDATA_NOTIFY_ON_EXCEPTION_CATCH_ENTER; + g_clrData->SetOtherNotificationFlags(flags); + ExtOut("Debuggee will break the next time a managed exception is caught during execution\n"); + return S_OK; +} + +// This is an undocumented SOS extension command intended to help test SOS +// It causes the Dml output to be printed to the console uninterpretted so +// that a test script can read the commands which are hidden in the markup +DECLARE_API(ExposeDML) +{ + Output::SetDMLExposed(true); + return S_OK; +} + +// According to kksharma the Windows debuggers always sign-extend +// arguments when calling externally, therefore StackObjAddr +// conforms to CLRDATA_ADDRESS contract. +HRESULT CALLBACK +_EFN_GetManagedExcepStack( + PDEBUG_CLIENT client, + ULONG64 StackObjAddr, + __out_ecount (cbString) PSTR szStackString, + ULONG cbString + ) +{ + INIT_API(); + + ArrayHolder<WCHAR> tmpStr = new NOTHROW WCHAR[cbString]; + if (tmpStr == NULL) + { + ReportOOM(); + return E_OUTOFMEMORY; + } + + if (FAILED(Status = ImplementEFNGetManagedExcepStack(StackObjAddr, tmpStr, cbString))) + { + return Status; + } + + if (WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, tmpStr, -1, szStackString, cbString, NULL, NULL) == 0) + { + return E_FAIL; + } + + return S_OK; +} + +// same as _EFN_GetManagedExcepStack, but returns the stack as a wide string. +HRESULT CALLBACK +_EFN_GetManagedExcepStackW( + PDEBUG_CLIENT client, + ULONG64 StackObjAddr, + __out_ecount(cchString) PWSTR wszStackString, + ULONG cchString + ) +{ + INIT_API(); + + return ImplementEFNGetManagedExcepStack(StackObjAddr, wszStackString, cchString); +} + +// According to kksharma the Windows debuggers always sign-extend +// arguments when calling externally, therefore objAddr +// conforms to CLRDATA_ADDRESS contract. +HRESULT CALLBACK +_EFN_GetManagedObjectName( + PDEBUG_CLIENT client, + ULONG64 objAddr, + __out_ecount (cbName) PSTR szName, + ULONG cbName + ) +{ + INIT_API (); + + if (!sos::IsObject(objAddr, false)) + { + return E_INVALIDARG; + } + + sos::Object obj = TO_TADDR(objAddr); + + if (WideCharToMultiByte(CP_ACP, 0, obj.GetTypeName(), (int) (_wcslen(obj.GetTypeName()) + 1), + szName, cbName, NULL, NULL) == 0) + { + return E_FAIL; + } + return S_OK; +} + +// According to kksharma the Windows debuggers always sign-extend +// arguments when calling externally, therefore objAddr +// conforms to CLRDATA_ADDRESS contract. +HRESULT CALLBACK +_EFN_GetManagedObjectFieldInfo( + PDEBUG_CLIENT client, + ULONG64 objAddr, + __out_ecount (mdNameLen) PSTR szFieldName, + PULONG64 pValue, + PULONG pOffset + ) +{ + INIT_API(); + DacpObjectData objData; + LPWSTR fieldName = (LPWSTR)alloca(mdNameLen * sizeof(WCHAR)); + + if (szFieldName == NULL || *szFieldName == '\0' || + objAddr == NULL) + { + return E_FAIL; + } + + if (pOffset == NULL && pValue == NULL) + { + // One of these needs to be valid + return E_FAIL; + } + + if (FAILED(objData.Request(g_sos, objAddr))) + { + return E_FAIL; + } + + MultiByteToWideChar(CP_ACP,0,szFieldName,-1,fieldName,mdNameLen); + + int iOffset = GetObjFieldOffset (objAddr, objData.MethodTable, fieldName); + if (iOffset <= 0) + { + return E_FAIL; + } + + if (pOffset) + { + *pOffset = (ULONG) iOffset; + } + + if (pValue) + { + if (FAILED(g_ExtData->ReadVirtual(UL64_TO_CDA(objAddr + iOffset), pValue, sizeof(ULONG64), NULL))) + { + return E_FAIL; + } + } + + return S_OK; +} + +void PrintHelp (__in_z LPCSTR pszCmdName) +{ + static LPSTR pText = NULL; + + if (pText == NULL) { +#ifndef FEATURE_PAL + HGLOBAL hResource = NULL; + HRSRC hResInfo = FindResource (g_hInstance, TEXT ("DOCUMENTATION"), TEXT ("TEXT")); + if (hResInfo) hResource = LoadResource (g_hInstance, hResInfo); + if (hResource) pText = (LPSTR) LockResource (hResource); + if (pText == NULL) + { + ExtOut("Error loading documentation resource\n"); + return; + } +#else + int err = PAL_InitializeDLL(); + if(err != 0) + { + ExtOut("Error initializing PAL\n"); + return; + } + char lpFilename[MAX_LONGPATH + 12]; // + 12 to make enough room for strcat function. + strcpy_s(lpFilename, _countof(lpFilename), g_ExtServices->GetCoreClrDirectory()); + strcat_s(lpFilename, _countof(lpFilename), "sosdocsunix.txt"); + + HANDLE hSosDocFile = CreateFileA(lpFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + if (hSosDocFile == INVALID_HANDLE_VALUE) { + ExtOut("Error finding documentation file\n"); + return; + } + + HANDLE hMappedSosDocFile = CreateFileMappingA(hSosDocFile, NULL, PAGE_READONLY, 0, 0, NULL); + CloseHandle(hSosDocFile); + if (hMappedSosDocFile == NULL) { + ExtOut("Error mapping documentation file\n"); + return; + } + + pText = (LPSTR)MapViewOfFile(hMappedSosDocFile, FILE_MAP_READ, 0, 0, 0); + CloseHandle(hMappedSosDocFile); + if (pText == NULL) + { + ExtOut("Error loading documentation file\n"); + return; + } +#endif + } + + // Find our line in the text file + char searchString[MAX_LONGPATH]; + sprintf_s(searchString, _countof(searchString), "COMMAND: %s.", pszCmdName); + + LPSTR pStart = strstr(pText, searchString); + LPSTR pEnd = NULL; + if (!pStart) + { + ExtOut("Documentation for %s not found.\n", pszCmdName); + return; + } + + // Go to the end of this line: + pStart = strchr(pStart, '\n'); + if (!pStart) + { + ExtOut("Expected newline in documentation resource.\n"); + return; + } + + // Bypass the newline that pStart points to and setup pEnd for the loop below. We set + // pEnd to be the old pStart since we add one to it when we call strstr. + pEnd = pStart++; + + // Find the first occurrence of \\ followed by an \r or an \n on a line by itself. + do + { + pEnd = strstr(pEnd+1, "\\\\"); + } while (pEnd && ((pEnd[-1] != '\r' && pEnd[-1] != '\n') || (pEnd[3] != '\r' && pEnd[3] != '\n'))); + + if (pEnd) + { + // We have found a \\ followed by a \r or \n. Do not print out the character pEnd points + // to, as this will be the first \ (this is why we don't add one to the second parameter). + ExtOut("%.*s", pEnd - pStart, pStart); + } + else + { + // If pEnd is false then we have run to the end of the document. However, we did find + // the command to print, so we should simply print to the end of the file. We'll add + // an extra newline here in case the file does not contain one. + ExtOut("%s\n", pStart); + } +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function displays the commands available in strike and the * +* arguments passed into each. +* * +\**********************************************************************/ +DECLARE_API(Help) +{ + // Call extension initialization functions directly, because we don't need the DAC dll to be initialized to get help. + HRESULT Status; + __ExtensionCleanUp __extensionCleanUp; + if ((Status = ExtQuery(client)) != S_OK) return Status; + ControlC = FALSE; + + StringHolder commandName; + CMDValue arg[] = + { + {&commandName.data, COSTRING} + }; + size_t nArg; + if (!GetCMDOption(args, NULL, 0, arg, _countof(arg), &nArg)) + { + return Status; + } + + ExtOut("-------------------------------------------------------------------------------\n"); + + if (nArg == 1) + { + // Convert commandName to lower-case + LPSTR curChar = commandName.data; + while (*curChar != '\0') + { + if ( ((unsigned) *curChar <= 0x7F) && isupper(*curChar)) + { + *curChar = (CHAR) tolower(*curChar); + } + curChar++; + } + + // Strip off leading "!" if the user put that. + curChar = commandName.data; + if (*curChar == '!') + curChar++; + + PrintHelp (curChar); + } + else + { + PrintHelp ("contents"); + } + + return S_OK; +} diff --git a/src/ToolBox/SOS/Strike/strike.h b/src/ToolBox/SOS/Strike/strike.h new file mode 100644 index 0000000000..e070898ff5 --- /dev/null +++ b/src/ToolBox/SOS/Strike/strike.h @@ -0,0 +1,144 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ==++== +// + +// +// ==--== +#ifndef __strike_h__ +#define __strike_h__ + +#ifndef _countof +#define _countof(x) (sizeof(x)/sizeof(x[0])) +#endif + +#if defined(_MSC_VER) +#pragma warning(disable:4245) // signed/unsigned mismatch +#pragma warning(disable:4100) // unreferenced formal parameter +#pragma warning(disable:4201) // nonstandard extension used : nameless struct/union +#pragma warning(disable:4127) // conditional expression is constant +#pragma warning(disable:6255) // Prefast: alloca indicates failure by raising a stack overflow exception +#endif + +#ifdef PAL_STDCPP_COMPAT +#define _iswprint PAL_iswprint +#define _wcslen PAL_wcslen +#define _wcsncmp PAL_wcsncmp +#define _wcsrchr PAL_wcsrchr +#define _wcscmp PAL_wcscmp +#define _wcschr PAL_wcschr +#define _wcscspn PAL_wcscspn +#define _wcscat PAL_wcscat +#define _wcsstr PAL_wcsstr +#else // PAL_STDCPP_COMPAT +#define _iswprint iswprint +#define _wcslen wcslen +#define _wcsncmp wcsncmp +#define _wcsrchr wcsrchr +#define _wcscmp wcscmp +#define _wcschr wcschr +#define _wcscspn wcscspn +#define _wcscat wcscat +#define _wcsstr wcsstr +#endif // !PAL_STDCPP_COMPAT + +#ifdef PLATFORM_UNIX +#define _vsnprintf vsnprintf +#endif + +#define ___in _SAL1_Source_(__in, (), _In_) +#define ___out _SAL1_Source_(__out, (), _Out_) + +#define _max(a, b) (((a) > (b)) ? (a) : (b)) +#define _min(a, b) (((a) < (b)) ? (a) : (b)) + +#include <winternl.h> +#include <winver.h> +#include <windows.h> + +#include <wchar.h> + +//#define NOEXTAPI +#define KDEXT_64BIT +#include <wdbgexts.h> +#undef DECLARE_API +#undef GetContext +#undef SetContext +#undef ReadMemory +#undef WriteMemory +#undef GetFieldValue +#undef StackTrace + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> + + +#ifndef PAL_STDCPP_COMPAT +#include <malloc.h> +#endif + +#ifdef FEATURE_PAL +#ifndef alloca +#define alloca __builtin_alloca +#endif +#ifndef _alloca +#define _alloca __builtin_alloca +#endif +#endif // FEATURE_PAL + +#include <stddef.h> + +#ifndef FEATURE_PAL +#include <basetsd.h> +#endif + +#define CORHANDLE_MASK 0x1 + +#include "static_assert.h" + +// exts.h includes dbgeng.h which has a bunch of IIDs we need instantiated. +#define INITGUID +#include "guiddef.h" + +#ifdef FEATURE_PAL +#define SOS_PTR(x) (size_t)(x) +#else // FEATURE_PAL +#define SOS_PTR(x) (unsigned __int64)(x) +#endif // FEATURE_PAL else + +#include "exts.h" + +//Alignment constant for allocation +#if defined(_TARGET_X86_) || defined(_TARGET_ARM_) +#define ALIGNCONST 3 +#else +#define ALIGNCONST 7 +#endif + +//The large object heap uses a different alignment +#define ALIGNCONSTLARGE 7 + +#ifdef _WIN64 +#define SIZEOF_OBJHEADER 8 +#else // !_WIN64 +#define SIZEOF_OBJHEADER 4 +#endif // !_WIN64 + +#define plug_skew SIZEOF_OBJHEADER +#define min_obj_size (sizeof(BYTE*)+plug_skew+sizeof(size_t)) + +extern BOOL CallStatus; + + +#ifndef NT_SUCCESS +#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) +#endif + +HRESULT SetNGENCompilerFlags(DWORD flags); + + +#endif // __strike_h__ diff --git a/src/ToolBox/SOS/Strike/util.cpp b/src/ToolBox/SOS/Strike/util.cpp new file mode 100644 index 0000000000..9eec76e42c --- /dev/null +++ b/src/ToolBox/SOS/Strike/util.cpp @@ -0,0 +1,6975 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ==++== +// + +// +// ==--== +#include "sos.h" +#include "disasm.h" +#include <dbghelp.h> + +#include "corhdr.h" +#include "cor.h" +#include "dacprivate.h" +#include "sospriv.h" +#include "corerror.h" +#include "safemath.h" + +#include <psapi.h> +#include <cordebug.h> +#include <xcordebug.h> +#include <metahost.h> +#include <mscoree.h> +#include <tchar.h> +#include "debugshim.h" + +#ifdef FEATURE_PAL +#include "datatarget.h" +#endif // FEATURE_PAL +#include "gcinfo.h" + +#ifndef STRESS_LOG +#define STRESS_LOG +#endif // STRESS_LOG +#define STRESS_LOG_READONLY +#include "stresslog.h" + +#ifndef FEATURE_PAL +#define MAX_SYMBOL_LEN 4096 +#define SYM_BUFFER_SIZE (sizeof(IMAGEHLP_SYMBOL) + MAX_SYMBOL_LEN) +char symBuffer[SYM_BUFFER_SIZE]; +PIMAGEHLP_SYMBOL sym = (PIMAGEHLP_SYMBOL) symBuffer; +#else +#include <sys/stat.h> +#include <coreruncommon.h> +#include <dlfcn.h> +#endif // !FEATURE_PAL + +#include <coreclrhost.h> +#include <set> + +LoadSymbolsForModuleDelegate SymbolReader::loadSymbolsForModuleDelegate; +DisposeDelegate SymbolReader::disposeDelegate; +ResolveSequencePointDelegate SymbolReader::resolveSequencePointDelegate; +GetLocalVariableName SymbolReader::getLocalVariableNameDelegate; +GetLineByILOffsetDelegate SymbolReader::getLineByILOffsetDelegate; + +const char * const CorElementTypeName[ELEMENT_TYPE_MAX]= +{ +#define TYPEINFO(e,ns,c,s,g,ia,ip,if,im,gv) c, +#include "cortypeinfo.h" +#undef TYPEINFO +}; + +const char * const CorElementTypeNamespace[ELEMENT_TYPE_MAX]= +{ +#define TYPEINFO(e,ns,c,s,g,ia,ip,if,im,gv) ns, +#include "cortypeinfo.h" +#undef TYPEINFO +}; + +IXCLRDataProcess *g_clrData = NULL; +ISOSDacInterface *g_sos = NULL; +ICorDebugProcess *g_pCorDebugProcess = NULL; + +#ifndef IfFailRet +#define IfFailRet(EXPR) do { Status = (EXPR); if(FAILED(Status)) { return (Status); } } while (0) +#endif + +#ifndef IfFailGoto +#define IfFailGoto(EXPR, label) do { Status = (EXPR); if(FAILED(Status)) { goto label; } } while (0) +#endif // IfFailGoto + +#ifndef IfFailGo +#define IfFailGo(EXPR) IfFailGoto(EXPR, Error) +#endif // IfFailGo + +// Max number of reverted rejit versions that !dumpmd and !ip2md will print +const UINT kcMaxRevertedRejitData = 10; + +#ifndef FEATURE_PAL + +// ensure we always allocate on the process heap +void* __cdecl operator new(size_t size) throw() +{ return HeapAlloc(GetProcessHeap(), 0, size); } +void __cdecl operator delete(void* pObj) throw() +{ HeapFree(GetProcessHeap(), 0, pObj); } + +void* __cdecl operator new[](size_t size) throw() +{ return HeapAlloc(GetProcessHeap(), 0, size); } +void __cdecl operator delete[](void* pObj) throw() +{ HeapFree(GetProcessHeap(), 0, pObj); } + +/**********************************************************************\ +* Here we define types and functions that support custom COM * +* activation rules, as defined by the CIOptions enum. * +* * +\**********************************************************************/ + +typedef unsigned __int64 QWORD; + +namespace com_activation +{ + // + // Forward declarations for the implementation methods + // + + HRESULT CreateInstanceCustomImpl( + REFCLSID clsid, + REFIID iid, + LPCWSTR dllName, + CIOptions cciOptions, + void** ppItf); + HRESULT ClrCreateInstance( + REFCLSID clsid, + REFIID iid, + LPCWSTR dllName, + CIOptions cciOptions, + void** ppItf); + HRESULT CreateInstanceFromPath( + REFCLSID clsid, + REFIID iid, + LPCWSTR path, + void** ppItf); + BOOL GetPathFromModule( + HMODULE hModule, + __in_ecount(cFqPath) LPWSTR fqPath, + DWORD cFqPath); + HRESULT PickClrRuntimeInfo( + ICLRMetaHost *pMetaHost, + CIOptions cciOptions, + ICLRRuntimeInfo** ppClr); + QWORD VerString2Qword(LPCWSTR vStr); + void CleanupClsidHmodMap(); + + // Helper structures for defining the CLSID -> HMODULE hash table we + // use for caching already activated objects + class hash_compareGUID + { + public: + static const size_t bucket_size = 4; + static const size_t min_buckets = 8; + hash_compareGUID() + { } + + size_t operator( )(const GUID& _Key) const + { + DWORD *pdw = (DWORD*)&_Key; + return (size_t)(pdw[0] ^ pdw[1] ^ pdw[2] ^ pdw[3]); + } + + bool operator( )(const GUID& _Key1, const GUID& _Key2) const + { return memcmp(&_Key1, &_Key2, sizeof(GUID)) == -1; } + }; + + static std::unordered_map<GUID, HMODULE, hash_compareGUID> *g_pClsidHmodMap = NULL; + + + +/**********************************************************************\ +* Routine Description: * +* * +* CreateInstanceCustomImpl() provides a way to activate a COM object * +* w/o triggering the FeatureOnDemand dialog. In order to do this we * +* must avoid using the CoCreateInstance() API, which, on a machine * +* with v4+ installed and w/o v2, would trigger this. * +* CreateInstanceCustom() activates the requested COM object according * +* to the specified passed in CIOptions, in the following order * +* (skipping the steps not enabled in the CIOptions flags passed in): * +* 1. Attempt to activate the COM object using a framework install: * +* a. If the debugger machine has a V4+ shell shim use the shim * +* to activate the object * +* b. Otherwise simply call CoCreateInstance * +* 2. If unsuccessful attempt to activate looking for the dllName in * +* the same folder as the DAC was loaded from * +* 3. If unsuccessful attempt to activate the COM object looking in * +* every path specified in the debugger's .exepath and .sympath * +\**********************************************************************/ +HRESULT CreateInstanceCustomImpl( + REFCLSID clsid, + REFIID iid, + LPCWSTR dllName, + CIOptions cciOptions, + void** ppItf) +{ + _ASSERTE(ppItf != NULL); + + if (ppItf == NULL) + return E_POINTER; + + WCHAR wszClsid[64] = W("<CLSID>"); + + // Step 1: Attempt activation using an installed runtime + if ((cciOptions & cciFxMask) != 0) + { + CIOptions opt = cciOptions & cciFxMask; + if (SUCCEEDED(ClrCreateInstance(clsid, iid, dllName, opt, ppItf))) + return S_OK; + + ExtDbgOut("Failed to instantiate {%ls} from installed .NET framework locations.\n", wszClsid); + } + + if ((cciOptions & cciDbiColocated) != 0) + { + // if we institute a way to retrieve the module for the current DBI we + // can perform the same steps as for the DAC. + } + + // Step 2: attempt activation using the folder the DAC was loaded from + if ((cciOptions & cciDacColocated) != 0) + { + _ASSERTE(dllName != NULL); + HMODULE hDac = NULL; + WCHAR path[MAX_LONGPATH]; + + if (SUCCEEDED(g_sos->GetDacModuleHandle(&hDac)) + && GetPathFromModule(hDac, path, _countof(path))) + { + // build the fully qualified file name and attempt instantiation + if (wcscat_s(path, dllName) == 0 + && SUCCEEDED(CreateInstanceFromPath(clsid, iid, path, ppItf))) + { + return S_OK; + } + } + + ExtDbgOut("Failed to instantiate {%ls} from DAC location.\n", wszClsid); + } + + // Step 3: attempt activation using the debugger's .exepath and .sympath + if ((cciOptions & cciDbgPath) != 0) + { + _ASSERTE(dllName != NULL); + + ToRelease<IDebugSymbols3> spSym3(NULL); + HRESULT hr = g_ExtSymbols->QueryInterface(__uuidof(IDebugSymbols3), (void**)&spSym3); + if (FAILED(hr)) + { + ExtDbgOut("Unable to query IDebugSymbol3 HRESULT=0x%x.\n", hr); + goto ErrDbgPath; + } + + typedef HRESULT (__stdcall IDebugSymbols3::*GetPathFunc)(LPWSTR , ULONG, ULONG*); + + // Handle both the image path and the symbol path + GetPathFunc rgGetPathFuncs[] = + { &IDebugSymbols3::GetImagePathWide, &IDebugSymbols3::GetSymbolPathWide }; + + for (int i = 0; i < _countof(rgGetPathFuncs); ++i) + { + ULONG pathSize = 0; + + // get the path buffer size + if ((spSym3.GetPtr()->*rgGetPathFuncs[i])(NULL, 0, &pathSize) != S_OK) + { + continue; + } + + ArrayHolder<WCHAR> imgPath = new WCHAR[pathSize+MAX_LONGPATH+1]; + if (imgPath == NULL) + { + continue; + } + + // actually get the path + if ((spSym3.GetPtr()->*rgGetPathFuncs[i])(imgPath, pathSize, NULL) != S_OK) + { + continue; + } + + LPWSTR ctx; + LPCWSTR pathElem = wcstok_s(imgPath, W(";"), &ctx); + while (pathElem != NULL) + { + WCHAR fullName[MAX_LONGPATH]; + wcscpy_s(fullName, _countof(fullName), pathElem); + if (wcscat_s(fullName, W("\\")) == 0 && wcscat_s(fullName, dllName) == 0) + { + if (SUCCEEDED(CreateInstanceFromPath(clsid, iid, fullName, ppItf))) + { + return S_OK; + } + } + + pathElem = wcstok_s(NULL, W(";"), &ctx); + } + } + + ErrDbgPath: + ExtDbgOut("Failed to instantiate {%ls} from debugger's image path.\n", wszClsid); + } + + return REGDB_E_CLASSNOTREG; +} + + +#ifdef _MSC_VER +// SOS is essentially single-threaded. ignore "construction of local static object is not thread-safe" +#pragma warning(push) +#pragma warning(disable:4640) +#endif // _MSC_VER + + +/**********************************************************************\ +* Routine Description: * +* * +* ClrCreateInstance() attempts to activate a COM object using an * +* installed framework: * +* a. If the debugger machine has a V4+ shell shim use the shim to * +* activate the object * +* b. Otherwise simply call CoCreateInstance * +\**********************************************************************/ +HRESULT ClrCreateInstance( + REFCLSID clsid, + REFIID iid, + LPCWSTR dllName, + CIOptions cciOptions, + void** ppItf) +{ + _ASSERTE((cciOptions & ~cciFxMask) == 0 && (cciOptions & cciFxMask) != 0); + HRESULT Status = S_OK; + + static CIOptions prevOpt = 0; + static HRESULT prevHr = S_OK; + + // if we already tried to use NetFx install and failed don't try it again + if (prevOpt == cciOptions && FAILED(prevHr)) + { + return prevHr; + } + + prevOpt = cciOptions; + + // first try usig the metahost API: + HRESULT (__stdcall *pfnCLRCreateInstance)(REFCLSID clsid, REFIID riid, LPVOID * ppInterface) = NULL; + HMODULE hMscoree = NULL; + + // if there's a v4+ shim on the debugger machine + if (GetProcAddressT("CLRCreateInstance", W("mscoree.dll"), &pfnCLRCreateInstance, &hMscoree)) + { + // attempt to create an ICLRMetaHost instance + ToRelease<ICLRMetaHost> spMH; + Status = pfnCLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (void**)&spMH); + if (Status == E_NOTIMPL) + { + // E_NOTIMPL means we have a v4 aware mscoree but no v4+ framework + IfFailGo( CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, iid, ppItf) ); + } + else + { + IfFailGo( Status ); + + // pick a runtime according to cciOptions + ToRelease<ICLRRuntimeInfo> spClr; + IfFailGo( PickClrRuntimeInfo(spMH, cciOptions, &spClr) ); + + // activate the COM object + Status = spClr->GetInterface(clsid, iid, ppItf); + + if (FAILED(Status) && dllName) + { + // if we have a v4+ runtime that does not have the fix to activate the requested CLSID + // try activating with the path + WCHAR clrDir[MAX_LONGPATH]; + DWORD cchClrDir = _countof(clrDir); + IfFailGo( spClr->GetRuntimeDirectory(clrDir, &cchClrDir) ); + IfFailGo( wcscat_s(clrDir, dllName) == 0 ? S_OK : E_FAIL ); + IfFailGo( CreateInstanceFromPath(clsid, iid, clrDir, ppItf) ); + } + } + } + else + { + // otherwise fallback to regular COM activation + IfFailGo( CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, iid, ppItf) ); + } + +Error: + if (hMscoree != NULL) + { + FreeLibrary(hMscoree); + } + + // remember if we succeeded or failed + prevHr = Status; + + return Status; +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif // _MSC_VER + + +/**********************************************************************\ +* Routine Description: * +* * +* CreateInstanceFromPath() instantiates a COM object using a passed in * +* fully-qualified path and a CLSID. * +* * +* Note: * +* * +* It uses a unordered_map to cache the mapping between a CLSID and the * +* HMODULE that is successfully used to activate the CLSID from. When * +* SOS is unloaded (in DebugExtensionUninitialize()) we call * +* FreeLibrary() for all cached HMODULEs. * +\**********************************************************************/ +HRESULT CreateInstanceFromPath( + REFCLSID clsid, + REFIID iid, + LPCWSTR path, + void** ppItf) +{ + HRESULT Status = S_OK; + HRESULT (__stdcall *pfnDllGetClassObject)(REFCLSID rclsid, REFIID riid, LPVOID *ppv) = NULL; + + HMODULE hmod = NULL; + + if (g_pClsidHmodMap == NULL) + { + g_pClsidHmodMap = new std::unordered_map<GUID, HMODULE, hash_compareGUID>(); + OnUnloadTask::Register(CleanupClsidHmodMap); + } + + auto it = g_pClsidHmodMap->find(clsid); + if (it != g_pClsidHmodMap->end()) + hmod = it->second; + + if (!GetProcAddressT("DllGetClassObject", path, &pfnDllGetClassObject, &hmod)) + return REGDB_E_CLASSNOTREG; + + ToRelease<IClassFactory> pFactory; + IfFailGo(pfnDllGetClassObject(clsid, IID_IClassFactory, (void**)&pFactory)); + + IfFailGo(pFactory->CreateInstance(NULL, iid, ppItf)); + + // only cache the HMODULE if we successfully created the COM object + (*g_pClsidHmodMap)[clsid] = hmod; + + return S_OK; + +Error: + if (hmod != NULL) + FreeLibrary(hmod); + + return Status; +} + + +/**********************************************************************\ +* Routine Description: * +* * +* CleanupClsidHmodMap() cleans up the CLSID -> HMODULE map used to * +* cache successful activations from specific paths. This is registered * +* as an OnUnloadTask in CreateInstanceFromPath(), and executes when * +* SOS is unloaded, in DebugExtensionUninitialize(). * +\**********************************************************************/ +void CleanupClsidHmodMap() +{ + if (g_pClsidHmodMap != NULL) + { + for (auto it = g_pClsidHmodMap->begin(); it != g_pClsidHmodMap->end(); ++it) + { + _ASSERTE(it->second != NULL); + FreeLibrary(it->second); + } + + delete g_pClsidHmodMap; + g_pClsidHmodMap = NULL; + } +} + +/**********************************************************************\ +* Routine Description: * +* * +* PickClrRuntimeInfo() selects on CLR runtime from the ones installed * +* on the debugger machine. If cciFxAny is specified in cciOptions it * +* simply returns the first runtime enumerated by the metahost * +* interface. If cciLatestFx is specified we pick the runtime with the * +* highest version (parsing the string returned from * +* ICLRRuntimeInfo::GetVersionString(). * +\**********************************************************************/ +HRESULT PickClrRuntimeInfo( + ICLRMetaHost *pMetaHost, + CIOptions cciOptions, + ICLRRuntimeInfo** ppClr) +{ + if (ppClr == NULL) + return E_POINTER; + + // only support "Any framework" and "latest framework" + if (cciOptions != cciAnyFx && cciOptions != cciLatestFx) + return E_INVALIDARG; + + HRESULT Status = S_OK; + *ppClr = NULL; + + // get the CLRRuntime enumerator + ToRelease<IEnumUnknown> spClrsEnum; + IfFailRet(pMetaHost->EnumerateInstalledRuntimes(&spClrsEnum)); + + ToRelease<ICLRRuntimeInfo> spChosenClr; + QWORD verMax = 0; + + int cntClr = 0; + while (1) + { + // retrieve the next ICLRRuntimeInfo + ULONG cnt; + ToRelease<IUnknown> spClrUnk; + if (spClrsEnum->Next(1, &spClrUnk, &cnt) != S_OK || cnt != 1) + break; + + ToRelease<ICLRRuntimeInfo> spClr; + BOOL bLoadable = FALSE; + // ignore un-loadable runtimes + if (FAILED(spClrUnk->QueryInterface(IID_ICLRRuntimeInfo, (void**)&spClr)) + || FAILED(spClr->IsLoadable(&bLoadable)) + || !bLoadable) + { + continue; + } + + WCHAR vStr[128]; + DWORD cStr = _countof(vStr); + if (FAILED(spClr->GetVersionString(vStr, &cStr))) + continue; + + ++cntClr; + + if ((cciOptions & cciAnyFx) != 0) + { + spChosenClr = spClr.Detach(); + break; + } + + QWORD ver = VerString2Qword(vStr); + if ((cciOptions & cciLatestFx) != 0) + { + if (ver > verMax) + { + verMax = ver; + spChosenClr = spClr.Detach(); + } + } + } + + if (cntClr == 0 || spChosenClr == NULL) + { + *ppClr = NULL; + return E_NOINTERFACE; + } + else + { + *ppClr = spChosenClr.Detach(); + return S_OK; + } +} + + +/**********************************************************************\ +* Routine Description: * +* * +* VerString2Qword() parses a string as returned from * +* ICLRRuntimeInfo::GetVersionString() into a QWORD, assuming every * +* numeric element is a WORD portion in the QWORD. * +\**********************************************************************/ +QWORD VerString2Qword(LPCWSTR vStr) +{ + _ASSERTE(vStr[0] == L'v' || vStr[0] == L'V'); + QWORD result = 0; + + DWORD v1, v2, v3; + if (swscanf_s(vStr+1, W("%d.%d.%d"), &v1, &v2, &v3) == 3) + { + result = ((QWORD)v1 << 48) | ((QWORD)v2 << 32) | ((QWORD)v3 << 16); + } + else if (swscanf_s(vStr+1, W("%d.%d"), &v1, &v2) == 2) + { + result = ((QWORD)v1 << 48) | ((QWORD)v2 << 32); + } + else if (swscanf_s(vStr+1, W("%d"), &v1) == 1) + { + result = ((QWORD)v1 << 48); + } + + return result; +} + + +/**********************************************************************\ +* Routine Description: * +* * +* GetPathFromModule() returns the name of the folder containing the * +* file associated with hModule. * + \**********************************************************************/ +BOOL GetPathFromModule( + HMODULE hModule, + __in_ecount(cFqPath) LPWSTR fqPath, + DWORD cFqPath) +{ + int len = GetModuleFileNameW(hModule, fqPath, cFqPath); + if (len == 0 || len == cFqPath) + return FALSE; + + WCHAR *pLastSep = _wcsrchr(fqPath, DIRECTORY_SEPARATOR_CHAR_W); + if (pLastSep == NULL || pLastSep+1 >= fqPath+cFqPath) + return FALSE; + + *(pLastSep+1) = L'\0'; + + return TRUE; +} + +} + +/**********************************************************************\ +* Routine Description: * +* * +* CreateInstanceCustom() provides a way to activate a COM object w/o * +* triggering the FeatureOnDemand dialog. In order to do this we * +* must avoid using the CoCreateInstance() API, which, on a machine * +* with v4+ installed and w/o v2, would trigger this. * +* CreateInstanceCustom() activates the requested COM object according * +* to the specified passed in CIOptions, in the following order * +* (skipping the steps not enabled in the CIOptions flags passed in): * +* 1. Attempt to activate the COM object using a framework install: * +* a. If the debugger machine has a V4+ shell shim use the shim * +* to activate the object * +* b. Otherwise simply call CoCreateInstance * +* 2. If unsuccessful attempt to activate looking for the dllName in * +* the same folder as the DAC was loaded from * +* 3. If unsuccessful attempt to activate the COM object looking in * +* every path specified in the debugger's .exepath and .sympath * +\**********************************************************************/ +HRESULT CreateInstanceCustom( + REFCLSID clsid, + REFIID iid, + LPCWSTR dllName, + CIOptions cciOptions, + void** ppItf) +{ + return com_activation::CreateInstanceCustomImpl(clsid, iid, dllName, cciOptions, ppItf); +} + + + + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to get the memory address given a symbol * +* name. It handles difference in symbol name between ntsd and * +* windbg. * +* * +\**********************************************************************/ +DWORD_PTR GetValueFromExpression (___in __in_z const char *const instr) +{ + ULONG64 dwAddr; + const char *str = instr; + char name[256]; + + dwAddr = 0; + HRESULT hr = g_ExtSymbols->GetOffsetByName (str, &dwAddr); + if (SUCCEEDED(hr)) + return (DWORD_PTR)dwAddr; + else if (hr == S_FALSE && dwAddr) + return (DWORD_PTR)dwAddr; + + strcpy_s (name, _countof(name), str); + char *ptr; + if ((ptr = strstr (name, "__")) != NULL) + { + ptr[0] = ':'; + ptr[1] = ':'; + ptr += 2; + while ((ptr = strstr(ptr, "__")) != NULL) + { + ptr[0] = ':'; + ptr[1] = ':'; + ptr += 2; + } + dwAddr = 0; + hr = g_ExtSymbols->GetOffsetByName (name, &dwAddr); + if (SUCCEEDED(hr)) + return (DWORD_PTR)dwAddr; + else if (hr == S_FALSE && dwAddr) + return (DWORD_PTR)dwAddr; + } + else if ((ptr = strstr (name, "::")) != NULL) + { + ptr[0] = '_'; + ptr[1] = '_'; + ptr += 2; + while ((ptr = strstr(ptr, "::")) != NULL) + { + ptr[0] = '_'; + ptr[1] = '_'; + ptr += 2; + } + dwAddr = 0; + hr = g_ExtSymbols->GetOffsetByName (name, &dwAddr); + if (SUCCEEDED(hr)) + return (DWORD_PTR)dwAddr; + else if (hr == S_FALSE && dwAddr) + return (DWORD_PTR)dwAddr; + } + return 0; +} + +#endif // FEATURE_PAL + +ModuleInfo moduleInfo[MSCOREND] = {{0,FALSE,0},{0,FALSE,0},{0,FALSE,0}}; + +void ReportOOM() +{ + ExtOut("SOS Error: Out of memory\n"); +} + +HRESULT CheckEEDll() +{ +#ifndef FEATURE_PAL + VS_FIXEDFILEINFO ee = {}; + + static VS_FIXEDFILEINFO sos = {}; + static BOOL sosDataInit = FALSE; + + BOOL result = GetEEVersion(&ee); + if (result && !sosDataInit) + { + result = GetSOSVersion(&sos); + + if (result) + sosDataInit = TRUE; + } + + // We will ignore errors because it's possible sos is being loaded before CLR. + if (result) + { + if ((ee.dwFileVersionMS != sos.dwFileVersionMS) || (ee.dwFileVersionLS != sos.dwFileVersionLS)) + { + ExtOut("The version of SOS does not match the version of CLR you are debugging. Please\n"); + ExtOut("load the matching version of SOS for the version of CLR you are debugging.\n"); + ExtOut("CLR Version: %u.%u.%u.%u\n", + HIWORD(ee.dwFileVersionMS), + LOWORD(ee.dwFileVersionMS), + HIWORD(ee.dwFileVersionLS), + LOWORD(ee.dwFileVersionLS)); + + ExtOut("SOS Version: %u.%u.%u.%u\n", + HIWORD(sos.dwFileVersionMS), + LOWORD(sos.dwFileVersionMS), + HIWORD(sos.dwFileVersionLS), + LOWORD(sos.dwFileVersionLS)); + } + } + + DEBUG_MODULE_PARAMETERS Params; + + // Do we have clr.dll + if (moduleInfo[MSCORWKS].baseAddr == 0) + { + g_ExtSymbols->GetModuleByModuleName (MAIN_CLR_MODULE_NAME_A,0,NULL, + &moduleInfo[MSCORWKS].baseAddr); + if (moduleInfo[MSCORWKS].baseAddr != 0 && moduleInfo[MSCORWKS].hasPdb == FALSE) + { + g_ExtSymbols->GetModuleParameters (1, &moduleInfo[MSCORWKS].baseAddr, 0, &Params); + if (Params.SymbolType == SymDeferred) + { + g_ExtSymbols->Reload("/f " MAIN_CLR_DLL_NAME_A); + g_ExtSymbols->GetModuleParameters (1, &moduleInfo[MSCORWKS].baseAddr, 0, &Params); + } + + if (Params.SymbolType == SymPdb || Params.SymbolType == SymDia) + { + moduleInfo[MSCORWKS].hasPdb = TRUE; + } + + moduleInfo[MSCORWKS].size = Params.Size; + } + if (moduleInfo[MSCORWKS].baseAddr != 0 && moduleInfo[MSCORWKS].hasPdb == FALSE) + ExtOut("PDB symbol for clr.dll not loaded\n"); + } + + return (moduleInfo[MSCORWKS].baseAddr != 0) ? S_OK : E_FAIL; +#else + return S_OK; +#endif // FEATURE_PAL +} + +EEFLAVOR GetEEFlavor () +{ +#ifdef FEATURE_PAL + return MSCORWKS; +#else // FEATUER_PAL + EEFLAVOR flavor = UNKNOWNEE; + + if (SUCCEEDED(g_ExtSymbols->GetModuleByModuleName(MAIN_CLR_MODULE_NAME_A,0,NULL,NULL))) { + flavor = MSCORWKS; + } + return flavor; +#endif // FEATURE_PAL else +} + +BOOL IsDumpFile () +{ + static int g_fDumpFile = -1; + if (g_fDumpFile == -1) { + ULONG Class; + ULONG Qualifier; + g_ExtControl->GetDebuggeeType(&Class,&Qualifier); + if (Qualifier >= DEBUG_DUMP_SMALL) + g_fDumpFile = 1; + else + g_fDumpFile = 0; + } + return g_fDumpFile != 0; +} + +BOOL g_InMinidumpSafeMode = FALSE; + +BOOL IsMiniDumpFileNODAC () +{ +#ifndef FEATURE_PAL + ULONG Class; + ULONG Qualifier; + g_ExtControl->GetDebuggeeType(&Class,&Qualifier); + if (Qualifier == DEBUG_DUMP_SMALL) + { + g_ExtControl->GetDumpFormatFlags(&Qualifier); + if ((Qualifier & DEBUG_FORMAT_USER_SMALL_FULL_MEMORY) == 0) + { + return TRUE; + } + } + +#endif // FEATURE_PAL + return FALSE; +} + + +// We use this predicate to mean the smallest, most restrictive kind of +// minidump file. There is no heap dump, only that set of information +// gathered to make !clrstack, !threads, !help, !eeversion and !pe work. +BOOL IsMiniDumpFile () +{ +#ifndef FEATURE_PAL + // It is okay for this to be static, because although the debugger may debug multiple + // managed processes at once, I don't believe multiple dumpfiles of different + // types is a scenario to worry about. + if (IsMiniDumpFileNODAC()) + { + // Beyond recognizing the dump type above, all we can rely on for this + // is a flag set by the user indicating they want a safe mode minidump + // experience. This is primarily for testing. + return g_InMinidumpSafeMode; + } + +#endif // FEATURE_PAL + return FALSE; +} + +ULONG DebuggeeType() +{ + static ULONG Class = DEBUG_CLASS_UNINITIALIZED; + if (Class == DEBUG_CLASS_UNINITIALIZED) { + ULONG Qualifier; + g_ExtControl->GetDebuggeeType(&Class,&Qualifier); + } + return Class; +} + +#ifndef FEATURE_PAL + +// Check if a file exist +BOOL FileExist (const char *filename) +{ + WIN32_FIND_DATA FindFileData; + HANDLE handle = FindFirstFile (filename, &FindFileData); + if (handle != INVALID_HANDLE_VALUE) { + FindClose (handle); + return TRUE; + } + else + return FALSE; +} + + +BOOL FileExist (const WCHAR *filename) +{ + WIN32_FIND_DATAW FindFileData; + HANDLE handle = FindFirstFileW (filename, &FindFileData); + if (handle != INVALID_HANDLE_VALUE) { + FindClose (handle); + return TRUE; + } + else + return FALSE; +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to find out if a dll is bbt-ized * +* * +\**********************************************************************/ +BOOL IsRetailBuild (size_t base) +{ + IMAGE_DOS_HEADER DosHeader; + if (g_ExtData->ReadVirtual(TO_CDADDR(base), &DosHeader, sizeof(DosHeader), NULL) != S_OK) + return FALSE; + IMAGE_NT_HEADERS32 Header32; + if (g_ExtData->ReadVirtual(TO_CDADDR(base + DosHeader.e_lfanew), &Header32, sizeof(Header32), NULL) != S_OK) + return FALSE; + // If there is no COMHeader, this can not be managed code. + if (Header32.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress == 0) + return FALSE; + + size_t debugDirAddr = base + Header32.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress; + size_t nSize = Header32.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].Size; + IMAGE_DEBUG_DIRECTORY debugDir; + size_t nbytes = 0; + while (nbytes < nSize) { + if (g_ExtData->ReadVirtual(TO_CDADDR(debugDirAddr+nbytes), &debugDir, sizeof(debugDir), NULL) != S_OK) + return FALSE; + if (debugDir.Type == 0xA) { + return TRUE; + } + nbytes += sizeof(debugDir); + } + return FALSE; +} + +#endif // !FEATURE_PAL + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to read memory from the debugee's * +* address space. If the initial read fails, it attempts to read * +* only up to the edge of the page containing "offset". * +* * +\**********************************************************************/ +BOOL SafeReadMemory (TADDR offset, PVOID lpBuffer, ULONG cb, + PULONG lpcbBytesRead) +{ + BOOL bRet = FALSE; + + bRet = SUCCEEDED(g_ExtData->ReadVirtual(TO_CDADDR(offset), lpBuffer, cb, + lpcbBytesRead)); + + if (!bRet) + { + cb = (ULONG)(NextOSPageAddress(offset) - offset); + bRet = SUCCEEDED(g_ExtData->ReadVirtual(TO_CDADDR(offset), lpBuffer, cb, + lpcbBytesRead)); + } + return bRet; +} + +ULONG OSPageSize () +{ + static ULONG pageSize = 0; + if (pageSize == 0) + g_ExtControl->GetPageSize(&pageSize); + + return pageSize; +} + +size_t NextOSPageAddress (size_t addr) +{ + size_t pageSize = OSPageSize(); + return (addr+pageSize)&(~(pageSize-1)); +} + + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to get the address of MethodDesc * +* given an ip address * +* * +\**********************************************************************/ +void IP2MethodDesc (DWORD_PTR IP, DWORD_PTR &methodDesc, JITTypes &jitType, + DWORD_PTR &gcinfoAddr) +{ + + CLRDATA_ADDRESS EIP = TO_CDADDR(IP); + DacpCodeHeaderData codeHeaderData; + + methodDesc = NULL; + gcinfoAddr = NULL; + + if (codeHeaderData.Request(g_sos, EIP) != S_OK) + { + return; + } + + methodDesc = (DWORD_PTR) codeHeaderData.MethodDescPtr; + jitType = (JITTypes) codeHeaderData.JITType; + gcinfoAddr = (DWORD_PTR) codeHeaderData.GCInfo; +} + +BOOL IsValueField (DacpFieldDescData *pFD) +{ + return (pFD->Type == ELEMENT_TYPE_VALUETYPE); +} + +void DisplayDataMember (DacpFieldDescData* pFD, DWORD_PTR dwAddr, BOOL fAlign=TRUE) +{ + if (dwAddr > 0) + { + // we must have called this function for a "real" (non-zero size) data type + PREFIX_ASSUME(gElementTypeInfo[pFD->Type] != 0); + + DWORD_PTR dwTmp = dwAddr; + bool bVTStatic = (pFD->bIsStatic && pFD->Type == ELEMENT_TYPE_VALUETYPE); + + if (gElementTypeInfo[pFD->Type] != NO_SIZE || bVTStatic) + { + union Value + { + char ch; + short Short; + DWORD_PTR ptr; + int Int; + unsigned int UInt; + __int64 Int64; + unsigned __int64 UInt64; + float Float; + double Double; + } value; + + ZeroMemory(&value, sizeof(value)); + if (bVTStatic) + { + // static VTypes are boxed + moveBlock (value, dwTmp, gElementTypeInfo[ELEMENT_TYPE_CLASS]); + } + else + { + moveBlock (value, dwTmp, gElementTypeInfo[pFD->Type]); + } + + switch (pFD->Type) + { + case ELEMENT_TYPE_I1: + // there's no ANSI conformant type specifier for + // signed char, so use the next best thing, + // signed short (sign extending) + if (fAlign) + ExtOut("%" POINTERSIZE "hd", (short)value.ch); + else + ExtOut("%d", value.ch); + break; + case ELEMENT_TYPE_I2: + if (fAlign) + ExtOut("%" POINTERSIZE "hd", value.Short); + else + ExtOut("%d", value.Short); + break; + case ELEMENT_TYPE_I4: + if (fAlign) + ExtOut("%" POINTERSIZE "d", value.Int); + else + ExtOut("%d", value.Int); + break; + case ELEMENT_TYPE_I8: + ExtOut("%I64d", value.Int64); + break; + case ELEMENT_TYPE_U1: + case ELEMENT_TYPE_BOOLEAN: + if (fAlign) + // there's no ANSI conformant type specifier for + // unsigned char, so use the next best thing, + // unsigned short, not extending the sign + ExtOut("%" POINTERSIZE "hu", (USHORT)value.Short); + else + ExtOut("%u", value.ch); + break; + case ELEMENT_TYPE_U2: + if (fAlign) + ExtOut("%" POINTERSIZE "hu", value.Short); + else + ExtOut("%u", value.Short); + break; + case ELEMENT_TYPE_U4: + if (fAlign) + ExtOut("%" POINTERSIZE "u", value.UInt); + else + ExtOut("%u", value.UInt); + break; + case ELEMENT_TYPE_U8: + ExtOut("%I64u", value.UInt64); + break; + case ELEMENT_TYPE_I: + case ELEMENT_TYPE_U: + if (fAlign) + ExtOut("%" POINTERSIZE "p", SOS_PTR(value.ptr)); + else + ExtOut("%p", SOS_PTR(value.ptr)); + break; + case ELEMENT_TYPE_R4: + ExtOut("%f", value.Float); + break; + case ELEMENT_TYPE_R8: + ExtOut("%f", value.Double); + break; + case ELEMENT_TYPE_CHAR: + if (fAlign) + ExtOut("%" POINTERSIZE "hx", value.Short); + else + ExtOut("%x", value.Short); + break; + case ELEMENT_TYPE_VALUETYPE: + if (value.ptr) + DMLOut(DMLValueClass(pFD->MTOfType, dwTmp)); + else + ExtOut("%p", SOS_PTR(0)); + break; + default: + if (value.ptr) + DMLOut(DMLObject(value.ptr)); + else + ExtOut("%p", SOS_PTR(0)); + break; + } + } + else + { + if (pFD->Type == ELEMENT_TYPE_VALUETYPE) + DMLOut(DMLValueClass(pFD->MTOfType, dwTmp)); + else + ExtOut("%p", SOS_PTR(0)); + } + } + else + { + ExtOut("%" POINTERSIZE "s", " "); + } +} + +void GetStaticFieldPTR(DWORD_PTR* pOutPtr, DacpDomainLocalModuleData* pDLMD, DacpMethodTableData* pMTD, DacpFieldDescData* pFDD, BYTE* pFlags = 0) +{ + DWORD_PTR dwTmp; + + if (pFDD->Type == ELEMENT_TYPE_VALUETYPE + || pFDD->Type == ELEMENT_TYPE_CLASS) + { + dwTmp = (DWORD_PTR) pDLMD->pGCStaticDataStart + pFDD->dwOffset; + } + else + { + dwTmp = (DWORD_PTR) pDLMD->pNonGCStaticDataStart + pFDD->dwOffset; + } + + *pOutPtr = 0; + + if (pMTD->bIsDynamic) + { + ExtOut("dynamic statics NYI"); + return; + } + else + { + if (pFlags && pMTD->bIsShared) + { + BYTE flags; + DWORD_PTR pTargetFlags = (DWORD_PTR) pDLMD->pClassData + RidFromToken(pMTD->cl) - 1; + move_xp (flags, pTargetFlags); + + *pFlags = flags; + } + + + *pOutPtr = dwTmp; + } + return; +} + +void GetDLMFlags(DacpDomainLocalModuleData* pDLMD, DacpMethodTableData* pMTD, BYTE* pFlags) +{ + if (pMTD->bIsDynamic) + { + ExtOut("dynamic statics NYI"); + return; + } + else + { + if (pFlags) + { + BYTE flags; + DWORD_PTR pTargetFlags = (DWORD_PTR) pDLMD->pClassData + RidFromToken(pMTD->cl) - 1; + move_xp (flags, pTargetFlags); + + *pFlags = flags; + } + } + return; +} + +void GetThreadStaticFieldPTR(DWORD_PTR* pOutPtr, DacpThreadLocalModuleData* pTLMD, DacpMethodTableData* pMTD, DacpFieldDescData* pFDD, BYTE* pFlags = 0) +{ + DWORD_PTR dwTmp; + + if (pFDD->Type == ELEMENT_TYPE_VALUETYPE + || pFDD->Type == ELEMENT_TYPE_CLASS) + { + dwTmp = (DWORD_PTR) pTLMD->pGCStaticDataStart + pFDD->dwOffset; + } + else + { + dwTmp = (DWORD_PTR) pTLMD->pNonGCStaticDataStart + pFDD->dwOffset; + } + + *pOutPtr = 0; + + if (pMTD->bIsDynamic) + { + ExtOut("dynamic thread statics NYI"); + return; + } + else + { + if (pFlags) + { + BYTE flags; + DWORD_PTR pTargetFlags = (DWORD_PTR) pTLMD->pClassData + RidFromToken(pMTD->cl) - 1; + move_xp (flags, pTargetFlags); + + *pFlags = flags; + } + + *pOutPtr = dwTmp; + } + return; +} + +void DisplaySharedStatic(ULONG64 dwModuleDomainID, DacpMethodTableData* pMT, DacpFieldDescData *pFD) +{ + DacpAppDomainStoreData adsData; + if (adsData.Request(g_sos)!=S_OK) + { + ExtOut("Unable to get AppDomain information\n"); + } + + ArrayHolder<CLRDATA_ADDRESS> pArray = new CLRDATA_ADDRESS[adsData.DomainCount]; + if (pArray==NULL) + { + ReportOOM(); + return; + } + + if (g_sos->GetAppDomainList(adsData.DomainCount,pArray, NULL)!=S_OK) + { + ExtOut("Unable to get array of AppDomains\n"); + return; + } + +#if defined(_TARGET_WIN64_) + ExtOut(" >> Domain:Value "); +#else + ExtOut(" >> Domain:Value "); +#endif + // Skip the SystemDomain and SharedDomain + for (int i = 0; i < adsData.DomainCount ; i ++) + { + DacpAppDomainData appdomainData; + if (appdomainData.Request(g_sos,pArray[i])!=S_OK) + { + ExtOut("Unable to get AppDomain %lx\n",pArray[i]); + return; + } + + DacpDomainLocalModuleData vDomainLocalModule; + if (g_sos->GetDomainLocalModuleDataFromAppDomain(appdomainData.AppDomainPtr, (int)dwModuleDomainID, &vDomainLocalModule) != S_OK) + { + DMLOut(" %s:NotInit ", DMLDomain(pArray[i])); + continue; + } + + DWORD_PTR dwTmp; + BYTE Flags = 0; + GetStaticFieldPTR(&dwTmp, &vDomainLocalModule , pMT, pFD, &Flags); + + if ((Flags&1) == 0) { + // We have not initialized this yet. + DMLOut(" %s:NotInit ", DMLDomain(pArray[i])); + continue; + } + else if (Flags & 2) { + // We have not initialized this yet. + DMLOut(" %s:FailInit", DMLDomain(pArray[i])); + continue; + } + + DMLOut(" %s:", DMLDomain(appdomainData.AppDomainPtr)); + DisplayDataMember(pFD, dwTmp, FALSE); + } + ExtOut(" <<\n"); +} + +void DisplayThreadStatic (DacpModuleData* pModule, DacpMethodTableData* pMT, DacpFieldDescData *pFD, BOOL fIsShared) +{ + SIZE_T dwModuleIndex = (SIZE_T)pModule->dwModuleIndex; + SIZE_T dwModuleDomainID = (SIZE_T)pModule->dwModuleID; + + DacpThreadStoreData ThreadStore; + ThreadStore.Request(g_sos); + + ExtOut(" >> Thread:Value"); + CLRDATA_ADDRESS CurThread = ThreadStore.firstThread; + while (CurThread) + { + DacpThreadData vThread; + if (vThread.Request(g_sos, CurThread) != S_OK) + { + ExtOut(" error getting thread %p, aborting this field\n", SOS_PTR(CurThread)); + return; + } + + if (vThread.osThreadId != 0) + { + CLRDATA_ADDRESS appDomainAddr = vThread.domain; + + // Get the DLM (we need this to check the ClassInit flags). + // It's annoying that we have to issue one request for + // domain-neutral modules and domain-specific modules. + DacpDomainLocalModuleData vDomainLocalModule; + if (fIsShared) + { + if (g_sos->GetDomainLocalModuleDataFromAppDomain(appDomainAddr, (int)dwModuleDomainID, &vDomainLocalModule) != S_OK) + { + // Not initialized, go to next thread + // and continue looping + CurThread = vThread.nextThread; + continue; + } + } + else + { + if (g_sos->GetDomainLocalModuleDataFromModule(pMT->Module, &vDomainLocalModule) != S_OK) + { + // Not initialized, go to next thread + // and continue looping + CurThread = vThread.nextThread; + continue; + } + } + + // Get the TLM + DacpThreadLocalModuleData vThreadLocalModule; + if (g_sos->GetThreadLocalModuleData(CurThread, (int)dwModuleIndex, &vThreadLocalModule) != S_OK) + { + // Not initialized, go to next thread + // and continue looping + CurThread = vThread.nextThread; + continue; + } + + DWORD_PTR dwTmp; + BYTE Flags = 0; + GetThreadStaticFieldPTR(&dwTmp, &vThreadLocalModule, pMT, pFD, &Flags); + + if ((Flags&4) == 0) + { + // Not allocated, go to next thread + // and continue looping + CurThread = vThread.nextThread; + continue; + } + + Flags = 0; + GetDLMFlags(&vDomainLocalModule, pMT, &Flags); + + if ((Flags&1) == 0) + { + // Not initialized, go to next thread + // and continue looping + CurThread = vThread.nextThread; + continue; + } + + ExtOut(" %x:", vThread.osThreadId); + DisplayDataMember(pFD, dwTmp, FALSE); + } + + // Go to next thread + CurThread = vThread.nextThread; + } + ExtOut(" <<\n"); +} + +void DisplayContextStatic (DacpFieldDescData *pFD, size_t offset, BOOL fIsShared) +{ + ExtOut("\nDisplay of context static variables is not implemented yet\n"); + /* + int numDomain; + DWORD_PTR *domainList = NULL; + GetDomainList (domainList, numDomain); + ToDestroy des0 ((void**)&domainList); + AppDomain vAppDomain; + Context vContext; + + ExtOut(" >> Domain:Value"); + for (int i = 0; i < numDomain; i ++) + { + DWORD_PTR dwAddr = domainList[i]; + if (dwAddr == 0) { + continue; + } + vAppDomain.Fill (dwAddr); + if (vAppDomain.m_pDefaultContext == 0) + continue; + dwAddr = (DWORD_PTR)vAppDomain.m_pDefaultContext; + vContext.Fill (dwAddr); + + if (fIsShared) + dwAddr = (DWORD_PTR)vContext.m_pSharedStaticData; + else + dwAddr = (DWORD_PTR)vContext.m_pUnsharedStaticData; + if (dwAddr == 0) + continue; + dwAddr += offsetof(STATIC_DATA, dataPtr); + dwAddr += offset; + if (safemove (dwAddr, dwAddr) == 0) + continue; + if (dwAddr == 0) + // We have not initialized this yet. + continue; + + dwAddr += pFD->dwOffset; + if (pFD->Type == ELEMENT_TYPE_CLASS + || pFD->Type == ELEMENT_TYPE_VALUETYPE) + { + if (safemove (dwAddr, dwAddr) == 0) + continue; + } + if (dwAddr == 0) + // We have not initialized this yet. + continue; + ExtOut(" %p:", (ULONG64)domainList[i]); + DisplayDataMember (pFD, dwAddr, FALSE); + } + ExtOut(" <<\n"); + */ +} + +const char * ElementTypeName(unsigned type) +{ + switch (type) { + case ELEMENT_TYPE_PTR: + return "PTR"; + break; + case ELEMENT_TYPE_BYREF: + return "BYREF"; + break; + case ELEMENT_TYPE_VALUETYPE: + return "VALUETYPE"; + break; + case ELEMENT_TYPE_CLASS: + return "CLASS"; + break; + case ELEMENT_TYPE_VAR: + return "VAR"; + break; + case ELEMENT_TYPE_ARRAY: + return "ARRAY"; + break; + case ELEMENT_TYPE_FNPTR: + return "FNPTR"; + break; + case ELEMENT_TYPE_SZARRAY: + return "SZARRAY"; + break; + case ELEMENT_TYPE_MVAR: + return "MVAR"; + break; + default: + if ((type >= _countof(CorElementTypeName)) || (CorElementTypeName[type] == NULL)) + { + return ""; + } + return CorElementTypeName[type]; + break; + } +} // ElementTypeName + +const char * ElementTypeNamespace(unsigned type) +{ + if ((type >= _countof(CorElementTypeName)) || (CorElementTypeNamespace[type] == NULL)) + { + return ""; + } + return CorElementTypeNamespace[type]; +} + +void ComposeName_s(CorElementType Type, __out_ecount(capacity_buffer) LPSTR buffer, size_t capacity_buffer) +{ + const char *p = ElementTypeNamespace(Type); + if ((p) && (*p != '\0')) + { + strcpy_s(buffer,capacity_buffer,p); + strcat_s(buffer,capacity_buffer,"."); + strcat_s(buffer,capacity_buffer,ElementTypeName(Type)); + } + else + { + strcpy_s(buffer,capacity_buffer,ElementTypeName(Type)); + } +} + +// NOTE: pszName is changed +// INPUT MAXCHARS RETURN +// HelloThere 5 ...re +// HelloThere 8 ...There +LPWSTR FormatTypeName (__out_ecount (maxChars) LPWSTR pszName, UINT maxChars) +{ + UINT iStart = 0; + UINT iLen = (int) _wcslen(pszName); + if (iLen > maxChars) + { + iStart = iLen - maxChars; + UINT numDots = (maxChars < 3) ? maxChars : 3; + for (UINT i=0; i < numDots; i++) + pszName[iStart+i] = '.'; + } + return pszName + iStart; +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to dump all fields of a managed object. * +* dwStartAddr specifies the beginning memory address. * +* bFirst is used to avoid printing header everytime. * +* * +\**********************************************************************/ +void DisplayFields(CLRDATA_ADDRESS cdaMT, DacpMethodTableData *pMTD, DacpMethodTableFieldData *pMTFD, DWORD_PTR dwStartAddr, BOOL bFirst, BOOL bValueClass) +{ + static DWORD numInstanceFields = 0; + if (bFirst) + { + ExtOutIndent(); + ExtOut("%" POINTERSIZE "s %8s %8s %20s %2s %8s %" POINTERSIZE "s %s\n", + "MT", "Field", "Offset", "Type", "VT", "Attr", "Value", "Name"); + numInstanceFields = 0; + } + + BOOL fIsShared = pMTD->bIsShared; + + if (pMTD->ParentMethodTable) + { + DacpMethodTableData vParentMethTable; + if (vParentMethTable.Request(g_sos,pMTD->ParentMethodTable) != S_OK) + { + ExtOut("Invalid parent MethodTable\n"); + return; + } + + DacpMethodTableFieldData vParentMethTableFields; + if (vParentMethTableFields.Request(g_sos,pMTD->ParentMethodTable) != S_OK) + { + ExtOut("Invalid parent EEClass\n"); + return; + } + + DisplayFields(pMTD->ParentMethodTable, &vParentMethTable, &vParentMethTableFields, dwStartAddr, FALSE, bValueClass); + } + + DWORD numStaticFields = 0; + CLRDATA_ADDRESS dwAddr = pMTFD->FirstField; + DacpFieldDescData vFieldDesc; + + // Get the module name + DacpModuleData module; + if (module.Request(g_sos, pMTD->Module)!=S_OK) + return; + + ToRelease<IMetaDataImport> pImport = MDImportForModule(&module); + + while (numInstanceFields < pMTFD->wNumInstanceFields + || numStaticFields < pMTFD->wNumStaticFields) + { + if (IsInterrupt()) + return; + + ExtOutIndent (); + + if ((vFieldDesc.Request(g_sos, dwAddr)!=S_OK) || + (vFieldDesc.Type >= ELEMENT_TYPE_MAX)) + { + ExtOut("Unable to display fields\n"); + return; + } + dwAddr = vFieldDesc.NextField; + + DWORD offset = vFieldDesc.dwOffset; + if(!((vFieldDesc.bIsThreadLocal || vFieldDesc.bIsContextLocal || fIsShared) && vFieldDesc.bIsStatic)) + { + if (!bValueClass) + { + offset += sizeof(BaseObject); + } + } + + DMLOut("%s %8x %8x ", DMLMethodTable(vFieldDesc.MTOfType), + TokenFromRid(vFieldDesc.mb, mdtFieldDef), + offset); + + char ElementName[mdNameLen]; + if ((vFieldDesc.Type == ELEMENT_TYPE_VALUETYPE || + vFieldDesc.Type == ELEMENT_TYPE_CLASS) && vFieldDesc.MTOfType) + { + NameForMT_s((DWORD_PTR)vFieldDesc.MTOfType, g_mdName, mdNameLen); + ExtOut("%20.20S ", FormatTypeName(g_mdName, 20)); + } + else + { + if (vFieldDesc.Type == ELEMENT_TYPE_CLASS && vFieldDesc.TokenOfType != mdTypeDefNil) + { + // Get the name from Metadata!!! + NameForToken_s(TokenFromRid(vFieldDesc.TokenOfType, mdtTypeDef), pImport, g_mdName, mdNameLen, false); + ExtOut("%20.20S ", FormatTypeName(g_mdName, 20)); + } + else + { + // If ET type from signature is different from fielddesc, then the signature one is more descriptive. + // For example, E_T_STRING in field desc will be E_T_CLASS. In minidump's case, we won't have + // the method table for it. + ComposeName_s(vFieldDesc.Type != vFieldDesc.sigType ? vFieldDesc.sigType : vFieldDesc.Type, ElementName, sizeof(ElementName)/sizeof(ElementName[0])); + ExtOut("%20.20s ", ElementName); + } + } + + ExtOut("%2s ", (IsElementValueType(vFieldDesc.Type)) ? "1" : "0"); + + if (vFieldDesc.bIsStatic && (vFieldDesc.bIsThreadLocal || vFieldDesc.bIsContextLocal)) + { + numStaticFields ++; + if (fIsShared) + ExtOut("%8s %" POINTERSIZE "s", "shared", vFieldDesc.bIsThreadLocal ? "TLstatic" : "CLstatic"); + else + ExtOut("%8s ", vFieldDesc.bIsThreadLocal ? "TLstatic" : "CLstatic"); + + NameForToken_s(TokenFromRid(vFieldDesc.mb, mdtFieldDef), pImport, g_mdName, mdNameLen, false); + ExtOut(" %S\n", g_mdName); + + if (IsMiniDumpFile()) + { + ExtOut(" <no information>\n"); + } + else + { + if (vFieldDesc.bIsThreadLocal) + { + DacpModuleData vModule; + if (vModule.Request(g_sos,pMTD->Module) == S_OK) + { + DisplayThreadStatic(&vModule, pMTD, &vFieldDesc, fIsShared); + } + } + else if (vFieldDesc.bIsContextLocal) + { + DisplayContextStatic(&vFieldDesc, + pMTFD->wContextStaticOffset, + fIsShared); + } + } + + } + else if (vFieldDesc.bIsStatic) + { + numStaticFields ++; + + if (fIsShared) + { + ExtOut("%8s %" POINTERSIZE "s", "shared", "static"); + + NameForToken_s(TokenFromRid(vFieldDesc.mb, mdtFieldDef), pImport, g_mdName, mdNameLen, false); + ExtOut(" %S\n", g_mdName); + + if (IsMiniDumpFile()) + { + ExtOut(" <no information>\n"); + } + else + { + DacpModuleData vModule; + if (vModule.Request(g_sos,pMTD->Module) == S_OK) + { + DisplaySharedStatic(vModule.dwModuleID, pMTD, &vFieldDesc); + } + } + } + else + { + ExtOut("%8s ", "static"); + + DacpDomainLocalModuleData vDomainLocalModule; + + // The MethodTable isn't shared, so the module must not be loaded domain neutral. We can + // get the specific DomainLocalModule instance without needing to know the AppDomain in advance. + if (g_sos->GetDomainLocalModuleDataFromModule(pMTD->Module, &vDomainLocalModule) != S_OK) + { + ExtOut(" <no information>\n"); + } + else + { + DWORD_PTR dwTmp; + GetStaticFieldPTR(&dwTmp, &vDomainLocalModule, pMTD, &vFieldDesc); + DisplayDataMember(&vFieldDesc, dwTmp); + + NameForToken_s(TokenFromRid(vFieldDesc.mb, mdtFieldDef), pImport, g_mdName, mdNameLen, false); + ExtOut(" %S\n", g_mdName); + } + } + } + else + { + numInstanceFields ++; + + ExtOut("%8s ", "instance"); + + if (dwStartAddr > 0) + { + DWORD_PTR dwTmp = dwStartAddr + vFieldDesc.dwOffset + (bValueClass ? 0 : sizeof(BaseObject)); + DisplayDataMember(&vFieldDesc, dwTmp); + } + else + { + ExtOut(" %8s", " "); + } + + + NameForToken_s(TokenFromRid(vFieldDesc.mb, mdtFieldDef), pImport, g_mdName, mdNameLen, false); + ExtOut(" %S\n", g_mdName); + } + + } + + return; +} + +// Return value: -1 = error, +// 0 = field not found, +// > 0 = offset to field from objAddr +int GetObjFieldOffset(CLRDATA_ADDRESS cdaObj, __in_z LPCWSTR wszFieldName, BOOL bFirst) +{ + TADDR mt = NULL; + if FAILED(GetMTOfObject(TO_TADDR(cdaObj), &mt)) + return -1; + + return GetObjFieldOffset(cdaObj, TO_CDADDR(mt), wszFieldName, bFirst); +} + +// Return value: -1 = error, +// 0 = field not found, +// > 0 = offset to field from objAddr +int GetObjFieldOffset(CLRDATA_ADDRESS cdaObj, CLRDATA_ADDRESS cdaMT, __in_z LPCWSTR wszFieldName, + BOOL bFirst/*=TRUE*/) +{ + +#define EXITPOINT(EXPR) do { if(!(EXPR)) { return -1; } } while (0) + + DacpObjectData objData; + DacpMethodTableData dmtd; + DacpMethodTableFieldData vMethodTableFields; + DacpFieldDescData vFieldDesc; + DacpModuleData module; + static DWORD numInstanceFields = 0; // Static due to recursion visiting parents + + if (bFirst) + { + numInstanceFields = 0; + } + + EXITPOINT(objData.Request(g_sos, cdaObj) == S_OK); + EXITPOINT(dmtd.Request(g_sos, cdaMT) == S_OK); + + if (dmtd.ParentMethodTable) + { + DWORD retVal = GetObjFieldOffset (cdaObj, dmtd.ParentMethodTable, + wszFieldName, FALSE); + if (retVal != 0) + { + // return in case of error or success. + // Fall through for field-not-found. + return retVal; + } + } + + EXITPOINT (vMethodTableFields.Request(g_sos,cdaMT) == S_OK); + EXITPOINT (module.Request(g_sos,dmtd.Module) == S_OK); + + CLRDATA_ADDRESS dwAddr = vMethodTableFields.FirstField; + ToRelease<IMetaDataImport> pImport = MDImportForModule(&module); + + while (numInstanceFields < vMethodTableFields.wNumInstanceFields) + { + EXITPOINT (vFieldDesc.Request(g_sos, dwAddr) == S_OK); + + if (!vFieldDesc.bIsStatic) + { + DWORD offset = vFieldDesc.dwOffset + sizeof(BaseObject); + NameForToken_s (TokenFromRid(vFieldDesc.mb, mdtFieldDef), pImport, g_mdName, mdNameLen, false); + if (_wcscmp (wszFieldName, g_mdName) == 0) + { + return offset; + } + numInstanceFields ++; + } + + dwAddr = vFieldDesc.NextField; + } + + // Field name not found... + return 0; + +#undef EXITPOINT +} + +// Returns an AppDomain address if AssemblyPtr is loaded into that domain only. Otherwise +// returns NULL +CLRDATA_ADDRESS IsInOneDomainOnly(CLRDATA_ADDRESS AssemblyPtr) +{ + CLRDATA_ADDRESS appDomain = NULL; + + DacpAppDomainStoreData adstore; + if (adstore.Request(g_sos) != S_OK) + { + ExtOut("Unable to get appdomain store\n"); + return NULL; + } + + size_t AllocSize; + if (!ClrSafeInt<size_t>::multiply(sizeof(CLRDATA_ADDRESS), adstore.DomainCount, AllocSize)) + { + ReportOOM(); + return NULL; + } + + ArrayHolder<CLRDATA_ADDRESS> pArray = new CLRDATA_ADDRESS[adstore.DomainCount]; + if (pArray==NULL) + { + ReportOOM(); + return NULL; + } + + if (g_sos->GetAppDomainList(adstore.DomainCount, pArray, NULL)!=S_OK) + { + ExtOut ("Failed to get appdomain list\n"); + return NULL; + } + + for (int i = 0; i < adstore.DomainCount; i++) + { + if (IsInterrupt()) + return NULL; + + DacpAppDomainData dadd; + if (dadd.Request(g_sos, pArray[i]) != S_OK) + { + ExtOut ("Unable to get AppDomain %p\n", SOS_PTR(pArray[i])); + return NULL; + } + + if (dadd.AssemblyCount) + { + size_t AssemblyAllocSize; + if (!ClrSafeInt<size_t>::multiply(sizeof(CLRDATA_ADDRESS), dadd.AssemblyCount, AssemblyAllocSize)) + { + ReportOOM(); + return NULL; + } + + ArrayHolder<CLRDATA_ADDRESS> pAsmArray = new CLRDATA_ADDRESS[dadd.AssemblyCount]; + if (pAsmArray==NULL) + { + ReportOOM(); + return NULL; + } + + if (g_sos->GetAssemblyList(dadd.AppDomainPtr,dadd.AssemblyCount,pAsmArray, NULL)!=S_OK) + { + ExtOut("Unable to get array of Assemblies\n"); + return NULL; + } + + for (LONG n = 0; n < dadd.AssemblyCount; n ++) + { + if (IsInterrupt()) + return NULL; + + if (AssemblyPtr == pAsmArray[n]) + { + if (appDomain != NULL) + { + // We have found more than one AppDomain that loaded this + // assembly, we must return NULL. + return NULL; + } + appDomain = dadd.AppDomainPtr; + } + } + } + } + + + return appDomain; +} + +CLRDATA_ADDRESS GetAppDomainForMT(CLRDATA_ADDRESS mtPtr) +{ + DacpMethodTableData mt; + if (mt.Request(g_sos, mtPtr) != S_OK) + { + return NULL; + } + + DacpModuleData module; + if (module.Request(g_sos, mt.Module) != S_OK) + { + return NULL; + } + + DacpAssemblyData assembly; + if (assembly.Request(g_sos, module.Assembly) != S_OK) + { + return NULL; + } + + DacpAppDomainStoreData adstore; + if (adstore.Request(g_sos) != S_OK) + { + return NULL; + } + + return (assembly.ParentDomain == adstore.sharedDomain) ? + IsInOneDomainOnly(assembly.AssemblyPtr) : + assembly.ParentDomain; +} + +CLRDATA_ADDRESS GetAppDomain(CLRDATA_ADDRESS objPtr) +{ + CLRDATA_ADDRESS appDomain = NULL; + + DacpObjectData objData; + if (objData.Request(g_sos,objPtr) != S_OK) + { + return NULL; + } + + // First check eeclass->module->assembly->domain. + // Then check the object flags word + // finally, search threads for a reference to the object, and look at the thread context. + + DacpMethodTableData mt; + if (mt.Request(g_sos,objData.MethodTable) != S_OK) + { + return NULL; + } + + DacpModuleData module; + if (module.Request(g_sos,mt.Module) != S_OK) + { + return NULL; + } + + DacpAssemblyData assembly; + if (assembly.Request(g_sos,module.Assembly) != S_OK) + { + return NULL; + } + + DacpAppDomainStoreData adstore; + if (adstore.Request(g_sos) != S_OK) + { + return NULL; + } + + if (assembly.ParentDomain == adstore.sharedDomain) + { + sos::Object obj(TO_TADDR(objPtr)); + ULONG value = 0; + if (!obj.TryGetHeader(value)) + { + return NULL; + } + + DWORD adIndex = (value >> SBLK_APPDOMAIN_SHIFT) & SBLK_MASK_APPDOMAININDEX; + if ( ((value & BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX) != 0) || adIndex==0) + { + // No AppDomainID information. We'll make use of a heuristic. + // If the assembly is in the shared domain, we can report it as + // being in domain X if the only other domain that has the assembly + // loaded is domain X. + appDomain = IsInOneDomainOnly(assembly.AssemblyPtr); + if (appDomain == NULL && ((value & BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX) != 0)) + { + if ((value & BIT_SBLK_IS_HASHCODE) == 0) + { + UINT index = value & MASK_SYNCBLOCKINDEX; + // We have a syncblock, the appdomain ID may be in there. + DacpSyncBlockData syncBlockData; + if (syncBlockData.Request(g_sos,index) == S_OK) + { + appDomain = syncBlockData.appDomainPtr; + } + } + } + } + else if ((value & BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX) == 0) + { + size_t AllocSize; + if (!ClrSafeInt<size_t>::multiply(sizeof(CLRDATA_ADDRESS), adstore.DomainCount, AllocSize)) + { + return NULL; + } + // we know we have a non-zero adIndex. Find the appdomain. + ArrayHolder<CLRDATA_ADDRESS> pArray = new CLRDATA_ADDRESS[adstore.DomainCount]; + if (pArray==NULL) + { + return NULL; + } + + if (g_sos->GetAppDomainList(adstore.DomainCount, pArray, NULL)!=S_OK) + { + return NULL; + } + + for (int i = 0; i < adstore.DomainCount; i++) + { + DacpAppDomainData dadd; + if (dadd.Request(g_sos, pArray[i]) != S_OK) + { + return NULL; + } + if (dadd.dwId == adIndex) + { + appDomain = pArray[i]; + break; + } + } + } + } + else + { + appDomain = assembly.ParentDomain; + } + + return appDomain; +} + +HRESULT FileNameForModule (DWORD_PTR pModuleAddr, __out_ecount (MAX_LONGPATH) WCHAR *fileName) +{ + DacpModuleData ModuleData; + fileName[0] = L'\0'; + + HRESULT hr = ModuleData.Request(g_sos, TO_CDADDR(pModuleAddr)); + if (SUCCEEDED(hr)) + { + hr = FileNameForModule(&ModuleData,fileName); + } + + return hr; +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to find the file name given a Module. * +* * +\**********************************************************************/ +// fileName should be at least MAX_LONGPATH +HRESULT FileNameForModule (DacpModuleData *pModule, __out_ecount (MAX_LONGPATH) WCHAR *fileName) +{ + fileName[0] = L'\0'; + + HRESULT hr = S_OK; + CLRDATA_ADDRESS dwAddr = pModule->File; + if (dwAddr == 0) + { + // TODO: We have dynamic module + return E_NOTIMPL; + } + + CLRDATA_ADDRESS base = 0; + hr = g_sos->GetPEFileBase(dwAddr, &base); + if (SUCCEEDED(hr)) + { + hr = g_sos->GetPEFileName(dwAddr, MAX_LONGPATH, fileName, NULL); + if (SUCCEEDED(hr)) + { + if (fileName[0] != W('\0')) + return hr; // done + } +#ifndef FEATURE_PAL + // Try the base * + if (base) + { + hr = DllsName((ULONG_PTR) base, fileName); + } +#endif // !FEATURE_PAL + } + + // If we got here, either DllsName worked, or we couldn't find a name + return hr; +} + +void AssemblyInfo(DacpAssemblyData *pAssembly) +{ + ExtOut("ClassLoader: %p\n", SOS_PTR(pAssembly->ClassLoader)); + if ((ULONG64)pAssembly->AssemblySecDesc != NULL) + ExtOut("SecurityDescriptor: %p\n", SOS_PTR(pAssembly->AssemblySecDesc)); + ExtOut(" Module Name\n"); + + ArrayHolder<CLRDATA_ADDRESS> Modules = new CLRDATA_ADDRESS[pAssembly->ModuleCount]; + if (Modules == NULL + || g_sos->GetAssemblyModuleList(pAssembly->AssemblyPtr, pAssembly->ModuleCount, Modules, NULL) != S_OK) + { + ReportOOM(); + return; + } + + for (UINT n=0;n<pAssembly->ModuleCount;n++) + { + if (IsInterrupt()) + { + return; + } + + CLRDATA_ADDRESS ModuleAddr = Modules[n]; + DMLOut("%s " WIN86_8SPACES, DMLModule(ModuleAddr)); + DacpModuleData moduleData; + if (moduleData.Request(g_sos,ModuleAddr)==S_OK) + { + WCHAR fileName[MAX_LONGPATH]; + FileNameForModule (&moduleData, fileName); + if (fileName[0]) + { + ExtOut("%S\n", fileName); + } + else + { + ExtOut("%S\n", (moduleData.bIsReflection) ? W("Dynamic Module") : W("Unknown Module")); + } + } + } +} + +const char *GetStageText(DacpAppDomainDataStage stage) +{ + switch(stage) + { + case STAGE_CREATING: + return "CREATING"; + case STAGE_READYFORMANAGEDCODE: + return "READYFORMANAGEDCODE"; + case STAGE_ACTIVE: + return "ACTIVE"; + case STAGE_OPEN: + return "OPEN"; + case STAGE_UNLOAD_REQUESTED: + return "UNLOAD_REQUESTED"; + case STAGE_EXITING: + return "EXITING"; + case STAGE_EXITED: + return "EXITED"; + case STAGE_FINALIZING: + return "FINALIZING"; + case STAGE_FINALIZED: + return "FINALIZED"; + case STAGE_HANDLETABLE_NOACCESS: + return "HANDLETABLE_NOACCESS"; + case STAGE_CLEARED: + return "CLEARED"; + case STAGE_COLLECTED: + return "COLLECTED"; + case STAGE_CLOSED: + return "CLOSED"; + } + return "UNKNOWN"; +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to dump the contents of a domain. * +* * +\**********************************************************************/ +void DomainInfo (DacpAppDomainData *pDomain) +{ + ExtOut("LowFrequencyHeap: %p\n", SOS_PTR(pDomain->pLowFrequencyHeap)); + ExtOut("HighFrequencyHeap: %p\n", SOS_PTR(pDomain->pHighFrequencyHeap)); + ExtOut("StubHeap: %p\n", SOS_PTR(pDomain->pStubHeap)); + ExtOut("Stage: %s\n", GetStageText(pDomain->appDomainStage)); + if ((ULONG64)pDomain->AppSecDesc != NULL) + ExtOut("SecurityDescriptor: %p\n", SOS_PTR(pDomain->AppSecDesc)); + ExtOut("Name: "); + + if (g_sos->GetAppDomainName(pDomain->AppDomainPtr, mdNameLen, g_mdName, NULL)!=S_OK) + { + ExtOut("Error getting AppDomain friendly name\n"); + } + else + { + ExtOut("%S\n", (g_mdName[0] != L'\0') ? g_mdName : W("None")); + } + + if (pDomain->AssemblyCount == 0) + return; + + ArrayHolder<CLRDATA_ADDRESS> pArray = new CLRDATA_ADDRESS[pDomain->AssemblyCount]; + if (pArray==NULL) + { + ReportOOM(); + return; + } + + if (g_sos->GetAssemblyList(pDomain->AppDomainPtr,pDomain->AssemblyCount,pArray, NULL)!=S_OK) + { + ExtOut("Unable to get array of Assemblies\n"); + return; + } + + LONG n; + // Assembly vAssembly; + for (n = 0; n < pDomain->AssemblyCount; n ++) + { + if (IsInterrupt()) + return; + + if (n != 0) + ExtOut("\n"); + + DMLOut("Assembly: %s", DMLAssembly(pArray[n])); + DacpAssemblyData assemblyData; + if (assemblyData.Request(g_sos, pArray[n], pDomain->AppDomainPtr) == S_OK) + { + if (assemblyData.isDynamic) + ExtOut(" (Dynamic)"); + + ExtOut(" ["); + if (g_sos->GetAssemblyName(pArray[n], mdNameLen, g_mdName, NULL) == S_OK) + ExtOut("%S", g_mdName); + ExtOut("]\n"); + + AssemblyInfo(&assemblyData); + } + } + + ExtOut("\n"); +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to find the name of a MethodDesc using * +* metadata API. * +* * +\**********************************************************************/ +BOOL NameForMD_s (DWORD_PTR pMD, __out_ecount (capacity_mdName) WCHAR *mdName, size_t capacity_mdName) +{ + mdName[0] = L'\0'; + CLRDATA_ADDRESS StartAddr = TO_CDADDR(pMD); + DacpMethodDescData MethodDescData; + + // don't need to check for minidump file as all commands are seals + // We also do not have EEJitManager to validate anyway. + // + if (!IsMiniDumpFile() && MethodDescData.Request(g_sos,StartAddr) != S_OK) + { + ExtOut("%p is not a MethodDesc\n", SOS_PTR(StartAddr)); + return FALSE; + } + + if (g_sos->GetMethodDescName(StartAddr, mdNameLen, mdName, NULL) != S_OK) + { + wcscpy_s(mdName, capacity_mdName, W("UNKNOWN")); + return FALSE; + } + return TRUE; +} + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to find the name of a MethodTable using * +* metadata API. * +* * +\**********************************************************************/ +BOOL NameForMT_s(DWORD_PTR MTAddr, __out_ecount (capacity_mdName) WCHAR *mdName, size_t capacity_mdName) +{ + HRESULT hr = g_sos->GetMethodTableName(TO_CDADDR(MTAddr), (ULONG32)capacity_mdName, mdName, NULL); + return SUCCEEDED(hr); +} + +WCHAR *CreateMethodTableName(TADDR mt, TADDR cmt) +{ + bool array = false; + WCHAR *res = NULL; + + if (mt == sos::MethodTable::GetFreeMT()) + { + res = new WCHAR[5]; + wcscpy_s(res, 5, W("Free")); + return res; + } + + if (mt == sos::MethodTable::GetArrayMT() && cmt != NULL) + { + mt = cmt; + array = true; + } + + unsigned int needed = 0; + HRESULT hr = g_sos->GetMethodTableName(mt, 0, NULL, &needed); + + // If failed, we will return null. + if (SUCCEEDED(hr)) + { + // +2 for [], if we need it. + res = new WCHAR[needed+2]; + hr = g_sos->GetMethodTableName(mt, needed, res, NULL); + + if (FAILED(hr)) + { + delete [] res; + res = NULL; + } + else if (array) + { + res[needed-1] = '['; + res[needed] = ']'; + res[needed+1] = 0; + } + } + + return res; +} + +/**********************************************************************\ +* Routine Description: * +* * +* Return TRUE if str2 is a substring of str1 and str1 and str2 * +* share the same file path. +* * +\**********************************************************************/ +BOOL IsSameModuleName (const char *str1, const char *str2) +{ + if (strlen (str1) < strlen (str2)) + return FALSE; + const char *ptr1 = str1 + strlen(str1)-1; + const char *ptr2 = str2 + strlen(str2)-1; + while (ptr2 >= str2) + { +#ifndef FEATURE_PAL + if (tolower(*ptr1) != tolower(*ptr2)) +#else + if (*ptr1 != *ptr2) +#endif + { + return FALSE; + } + ptr2--; + ptr1--; + } + if (ptr1 >= str1 && *ptr1 != DIRECTORY_SEPARATOR_CHAR_A && *ptr1 != ':') + { + return FALSE; + } + return TRUE; +} + +/**********************************************************************\ +* Routine Description: * +* * +* Return TRUE if moduleAddr is the address of a module. * +* * +\**********************************************************************/ +BOOL IsModule (DWORD_PTR moduleAddr) +{ + DacpModuleData module; + return (module.Request(g_sos, TO_CDADDR(moduleAddr))==S_OK); +} + +/**********************************************************************\ +* Routine Description: * +* * +* Return TRUE if value is the address of a MethodTable. * +* We verify that MethodTable and EEClass are right. +* * +\**********************************************************************/ +BOOL IsMethodTable (DWORD_PTR value) +{ + DacpMethodTableData mtabledata; + if (mtabledata.Request(g_sos, TO_CDADDR(value))!=S_OK) + { + return FALSE; + } + + return TRUE; +} + +/**********************************************************************\ +* Routine Description: * +* * +* Return TRUE if value is the address of a MethodDesc. * +* We verify that MethodTable and EEClass are right. +* * +\**********************************************************************/ +BOOL IsMethodDesc (DWORD_PTR value) +{ + // Just by retrieving one successfully from the DAC, we know we have a MethodDesc. + DacpMethodDescData MethodDescData; + if (MethodDescData.Request(g_sos, TO_CDADDR(value)) != S_OK) + { + return FALSE; + } + + return TRUE; +} + +DacpUsefulGlobalsData g_special_usefulGlobals; + +BOOL IsObjectArray (DacpObjectData *pData) +{ + if (pData->ObjectType == OBJ_ARRAY) + return g_special_usefulGlobals.ArrayMethodTable == pData->MethodTable; + + return FALSE; +} + +BOOL IsObjectArray (DWORD_PTR obj) +{ + DWORD_PTR mtAddr = NULL; + if (SUCCEEDED(GetMTOfObject(obj, &mtAddr))) + return TO_TADDR(g_special_usefulGlobals.ArrayMethodTable) == mtAddr; + + return FALSE; +} + +BOOL IsStringObject (size_t obj) +{ + DWORD_PTR mtAddr = NULL; + + if (SUCCEEDED(GetMTOfObject(obj, &mtAddr))) + return TO_TADDR(g_special_usefulGlobals.StringMethodTable) == mtAddr; + + return FALSE; +} + +void DumpStackObjectsOutput(const char *location, DWORD_PTR objAddr, BOOL verifyFields) +{ + // rule out pointers that are outside of the gc heap. + if (g_snapshot.GetHeap(objAddr) == NULL) + return; + + DacpObjectData objectData; + if (objectData.Request(g_sos, TO_CDADDR(objAddr)) != S_OK) + return; + + if (sos::IsObject(objAddr, verifyFields != FALSE) + && !sos::MethodTable::IsFreeMT(TO_TADDR(objectData.MethodTable))) + { + DMLOut("%-" POINTERSIZE "s %s ", location, DMLObject(objAddr)); + if (g_sos->GetObjectClassName(TO_CDADDR(objAddr), mdNameLen, g_mdName, NULL)==S_OK) + { + ExtOut("%S", g_mdName); + + if (IsStringObject(objAddr)) + { + ExtOut(" "); + StringObjectContent(objAddr, FALSE, 40); + } + else if (IsObjectArray(objAddr) && + (g_sos->GetMethodTableName(objectData.ElementTypeHandle, mdNameLen, g_mdName, NULL) == S_OK)) + { + ExtOut(" "); + ExtOut("(%S[])", g_mdName); + } + } + else + { + ExtOut("<unknown type>"); + } + ExtOut("\n"); + } +} + +void DumpStackObjectsOutput(DWORD_PTR ptr, DWORD_PTR objAddr, BOOL verifyFields) +{ + char location[64]; + sprintf_s(location, 64, "%p", (DWORD_PTR *)ptr); + + DumpStackObjectsOutput(location, objAddr, verifyFields); +} + +void DumpStackObjectsInternal(size_t StackTop, size_t StackBottom, BOOL verifyFields) +{ + for (DWORD_PTR ptr = StackTop; ptr <= StackBottom; ptr += sizeof(DWORD_PTR)) + { + if (IsInterrupt()) + return; + + DWORD_PTR objAddr; + move_xp(objAddr, ptr); + + DumpStackObjectsOutput(ptr, objAddr, verifyFields); + } +} + +void DumpRegObjectHelper(const char *regName, BOOL verifyFields) +{ + DWORD_PTR reg; +#ifdef FEATURE_PAL + if (FAILED(g_ExtRegisters->GetValueByName(regName, ®))) + return; +#else + DEBUG_VALUE value; + ULONG IREG; + if (FAILED(g_ExtRegisters->GetIndexByName(regName, &IREG)) || + FAILED(g_ExtRegisters->GetValue(IREG, &value))) + return; + +#if defined(SOS_TARGET_X86) || defined(SOS_TARGET_ARM) + reg = (DWORD_PTR) value.I32; +#elif defined(SOS_TARGET_AMD64) || defined(SOS_TARGET_ARM64) + reg = (DWORD_PTR) value.I64; +#else +#error Unsupported target +#endif +#endif // FEATURE_PAL + + DumpStackObjectsOutput(regName, reg, verifyFields); +} + +void DumpStackObjectsHelper ( + TADDR StackTop, + TADDR StackBottom, + BOOL verifyFields) +{ + ExtOut(g_targetMachine->GetDumpStackObjectsHeading()); + + LPCSTR* regs; + unsigned int cnt; + g_targetMachine->GetGCRegisters(®s, &cnt); + + for (size_t i = 0; i < cnt; ++i) + DumpRegObjectHelper(regs[i], verifyFields); + + // Make certain StackTop is dword aligned: + DumpStackObjectsInternal(StackTop & ~ALIGNCONST, StackBottom, verifyFields); +} + +void AddToModuleList(DWORD_PTR * &moduleList, int &numModule, int &maxList, + DWORD_PTR dwModuleAddr) +{ + int i; + for (i = 0; i < numModule; i ++) + { + if (moduleList[i] == dwModuleAddr) + break; + } + if (i == numModule) + { + moduleList[numModule] = dwModuleAddr; + numModule ++; + if (numModule == maxList) + { + int listLength = 0; + if (!ClrSafeInt<int>::multiply(maxList, 2, listLength)) + { + ExtOut("<integer overflow>\n"); + numModule = 0; + ControlC = 1; + return; + } + DWORD_PTR *list = new DWORD_PTR [listLength]; + + if (list == NULL) + { + numModule = 0; + ControlC = 1; + return; + } + memcpy (list, moduleList, maxList * sizeof(PVOID)); + delete[] moduleList; + moduleList = list; + maxList *= 2; + } + } +} + +BOOL IsFusionLoadedModule (LPCSTR fusionName, LPCSTR mName) +{ + // The fusion name will be in this format: + // <module name>, Version=<version>, Culture=<culture>, PublicKeyToken=<token> + // If fusionName up to the comma matches mName (case insensitive), + // we consider that a match was found. + LPCSTR commaPos = strchr (fusionName, ','); + if (commaPos) + { + // verify that fusionName and mName match up to a comma. + while (*fusionName != ',') + { + if (*mName == '\0') + { + return FALSE; + } + +#ifndef FEATURE_PAL + if (tolower(*fusionName) != tolower(*mName)) +#else + if (*fusionName != *mName) +#endif + { + return FALSE; + } + fusionName++; + mName++; + } + return TRUE; + } + return FALSE; +} + +BOOL DebuggerModuleNamesMatch (CLRDATA_ADDRESS PEFileAddr, ___in __in_z LPSTR mName) +{ + // Another way to see if a module is the same is + // to accept that mName may be the debugger's name for + // a loaded module. We can get the debugger's name for + // the module we are looking at right now, and compare + // it with mName, if they match exactly, we can add + // the module to the list. + if (PEFileAddr) + { + CLRDATA_ADDRESS pebase = 0; + if (g_sos->GetPEFileBase(PEFileAddr, &pebase) == S_OK) + { + if (pebase) + { + ULONG Index; + ULONG64 base; + if (g_ExtSymbols->GetModuleByOffset(pebase, 0, &Index, &base) == S_OK) + { + CHAR ModuleName[MAX_LONGPATH+1]; + + if (g_ExtSymbols->GetModuleNames(Index, base, NULL, 0, NULL, ModuleName, + MAX_LONGPATH, NULL, NULL, 0, NULL) == S_OK) + { + if (_stricmp (ModuleName, mName) == 0) + { + return TRUE; + } + } + } + } + } + } + return FALSE; +} + +DWORD_PTR *ModuleFromName(__in_opt LPSTR mName, int *numModule) +{ + if (numModule == NULL) + return NULL; + + DWORD_PTR *moduleList = NULL; + *numModule = 0; + + DacpAppDomainStoreData adsData; + if (adsData.Request(g_sos)!=S_OK) + return NULL; + + ArrayHolder<CLRDATA_ADDRESS> pAssemblyArray = NULL; + ArrayHolder<CLRDATA_ADDRESS> pModules = NULL; + int arrayLength = 0; + if (!ClrSafeInt<int>::addition(adsData.DomainCount, 2, arrayLength)) + { + ExtOut("<integer overflow>\n"); + return NULL; + } + ArrayHolder<CLRDATA_ADDRESS> pArray = new CLRDATA_ADDRESS[arrayLength]; + + if (pArray==NULL) + { + ReportOOM(); + return NULL; + } + + pArray[0] = adsData.systemDomain; + pArray[1] = adsData.sharedDomain; + if (g_sos->GetAppDomainList(adsData.DomainCount, pArray.GetPtr()+2, NULL)!=S_OK) + { + ExtOut("Unable to get array of AppDomains\n"); + return NULL; + } + + // List all domain + size_t AllocSize; + int maxList = arrayLength; // account for system and shared domains + if (maxList <= 0 || !ClrSafeInt<size_t>::multiply(maxList, sizeof(PVOID), AllocSize)) + { + ExtOut("Integer overflow error.\n"); + return NULL; + } + + moduleList = new DWORD_PTR[maxList]; + if (moduleList == NULL) + { + ReportOOM(); + return NULL; + } + + WCHAR StringData[MAX_LONGPATH]; + char fileName[sizeof(StringData)/2]; + + // Search all domains to find a module + for (int n = 0; n < adsData.DomainCount+2; n++) + { + if (IsInterrupt()) + { + ExtOut("<interrupted>\n"); + goto Failure; + } + + DacpAppDomainData appDomain; + if (FAILED(appDomain.Request(g_sos,pArray[n]))) + { + // Don't print a failure message here, there is a very normal case when checking + // for modules after clr is loaded but before any AppDomains or assemblies are created + // for example: + // >sxe ld:clr + // >g + // ... + // ModLoad: clr.dll + // >!bpmd Foo.dll Foo.Bar + + // we will correctly give the answer that whatever module you were looking for, it isn't loaded yet + goto Failure; + } + + if (appDomain.AssemblyCount) + { + pAssemblyArray = new CLRDATA_ADDRESS[appDomain.AssemblyCount]; + if (pAssemblyArray==NULL) + { + ReportOOM(); + goto Failure; + } + + if (FAILED(g_sos->GetAssemblyList(appDomain.AppDomainPtr, appDomain.AssemblyCount, pAssemblyArray, NULL))) + { + ExtOut("Unable to get array of Assemblies for the given AppDomain..\n"); + goto Failure; + } + + for (int nAssem = 0; nAssem < appDomain.AssemblyCount; nAssem ++) + { + if (IsInterrupt()) + { + ExtOut("<interrupted>\n"); + goto Failure; + } + + DacpAssemblyData assemblyData; + if (FAILED(assemblyData.Request(g_sos, pAssemblyArray[nAssem]))) + { + ExtOut("Failed to request assembly.\n"); + goto Failure; + } + + pModules = new CLRDATA_ADDRESS[assemblyData.ModuleCount]; + if (FAILED(g_sos->GetAssemblyModuleList(assemblyData.AssemblyPtr, assemblyData.ModuleCount, pModules, NULL))) + { + ExtOut("Failed to get the modules for the given assembly.\n"); + goto Failure; + } + + for (UINT nModule = 0; nModule < assemblyData.ModuleCount; nModule++) + { + if (IsInterrupt()) + { + ExtOut("<interrupted>\n"); + goto Failure; + } + + CLRDATA_ADDRESS ModuleAddr = pModules[nModule]; + DacpModuleData ModuleData; + if (FAILED(ModuleData.Request(g_sos,ModuleAddr))) + { + ExtOut("Failed to request Module data from assembly.\n"); + goto Failure; + } + + FileNameForModule ((DWORD_PTR)ModuleAddr, StringData); + int m; + for (m = 0; StringData[m] != L'\0'; m++) + { + fileName[m] = (char)StringData[m]; + } + fileName[m] = '\0'; + + if ((mName == NULL) || + IsSameModuleName(fileName, mName) || + DebuggerModuleNamesMatch(ModuleData.File, mName) || + IsFusionLoadedModule(fileName, mName)) + { + AddToModuleList(moduleList, *numModule, maxList, (DWORD_PTR)ModuleAddr); + } + } + + pModules = NULL; + } + pAssemblyArray = NULL; + } + } + + return moduleList; + + // We do not want to return a half-constructed list. Instead, we return NULL on a failure. +Failure: + delete [] moduleList; + return NULL; +} + +/**********************************************************************\ +* Routine Description: * +* * +* Find the EE data given a name. * +* * +\**********************************************************************/ +void GetInfoFromName(DWORD_PTR ModulePtr, const char* name) +{ + ToRelease<IMetaDataImport> pImport = MDImportForModule (ModulePtr); + if (pImport == 0) + return; + + static WCHAR wszName[MAX_CLASSNAME_LENGTH]; + size_t n; + size_t length = strlen (name); + for (n = 0; n <= length; n ++) + wszName[n] = name[n]; + + // First enumerate methods. We're taking advantage of the DAC's + // CLRDataModule::EnumMethodDefinitionByName which can parse + // method names (whether in nested classes, or explicit interface + // method implementations). + ToRelease<IXCLRDataModule> ModuleDefinition; + if (g_sos->GetModule(ModulePtr, &ModuleDefinition) == S_OK) + { + CLRDATA_ENUM h; + if (ModuleDefinition->StartEnumMethodDefinitionsByName(wszName, 0, &h) == S_OK) + { + IXCLRDataMethodDefinition *pMeth = NULL; + BOOL fStatus = FALSE; + while (ModuleDefinition->EnumMethodDefinitionByName(&h, &pMeth) == S_OK) + { + if (fStatus) + ExtOut("-----------------------\n"); + + mdTypeDef token; + if (pMeth->GetTokenAndScope(&token, NULL) == S_OK) + { + GetInfoFromModule(ModulePtr, token); + fStatus = TRUE; + } + pMeth->Release(); + } + ModuleDefinition->EndEnumMethodDefinitionsByName(h); + if (fStatus) + return; + } + } + + // Now look for types, type members and fields + mdTypeDef cl; + mdToken tkEnclose = mdTokenNil; + WCHAR *pName; + WCHAR *pHead = wszName; + while ( ((pName = _wcschr (pHead,L'+')) != NULL) || + ((pName = _wcschr (pHead,L'/')) != NULL)) { + pName[0] = L'\0'; + if (FAILED(pImport->FindTypeDefByName(pHead,tkEnclose,&tkEnclose))) + return; + pHead = pName+1; + } + + pName = pHead; + + // @todo: Handle Nested classes correctly. + if (SUCCEEDED (pImport->FindTypeDefByName (pName, tkEnclose, &cl))) + { + GetInfoFromModule(ModulePtr, cl); + return; + } + + // See if it is a method + WCHAR *pwzMethod; + if ((pwzMethod = _wcsrchr(pName, L'.')) == NULL) + return; + + if (pwzMethod[-1] == L'.') + pwzMethod --; + pwzMethod[0] = L'\0'; + pwzMethod ++; + + // @todo: Handle Nested classes correctly. + if (SUCCEEDED(pImport->FindTypeDefByName (pName, tkEnclose, &cl))) + { + mdMethodDef token; + ULONG cTokens; + HCORENUM henum = NULL; + + // is Member? + henum = NULL; + if (SUCCEEDED (pImport->EnumMembersWithName (&henum, cl, pwzMethod, + &token, 1, &cTokens)) + && cTokens == 1) + { + ExtOut("Member (mdToken token) of\n"); + GetInfoFromModule(ModulePtr, cl); + return; + } + + // is Field? + henum = NULL; + if (SUCCEEDED (pImport->EnumFieldsWithName (&henum, cl, pwzMethod, + &token, 1, &cTokens)) + && cTokens == 1) + { + ExtOut("Field (mdToken token) of\n"); + GetInfoFromModule(ModulePtr, cl); + return; + } + } +} + +/**********************************************************************\ +* Routine Description: * +* * +* Find the EE data given a token. * +* * +\**********************************************************************/ +DWORD_PTR GetMethodDescFromModule(DWORD_PTR ModuleAddr, ULONG token) +{ + if (TypeFromToken(token) != mdtMethodDef) + return NULL; + + CLRDATA_ADDRESS md = 0; + if (FAILED(g_sos->GetMethodDescFromToken(ModuleAddr, token, &md))) + { + return NULL; + } + else if (0 == md) + { + // a NULL ReturnValue means the method desc is not loaded yet + return MD_NOT_YET_LOADED; + } + else if ( !IsMethodDesc((DWORD_PTR)md)) + { + return NULL; + } + + return (DWORD_PTR)md; +} + +/**********************************************************************\ +* Routine Description: * +* * +* Find the MethodDefinitions given a name. * +* * +\**********************************************************************/ +HRESULT GetMethodDefinitionsFromName(TADDR ModulePtr, IXCLRDataModule* mod, const char *name, IXCLRDataMethodDefinition **ppOut, int numMethods, int *numMethodsNeeded) +{ + if (name == NULL) + return E_FAIL; + + size_t n; + size_t length = strlen (name); + for (n = 0; n <= length; n ++) + g_mdName[n] = name[n]; + + CLRDATA_ENUM h; + int methodCount = 0; + if (mod->StartEnumMethodDefinitionsByName(g_mdName, 0, &h) == S_OK) + { + IXCLRDataMethodDefinition *pMeth = NULL; + while (mod->EnumMethodDefinitionByName(&h, &pMeth) == S_OK) + { + methodCount++; + pMeth->Release(); + } + mod->EndEnumMethodDefinitionsByName(h); + } + + if(numMethodsNeeded != NULL) + *numMethodsNeeded = methodCount; + if(ppOut == NULL) + return S_OK; + if(numMethods > methodCount) + numMethods = methodCount; + + if (methodCount > 0) + { + if (mod->StartEnumMethodDefinitionsByName(g_mdName, 0, &h) == S_OK) + { + IXCLRDataMethodDefinition *pMeth = NULL; + for (int i = 0; i < numMethods && mod->EnumMethodDefinitionByName(&h, &pMeth) == S_OK; i++) + { + ppOut[i] = pMeth; + } + mod->EndEnumMethodDefinitionsByName(h); + } + } + + return S_OK; +} + +/**********************************************************************\ +* Routine Description: * +* * +* Find the EE data given a name. * +* * +\**********************************************************************/ +HRESULT GetMethodDescsFromName(TADDR ModulePtr, IXCLRDataModule* mod, const char *name, DWORD_PTR **pOut,int *numMethods) +{ + if (name == NULL || pOut == NULL || numMethods == NULL) + return E_FAIL; + + *pOut = NULL; + *numMethods = 0; + + size_t n; + size_t length = strlen (name); + for (n = 0; n <= length; n ++) + g_mdName[n] = name[n]; + + CLRDATA_ENUM h; + int methodCount = 0; + if (mod->StartEnumMethodDefinitionsByName(g_mdName, 0, &h) == S_OK) + { + IXCLRDataMethodDefinition *pMeth = NULL; + while (mod->EnumMethodDefinitionByName(&h, &pMeth) == S_OK) + { + methodCount++; + pMeth->Release(); + } + mod->EndEnumMethodDefinitionsByName(h); + } + + if (methodCount > 0) + { + *pOut = new TADDR[methodCount]; + if (*pOut==NULL) + { + ReportOOM(); + return E_OUTOFMEMORY; + } + + *numMethods = methodCount; + + if (mod->StartEnumMethodDefinitionsByName(g_mdName, 0, &h) == S_OK) + { + int i = 0; + IXCLRDataMethodDefinition *pMeth = NULL; + while (mod->EnumMethodDefinitionByName(&h, &pMeth) == S_OK) + { + mdTypeDef token; + if (pMeth->GetTokenAndScope(&token, NULL) != S_OK) + (*pOut)[i] = NULL; + (*pOut)[i] = GetMethodDescFromModule(ModulePtr, token); + if ((*pOut)[i] == NULL) + { + *numMethods = 0; + return E_FAIL; + } + i++; + pMeth->Release(); + } + mod->EndEnumMethodDefinitionsByName(h); + } + } + + return S_OK; +} + +/**********************************************************************\ +* Routine Description: * +* * +* Find the EE data given a token. * +* * +\**********************************************************************/ +void GetInfoFromModule (DWORD_PTR ModuleAddr, ULONG token, DWORD_PTR *ret) +{ + switch (TypeFromToken(token)) + { + case mdtMethodDef: + break; + case mdtTypeDef: + break; + case mdtTypeRef: + break; + case mdtFieldDef: + break; + default: + ExtOut("This token type is not supported\n"); + return; + break; + } + + CLRDATA_ADDRESS md = 0; + if (FAILED(g_sos->GetMethodDescFromToken(ModuleAddr, token, &md)) || !IsValidToken (ModuleAddr, token)) + { + ExtOut("<invalid module token>\n"); + return; + } + + if (ret != NULL) + { + *ret = (DWORD_PTR)md; + return; + } + + ExtOut("Token: %p\n", SOS_PTR(token)); + + switch (TypeFromToken(token)) + { + case mdtFieldDef: + { + NameForToken_s(ModuleAddr, token, g_mdName, mdNameLen); + ExtOut("Field name: %S\n", g_mdName); + break; + } + case mdtMethodDef: + { + if (md) + { + DMLOut("MethodDesc: %s\n", DMLMethodDesc(md)); + + // Easiest to get full parameterized method name from ..::GetMethodName + if (g_sos->GetMethodDescName(md, mdNameLen, g_mdName, NULL) != S_OK) + { + // Fall back to just method name without parameters.. + NameForToken_s(ModuleAddr, token, g_mdName, mdNameLen); + } + } + else + { + ExtOut("MethodDesc: <not loaded yet>\n"); + NameForToken_s(ModuleAddr, token, g_mdName, mdNameLen); + } + + ExtOut("Name: %S\n", g_mdName); + // Nice to have a little more data + if (md) + { + DacpMethodDescData MethodDescData; + if (MethodDescData.Request(g_sos, md) == S_OK) + { + if (MethodDescData.bHasNativeCode) + { + DMLOut("JITTED Code Address: %s\n", DMLIP(MethodDescData.NativeCodeAddr)); + } + else + { +#ifndef FEATURE_PAL + if (IsDMLEnabled()) + DMLOut("Not JITTED yet. Use <exec cmd=\"!bpmd -md %p\">!bpmd -md %p</exec> to break on run.\n", + SOS_PTR(md), SOS_PTR(md)); + else + ExtOut("Not JITTED yet. Use !bpmd -md %p to break on run.\n", SOS_PTR(md)); +#else + ExtOut("Not JITTED yet. Use 'bpmd -md %p' to break on run.\n", SOS_PTR(md)); +#endif + } + } + else + { + ExtOut ("<Error getting MethodDesc information>\n"); + } + } + else + { + ExtOut("Not JITTED yet.\n"); + } + break; + } + case mdtTypeDef: + case mdtTypeRef: + { + if (md) + { + DMLOut("MethodTable: %s\n", DMLMethodTable(md)); + DacpMethodTableData mtabledata; + if (mtabledata.Request(g_sos, md) == S_OK) + { + DMLOut("EEClass: %s\n", DMLClass(mtabledata.Class)); + } + else + { + ExtOut("EEClass: <error getting EEClass>\n"); + } + } + else + { + ExtOut("MethodTable: <not loaded yet>\n"); + ExtOut("EEClass: <not loaded yet>\n"); + } + NameForToken_s(ModuleAddr, token, g_mdName, mdNameLen); + ExtOut("Name: %S\n", g_mdName); + break; + } + default: + break; + } + return; +} + +BOOL IsMTForFreeObj(DWORD_PTR pMT) +{ + return (pMT == g_special_usefulGlobals.FreeMethodTable); +} + +const char *EHTypeName(EHClauseType et) +{ + if (et == EHFault) + return "FAULT"; + else if (et == EHFinally) + return "FINALLY"; + else if (et == EHFilter) + return "FILTER"; + else if (et == EHTyped) + return "TYPED"; + else + return "UNKNOWN"; +} + +void DumpRejitData(DacpReJitData * pReJitData) +{ + ExtOut(" ReJITID %p: ", SOS_PTR(pReJitData->rejitID)); + DMLOut("CodeAddr = %s", DMLIP(pReJitData->NativeCodeAddr)); + + LPCSTR szFlags; + switch (pReJitData->flags) + { + default: + case DacpReJitData::kUnknown: + szFlags = ""; + break; + + case DacpReJitData::kRequested: + szFlags = " (READY to jit on next call)"; + break; + + case DacpReJitData::kActive: + szFlags = " (CURRENT)"; + break; + + case DacpReJitData::kReverted: + szFlags = " (reverted)"; + break; + } + ExtOut("%s\n", szFlags); +} + +// For !ip2md requests, this function helps us ensure that rejitted version corresponding +// to the specified IP always gets dumped. It may have already been dumped if it was the +// current rejit version (which is always dumped) or one of the reverted versions that we +// happened to dump before we clipped their number down to kcRejitDataRevertedMax. +BOOL ShouldDumpRejitDataRequested(DacpMethodDescData * pMethodDescData, DacpReJitData * pRevertedRejitData, UINT cRevertedRejitData) +{ + if (pMethodDescData->rejitDataRequested.rejitID == 0) + return FALSE; + + if (pMethodDescData->rejitDataRequested.rejitID == pMethodDescData->rejitDataCurrent.rejitID) + return FALSE; + + for (ULONG i=0; i < cRevertedRejitData; i++) + { + if (pMethodDescData->rejitDataRequested.rejitID == pRevertedRejitData[i].rejitID) + return FALSE; + } + + return TRUE; +} + + +void DumpAllRejitDataIfNecessary(DacpMethodDescData * pMethodDescData, DacpReJitData * pRevertedRejitData, UINT cRevertedRejitData) +{ + // If there's no rejit info to output, then skip + if ((pMethodDescData->rejitDataCurrent.rejitID == 0) && + (pMethodDescData->rejitDataRequested.rejitID == 0) && + (cRevertedRejitData == 0)) + { + return; + } + ExtOut("ReJITed versions:\n"); + + // Dump CURRENT rejit info + DumpRejitData(&pMethodDescData->rejitDataCurrent); + + // Dump reverted rejit infos + for (ULONG i=0; i < cRevertedRejitData; i++) + { + DumpRejitData(&pRevertedRejitData[i]); + } + + // For !ip2md, ensure we dump the rejit version corresponding to the specified IP + // (if not already dumped) + if (ShouldDumpRejitDataRequested(pMethodDescData, pRevertedRejitData, cRevertedRejitData)) + DumpRejitData(&pMethodDescData->rejitDataRequested); + + // If we maxed out the reverted versions we dumped, let user know there may be more + if (cRevertedRejitData == kcMaxRevertedRejitData) + ExtOut(" (... possibly more reverted versions ...)\n"); +} + +void DumpMDInfoFromMethodDescData(DacpMethodDescData * pMethodDescData, DacpReJitData * pRevertedRejitData, UINT cRevertedRejitData, BOOL fStackTraceFormat) +{ + static WCHAR wszNameBuffer[1024]; // should be large enough + BOOL bFailed = FALSE; + if (g_sos->GetMethodDescName(pMethodDescData->MethodDescPtr, 1024, wszNameBuffer, NULL) != S_OK) + { + wcscpy_s(wszNameBuffer, _countof(wszNameBuffer), W("UNKNOWN")); + bFailed = TRUE; + } + + if (!fStackTraceFormat) + { + ExtOut("Method Name: %S\n", wszNameBuffer); + + DacpMethodTableData mtdata; + if (SUCCEEDED(mtdata.Request(g_sos, pMethodDescData->MethodTablePtr))) + { + DMLOut("Class: %s\n", DMLClass(mtdata.Class)); + } + + DMLOut("MethodTable: %s\n", DMLMethodTable(pMethodDescData->MethodTablePtr)); + ExtOut("mdToken: %p\n", SOS_PTR(pMethodDescData->MDToken)); + DMLOut("Module: %s\n", DMLModule(pMethodDescData->ModulePtr)); + ExtOut("IsJitted: %s\n", pMethodDescData->bHasNativeCode ? "yes" : "no"); + DMLOut("CodeAddr: %s\n", DMLIP(pMethodDescData->NativeCodeAddr)); + + DacpMethodDescTransparencyData transparency; + if (SUCCEEDED(transparency.Request(g_sos, pMethodDescData->MethodDescPtr))) + { + ExtOut("Transparency: %s\n", GetTransparency(transparency)); + } + + DumpAllRejitDataIfNecessary(pMethodDescData, pRevertedRejitData, cRevertedRejitData); + } + else + { + if (!bFailed) + { + ExtOut("%S", wszNameBuffer); + } + else + { + // Only clutter the display with module/token for cases where we + // can't get the MethodDesc name for some reason. + DMLOut("Unknown MethodDesc (Module %s, mdToken %08x)", + DMLModule(pMethodDescData->ModulePtr), + pMethodDescData->MDToken); + } + } +} + +void DumpMDInfo(DWORD_PTR dwMethodDescAddr, CLRDATA_ADDRESS dwRequestedIP /* = 0 */, BOOL fStackTraceFormat /* = FALSE */) +{ + DacpMethodDescData MethodDescData; + DacpReJitData revertedRejitData[kcMaxRevertedRejitData]; + ULONG cNeededRevertedRejitData; + if (g_sos->GetMethodDescData( + TO_CDADDR(dwMethodDescAddr), + dwRequestedIP, + &MethodDescData, + _countof(revertedRejitData), + revertedRejitData, + &cNeededRevertedRejitData) != S_OK) + { + ExtOut("%p is not a MethodDesc\n", SOS_PTR(dwMethodDescAddr)); + return; + } + + DumpMDInfoFromMethodDescData(&MethodDescData, revertedRejitData, cNeededRevertedRejitData, fStackTraceFormat); +} + +void GetDomainList (DWORD_PTR *&domainList, int &numDomain) +{ + DacpAppDomainStoreData adsData; + + numDomain = 0; + + if (adsData.Request(g_sos)!=S_OK) + { + return; + } + + // Do prefast integer checks before the malloc. + size_t AllocSize; + LONG DomainAllocCount; + if (!ClrSafeInt<LONG>::addition(adsData.DomainCount, 2, DomainAllocCount) || + !ClrSafeInt<size_t>::multiply(DomainAllocCount, sizeof(PVOID), AllocSize) || + (domainList = new DWORD_PTR[DomainAllocCount]) == NULL) + { + return; + } + + domainList[numDomain++] = (DWORD_PTR) adsData.systemDomain; + domainList[numDomain++] = (DWORD_PTR) adsData.sharedDomain; + + CLRDATA_ADDRESS *pArray = new CLRDATA_ADDRESS[adsData.DomainCount]; + if (pArray==NULL) + { + return; + } + + if (g_sos->GetAppDomainList(adsData.DomainCount, pArray, NULL)!=S_OK) + { + delete [] pArray; + return; + } + + for (int n=0;n<adsData.DomainCount;n++) + { + if (IsInterrupt()) + break; + domainList[numDomain++] = (DWORD_PTR) pArray[n]; + } + + delete [] pArray; +} + + +HRESULT GetThreadList(DWORD_PTR **threadList, int *numThread) +{ + _ASSERTE(threadList != NULL); + _ASSERTE(numThread != NULL); + + if (threadList == NULL || numThread == NULL) + { + return E_FAIL; + } + + *numThread = 0; + + DacpThreadStoreData ThreadStore; + if ( ThreadStore.Request(g_sos) != S_OK) + { + ExtOut("Failed to request threads from the thread store."); + return E_FAIL; + } + + *threadList = new DWORD_PTR[ThreadStore.threadCount]; + if (*threadList == NULL) + { + ReportOOM(); + return E_OUTOFMEMORY; + } + + CLRDATA_ADDRESS CurThread = ThreadStore.firstThread; + while (CurThread != NULL) + { + if (IsInterrupt()) + return S_FALSE; + + DacpThreadData Thread; + if (Thread.Request(g_sos, CurThread) != S_OK) + { + ExtOut("Failed to request Thread at %p\n", SOS_PTR(CurThread)); + return E_FAIL; + } + + (*threadList)[(*numThread)++] = (DWORD_PTR)CurThread; + CurThread = Thread.nextThread; + } + + return S_OK; +} + +CLRDATA_ADDRESS GetCurrentManagedThread () +{ + DacpThreadStoreData ThreadStore; + ThreadStore.Request(g_sos); + + ULONG Tid; + g_ExtSystem->GetCurrentThreadSystemId(&Tid); + + CLRDATA_ADDRESS CurThread = ThreadStore.firstThread; + while (CurThread) + { + DacpThreadData Thread; + if (Thread.Request(g_sos, CurThread) != S_OK) + { + return NULL; + } + + if (Thread.osThreadId == Tid) + { + return CurThread; + } + + CurThread = Thread.nextThread; + } + return NULL; +} + + +void ReloadSymbolWithLineInfo() +{ +#ifndef FEATURE_PAL + static BOOL bLoadSymbol = FALSE; + if (!bLoadSymbol) + { + ULONG Options; + g_ExtSymbols->GetSymbolOptions(&Options); + if (!(Options & SYMOPT_LOAD_LINES)) + { + g_ExtSymbols->AddSymbolOptions(SYMOPT_LOAD_LINES); + + if (SUCCEEDED(g_ExtSymbols->GetModuleByModuleName(MSCOREE_SHIM_A, 0, NULL, NULL))) + g_ExtSymbols->Reload("/f " MSCOREE_SHIM_A); + + EEFLAVOR flavor = GetEEFlavor(); + if (flavor == MSCORWKS) + g_ExtSymbols->Reload("/f " MAIN_CLR_DLL_NAME_A); + } + + // reload mscoree.pdb and clrjit.pdb to get line info + bLoadSymbol = TRUE; + } +#endif +} + +// Return 1 if the function is our stub +// Return MethodDesc if the function is managed +// Otherwise return 0 +size_t FunctionType (size_t EIP) +{ + ULONG64 base = 0; + ULONG ulLoaded, ulUnloaded, ulIndex; + + // Get the number of loaded and unloaded modules + if (FAILED(g_ExtSymbols->GetNumberModules(&ulLoaded, &ulUnloaded))) + return 0; + + + if (SUCCEEDED(g_ExtSymbols->GetModuleByOffset(TO_CDADDR(EIP), 0, &ulIndex, &base)) && base != 0) + { + if (ulIndex < ulLoaded) + { + IMAGE_DOS_HEADER DosHeader; + if (g_ExtData->ReadVirtual(TO_CDADDR(base), &DosHeader, sizeof(DosHeader), NULL) != S_OK) + return 0; + IMAGE_NT_HEADERS Header; + if (g_ExtData->ReadVirtual(TO_CDADDR(base + DosHeader.e_lfanew), &Header, sizeof(Header), NULL) != S_OK) + return 0; + // If there is no COMHeader, this can not be managed code. + if (Header.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_COMHEADER].VirtualAddress == 0) + return 0; + + IMAGE_COR20_HEADER ComPlusHeader; + if (g_ExtData->ReadVirtual(TO_CDADDR(base + Header.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_COMHEADER].VirtualAddress), + &ComPlusHeader, sizeof(ComPlusHeader), NULL) != S_OK) + return 0; + + // If there is no Precompiled image info, it can not be prejit code + if (ComPlusHeader.ManagedNativeHeader.VirtualAddress == 0) { + return 0; + } + } + } + + CLRDATA_ADDRESS dwStartAddr = TO_CDADDR(EIP); + CLRDATA_ADDRESS pMD; + if (g_sos->GetMethodDescPtrFromIP(dwStartAddr, &pMD) != S_OK) + { + return 1; + } + + return (size_t) pMD; +} + +#ifndef FEATURE_PAL + +// +// Gets version info for the CLR in the debuggee process. +// +BOOL GetEEVersion(VS_FIXEDFILEINFO *pFileInfo) +{ + _ASSERTE(g_ExtSymbols2); + _ASSERTE(pFileInfo); + // Grab the version info directly from the module. + return g_ExtSymbols2->GetModuleVersionInformation(DEBUG_ANY_ID, + moduleInfo[GetEEFlavor()].baseAddr, + "\\", pFileInfo, sizeof(VS_FIXEDFILEINFO), NULL) == S_OK; +} + +extern HMODULE g_hInstance; +BOOL GetSOSVersion(VS_FIXEDFILEINFO *pFileInfo) +{ + _ASSERTE(pFileInfo); + + WCHAR wszFullPath[MAX_LONGPATH]; + DWORD cchFullPath = GetModuleFileNameW(g_hInstance, wszFullPath, _countof(wszFullPath)); + + DWORD dwHandle = 0; + DWORD infoSize = GetFileVersionInfoSizeW(wszFullPath, &dwHandle); + if (infoSize) + { + ArrayHolder<BYTE> pVersionInfo = new BYTE[infoSize]; + if (pVersionInfo) + { + if (GetFileVersionInfoW(wszFullPath, NULL, infoSize, pVersionInfo)) + { + VS_FIXEDFILEINFO *pTmpFileInfo = NULL; + UINT uLen = 0; + if (VerQueryValue(pVersionInfo, "\\", (LPVOID *) &pTmpFileInfo, &uLen)) + { + *pFileInfo = *pTmpFileInfo; // Copy the info + return TRUE; + } + } + } + } + + return FALSE; +} + +#endif // !FEATURE_PAL + +size_t ObjectSize(DWORD_PTR obj,BOOL fIsLargeObject) +{ + DWORD_PTR dwMT; + MOVE(dwMT, obj); + return ObjectSize(obj, dwMT, FALSE, fIsLargeObject); +} + +size_t ObjectSize(DWORD_PTR obj, DWORD_PTR mt, BOOL fIsValueClass, BOOL fIsLargeObject) +{ + BOOL bContainsPointers; + size_t size = 0; + if (!GetSizeEfficient(obj, mt, fIsLargeObject, size, bContainsPointers)) + { + return 0; + } + return size; +} + +// This takes an array of values and sets every non-printable character +// to be a period. +void Flatten(__out_ecount(len) char *data, unsigned int len) +{ + for (unsigned int i = 0; i < len; ++i) + if (data[i] < 32 || data[i] > 126) + data[i] = '.'; + data[len] = 0; +} + +void CharArrayContent(TADDR pos, ULONG num, bool widechar) +{ + if (!pos || num <= 0) + return; + + if (widechar) + { + ArrayHolder<WCHAR> data = new WCHAR[num+1]; + if (!data) + { + ReportOOM(); + return; + } + + ULONG readLen = 0; + if (!SafeReadMemory(pos, data, num<<1, &readLen)) + return; + + Flatten(data.GetPtr(), readLen >> 1); + ExtOut("%S", data.GetPtr()); + } + else + { + ArrayHolder<char> data = new char[num+1]; + if (!data) + { + ReportOOM(); + return; + } + + ULONG readLen = 0; + if (!SafeReadMemory(pos, data, num, &readLen)) + return; + + _ASSERTE(readLen <= num); + Flatten(data, readLen); + + ExtOut("%s", data.GetPtr()); + } +} + +void StringObjectContent(size_t obj, BOOL fLiteral, const int length) +{ + DacpObjectData objData; + if (objData.Request(g_sos, TO_CDADDR(obj))!=S_OK) + { + ExtOut("<Invalid Object>"); + return; + } + + strobjInfo stInfo; + + if (MOVE(stInfo,obj) != S_OK) + { + ExtOut ("Error getting string data\n"); + return; + } + + if (objData.Size > 0x200000 || + stInfo.m_StringLength > 0x200000) + { + ExtOut ("<String is invalid or too large to print>\n"); + return; + } + + ArrayHolder<WCHAR> pwszBuf = new WCHAR[stInfo.m_StringLength+1]; + if (pwszBuf == NULL) + { + return; + } + + DWORD_PTR dwAddr = (DWORD_PTR)pwszBuf.GetPtr(); + if (g_sos->GetObjectStringData(TO_CDADDR(obj), stInfo.m_StringLength+1, pwszBuf, NULL)!=S_OK) + { + ExtOut("Error getting string data\n"); + return; + } + + if (!fLiteral) + { + pwszBuf[stInfo.m_StringLength] = L'\0'; + ExtOut ("%S", pwszBuf.GetPtr()); + } + else + { + ULONG32 count = stInfo.m_StringLength; + WCHAR buffer[256]; + WCHAR out[512]; + while (count) + { + DWORD toRead = 255; + if (count < toRead) + toRead = count; + + ULONG bytesRead; + wcsncpy_s(buffer,_countof(buffer),(LPWSTR) dwAddr, toRead); + bytesRead = toRead*sizeof(WCHAR); + DWORD wcharsRead = bytesRead/2; + buffer[wcharsRead] = L'\0'; + + ULONG j,k=0; + for (j = 0; j < wcharsRead; j ++) + { + if (_iswprint (buffer[j])) { + out[k] = buffer[j]; + k ++; + } + else + { + out[k++] = L'\\'; + switch (buffer[j]) { + case L'\n': + out[k++] = L'n'; + break; + case L'\0': + out[k++] = L'0'; + break; + case L'\t': + out[k++] = L't'; + break; + case L'\v': + out[k++] = L'v'; + break; + case L'\b': + out[k++] = L'b'; + break; + case L'\r': + out[k++] = L'r'; + break; + case L'\f': + out[k++] = L'f'; + break; + case L'\a': + out[k++] = L'a'; + break; + case L'\\': + break; + case L'\?': + out[k++] = L'?'; + break; + default: + out[k++] = L'?'; + break; + } + } + } + + out[k] = L'\0'; + ExtOut ("%S", out); + + count -= wcharsRead; + dwAddr += bytesRead; + } + } +} + +#ifdef _TARGET_WIN64_ + +#include <limits.h> + +__int64 str64hex(const char *ptr) +{ + __int64 value = 0; + unsigned char nCount = 0; + + if(ptr==NULL) + return 0; + + // Ignore leading 0x if present + if (*ptr=='0' && toupper(*(ptr+1))=='X') { + ptr = ptr + 2; + } + + while (1) { + + char digit; + + if (isdigit(*ptr)) { + digit = *ptr - '0'; + } else if (isalpha(*ptr)) { + digit = (((char)toupper(*ptr)) - 'A') + 10; + if (digit >= 16) { + break; // terminate + } + } else { + break; + } + + if (nCount>15) { + return _UI64_MAX; // would be an overflow + } + + value = value << 4; + value |= digit; + + ptr++; + nCount++; + } + + return value; +} + +#endif // _TARGET_WIN64_ + +BOOL GetValueForCMD (const char *ptr, const char *end, ARGTYPE type, size_t *value) +{ + if (type == COSTRING) { + // Allocate memory for the length of the string. Whitespace terminates + // User must free the string data. + char *pszValue = NULL; + size_t dwSize = (end - ptr); + pszValue= new char[dwSize+1]; + if (pszValue == NULL) + { + return FALSE; + } + strncpy_s(pszValue,dwSize+1,ptr,dwSize); // _TRUNCATE + *value = (size_t) pszValue; + } else { + char *last; + if (type == COHEX) { +#ifdef _TARGET_WIN64_ + *value = str64hex(ptr); +#else + *value = strtoul(ptr,&last,16); +#endif + } + else { +#ifdef _TARGET_WIN64_ + *value = _atoi64(ptr); +#else + *value = strtoul(ptr,&last,10); +#endif + } + +#ifdef _TARGET_WIN64_ + last = (char *) ptr; + // Ignore leading 0x if present + if (*last=='0' && toupper(*(last+1))=='X') { + last = last + 2; + } + + while (isdigit(*last) || (toupper(*last)>='A' && toupper(*last)<='F')) { + last++; + } +#endif + + if (last != end) { + return FALSE; + } + } + + return TRUE; +} + +void SetValueForCMD (void *vptr, ARGTYPE type, size_t value) +{ + switch (type) { + case COBOOL: + *(BOOL*)vptr = (BOOL) value; + break; + case COSIZE_T: + case COSTRING: + case COHEX: + *(SIZE_T*)vptr = value; + break; + } +} + +BOOL GetCMDOption(const char *string, CMDOption *option, size_t nOption, + CMDValue *arg, size_t maxArg, size_t *nArg) +{ + const char *end; + const char *ptr = string; + BOOL endofOption = FALSE; + + for (size_t n = 0; n < nOption; n ++) + { + if (IsInterrupt()) + return FALSE; + + option[n].hasSeen = FALSE; + } + + if (nArg) { + *nArg = 0; + } + + while (ptr[0] != '\0') + { + if (IsInterrupt()) + return FALSE; + + // skip any space + if (isspace (ptr[0])) { + while (isspace (ptr[0])) + { + if (IsInterrupt()) + return FALSE; + + ptr ++; + } + + continue; + } + + end = ptr; + + // Arguments can be quoted with ". We'll remove the quotes and + // allow spaces to exist in the string. + BOOL bQuotedArg = FALSE; + if (ptr[0] == '\'' && ptr[1] != '-') + { + bQuotedArg = TRUE; + + // skip quote + ptr++; + end++; + + while (end[0] != '\'' && end[0] != '\0') + { + if (IsInterrupt()) + return FALSE; + + end ++; + } + if (end[0] != '\'') + { + // Error, th ere was a start quote but no end quote + ExtOut ("Missing quote in %s\n", ptr); + return FALSE; + } + } + else // whitespace terminates + { + while (!isspace(end[0]) && end[0] != '\0') + { + if (IsInterrupt()) + return FALSE; + + end ++; + } + } + +#ifndef FEATURE_PAL + if (ptr[0] != '-' && ptr[0] != '/') { +#else + if (ptr[0] != '-') { +#endif + if (maxArg == 0) { + ExtOut ("Incorrect argument: %s\n", ptr); + return FALSE; + } + endofOption = TRUE; + if (*nArg >= maxArg) { + ExtOut ("Incorrect argument: %s\n", ptr); + return FALSE; + } + + size_t value; + if (!GetValueForCMD (ptr,end,arg[*nArg].type,&value)) { + + char oldChar = *end; + *(char *)end = '\0'; + value = (size_t)GetExpression (ptr); + *(char *)end = oldChar; + + /* + + It is silly to do this, what if 0 is a valid expression for + the command? + + if (value == 0) { + ExtOut ("Invalid argument: %s\n", ptr); + return FALSE; + } + */ + } + + SetValueForCMD (arg[*nArg].vptr, arg[*nArg].type, value); + + (*nArg) ++; + } + else if (endofOption) { + ExtOut ("Wrong option: %s\n", ptr); + return FALSE; + } + else { + char buffer[80]; + if (end-ptr > 79) { + ExtOut ("Invalid option %s\n", ptr); + return FALSE; + } + strncpy_s (buffer,_countof(buffer), ptr, end-ptr); + + size_t n; + for (n = 0; n < nOption; n ++) + { + if (IsInterrupt()) + return FALSE; + + if (_stricmp (buffer, option[n].name) == 0) { + if (option[n].hasSeen) { + ExtOut ("Invalid option: option specified multiple times: %s\n", buffer); + return FALSE; + } + option[n].hasSeen = TRUE; + if (option[n].hasValue) { + // skip any space + ptr = end; + if (isspace (ptr[0])) { + while (isspace (ptr[0])) + { + if (IsInterrupt()) + return FALSE; + + ptr ++; + } + } + if (ptr[0] == '\0') { + ExtOut ("Missing value for option %s\n", buffer); + return FALSE; + } + end = ptr; + while (!isspace(end[0]) && end[0] != '\0') + { + if (IsInterrupt()) + return FALSE; + + end ++; + } + + size_t value; + if (!GetValueForCMD (ptr,end,option[n].type,&value)) { + + char oldChar = *end; + *(char *)end = '\0'; + value = (size_t)GetExpression (ptr); + *(char *)end = oldChar; + } + + SetValueForCMD (option[n].vptr,option[n].type,value); + } + else { + SetValueForCMD (option[n].vptr,option[n].type,TRUE); + } + break; + } + } + if (n == nOption) { + ExtOut ("Unknown option: %s\n", buffer); + return FALSE; + } + } + + ptr = end; + if (bQuotedArg) + { + ptr++; + } + } + return TRUE; +} + +ReadVirtualCache g_special_rvCacheSpace; +ReadVirtualCache *rvCache = &g_special_rvCacheSpace; + +void ResetGlobals(void) +{ + // There are some globals used in SOS that exist for efficiency in one command, + // but should be reset because the next execution of an SOS command could be on + // another managed process. Reset them to a default state here, as this command + // is called on every SOS entry point. + g_sos->GetUsefulGlobals(&g_special_usefulGlobals); + g_special_mtCache.Clear(); + g_special_rvCacheSpace.Clear(); + Output::ResetIndent(); +} + +//--------------------------------------------------------------------------------------- +// +// Loads private DAC interface, and points g_clrData to it. +// +// Return Value: +// HRESULT indicating success or failure +// +HRESULT LoadClrDebugDll(void) +{ + HRESULT hr = S_OK; +#ifdef FEATURE_PAL + static IXCLRDataProcess* s_clrDataProcess = NULL; + if (s_clrDataProcess == NULL) + { + int err = PAL_InitializeDLL(); + if(err != 0) + { + return CORDBG_E_UNSUPPORTED; + } + char dacModulePath[MAX_LONGPATH]; + strcpy_s(dacModulePath, _countof(dacModulePath), g_ExtServices->GetCoreClrDirectory()); + strcat_s(dacModulePath, _countof(dacModulePath), MAKEDLLNAME_A("mscordaccore")); + + HMODULE hdac = LoadLibraryA(dacModulePath); + if (hdac == NULL) + { + return CORDBG_E_MISSING_DEBUGGER_EXPORTS; + } + PFN_CLRDataCreateInstance pfnCLRDataCreateInstance = (PFN_CLRDataCreateInstance)GetProcAddress(hdac, "CLRDataCreateInstance"); + if (pfnCLRDataCreateInstance == NULL) + { + FreeLibrary(hdac); + return CORDBG_E_MISSING_DEBUGGER_EXPORTS; + } + ICLRDataTarget *target = new DataTarget(); + hr = pfnCLRDataCreateInstance(__uuidof(IXCLRDataProcess), target, (void**)&s_clrDataProcess); + if (FAILED(hr)) + { + s_clrDataProcess = NULL; + return hr; + } + ULONG32 flags = 0; + s_clrDataProcess->GetOtherNotificationFlags(&flags); + flags |= (CLRDATA_NOTIFY_ON_MODULE_LOAD | CLRDATA_NOTIFY_ON_MODULE_UNLOAD | CLRDATA_NOTIFY_ON_EXCEPTION); + s_clrDataProcess->SetOtherNotificationFlags(flags); + } + g_clrData = s_clrDataProcess; + g_clrData->AddRef(); + g_clrData->Flush(); +#else + WDBGEXTS_CLR_DATA_INTERFACE Query; + + Query.Iid = &__uuidof(IXCLRDataProcess); + if (!Ioctl(IG_GET_CLR_DATA_INTERFACE, &Query, sizeof(Query))) + { + return E_FAIL; + } + + g_clrData = (IXCLRDataProcess*)Query.Iface; +#endif + hr = g_clrData->QueryInterface(__uuidof(ISOSDacInterface), (void**)&g_sos); + if (FAILED(hr)) + { + g_sos = NULL; + return hr; + } + return S_OK; +} + +#ifndef FEATURE_PAL + +// This structure carries some input/output data to the FindFileInPathCallback below +typedef struct _FindFileCallbackData +{ + DWORD timestamp; + DWORD filesize; + HMODULE hModule; +} FindFileCallbackData; + + +// A callback used by SymFindFileInPath - called once for each file that matches +// the initial search criteria and allows the user to do arbitrary processing +// This implementation checks that filesize and timestamp are correct, then +// saves the loaded module handle +// Parameters +// filename - the full path the file which was found +// context - a user specified pointer to arbitrary data, in this case a FindFileCallbackData +// Return Value +// TRUE if the search should continue (the file is no good) +// FALSE if the search should stop (the file is good) +BOOL +FindFileInPathCallback( + ___in PCWSTR filename, + ___in PVOID context + ) +{ + HRESULT hr; + FindFileCallbackData* pCallbackData; + pCallbackData = (FindFileCallbackData*)context; + if (!pCallbackData) + return TRUE; + + pCallbackData->hModule = LoadLibraryExW( + filename, + NULL, // __reserved + LOAD_WITH_ALTERED_SEARCH_PATH); // Ensure we check the dir in wszFullPath first + if (pCallbackData->hModule == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + ExtOut("Unable to load '%S'. HRESULT = 0x%x.\n", filename, hr); + return TRUE; + } + + // Did we load the right one? + MODULEINFO modInfo = {0}; + if (!GetModuleInformation( + GetCurrentProcess(), + pCallbackData->hModule, + &modInfo, + sizeof(modInfo))) + { + ExtOut("Failed to read module information for '%S'. HRESULT = 0x%x.\n", filename, HRESULT_FROM_WIN32(GetLastError())); + FreeLibrary(pCallbackData->hModule); + return TRUE; + } + + IMAGE_DOS_HEADER * pDOSHeader = (IMAGE_DOS_HEADER *) modInfo.lpBaseOfDll; + IMAGE_NT_HEADERS * pNTHeaders = (IMAGE_NT_HEADERS *) (((LPBYTE) modInfo.lpBaseOfDll) + pDOSHeader->e_lfanew); + DWORD dwSizeActual = pNTHeaders->OptionalHeader.SizeOfImage; + DWORD dwTimeStampActual = pNTHeaders->FileHeader.TimeDateStamp; + if ((dwSizeActual != pCallbackData->filesize) || (dwTimeStampActual != pCallbackData->timestamp)) + { + ExtOut("Found '%S', but it does not match the CLR being debugged.\n", filename); + ExtOut("Size: Expected '0x%x', Actual '0x%x'\n", pCallbackData->filesize, dwSizeActual); + ExtOut("Time stamp: Expected '0x%x', Actual '0x%x'\n", pCallbackData->timestamp, dwTimeStampActual); + FreeLibrary(pCallbackData->hModule); + return TRUE; + } + + ExtOut("Loaded %S\n", filename); + return FALSE; +} + +#endif // FEATURE_PAL + +//--------------------------------------------------------------------------------------- +// Provides a way for the public CLR debugging interface to find the appropriate +// mscordbi.dll, DAC, etc. +class SOSLibraryProvider : public ICLRDebuggingLibraryProvider +{ +public: + SOSLibraryProvider() : m_ref(0) + { + } + + virtual ~SOSLibraryProvider() {} + + virtual HRESULT STDMETHODCALLTYPE QueryInterface( + REFIID InterfaceId, + PVOID* pInterface) + { + if (InterfaceId == IID_IUnknown) + { + *pInterface = static_cast<IUnknown *>(this); + } + else if (InterfaceId == IID_ICLRDebuggingLibraryProvider) + { + *pInterface = static_cast<ICLRDebuggingLibraryProvider *>(this); + } + else + { + *pInterface = NULL; + return E_NOINTERFACE; + } + + AddRef(); + return S_OK; + } + + virtual ULONG STDMETHODCALLTYPE AddRef() + { + return InterlockedIncrement(&m_ref); + } + + virtual ULONG STDMETHODCALLTYPE Release() + { + LONG ref = InterlockedDecrement(&m_ref); + if (ref == 0) + { + delete this; + } + return ref; + } + + + + // Called by the shim to locate and load mscordacwks and mscordbi + // Parameters: + // pwszFileName - the name of the file to load + // dwTimestamp - the expected timestamp of the file + // dwSizeOfImage - the expected SizeOfImage (a PE header data value) + // phModule - a handle to loaded module + // + // Return Value + // S_OK if the file was loaded, or any error if not + virtual HRESULT STDMETHODCALLTYPE ProvideLibrary( + const WCHAR * pwszFileName, + DWORD dwTimestamp, + DWORD dwSizeOfImage, + HMODULE * phModule) + { +#ifndef FEATURE_PAL + HRESULT hr; + FindFileCallbackData callbackData = {0}; + callbackData.timestamp = dwTimestamp; + callbackData.filesize = dwSizeOfImage; + + if ((phModule == NULL) || (pwszFileName == NULL)) + { + return E_INVALIDARG; + } + + HMODULE dacModule; + if(g_sos == NULL) + { + // we ensure that windbg loads DAC first so that we can be sure to use the same one + return E_UNEXPECTED; + } + if (FAILED(hr = g_sos->GetDacModuleHandle(&dacModule))) + { + ExtOut("Failed to get the dac module handle. hr=0x%x.\n", hr); + return hr; + } + + WCHAR dacPath[MAX_LONGPATH]; + DWORD len = GetModuleFileNameW(dacModule, dacPath, MAX_LONGPATH); + if(len == 0 || len == MAX_LONGPATH) + { + ExtOut("GetModuleFileName(dacModuleHandle) failed. Last error = 0x%x\n", GetLastError()); + return E_FAIL; + } + + // if we are looking for the DAC, just load the one windbg already found + if(_wcsncmp(pwszFileName, W("mscordac"), _wcslen(W("mscordac")))==0) + { + FindFileInPathCallback(dacPath, &callbackData); + *phModule = callbackData.hModule; + return hr; + } + + ULONG64 hProcess; + hr = g_ExtSystem->GetCurrentProcessHandle(&hProcess); + if (FAILED(hr)) + { + ExtOut("IDebugSystemObjects::GetCurrentProcessHandle HRESULT=0x%x.\n", hr); + return hr; + } + + ToRelease<IDebugSymbols3> spSym3(NULL); + hr = g_ExtSymbols->QueryInterface(__uuidof(IDebugSymbols3), (void**)&spSym3); + if (FAILED(hr)) + { + ExtOut("Unable to query IDebugSymbol3 HRESULT=0x%x.\n", hr); + return hr; + } + + ULONG pathSize = 0; + hr = spSym3->GetSymbolPathWide(NULL, 0, &pathSize); + if(FAILED(hr)) //S_FALSE if the path doesn't fit, but if the path was size 0 perhaps we would get S_OK? + { + ExtOut("Unable to get symbol path length. IDebugSymbols3::GetSymbolPathWide HRESULT=0x%x.\n", hr); + return hr; + } + + ArrayHolder<WCHAR> symbolPath = new WCHAR[pathSize+MAX_LONGPATH+1]; + + + + hr = spSym3->GetSymbolPathWide(symbolPath, pathSize, NULL); + if(S_OK != hr) + { + ExtOut("Unable to get symbol path. IDebugSymbols3::GetSymbolPathWide HRESULT=0x%x.\n", hr); + return hr; + } + + WCHAR foundPath[MAX_LONGPATH]; + BOOL rc = SymFindFileInPathW((HANDLE)hProcess, + symbolPath, + pwszFileName, + (PVOID)(ULONG_PTR) dwTimestamp, + dwSizeOfImage, + 0, + SSRVOPT_DWORD, + foundPath, + (PFINDFILEINPATHCALLBACKW) &FindFileInPathCallback, + (PVOID) &callbackData + ); + if(!rc) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + ExtOut("SymFindFileInPath failed for %S. HRESULT=0x%x.\nPlease ensure that %S is on your symbol path.", pwszFileName, hr, pwszFileName); + } + + *phModule = callbackData.hModule; + return hr; +#else + WCHAR modulePath[MAX_LONGPATH]; + int length = MultiByteToWideChar(CP_ACP, 0, g_ExtServices->GetCoreClrDirectory(), -1, modulePath, _countof(modulePath)); + if (0 >= length) + { + ExtOut("MultiByteToWideChar(coreclrDirectory) failed. Last error = 0x%x\n", GetLastError()); + return E_FAIL; + } + wcscat_s(modulePath, _countof(modulePath), pwszFileName); + + *phModule = LoadLibraryW(modulePath); + if (*phModule == NULL) + { + HRESULT hr = HRESULT_FROM_WIN32(GetLastError()); + ExtOut("Unable to load '%S'. HRESULT = 0x%x.\n", pwszFileName, hr); + return hr; + } + return S_OK; +#endif // FEATURE_PAL + } + +protected: + LONG m_ref; +}; + +//--------------------------------------------------------------------------------------- +// Data target for the debugged process. Provided to OpenVirtualProcess in order to +// get an ICorDebugProcess back +// +class SOSDataTarget : public ICorDebugMutableDataTarget +#ifdef FEATURE_PAL +, public ICorDebugDataTarget4 +#endif +{ +public: + SOSDataTarget() : m_ref(0) + { + } + + virtual ~SOSDataTarget() {} + + virtual HRESULT STDMETHODCALLTYPE QueryInterface( + REFIID InterfaceId, + PVOID* pInterface) + { + if (InterfaceId == IID_IUnknown) + { + *pInterface = static_cast<IUnknown *>(static_cast<ICorDebugDataTarget *>(this)); + } + else if (InterfaceId == IID_ICorDebugDataTarget) + { + *pInterface = static_cast<ICorDebugDataTarget *>(this); + } + else if (InterfaceId == IID_ICorDebugMutableDataTarget) + { + *pInterface = static_cast<ICorDebugMutableDataTarget *>(this); + } +#ifdef FEATURE_PAL + else if (InterfaceId == IID_ICorDebugDataTarget4) + { + *pInterface = static_cast<ICorDebugDataTarget4 *>(this); + } +#endif + else + { + *pInterface = NULL; + return E_NOINTERFACE; + } + + AddRef(); + return S_OK; + } + + virtual ULONG STDMETHODCALLTYPE AddRef() + { + return InterlockedIncrement(&m_ref); + } + + virtual ULONG STDMETHODCALLTYPE Release() + { + LONG ref = InterlockedDecrement(&m_ref); + if (ref == 0) + { + delete this; + } + return ref; + } + + // + // ICorDebugDataTarget. + // + + virtual HRESULT STDMETHODCALLTYPE GetPlatform(CorDebugPlatform * pPlatform) + { + ULONG platformKind = g_targetMachine->GetPlatform(); +#ifdef FEATURE_PAL + if(platformKind == IMAGE_FILE_MACHINE_I386) + *pPlatform = CORDB_PLATFORM_POSIX_X86; + else if(platformKind == IMAGE_FILE_MACHINE_AMD64) + *pPlatform = CORDB_PLATFORM_POSIX_AMD64; + else if(platformKind == IMAGE_FILE_MACHINE_ARMNT) + *pPlatform = CORDB_PLATFORM_POSIX_ARM; + else + return E_FAIL; +#else + if(platformKind == IMAGE_FILE_MACHINE_I386) + *pPlatform = CORDB_PLATFORM_WINDOWS_X86; + else if(platformKind == IMAGE_FILE_MACHINE_AMD64) + *pPlatform = CORDB_PLATFORM_WINDOWS_AMD64; + else if(platformKind == IMAGE_FILE_MACHINE_ARMNT) + *pPlatform = CORDB_PLATFORM_WINDOWS_ARM; + else if(platformKind == IMAGE_FILE_MACHINE_ARM64) + *pPlatform = CORDB_PLATFORM_WINDOWS_ARM64; + else + return E_FAIL; +#endif + + return S_OK; + } + + virtual HRESULT STDMETHODCALLTYPE ReadVirtual( + CORDB_ADDRESS address, + BYTE * pBuffer, + ULONG32 request, + ULONG32 * pcbRead) + { + if (g_ExtData == NULL) + { + return E_UNEXPECTED; + } + return g_ExtData->ReadVirtual(address, pBuffer, request, (PULONG) pcbRead); + } + + virtual HRESULT STDMETHODCALLTYPE GetThreadContext( + DWORD dwThreadOSID, + ULONG32 contextFlags, + ULONG32 contextSize, + BYTE * context) + { +#ifdef FEATURE_PAL + if (g_ExtSystem == NULL) + { + return E_UNEXPECTED; + } + return g_ExtSystem->GetThreadContextById(dwThreadOSID, contextFlags, contextSize, context); +#else + ULONG ulThreadIDOrig; + ULONG ulThreadIDRequested; + HRESULT hr; + HRESULT hrRet; + + hr = g_ExtSystem->GetCurrentThreadId(&ulThreadIDOrig); + if (FAILED(hr)) + { + return hr; + } + + hr = g_ExtSystem->GetThreadIdBySystemId(dwThreadOSID, &ulThreadIDRequested); + if (FAILED(hr)) + { + return hr; + } + + hr = g_ExtSystem->SetCurrentThreadId(ulThreadIDRequested); + if (FAILED(hr)) + { + return hr; + } + + // Prepare context structure + ZeroMemory(context, contextSize); + ((CONTEXT*) context)->ContextFlags = contextFlags; + + // Ok, do it! + hrRet = g_ExtAdvanced3->GetThreadContext((LPVOID) context, contextSize); + + // This is cleanup; failure here doesn't mean GetThreadContext should fail + // (that's determined by hrRet). + g_ExtSystem->SetCurrentThreadId(ulThreadIDOrig); + + return hrRet; +#endif // FEATURE_PAL + } + + // + // ICorDebugMutableDataTarget. + // + virtual HRESULT STDMETHODCALLTYPE WriteVirtual(CORDB_ADDRESS address, + const BYTE * pBuffer, + ULONG32 bytesRequested) + { + if (g_ExtData == NULL) + { + return E_UNEXPECTED; + } + return g_ExtData->WriteVirtual(address, (PVOID)pBuffer, bytesRequested, NULL); + } + + virtual HRESULT STDMETHODCALLTYPE SetThreadContext(DWORD dwThreadID, + ULONG32 contextSize, + const BYTE * pContext) + { + return E_NOTIMPL; + } + + virtual HRESULT STDMETHODCALLTYPE ContinueStatusChanged(DWORD dwThreadId, + CORDB_CONTINUE_STATUS continueStatus) + { + return E_NOTIMPL; + } + +#ifdef FEATURE_PAL + // + // ICorDebugDataTarget4 + // + virtual HRESULT STDMETHODCALLTYPE VirtualUnwind(DWORD threadId, ULONG32 contextSize, PBYTE context) + { + if (g_ExtServices == NULL) + { + return E_UNEXPECTED; + } + return g_ExtServices->VirtualUnwind(threadId, contextSize, context); + + } +#endif // FEATURE_PAL + +protected: + LONG m_ref; +}; + +HRESULT InitCorDebugInterfaceFromModule(ULONG64 ulBase, ICLRDebugging * pClrDebugging) +{ + HRESULT hr; + + ToRelease<ICorDebugMutableDataTarget> pSOSDataTarget = new SOSDataTarget; + pSOSDataTarget->AddRef(); + + ToRelease<ICLRDebuggingLibraryProvider> pSOSLibraryProvider = new SOSLibraryProvider; + pSOSLibraryProvider->AddRef(); + + CLR_DEBUGGING_VERSION clrDebuggingVersionRequested = {0}; + clrDebuggingVersionRequested.wMajor = 4; + + CLR_DEBUGGING_VERSION clrDebuggingVersionActual = {0}; + + CLR_DEBUGGING_PROCESS_FLAGS clrDebuggingFlags = (CLR_DEBUGGING_PROCESS_FLAGS)0; + + ToRelease<IUnknown> pUnkProcess; + + hr = pClrDebugging->OpenVirtualProcess( + ulBase, + pSOSDataTarget, + pSOSLibraryProvider, + &clrDebuggingVersionRequested, + IID_ICorDebugProcess, + &pUnkProcess, + &clrDebuggingVersionActual, + &clrDebuggingFlags); + if (FAILED(hr)) + { + return hr; + } + + ICorDebugProcess * pCorDebugProcess = NULL; + hr = pUnkProcess->QueryInterface(IID_ICorDebugProcess, (PVOID*) &pCorDebugProcess); + if (FAILED(hr)) + { + return hr; + } + + // Transfer memory ownership of refcount to global + g_pCorDebugProcess = pCorDebugProcess; + return S_OK; +} + +//--------------------------------------------------------------------------------------- +// +// Unloads public ICorDebug interfaces, and clears g_pCorDebugProcess +// This is only needed once after CLR unloads, not after every InitCorDebugInterface call +// +VOID UninitCorDebugInterface() +{ + if(g_pCorDebugProcess != NULL) + { + g_pCorDebugProcess->Detach(); + g_pCorDebugProcess->Release(); + g_pCorDebugProcess = NULL; + } +} + +//--------------------------------------------------------------------------------------- +// +// Loads public ICorDebug interfaces, and points g_pCorDebugProcess to them +// This should be called at least once per windbg stop state to ensure that +// the interface is available and that it doesn't hold stale data. Calling it +// more than once isn't an error, but does have perf overhead from needlessly +// flushing memory caches. +// +// Return Value: +// HRESULT indicating success or failure +// + +HRESULT InitCorDebugInterface() +{ + HMODULE hModule = NULL; + HRESULT hr; + ToRelease<ICLRDebugging> pClrDebugging; + + // we may already have an ICorDebug instance we can use + if(g_pCorDebugProcess != NULL) + { + // ICorDebugProcess4 is currently considered a private experimental interface on ICorDebug, it might go away so + // we need to be sure to handle its absense gracefully + ToRelease<ICorDebugProcess4> pProcess4 = NULL; + if(SUCCEEDED(g_pCorDebugProcess->QueryInterface(__uuidof(ICorDebugProcess4), (void**)&pProcess4))) + { + // FLUSH_ALL is more expensive than PROCESS_RUNNING, but this allows us to be safe even if things + // like IDNA are in use where we might be looking at non-sequential snapshots of process state + if(SUCCEEDED(pProcess4->ProcessStateChanged(FLUSH_ALL))) + { + // we already have an ICorDebug instance loaded and flushed, nothing more to do + return S_OK; + } + } + + // this is a very heavy handed way of reseting + UninitCorDebugInterface(); + } + + // SOS now has a statically linked version of the loader code that is normally found in mscoree/mscoreei.dll + // Its not much code and takes a big step towards 0 install dependencies + // Need to pick the appropriate SKU of CLR to detect +#if defined(FEATURE_CORESYSTEM) + GUID skuId = CLR_ID_ONECORE_CLR; +#elif defined(FEATURE_CORECLR) + GUID skuId = CLR_ID_CORECLR; +#else + GUID skuId = CLR_ID_V4_DESKTOP; +#endif + CLRDebuggingImpl* pDebuggingImpl = new CLRDebuggingImpl(skuId); + hr = pDebuggingImpl->QueryInterface(IID_ICLRDebugging, (LPVOID *)&pClrDebugging); + if (FAILED(hr)) + { + delete pDebuggingImpl; + return hr; + } + +#ifndef FEATURE_PAL + ULONG cLoadedModules; + ULONG cUnloadedModules; + hr = g_ExtSymbols->GetNumberModules(&cLoadedModules, &cUnloadedModules); + if (FAILED(hr)) + { + return hr; + } + + ULONG64 ulBase; + for (ULONG i = 0; i < cLoadedModules; i++) + { + hr = g_ExtSymbols->GetModuleByIndex(i, &ulBase); + if (FAILED(hr)) + { + return hr; + } + + // Dunno if this is a CLR module or not (or even if it's the particular one the + // user cares about during inproc SxS scenarios). For now, just try to use it + // to grab an ICorDebugProcess. If it works, great. Else, continue the loop + // until we find the first one that works. + hr = InitCorDebugInterfaceFromModule(ulBase, pClrDebugging); + if (SUCCEEDED(hr)) + { + return hr; + } + + // On failure, just iterate to the next module and try again... + } + + // Still here? Didn't find the right module. + // TODO: Anything useful to return or log here? + return E_FAIL; +#else + ULONG64 ulBase; + hr = g_ExtSymbols->GetModuleByModuleName(MAIN_CLR_DLL_NAME_A, 0, NULL, &ulBase); + if (SUCCEEDED(hr)) + { + hr = InitCorDebugInterfaceFromModule(ulBase, pClrDebugging); + } + return hr; +#endif // FEATURE_PAL +} + + +typedef enum +{ + GC_HEAP_INVALID = 0, + GC_HEAP_WKS = 1, + GC_HEAP_SVR = 2 +} GC_HEAP_TYPE; + +/**********************************************************************\ +* Routine Description: * +* * +* This function is called to find out if runtime is server build * +* * +\**********************************************************************/ + +DacpGcHeapData *g_pHeapData = NULL; +DacpGcHeapData g_HeapData; + +BOOL InitializeHeapData() +{ + if (g_pHeapData == NULL) + { + if (g_HeapData.Request(g_sos) != S_OK) + { + return FALSE; + } + g_pHeapData = &g_HeapData; + } + return TRUE; +} + +BOOL IsServerBuild() +{ + return InitializeHeapData() ? g_pHeapData->bServerMode : FALSE; +} + +UINT GetMaxGeneration() +{ + return InitializeHeapData() ? g_pHeapData->g_max_generation : 0; +} + +UINT GetGcHeapCount() +{ + return InitializeHeapData() ? g_pHeapData->HeapCount : 0; +} + +BOOL GetGcStructuresValid() +{ + // We don't want to use the cached HeapData, because this can change + // each time the program runs for a while. + DacpGcHeapData heapData; + if (heapData.Request(g_sos) != S_OK) + { + return FALSE; + } + + return heapData.bGcStructuresValid; +} + +void GetAllocContextPtrs(AllocInfo *pallocInfo) +{ + // gets the allocation contexts for all threads. This provides information about how much of + // the current allocation quantum has been allocated and the heap to which the quantum belongs. + // The allocation quantum is a fixed size chunk of zeroed memory from which allocations will come + // until it's filled. Each managed thread has its own allocation context. + + pallocInfo->num = 0; + pallocInfo->array = NULL; + + // get the thread store (See code:ClrDataAccess::RequestThreadStoreData for details) + DacpThreadStoreData ThreadStore; + if ( ThreadStore.Request(g_sos) != S_OK) + { + return; + } + + int numThread = ThreadStore.threadCount; + if (numThread) + { + pallocInfo->array = new needed_alloc_context[numThread]; + if (pallocInfo->array == NULL) + { + return; + } + } + + // get details for each thread in the thread store + CLRDATA_ADDRESS CurThread = ThreadStore.firstThread; + while (CurThread != NULL) + { + if (IsInterrupt()) + return; + + DacpThreadData Thread; + // Get information about the thread (we're getting the values of several of the + // fields of the Thread instance from the target) See code:ClrDataAccess::RequestThreadData for + // details + if (Thread.Request(g_sos, CurThread) != S_OK) + { + return; + } + + if (Thread.allocContextPtr != 0) + { + // get a list of all the allocation contexts + int j; + for (j = 0; j < pallocInfo->num; j ++) + { + if (pallocInfo->array[j].alloc_ptr == (BYTE *) Thread.allocContextPtr) + break; + } + if (j == pallocInfo->num) + { + pallocInfo->num ++; + pallocInfo->array[j].alloc_ptr = (BYTE *) Thread.allocContextPtr; + pallocInfo->array[j].alloc_limit = (BYTE *) Thread.allocContextLimit; + } + } + + CurThread = Thread.nextThread; + } +} + +HRESULT ReadVirtualCache::Read(TADDR taOffset, PVOID Buffer, ULONG BufferSize, PULONG lpcbBytesRead) +{ + // sign extend the passed in Offset so we can use it in when calling + // IDebugDataSpaces::ReadVirtual() + + CLRDATA_ADDRESS Offset = TO_CDADDR(taOffset); + // Offset can be any random ULONG64, as it can come from VerifyObjectMember(), and this + // can pass random pointer values in case of GC heap corruption + HRESULT ret; + ULONG cbBytesRead = 0; + + if (BufferSize == 0) + return S_OK; + + if (BufferSize > CACHE_SIZE) + { + // Don't even try with the cache + return g_ExtData->ReadVirtual(Offset, Buffer, BufferSize, lpcbBytesRead); + } + + if ((m_cacheValid) + && (taOffset >= m_startCache) + && (taOffset <= m_startCache + m_cacheSize - BufferSize)) + + { + // It is within the cache + memcpy(Buffer,(LPVOID) ((ULONG64)m_cache + (taOffset - m_startCache)), BufferSize); + + if (lpcbBytesRead != NULL) + { + *lpcbBytesRead = BufferSize; + } + + return S_OK; + } + + m_cacheValid = FALSE; + m_startCache = taOffset; + + // avoid an int overflow + if (m_startCache + CACHE_SIZE < m_startCache) + m_startCache = (TADDR)(-CACHE_SIZE); + + ret = g_ExtData->ReadVirtual(TO_CDADDR(m_startCache), m_cache, CACHE_SIZE, &cbBytesRead); + if (ret != S_OK) + { + return ret; + } + + m_cacheSize = cbBytesRead; + m_cacheValid = TRUE; + memcpy(Buffer, (LPVOID) ((ULONG64)m_cache + (taOffset - m_startCache)), BufferSize); + + if (lpcbBytesRead != NULL) + { + *lpcbBytesRead = cbBytesRead; + } + + return S_OK; +} + +HRESULT GetMTOfObject(TADDR obj, TADDR *mt) +{ + if (!mt) + return E_POINTER; + + // Read the MethodTable and if we succeed, get rid of the mark bits. + HRESULT hr = rvCache->Read(obj, mt, sizeof(TADDR), NULL); + if (SUCCEEDED(hr)) + *mt &= ~3; + + return hr; +} + +#ifndef FEATURE_PAL + +StressLogMem::~StressLogMem () +{ + MemRange * range = list; + + while (range) + { + MemRange * temp = range->next; + delete range; + range = temp; + } +} + +bool StressLogMem::Init (ULONG64 stressLogAddr, IDebugDataSpaces* memCallBack) +{ + size_t ThreadStressLogAddr = NULL; + HRESULT hr = memCallBack->ReadVirtual(UL64_TO_CDA(stressLogAddr + offsetof (StressLog, logs)), + &ThreadStressLogAddr, sizeof (ThreadStressLogAddr), 0); + if (hr != S_OK) + { + return false; + } + + while(ThreadStressLogAddr != NULL) + { + size_t ChunkListHeadAddr = NULL; + hr = memCallBack->ReadVirtual(TO_CDADDR(ThreadStressLogAddr + ThreadStressLog::OffsetOfListHead ()), + &ChunkListHeadAddr, sizeof (ChunkListHeadAddr), 0); + if (hr != S_OK || ChunkListHeadAddr == NULL) + { + return false; + } + + size_t StressLogChunkAddr = ChunkListHeadAddr; + + do + { + AddRange (StressLogChunkAddr, sizeof (StressLogChunk)); + hr = memCallBack->ReadVirtual(TO_CDADDR(StressLogChunkAddr + offsetof (StressLogChunk, next)), + &StressLogChunkAddr, sizeof (StressLogChunkAddr), 0); + if (hr != S_OK) + { + return false; + } + if (StressLogChunkAddr == NULL) + { + return true; + } + } while (StressLogChunkAddr != ChunkListHeadAddr); + + hr = memCallBack->ReadVirtual(TO_CDADDR(ThreadStressLogAddr + ThreadStressLog::OffsetOfNext ()), + &ThreadStressLogAddr, sizeof (ThreadStressLogAddr), 0); + if (hr != S_OK) + { + return false; + } + } + + return true; +} + +bool StressLogMem::IsInStressLog (ULONG64 addr) +{ + MemRange * range = list; + while (range) + { + if (range->InRange (addr)) + return true; + range = range->next; + } + + return false; +} + +#endif // !FEATURE_PAL + +unsigned int Output::g_bSuppressOutput = 0; +unsigned int Output::g_Indent = 0; +bool Output::g_bDbgOutput = false; +bool Output::g_bDMLExposed = false; +unsigned int Output::g_DMLEnable = 0; + +template <class T, int count, int size> const int StaticData<T, count, size>::Count = count; +template <class T, int count, int size> const int StaticData<T, count, size>::Size = size; + +StaticData<char, 4, 1024> CachedString::cache; + +CachedString::CachedString() +: mPtr(0), mRefCount(0), mIndex(~0), mSize(cache.Size) +{ + Create(); +} + +CachedString::CachedString(const CachedString &rhs) +: mPtr(0), mRefCount(0), mIndex(~0), mSize(cache.Size) +{ + Copy(rhs); +} + +CachedString::~CachedString() +{ + Clear(); +} + +const CachedString &CachedString::operator=(const CachedString &rhs) +{ + Clear(); + Copy(rhs); + return *this; +} + +void CachedString::Copy(const CachedString &rhs) +{ + if (rhs.IsOOM()) + { + SetOOM(); + } + else + { + mPtr = rhs.mPtr; + mIndex = rhs.mIndex; + mSize = rhs.mSize; + + if (rhs.mRefCount) + { + mRefCount = rhs.mRefCount; + (*mRefCount)++; + } + else + { + // We only create count the first time we copy it, so + // we initialize it to 2. + mRefCount = rhs.mRefCount = new unsigned int(2); + if (!mRefCount) + SetOOM(); + } + } +} + +void CachedString::Clear() +{ + if (!mRefCount || --*mRefCount == 0) + { + if (mIndex == -1) + { + if (mPtr) + delete [] mPtr; + } + else if (mIndex >= 0 && mIndex < cache.Count) + { + cache.InUse[mIndex] = false; + } + + if (mRefCount) + delete mRefCount; + } + + mPtr = 0; + mIndex = ~0; + mRefCount = 0; + mSize = cache.Size; +} + + +void CachedString::Create() +{ + mIndex = -1; + mRefCount = 0; + + // First try to find a string in the cache to use. + for (int i = 0; i < cache.Count; ++i) + if (!cache.InUse[i]) + { + cache.InUse[i] = true; + mPtr = cache.Data[i]; + mIndex = i; + break; + } + + // We did not find a string to use, so we'll create a new one. + if (mIndex == -1) + { + mPtr = new char[cache.Size]; + if (!mPtr) + SetOOM(); + } +} + + +void CachedString::SetOOM() +{ + Clear(); + mIndex = -2; +} + +void CachedString::Allocate(int size) +{ + Clear(); + mPtr = new char[size]; + + if (mPtr) + { + mSize = size; + mIndex = -1; + } + else + { + SetOOM(); + } +} + +size_t CountHexCharacters(CLRDATA_ADDRESS val) +{ + size_t ret = 0; + + while (val) + { + val >>= 4; + ret++; + } + + return ret; +} + +void WhitespaceOut(int count) +{ + static const int FixedIndentWidth = 0x40; + static const char FixedIndentString[FixedIndentWidth+1] = + " "; + + if (count <= 0) + return; + + int mod = count & 0x3F; + count &= ~0x3F; + + if (mod > 0) + g_ExtControl->Output(DEBUG_OUTPUT_NORMAL, "%.*s", mod, FixedIndentString); + + for ( ; count > 0; count -= FixedIndentWidth) + g_ExtControl->Output(DEBUG_OUTPUT_NORMAL, FixedIndentString); +} + +void DMLOut(PCSTR format, ...) +{ + if (Output::IsOutputSuppressed()) + return; + + va_list args; + va_start(args, format); + ExtOutIndent(); + +#ifndef FEATURE_PAL + if (IsDMLEnabled() && !Output::IsDMLExposed()) + { + g_ExtControl->ControlledOutputVaList(DEBUG_OUTCTL_AMBIENT_DML, DEBUG_OUTPUT_NORMAL, format, args); + } + else +#endif + { + g_ExtControl->OutputVaList(DEBUG_OUTPUT_NORMAL, format, args); + } + + va_end(args); +} + +void IfDMLOut(PCSTR format, ...) +{ +#ifndef FEATURE_PAL + if (Output::IsOutputSuppressed() || !IsDMLEnabled()) + return; + + va_list args; + + va_start(args, format); + ExtOutIndent(); + g_ExtControl->ControlledOutputVaList(DEBUG_OUTCTL_AMBIENT_DML, DEBUG_OUTPUT_NORMAL, format, args); + va_end(args); +#endif +} + +void ExtOut(PCSTR Format, ...) +{ + if (Output::IsOutputSuppressed()) + return; + + va_list Args; + + va_start(Args, Format); + ExtOutIndent(); + g_ExtControl->OutputVaList(DEBUG_OUTPUT_NORMAL, Format, Args); + va_end(Args); +} + +void ExtWarn(PCSTR Format, ...) +{ + if (Output::IsOutputSuppressed()) + return; + + va_list Args; + + va_start(Args, Format); + g_ExtControl->OutputVaList(DEBUG_OUTPUT_WARNING, Format, Args); + va_end(Args); +} + +void ExtErr(PCSTR Format, ...) +{ + va_list Args; + + va_start(Args, Format); + g_ExtControl->OutputVaList(DEBUG_OUTPUT_ERROR, Format, Args); + va_end(Args); +} + + +void ExtDbgOut(PCSTR Format, ...) +{ +#ifdef _DEBUG + if (Output::g_bDbgOutput) + { + va_list Args; + + va_start(Args, Format); + ExtOutIndent(); + g_ExtControl->OutputVaList(DEBUG_OUTPUT_NORMAL, Format, Args); + va_end(Args); + } +#endif +} + +const char * const DMLFormats[] = +{ + NULL, // DML_None (do not use) + "<exec cmd=\"!DumpMT /d %s\">%s</exec>", // DML_MethodTable + "<exec cmd=\"!DumpMD /d %s\">%s</exec>", // DML_MethodDesc + "<exec cmd=\"!DumpClass /d %s\">%s</exec>", // DML_EEClass + "<exec cmd=\"!DumpModule /d %s\">%s</exec>", // DML_Module + "<exec cmd=\"!U /d %s\">%s</exec>", // DML_IP + "<exec cmd=\"!DumpObj /d %s\">%s</exec>", // DML_Object + "<exec cmd=\"!DumpDomain /d %s\">%s</exec>", // DML_Domain + "<exec cmd=\"!DumpAssembly /d %s\">%s</exec>", // DML_Assembly + "<exec cmd=\"~~[%s]s\">%s</exec>", // DML_ThreadID + "<exec cmd=\"!DumpVC /d %s %s\">%s</exec>", // DML_ValueClass + "<exec cmd=\"!DumpHeap /d -mt %s\">%s</exec>", // DML_DumpHeapMT + "<exec cmd=\"!ListNearObj /d %s\">%s</exec>", // DML_ListNearObj + "<exec cmd=\"!ThreadState %s\">%s</exec>", // DML_ThreadState + "<exec cmd=\"!PrintException /d %s\">%s</exec>",// DML_PrintException + "<exec cmd=\"!DumpRCW /d %s\">%s</exec>", // DML_RCWrapper + "<exec cmd=\"!DumpCCW /d %s\">%s</exec>", // DML_CCWrapper + "<exec cmd=\"!ClrStack -i %S %d\">%S</exec>", // DML_ManagedVar +}; + +void ConvertToLower(__out_ecount(len) char *buffer, size_t len) +{ + for (size_t i = 0; i < len && buffer[i]; ++i) + buffer[i] = (char)tolower(buffer[i]); +} + +/* Build a hex display of addr. + */ +int GetHex(CLRDATA_ADDRESS addr, __out_ecount(len) char *out, size_t len, bool fill) +{ + int count = sprintf_s(out, len, fill ? "%p" : "%x", (size_t)addr); + + ConvertToLower(out, len); + + return count; +} + +CachedString Output::BuildHexValue(CLRDATA_ADDRESS addr, FormatType type, bool fill) +{ + CachedString ret; + if (ret.IsOOM()) + { + ReportOOM(); + return ret; + } + + if (IsDMLEnabled()) + { + char hex[POINTERSIZE_BYTES*2 + 1]; + GetHex(addr, hex, _countof(hex), fill); + sprintf_s(ret, ret.GetStrLen(), DMLFormats[type], hex, hex); + } + else + { + GetHex(addr, ret, ret.GetStrLen(), fill); + } + + return ret; +} + +CachedString Output::BuildVCValue(CLRDATA_ADDRESS mt, CLRDATA_ADDRESS addr, FormatType type, bool fill) +{ + _ASSERTE(type == DML_ValueClass); + CachedString ret; + if (ret.IsOOM()) + { + ReportOOM(); + return ret; + } + + if (IsDMLEnabled()) + { + char hexaddr[POINTERSIZE_BYTES*2 + 1]; + char hexmt[POINTERSIZE_BYTES*2 + 1]; + + GetHex(addr, hexaddr, _countof(hexaddr), fill); + GetHex(mt, hexmt, _countof(hexmt), fill); + + sprintf_s(ret, ret.GetStrLen(), DMLFormats[type], hexmt, hexaddr, hexaddr); + } + else + { + GetHex(addr, ret, ret.GetStrLen(), fill); + } + + return ret; +} + +CachedString Output::BuildManagedVarValue(__in_z LPCWSTR expansionName, ULONG frame, __in_z LPCWSTR simpleName, FormatType type) +{ + _ASSERTE(type == DML_ManagedVar); + CachedString ret; + if (ret.IsOOM()) + { + ReportOOM(); + return ret; + } + + // calculate the number of digits in frame (this assumes base-10 display of frames) + int numFrameDigits = 0; + if (frame > 0) + { + ULONG tempFrame = frame; + while (tempFrame > 0) + { + ++numFrameDigits; + tempFrame /= 10; + } + } + else + { + numFrameDigits = 1; + } + + size_t totalStringLength = strlen(DMLFormats[type]) + _wcslen(expansionName) + numFrameDigits + _wcslen(simpleName) + 1; + if (totalStringLength > ret.GetStrLen()) + { + ret.Allocate(static_cast<int>(totalStringLength)); + if (ret.IsOOM()) + { + ReportOOM(); + return ret; + } + } + + if (IsDMLEnabled()) + { + sprintf_s(ret, ret.GetStrLen(), DMLFormats[type], expansionName, frame, simpleName); + } + else + { + sprintf_s(ret, ret.GetStrLen(), "%S", simpleName); + } + + return ret; +} + +CachedString Output::BuildManagedVarValue(__in_z LPCWSTR expansionName, ULONG frame, int indexInArray, FormatType type) +{ + WCHAR indexString[24]; + swprintf_s(indexString, _countof(indexString), W("[%d]"), indexInArray); + return BuildManagedVarValue(expansionName, frame, indexString, type); +} + +EnableDMLHolder::EnableDMLHolder(BOOL enable) + : mEnable(enable) +{ +#ifndef FEATURE_PAL + // If the user has not requested that we use DML, it's still possible that + // they have instead specified ".prefer_dml 1". If enable is false, + // we will check here for .prefer_dml. Since this class is only used once + // per command issued to SOS, this should only check the setting once per + // sos command issued. + if (!mEnable && Output::g_DMLEnable <= 0) + { + ULONG opts; + HRESULT hr = g_ExtControl->GetEngineOptions(&opts); + mEnable = SUCCEEDED(hr) && (opts & DEBUG_ENGOPT_PREFER_DML) == DEBUG_ENGOPT_PREFER_DML; + } + + if (mEnable) + { + Output::g_DMLEnable++; + } +#endif // FEATURE_PAL +} + +EnableDMLHolder::~EnableDMLHolder() +{ +#ifndef FEATURE_PAL + if (mEnable) + Output::g_DMLEnable--; +#endif +} + +bool IsDMLEnabled() +{ + return Output::g_DMLEnable > 0; +} + +NoOutputHolder::NoOutputHolder(BOOL bSuppress) + : mSuppress(bSuppress) +{ + if (mSuppress) + Output::g_bSuppressOutput++; +} + +NoOutputHolder::~NoOutputHolder() +{ + if (mSuppress) + Output::g_bSuppressOutput--; +} + +// +// Code to support mapping RVAs to managed code line numbers. +// + +// +// Retrieves the IXCLRDataMethodInstance* instance associated with the +// passed in native offset. +HRESULT +GetClrMethodInstance( + ___in ULONG64 NativeOffset, + ___out IXCLRDataMethodInstance** Method) +{ + HRESULT Status; + CLRDATA_ENUM MethEnum; + + Status = g_clrData->StartEnumMethodInstancesByAddress(NativeOffset, NULL, &MethEnum); + + if (Status == S_OK) + { + Status = g_clrData->EnumMethodInstanceByAddress(&MethEnum, Method); + g_clrData->EndEnumMethodInstancesByAddress(MethEnum); + } + + // Any alternate success is a true failure here. + return (Status == S_OK || FAILED(Status)) ? Status : E_NOINTERFACE; +} + +// +// Enumerates over the IL address map associated with the passed in +// managed method, and returns the highest non-epilog offset. +HRESULT +GetLastMethodIlOffset( + ___in IXCLRDataMethodInstance* Method, + ___out PULONG32 MethodOffs) +{ + HRESULT Status; + CLRDATA_IL_ADDRESS_MAP MapLocal[16]; + CLRDATA_IL_ADDRESS_MAP* Map = MapLocal; + ULONG32 MapCount = _countof(MapLocal); + ULONG32 MapNeeded; + ULONG32 HighestOffset; + + for (;;) + { + if ((Status = Method->GetILAddressMap(MapCount, &MapNeeded, Map)) != S_OK) + { + return Status; + } + + if (MapNeeded <= MapCount) + { + break; + } + + // Need more map entries. + if (Map != MapLocal) + { + // Already went around and the answer changed, + // which should not be possible. + delete[] Map; + return E_UNEXPECTED; + } + + Map = new CLRDATA_IL_ADDRESS_MAP[MapNeeded]; + if (!Map) + { + return E_OUTOFMEMORY; + } + + MapCount = MapNeeded; + } + + HighestOffset = 0; + for (size_t i = 0; i < MapNeeded; i++) + { + if (Map[i].ilOffset != (ULONG32)CLRDATA_IL_OFFSET_NO_MAPPING && + Map[i].ilOffset != (ULONG32)CLRDATA_IL_OFFSET_PROLOG && + Map[i].ilOffset != (ULONG32)CLRDATA_IL_OFFSET_EPILOG && + Map[i].ilOffset > HighestOffset) + { + HighestOffset = Map[i].ilOffset; + } + } + + if (Map != MapLocal) + { + delete[] Map; + } + + *MethodOffs = HighestOffset; + return S_OK; +} + +// +// Convert a native offset (possibly already associated with a managed +// method identified by the passed in IXCLRDataMethodInstance) to a +// triplet (ImageInfo, MethodToken, MethodOffset) that can be used to +// represent an "IL offset". +HRESULT +ConvertNativeToIlOffset( + ___in ULONG64 native, + ___out IXCLRDataModule** ppModule, + ___out mdMethodDef* methodToken, + ___out PULONG32 methodOffs) +{ + ToRelease<IXCLRDataMethodInstance> pMethodInst(NULL); + HRESULT Status; + + if ((Status = GetClrMethodInstance(native, &pMethodInst)) != S_OK) + { + return Status; + } + + if ((Status = pMethodInst->GetILOffsetsByAddress(native, 1, NULL, methodOffs)) != S_OK) + { + *methodOffs = 0; + } + else + { + switch((LONG)*methodOffs) + { + case CLRDATA_IL_OFFSET_NO_MAPPING: + return E_NOINTERFACE; + + case CLRDATA_IL_OFFSET_PROLOG: + // Treat all of the prologue as part of + // the first source line. + *methodOffs = 0; + break; + + case CLRDATA_IL_OFFSET_EPILOG: + // Back up until we find the last real + // IL offset. + if ((Status = GetLastMethodIlOffset(pMethodInst, methodOffs)) != S_OK) + { + return Status; + } + break; + } + } + + return pMethodInst->GetTokenAndScope(methodToken, ppModule); +} + +// Based on a native offset, passed in the first argument this function +// identifies the corresponding source file name and line number. +HRESULT +GetLineByOffset( + ___in ULONG64 offset, + ___out ULONG *pLinenum, + __out_ecount(cchFileName) WCHAR* pwszFileName, + ___in ULONG cchFileName) +{ + HRESULT Status = S_OK; + ULONG32 methodToken; + ULONG32 methodOffs; + + // Find the image, method token and IL offset that correspond to "offset" + ToRelease<IXCLRDataModule> pModule(NULL); + IfFailRet(ConvertNativeToIlOffset(offset, &pModule, &methodToken, &methodOffs)); + + ToRelease<IMetaDataImport> pMDImport(NULL); + IfFailRet(pModule->QueryInterface(IID_IMetaDataImport, (LPVOID *) &pMDImport)); + + SymbolReader symbolReader; + IfFailRet(symbolReader.LoadSymbols(pMDImport, pModule)); + + return symbolReader.GetLineByILOffset(methodToken, methodOffs, pLinenum, pwszFileName, cchFileName); +} + +void TableOutput::ReInit(int numColumns, int defaultColumnWidth, Alignment alignmentDefault, int indent, int padding) +{ + Clear(); + + mColumns = numColumns; + mDefaultWidth = defaultColumnWidth; + mIndent = indent; + mPadding = padding; + mCurrCol = 0; + mDefaultAlign = alignmentDefault; +} + +void TableOutput::SetWidths(int columns, ...) +{ + SOS_Assert(columns > 0); + SOS_Assert(columns <= mColumns); + + AllocWidths(); + + va_list list; + va_start(list, columns); + + for (int i = 0; i < columns; ++i) + mWidths[i] = va_arg(list, int); + + va_end(list); +} + +void TableOutput::SetColWidth(int col, int width) +{ + SOS_Assert(col >= 0 && col < mColumns); + SOS_Assert(width >= 0); + + AllocWidths(); + + mWidths[col] = width; +} + +void TableOutput::SetColAlignment(int col, Alignment align) +{ + SOS_Assert(col >= 0 && col < mColumns); + + if (!mAlignments) + { + mAlignments = new Alignment[mColumns]; + for (int i = 0; i < mColumns; ++i) + mAlignments[i] = mDefaultAlign; + } + + mAlignments[col] = align; +} + + + +void TableOutput::Clear() +{ + if (mAlignments) + { + delete [] mAlignments; + mAlignments = 0; + } + + if (mWidths) + { + delete [] mWidths; + mWidths = 0; + } +} + +void TableOutput::AllocWidths() +{ + if (!mWidths) + { + mWidths = new int[mColumns]; + for (int i = 0; i < mColumns; ++i) + mWidths[i] = mDefaultWidth; + } +} + +int TableOutput::GetColumnWidth(int col) +{ + SOS_Assert(col < mColumns); + + if (mWidths) + return mWidths[col]; + + return mDefaultWidth; +} + +Alignment TableOutput::GetColAlign(int col) +{ + SOS_Assert(col < mColumns); + if (mAlignments) + return mAlignments[col]; + + return mDefaultAlign; +} + +const char *TableOutput::GetWhitespace(int amount) +{ + static char WhiteSpace[256] = ""; + static int count = 0; + + if (count == 0) + { + count = _countof(WhiteSpace); + for (int i = 0; i < count-1; ++i) + WhiteSpace[i] = ' '; + WhiteSpace[count-1] = 0; + } + + SOS_Assert(amount < count); + return &WhiteSpace[count-amount-1]; +} + +void TableOutput::OutputBlankColumns(int col) +{ + if (col < mCurrCol) + { + ExtOut("\n"); + mCurrCol = 0; + } + + int whitespace = 0; + for (int i = mCurrCol; i < col; ++i) + whitespace += GetColumnWidth(i) + mPadding; + + ExtOut(GetWhitespace(whitespace)); +} + +void TableOutput::OutputIndent() +{ + if (mIndent) + ExtOut(GetWhitespace(mIndent)); +} + +#ifndef FEATURE_PAL + +PEOffsetMemoryReader::PEOffsetMemoryReader(TADDR moduleBaseAddress) : + m_moduleBaseAddress(moduleBaseAddress), + m_refCount(1) + {} + +HRESULT __stdcall PEOffsetMemoryReader::QueryInterface(REFIID riid, VOID** ppInterface) +{ + if(riid == __uuidof(IDiaReadExeAtOffsetCallback)) + { + *ppInterface = static_cast<IDiaReadExeAtOffsetCallback*>(this); + AddRef(); + return S_OK; + } + else if(riid == __uuidof(IUnknown)) + { + *ppInterface = static_cast<IUnknown*>(this); + AddRef(); + return S_OK; + } + else + { + return E_NOINTERFACE; + } +} + +ULONG __stdcall PEOffsetMemoryReader::AddRef() +{ + return InterlockedIncrement((volatile LONG *) &m_refCount); +} + +ULONG __stdcall PEOffsetMemoryReader::Release() +{ + ULONG count = InterlockedDecrement((volatile LONG *) &m_refCount); + if(count == 0) + { + delete this; + } + return count; +} + +// IDiaReadExeAtOffsetCallback implementation +HRESULT __stdcall PEOffsetMemoryReader::ReadExecutableAt(DWORDLONG fileOffset, DWORD cbData, DWORD* pcbData, BYTE data[]) +{ + return SafeReadMemory(m_moduleBaseAddress + fileOffset, data, cbData, pcbData) ? S_OK : E_FAIL; +} + +PERvaMemoryReader::PERvaMemoryReader(TADDR moduleBaseAddress) : + m_moduleBaseAddress(moduleBaseAddress), + m_refCount(1) + {} + +HRESULT __stdcall PERvaMemoryReader::QueryInterface(REFIID riid, VOID** ppInterface) +{ + if(riid == __uuidof(IDiaReadExeAtRVACallback)) + { + *ppInterface = static_cast<IDiaReadExeAtRVACallback*>(this); + AddRef(); + return S_OK; + } + else if(riid == __uuidof(IUnknown)) + { + *ppInterface = static_cast<IUnknown*>(this); + AddRef(); + return S_OK; + } + else + { + return E_NOINTERFACE; + } +} + +ULONG __stdcall PERvaMemoryReader::AddRef() +{ + return InterlockedIncrement((volatile LONG *) &m_refCount); +} + +ULONG __stdcall PERvaMemoryReader::Release() +{ + ULONG count = InterlockedDecrement((volatile LONG *) &m_refCount); + if(count == 0) + { + delete this; + } + return count; +} + +// IDiaReadExeAtOffsetCallback implementation +HRESULT __stdcall PERvaMemoryReader::ReadExecutableAtRVA(DWORD relativeVirtualAddress, DWORD cbData, DWORD* pcbData, BYTE data[]) +{ + return SafeReadMemory(m_moduleBaseAddress + relativeVirtualAddress, data, cbData, pcbData) ? S_OK : E_FAIL; +} + +#endif // FEATURE_PAL + +HRESULT SymbolReader::LoadSymbols(___in IMetaDataImport* pMD, ___in ICorDebugModule* pModule) +{ + HRESULT Status = S_OK; + BOOL isDynamic = FALSE; + BOOL isInMemory = FALSE; + IfFailRet(pModule->IsDynamic(&isDynamic)); + IfFailRet(pModule->IsInMemory(&isInMemory)); + + if (isDynamic) + { + // Dynamic and in memory assemblies are a special case which we will ignore for now + ExtWarn("SOS Warning: Loading symbols for dynamic assemblies is not yet supported\n"); + return E_FAIL; + } + + ULONG64 peAddress = 0; + ULONG32 peSize = 0; + IfFailRet(pModule->GetBaseAddress(&peAddress)); + IfFailRet(pModule->GetSize(&peSize)); + + ULONG32 len = 0; + WCHAR moduleName[MAX_LONGPATH]; + IfFailRet(pModule->GetName(_countof(moduleName), &len, moduleName)); + +#ifndef FEATURE_PAL + if (SUCCEEDED(LoadSymbolsForWindowsPDB(pMD, peAddress, moduleName, isInMemory))) + { + return S_OK; + } +#endif // FEATURE_PAL + return LoadSymbolsForPortablePDB(moduleName, isInMemory, isInMemory, peAddress, peSize, 0, 0); +} + +HRESULT SymbolReader::LoadSymbols(___in IMetaDataImport* pMD, ___in IXCLRDataModule* pModule) +{ + DacpGetModuleData moduleData; + HRESULT hr = moduleData.Request(pModule); + if (FAILED(hr)) + { + ExtOut("LoadSymbols moduleData.Request FAILED 0x%08x\n", hr); + return hr; + } + + if (moduleData.IsDynamic) + { + ExtWarn("SOS Warning: Loading symbols for dynamic assemblies is not yet supported\n"); + return E_FAIL; + } + + ArrayHolder<WCHAR> pModuleName = new WCHAR[MAX_LONGPATH + 1]; + ULONG32 nameLen = 0; + hr = pModule->GetFileName(MAX_LONGPATH, &nameLen, pModuleName); + if (FAILED(hr)) + { + ExtOut("LoadSymbols: IXCLRDataModule->GetFileName FAILED 0x%08x\n", hr); + return hr; + } + +#ifndef FEATURE_PAL + // TODO: in-memory windows PDB not supported + hr = LoadSymbolsForWindowsPDB(pMD, moduleData.LoadedPEAddress, pModuleName, moduleData.IsFileLayout); + if (SUCCEEDED(hr)) + { + return hr; + } +#endif // FEATURE_PAL + + return LoadSymbolsForPortablePDB( + pModuleName, + moduleData.IsInMemory, + moduleData.IsFileLayout, + moduleData.LoadedPEAddress, + moduleData.LoadedPESize, + moduleData.InMemoryPdbAddress, + moduleData.InMemoryPdbSize); +} + +#ifndef FEATURE_PAL + +HRESULT SymbolReader::LoadSymbolsForWindowsPDB(___in IMetaDataImport* pMD, ___in ULONG64 peAddress, __in_z WCHAR* pModuleName, ___in BOOL isFileLayout) +{ + HRESULT Status = S_OK; + + if (m_pSymReader != NULL) + return S_OK; + + IfFailRet(CoInitialize(NULL)); + + // We now need a binder object that will take the module and return a + // reader object + ToRelease<ISymUnmanagedBinder3> pSymBinder; + if (FAILED(Status = CreateInstanceCustom(CLSID_CorSymBinder_SxS, + IID_ISymUnmanagedBinder3, + W("diasymreader.dll"), + cciLatestFx|cciDacColocated|cciDbgPath, + (void**)&pSymBinder))) + { + ExtOut("SOS Error: Unable to CoCreateInstance class=CLSID_CorSymBinder_SxS, interface=IID_ISymUnmanagedBinder3, hr=0x%x\n", Status); + ExtOut("This usually means the installation of .Net Framework on your machine is missing or needs repair\n"); + return Status; + } + + ToRelease<IDebugSymbols3> spSym3(NULL); + Status = g_ExtSymbols->QueryInterface(__uuidof(IDebugSymbols3), (void**)&spSym3); + if (FAILED(Status)) + { + ExtOut("SOS Error: Unable to query IDebugSymbols3 HRESULT=0x%x.\n", Status); + return Status; + } + + ULONG pathSize = 0; + Status = spSym3->GetSymbolPathWide(NULL, 0, &pathSize); + if (FAILED(Status)) //S_FALSE if the path doesn't fit, but if the path was size 0 perhaps we would get S_OK? + { + ExtOut("SOS Error: Unable to get symbol path length. IDebugSymbols3::GetSymbolPathWide HRESULT=0x%x.\n", Status); + return Status; + } + + ArrayHolder<WCHAR> symbolPath = new WCHAR[pathSize]; + Status = spSym3->GetSymbolPathWide(symbolPath, pathSize, NULL); + if (S_OK != Status) + { + ExtOut("SOS Error: Unable to get symbol path. IDebugSymbols3::GetSymbolPathWide HRESULT=0x%x.\n", Status); + return Status; + } + + ToRelease<IUnknown> pCallback = NULL; + if (isFileLayout) + { + pCallback = (IUnknown*) new PEOffsetMemoryReader(TO_TADDR(peAddress)); + } + else + { + pCallback = (IUnknown*) new PERvaMemoryReader(TO_TADDR(peAddress)); + } + + // TODO: this should be better integrated with windbg's symbol lookup + Status = pSymBinder->GetReaderFromCallback(pMD, pModuleName, symbolPath, + AllowRegistryAccess | AllowSymbolServerAccess | AllowOriginalPathAccess | AllowReferencePathAccess, pCallback, &m_pSymReader); + + if (FAILED(Status) && m_pSymReader != NULL) + { + m_pSymReader->Release(); + m_pSymReader = NULL; + } + return Status; +} + +#endif // FEATURE_PAL + +// +// Pass to managed helper code to read in-memory PEs/PDBs +// Returns the number of bytes read. +// +int ReadMemoryForSymbols(ULONG64 address, char *buffer, int cb) +{ + ULONG read; + if (SafeReadMemory(address, (PVOID)buffer, cb, &read)) + { + return read; + } + return 0; +} + +HRESULT SymbolReader::LoadSymbolsForPortablePDB(__in_z WCHAR* pModuleName, ___in BOOL isInMemory, ___in BOOL isFileLayout, + ___in ULONG64 peAddress, ___in ULONG64 peSize, ___in ULONG64 inMemoryPdbAddress, ___in ULONG64 inMemoryPdbSize) +{ + HRESULT Status = S_OK; + + if (loadSymbolsForModuleDelegate == nullptr) + { + IfFailRet(PrepareSymbolReader()); + } + + // The module name needs to be null for in-memory PE's. + ArrayHolder<char> szModuleName = nullptr; + if (!isInMemory && pModuleName != nullptr) + { + szModuleName = new char[MAX_LONGPATH]; + if (WideCharToMultiByte(CP_ACP, 0, pModuleName, (int)(_wcslen(pModuleName) + 1), szModuleName, MAX_LONGPATH, NULL, NULL) == 0) + { + return E_FAIL; + } + } + + m_symbolReaderHandle = loadSymbolsForModuleDelegate(szModuleName, isFileLayout, peAddress, + (int)peSize, inMemoryPdbAddress, (int)inMemoryPdbSize, ReadMemoryForSymbols); + + if (m_symbolReaderHandle == 0) + { + return E_FAIL; + } + + return Status; +} + +#ifndef FEATURE_PAL + +void AddFilesFromDirectoryToTpaList(const char* directory, std::string& tpaList) +{ + const char * const tpaExtensions[] = { + "*.ni.dll", // Probe for .ni.dll first so that it's preferred if ni and il coexist in the same dir + "*.dll", + "*.ni.exe", + "*.exe", + }; + + std::set<std::string> addedAssemblies; + + // Walk the directory for each extension separately so that we first get files with .ni.dll extension, + // then files with .dll extension, etc. + for (int extIndex = 0; extIndex < sizeof(tpaExtensions) / sizeof(tpaExtensions[0]); extIndex++) + { + const char* ext = tpaExtensions[extIndex]; + size_t extLength = strlen(ext); + + std::string assemblyPath(directory); + assemblyPath.append(DIRECTORY_SEPARATOR_STR_A); + assemblyPath.append(tpaExtensions[extIndex]); + + WIN32_FIND_DATAA data; + HANDLE findHandle = FindFirstFileA(assemblyPath.c_str(), &data); + + if (findHandle != INVALID_HANDLE_VALUE) + { + do + { + if (!(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + { + + std::string filename(data.cFileName); + size_t extPos = filename.length() - extLength; + std::string filenameWithoutExt(filename.substr(0, extPos)); + + // Make sure if we have an assembly with multiple extensions present, + // we insert only one version of it. + if (addedAssemblies.find(filenameWithoutExt) == addedAssemblies.end()) + { + addedAssemblies.insert(filenameWithoutExt); + + tpaList.append(directory); + tpaList.append(DIRECTORY_SEPARATOR_STR_A); + tpaList.append(filename); + tpaList.append(";"); + } + } + } + while (0 != FindNextFileA(findHandle, &data)); + + FindClose(findHandle); + } + } +} + +bool GetEntrypointExecutableAbsolutePath(std::string& entrypointExecutable) +{ + ArrayHolder<char> hostPath = new char[MAX_LONGPATH+1]; + if (::GetModuleFileName(NULL, hostPath, MAX_LONGPATH) == 0) + { + return false; + } + + entrypointExecutable.clear(); + entrypointExecutable.append(hostPath); + + return true; +} + +#endif // FEATURE_PAL + +HRESULT SymbolReader::PrepareSymbolReader() +{ + static bool attemptedSymbolReaderPreparation = false; + if (attemptedSymbolReaderPreparation) + { + // If we already tried to set up the symbol reader, we won't try again. + return E_FAIL; + } + + attemptedSymbolReaderPreparation = true; + + std::string absolutePath; + std::string coreClrPath; + HRESULT Status; + +#ifdef FEATURE_PAL + coreClrPath = g_ExtServices->GetCoreClrDirectory(); + if (!GetAbsolutePath(coreClrPath.c_str(), absolutePath)) + { + ExtErr("Error: Failed to get coreclr absolute path\n"); + return E_FAIL; + } + coreClrPath.append(DIRECTORY_SEPARATOR_STR_A); + coreClrPath.append(MAIN_CLR_DLL_NAME_A); +#else + ULONG index; + Status = g_ExtSymbols->GetModuleByModuleName(MAIN_CLR_MODULE_NAME_A, 0, &index, NULL); + if (FAILED(Status)) + { + ExtErr("Error: Can't find coreclr module\n"); + return Status; + } + ArrayHolder<char> szModuleName = new char[MAX_LONGPATH + 1]; + Status = g_ExtSymbols->GetModuleNames(index, 0, szModuleName, MAX_LONGPATH, NULL, NULL, 0, NULL, NULL, 0, NULL); + if (FAILED(Status)) + { + ExtErr("Error: Failed to get coreclr module name\n"); + return Status; + } + coreClrPath = szModuleName; + + // Parse off the module name to get just the path + size_t pos = coreClrPath.rfind(DIRECTORY_SEPARATOR_CHAR_A); + if (pos == std::string::npos) + { + ExtErr("Error: Failed to parse coreclr module name\n"); + return E_FAIL; + } + absolutePath.assign(coreClrPath, 0, pos); +#endif // FEATURE_PAL + + HMODULE coreclrLib = LoadLibraryA(coreClrPath.c_str()); + if (coreclrLib == nullptr) + { + ExtErr("Error: Failed to load %s\n", coreClrPath.c_str()); + return E_FAIL; + } + + void *hostHandle; + unsigned int domainId; + coreclr_initialize_ptr initializeCoreCLR = (coreclr_initialize_ptr)GetProcAddress(coreclrLib, "coreclr_initialize"); + if (initializeCoreCLR == nullptr) + { + ExtErr("Error: coreclr_initialize not found\n"); + return E_FAIL; + } + + std::string tpaList; + AddFilesFromDirectoryToTpaList(absolutePath.c_str(), tpaList); + + const char *propertyKeys[] = { + "TRUSTED_PLATFORM_ASSEMBLIES", "APP_PATHS", "APP_NI_PATHS", + "NATIVE_DLL_SEARCH_DIRECTORIES", "AppDomainCompatSwitch"}; + + const char *propertyValues[] = {// TRUSTED_PLATFORM_ASSEMBLIES + tpaList.c_str(), + // APP_PATHS + absolutePath.c_str(), + // APP_NI_PATHS + absolutePath.c_str(), + // NATIVE_DLL_SEARCH_DIRECTORIES + absolutePath.c_str(), + // AppDomainCompatSwitch + "UseLatestBehaviorWhenTFMNotSpecified"}; + + std::string entryPointExecutablePath; + if (!GetEntrypointExecutableAbsolutePath(entryPointExecutablePath)) + { + ExtErr("Could not get full path to current executable"); + return E_FAIL; + } + + Status = initializeCoreCLR(entryPointExecutablePath.c_str(), "sos", + sizeof(propertyKeys) / sizeof(propertyKeys[0]), propertyKeys, propertyValues, &hostHandle, &domainId); + + if (FAILED(Status)) + { + ExtErr("Error: Fail to initialize CoreCLR %08x\n", Status); + return Status; + } + + coreclr_create_delegate_ptr createDelegate = (coreclr_create_delegate_ptr)GetProcAddress(coreclrLib, "coreclr_create_delegate"); + if (createDelegate == nullptr) + { + ExtErr("Error: coreclr_create_delegate not found\n"); + return E_FAIL; + } + + IfFailRet(createDelegate(hostHandle, domainId, SymbolReaderDllName, SymbolReaderClassName, "LoadSymbolsForModule", (void **)&loadSymbolsForModuleDelegate)); + IfFailRet(createDelegate(hostHandle, domainId, SymbolReaderDllName, SymbolReaderClassName, "Dispose", (void **)&disposeDelegate)); + IfFailRet(createDelegate(hostHandle, domainId, SymbolReaderDllName, SymbolReaderClassName, "ResolveSequencePoint", (void **)&resolveSequencePointDelegate)); + IfFailRet(createDelegate(hostHandle, domainId, SymbolReaderDllName, SymbolReaderClassName, "GetLocalVariableName", (void **)&getLocalVariableNameDelegate)); + IfFailRet(createDelegate(hostHandle, domainId, SymbolReaderDllName, SymbolReaderClassName, "GetLineByILOffset", (void **)&getLineByILOffsetDelegate)); + + return Status; +} + +HRESULT SymbolReader::GetLineByILOffset(___in mdMethodDef methodToken, ___in ULONG64 ilOffset, + ___out ULONG *pLinenum, __out_ecount(cchFileName) WCHAR* pwszFileName, ___in ULONG cchFileName) +{ + HRESULT Status = S_OK; + + if (m_symbolReaderHandle != 0) + { + _ASSERTE(getLineByILOffsetDelegate != nullptr); + + BSTR bstrFileName = SysAllocStringLen(0, MAX_LONGPATH); + if (bstrFileName == nullptr) + { + return E_OUTOFMEMORY; + } + // Source lines with 0xFEEFEE markers are filtered out on the managed side. + if ((getLineByILOffsetDelegate(m_symbolReaderHandle, methodToken, ilOffset, pLinenum, &bstrFileName) == FALSE) || (*pLinenum == 0)) + { + SysFreeString(bstrFileName); + return E_FAIL; + } + wcscpy_s(pwszFileName, cchFileName, bstrFileName); + SysFreeString(bstrFileName); + return S_OK; + } + +#ifndef FEATURE_PAL + if (m_pSymReader == NULL) + return E_FAIL; + + ToRelease<ISymUnmanagedMethod> pSymMethod(NULL); + IfFailRet(m_pSymReader->GetMethod(methodToken, &pSymMethod)); + + ULONG32 seqPointCount = 0; + IfFailRet(pSymMethod->GetSequencePointCount(&seqPointCount)); + + if (seqPointCount == 0) + return E_FAIL; + + // allocate memory for the objects to be fetched + ArrayHolder<ULONG32> offsets(new ULONG32[seqPointCount]); + ArrayHolder<ULONG32> lines(new ULONG32[seqPointCount]); + ArrayHolder<ULONG32> columns(new ULONG32[seqPointCount]); + ArrayHolder<ULONG32> endlines(new ULONG32[seqPointCount]); + ArrayHolder<ULONG32> endcolumns(new ULONG32[seqPointCount]); + ArrayHolder<ToRelease<ISymUnmanagedDocument>> documents(new ToRelease<ISymUnmanagedDocument>[seqPointCount]); + + ULONG32 realSeqPointCount = 0; + IfFailRet(pSymMethod->GetSequencePoints(seqPointCount, &realSeqPointCount, offsets, &(documents[0]), lines, columns, endlines, endcolumns)); + + const ULONG32 HiddenLine = 0x00feefee; + int bestSoFar = -1; + + for (int i = 0; i < (int)realSeqPointCount; i++) + { + if (offsets[i] > ilOffset) + break; + + if (lines[i] != HiddenLine) + bestSoFar = i; + } + + if (bestSoFar != -1) + { + ULONG32 cchNeeded = 0; + IfFailRet(documents[bestSoFar]->GetURL(cchFileName, &cchNeeded, pwszFileName)); + + *pLinenum = lines[bestSoFar]; + return S_OK; + } +#endif // FEATURE_PAL + + return E_FAIL; +} + +HRESULT SymbolReader::GetNamedLocalVariable(___in ISymUnmanagedScope * pScope, ___in ICorDebugILFrame * pILFrame, ___in mdMethodDef methodToken, + ___in ULONG localIndex, __out_ecount(paramNameLen) WCHAR* paramName, ___in ULONG paramNameLen, ICorDebugValue** ppValue) +{ + HRESULT Status = S_OK; + + if (m_symbolReaderHandle != 0) + { + _ASSERTE(getLocalVariableNameDelegate != nullptr); + + BSTR wszParamName = SysAllocStringLen(0, mdNameLen); + if (wszParamName == NULL) + { + return E_OUTOFMEMORY; + } + + if (getLocalVariableNameDelegate(m_symbolReaderHandle, methodToken, localIndex, &wszParamName) == FALSE) + { + SysFreeString(wszParamName); + return E_FAIL; + } + + wcscpy_s(paramName, paramNameLen, wszParamName); + SysFreeString(wszParamName); + + if (FAILED(pILFrame->GetLocalVariable(localIndex, ppValue)) || (*ppValue == NULL)) + { + *ppValue = NULL; + return E_FAIL; + } + return S_OK; + } + +#ifndef FEATURE_PAL + if (m_pSymReader == NULL) + return E_FAIL; + + if (pScope == NULL) + { + ToRelease<ISymUnmanagedMethod> pSymMethod; + IfFailRet(m_pSymReader->GetMethod(methodToken, &pSymMethod)); + + ToRelease<ISymUnmanagedScope> pScope; + IfFailRet(pSymMethod->GetRootScope(&pScope)); + + return GetNamedLocalVariable(pScope, pILFrame, methodToken, localIndex, paramName, paramNameLen, ppValue); + } + else + { + ULONG32 numVars = 0; + IfFailRet(pScope->GetLocals(0, &numVars, NULL)); + + ArrayHolder<ISymUnmanagedVariable*> pLocals = new ISymUnmanagedVariable*[numVars]; + IfFailRet(pScope->GetLocals(numVars, &numVars, pLocals)); + + for (ULONG i = 0; i < numVars; i++) + { + ULONG32 varIndexInMethod = 0; + if (SUCCEEDED(pLocals[i]->GetAddressField1(&varIndexInMethod))) + { + if (varIndexInMethod != localIndex) + continue; + + ULONG32 nameLen = 0; + if (FAILED(pLocals[i]->GetName(paramNameLen, &nameLen, paramName))) + swprintf_s(paramName, paramNameLen, W("local_%d\0"), localIndex); + + if (SUCCEEDED(pILFrame->GetLocalVariable(varIndexInMethod, ppValue)) && (*ppValue != NULL)) + { + for(ULONG j = 0; j < numVars; j++) pLocals[j]->Release(); + return S_OK; + } + else + { + *ppValue = NULL; + for(ULONG j = 0; j < numVars; j++) pLocals[j]->Release(); + return E_FAIL; + } + } + } + + ULONG32 numChildren = 0; + IfFailRet(pScope->GetChildren(0, &numChildren, NULL)); + + ArrayHolder<ISymUnmanagedScope*> pChildren = new ISymUnmanagedScope*[numChildren]; + IfFailRet(pScope->GetChildren(numChildren, &numChildren, pChildren)); + + for (ULONG i = 0; i < numChildren; i++) + { + if (SUCCEEDED(GetNamedLocalVariable(pChildren[i], pILFrame, methodToken, localIndex, paramName, paramNameLen, ppValue))) + { + for (ULONG j = 0; j < numChildren; j++) pChildren[j]->Release(); + return S_OK; + } + } + + for (ULONG j = 0; j < numChildren; j++) pChildren[j]->Release(); + } +#endif // FEATURE_PAL + + return E_FAIL; +} + +HRESULT SymbolReader::GetNamedLocalVariable(___in ICorDebugFrame * pFrame, ___in ULONG localIndex, __out_ecount(paramNameLen) WCHAR* paramName, + ___in ULONG paramNameLen, ___out ICorDebugValue** ppValue) +{ + HRESULT Status = S_OK; + + *ppValue = NULL; + paramName[0] = L'\0'; + + ToRelease<ICorDebugILFrame> pILFrame; + IfFailRet(pFrame->QueryInterface(IID_ICorDebugILFrame, (LPVOID*) &pILFrame)); + + ToRelease<ICorDebugFunction> pFunction; + IfFailRet(pFrame->GetFunction(&pFunction)); + + mdMethodDef methodDef; + ToRelease<ICorDebugClass> pClass; + ToRelease<ICorDebugModule> pModule; + IfFailRet(pFunction->GetClass(&pClass)); + IfFailRet(pFunction->GetModule(&pModule)); + IfFailRet(pFunction->GetToken(&methodDef)); + + return GetNamedLocalVariable(NULL, pILFrame, methodDef, localIndex, paramName, paramNameLen, ppValue); +} + +HRESULT SymbolReader::ResolveSequencePoint(__in_z WCHAR* pFilename, ___in ULONG32 lineNumber, ___in TADDR mod, ___out mdMethodDef* pToken, ___out ULONG32* pIlOffset) +{ + HRESULT Status = S_OK; + + if (m_symbolReaderHandle != 0) + { + _ASSERTE(resolveSequencePointDelegate != nullptr); + + char szName[mdNameLen]; + if (WideCharToMultiByte(CP_ACP, 0, pFilename, (int)(_wcslen(pFilename) + 1), szName, mdNameLen, NULL, NULL) == 0) + { + return E_FAIL; + } + if (resolveSequencePointDelegate(m_symbolReaderHandle, szName, lineNumber, pToken, pIlOffset) == FALSE) + { + return E_FAIL; + } + return S_OK; + } + +#ifndef FEATURE_PAL + if (m_pSymReader == NULL) + return E_FAIL; + + ULONG32 cDocs = 0; + ULONG32 cDocsNeeded = 0; + ArrayHolder<ToRelease<ISymUnmanagedDocument>> pDocs = NULL; + + IfFailRet(m_pSymReader->GetDocuments(cDocs, &cDocsNeeded, NULL)); + pDocs = new ToRelease<ISymUnmanagedDocument>[cDocsNeeded]; + cDocs = cDocsNeeded; + IfFailRet(m_pSymReader->GetDocuments(cDocs, &cDocsNeeded, &(pDocs[0]))); + + ULONG32 filenameLen = (ULONG32) _wcslen(pFilename); + + for (ULONG32 i = 0; i < cDocs; i++) + { + ULONG32 cchUrl = 0; + ULONG32 cchUrlNeeded = 0; + ArrayHolder<WCHAR> pUrl = NULL; + IfFailRet(pDocs[i]->GetURL(cchUrl, &cchUrlNeeded, pUrl)); + pUrl = new WCHAR[cchUrlNeeded]; + cchUrl = cchUrlNeeded; + IfFailRet(pDocs[i]->GetURL(cchUrl, &cchUrlNeeded, pUrl)); + + // If the URL is exactly as long as the filename then compare the two names directly + if (cchUrl-1 == filenameLen) + { + if (0!=_wcsicmp(pUrl, pFilename)) + continue; + } + // does the URL suffix match [back]slash + filename? + else if (cchUrl-1 > filenameLen) + { + WCHAR* slashLocation = pUrl + (cchUrl - filenameLen - 2); + if (*slashLocation != L'\\' && *slashLocation != L'/') + continue; + if (0 != _wcsicmp(slashLocation+1, pFilename)) + continue; + } + // URL is too short to match + else + continue; + + ULONG32 closestLine = 0; + if (FAILED(pDocs[i]->FindClosestLine(lineNumber, &closestLine))) + continue; + + ToRelease<ISymUnmanagedMethod> pSymUnmanagedMethod; + IfFailRet(m_pSymReader->GetMethodFromDocumentPosition(pDocs[i], closestLine, 0, &pSymUnmanagedMethod)); + IfFailRet(pSymUnmanagedMethod->GetToken(pToken)); + IfFailRet(pSymUnmanagedMethod->GetOffset(pDocs[i], closestLine, 0, pIlOffset)); + + // If this IL + if (*pIlOffset == -1) + { + return E_FAIL; + } + return S_OK; + } +#endif // FEATURE_PAL + + return E_FAIL; +} + +static void AddAssemblyName(WString& methodOutput, CLRDATA_ADDRESS mdesc) +{ + DacpMethodDescData mdescData; + if (SUCCEEDED(mdescData.Request(g_sos, mdesc))) + { + DacpModuleData dmd; + if (SUCCEEDED(dmd.Request(g_sos, mdescData.ModulePtr))) + { + ToRelease<IXCLRDataModule> pModule; + if (SUCCEEDED(g_sos->GetModule(mdescData.ModulePtr, &pModule))) + { + ArrayHolder<WCHAR> wszFileName = new WCHAR[MAX_LONGPATH + 1]; + ULONG32 nameLen = 0; + if (SUCCEEDED(pModule->GetFileName(MAX_LONGPATH, &nameLen, wszFileName))) + { + if (wszFileName[0] != W('\0')) + { + WCHAR *pJustName = _wcsrchr(wszFileName, DIRECTORY_SEPARATOR_CHAR_W); + if (pJustName == NULL) + pJustName = wszFileName - 1; + methodOutput += (pJustName + 1); + methodOutput += W("!"); + } + } + } + } + } +} + +WString GetFrameFromAddress(TADDR frameAddr, IXCLRDataStackWalk *pStackWalk, BOOL bAssemblyName) +{ + TADDR vtAddr; + MOVE(vtAddr, frameAddr); + + WString frameOutput; + frameOutput += W("["); + + if (SUCCEEDED(g_sos->GetFrameName(TO_CDADDR(vtAddr), mdNameLen, g_mdName, NULL))) + frameOutput += g_mdName; + else + frameOutput += W("Frame"); + + frameOutput += WString(W(": ")) + Pointer(frameAddr) + W("] "); + + // Print the frame's associated function info, if it has any. + CLRDATA_ADDRESS mdesc = 0; + if (SUCCEEDED(g_sos->GetMethodDescPtrFromFrame(frameAddr, &mdesc))) + { + if (SUCCEEDED(g_sos->GetMethodDescName(mdesc, mdNameLen, g_mdName, NULL))) + { + if (bAssemblyName) + { + AddAssemblyName(frameOutput, mdesc); + } + + frameOutput += g_mdName; + } + else + { + frameOutput += W("<unknown method>"); + } + } + else if (pStackWalk) + { + // The Frame did not have direct function info, so try to get the method instance + // (in this case a MethodDesc), and read the name from it. + ToRelease<IXCLRDataFrame> frame; + if (SUCCEEDED(pStackWalk->GetFrame(&frame))) + { + ToRelease<IXCLRDataMethodInstance> methodInstance; + if (SUCCEEDED(frame->GetMethodInstance(&methodInstance))) + { + // GetName can return S_FALSE if mdNameLen is not large enough. However we are already + // passing a pretty big buffer in. If this returns S_FALSE (meaning the buffer is too + // small) then we should not output it anyway. + if (methodInstance->GetName(0, mdNameLen, NULL, g_mdName) == S_OK) + frameOutput += g_mdName; + } + } + } + + return frameOutput; +} + +WString MethodNameFromIP(CLRDATA_ADDRESS ip, BOOL bSuppressLines, BOOL bAssemblyName, BOOL bDisplacement) +{ + ULONG linenum; + WString methodOutput; + CLRDATA_ADDRESS mdesc = 0; + + if (FAILED(g_sos->GetMethodDescPtrFromIP(ip, &mdesc))) + { + methodOutput = W("<unknown>"); + } + else + { + DacpMethodDescData mdescData; + if (SUCCEEDED(g_sos->GetMethodDescName(mdesc, mdNameLen, g_mdName, NULL))) + { + if (bAssemblyName) + { + AddAssemblyName(methodOutput, mdesc); + } + + methodOutput += g_mdName; + + if (bDisplacement) + { + if (SUCCEEDED(mdescData.Request(g_sos, mdesc))) + { + ULONG64 disp = (ip - mdescData.NativeCodeAddr); + if (disp) + { + methodOutput += W(" + "); + methodOutput += Decimal(disp); + } + } + } + } + else if (SUCCEEDED(mdescData.Request(g_sos, mdesc))) + { + DacpModuleData dmd; + BOOL bModuleNameWorked = FALSE; + ULONG64 addrInModule = ip; + if (SUCCEEDED(dmd.Request(g_sos, mdescData.ModulePtr))) + { + CLRDATA_ADDRESS peFileBase = 0; + if (SUCCEEDED(g_sos->GetPEFileBase(dmd.File, &peFileBase))) + { + if (peFileBase) + { + addrInModule = peFileBase; + } + } + } + ULONG Index; + ULONG64 moduleBase; + if (SUCCEEDED(g_ExtSymbols->GetModuleByOffset(UL64_TO_CDA(addrInModule), 0, &Index, &moduleBase))) + { + ArrayHolder<char> szModuleName = new char[MAX_LONGPATH+1]; + + if (SUCCEEDED(g_ExtSymbols->GetModuleNames(Index, moduleBase, NULL, 0, NULL, szModuleName, MAX_LONGPATH, NULL, NULL, 0, NULL))) + { + MultiByteToWideChar (CP_ACP, 0, szModuleName, MAX_LONGPATH, g_mdName, _countof(g_mdName)); + methodOutput += g_mdName; + methodOutput += W("!"); + } + } + methodOutput += W("<unknown method>"); + } + else + { + methodOutput = W("<unknown>"); + } + + ArrayHolder<WCHAR> wszFileName = new WCHAR[MAX_LONGPATH]; + if (!bSuppressLines && + SUCCEEDED(GetLineByOffset(TO_CDADDR(ip), &linenum, wszFileName, MAX_LONGPATH))) + { + methodOutput += WString(W(" [")) + wszFileName + W(" @ ") + Decimal(linenum) + W("]"); + } + } + + return methodOutput; +} + +HRESULT GetGCRefs(ULONG osID, SOSStackRefData **ppRefs, unsigned int *pRefCnt, SOSStackRefError **ppErrors, unsigned int *pErrCount) +{ + if (ppRefs == NULL || pRefCnt == NULL) + return E_POINTER; + + if (pErrCount) + *pErrCount = 0; + + *pRefCnt = 0; + unsigned int count = 0; + ToRelease<ISOSStackRefEnum> pEnum; + if (FAILED(g_sos->GetStackReferences(osID, &pEnum)) || FAILED(pEnum->GetCount(&count))) + { + ExtOut("Failed to enumerate GC references.\n"); + return E_FAIL; + } + + *ppRefs = new SOSStackRefData[count]; + if (FAILED(pEnum->Next(count, *ppRefs, pRefCnt))) + { + ExtOut("Failed to enumerate GC references.\n"); + return E_FAIL; + } + + SOS_Assert(count == *pRefCnt); + + // Enumerate errors found. Any bad HRESULT recieved while enumerating errors is NOT a fatal error. + // Hence we return S_FALSE if we encounter one. + + if (ppErrors && pErrCount) + { + ToRelease<ISOSStackRefErrorEnum> pErrors; + if (FAILED(pEnum->EnumerateErrors(&pErrors))) + { + ExtOut("Failed to enumerate GC reference errors.\n"); + return S_FALSE; + } + + if (FAILED(pErrors->GetCount(&count))) + { + ExtOut("Failed to enumerate GC reference errors.\n"); + return S_FALSE; + } + + *ppErrors = new SOSStackRefError[count]; + if (FAILED(pErrors->Next(count, *ppErrors, pErrCount))) + { + ExtOut("Failed to enumerate GC reference errors.\n"); + *pErrCount = 0; + return S_FALSE; + } + + SOS_Assert(count == *pErrCount); + } + return S_OK; +} + + +InternalFrameManager::InternalFrameManager() : m_cInternalFramesActual(0), m_iInternalFrameCur(0) {} + +HRESULT InternalFrameManager::Init(ICorDebugThread3 * pThread3) +{ + _ASSERTE(pThread3 != NULL); + + return pThread3->GetActiveInternalFrames( + _countof(m_rgpInternalFrame2), + &m_cInternalFramesActual, + &(m_rgpInternalFrame2[0])); +} + +HRESULT InternalFrameManager::PrintPrecedingInternalFrames(ICorDebugFrame * pFrame) +{ + HRESULT Status; + + for (; m_iInternalFrameCur < m_cInternalFramesActual; m_iInternalFrameCur++) + { + BOOL bIsCloser = FALSE; + IfFailRet(m_rgpInternalFrame2[m_iInternalFrameCur]->IsCloserToLeaf(pFrame, &bIsCloser)); + + if (!bIsCloser) + { + // Current internal frame is now past pFrame, so we're done + return S_OK; + } + + IfFailRet(PrintCurrentInternalFrame()); + } + + // Exhausted list of internal frames. Done! + return S_OK; +} + +HRESULT InternalFrameManager::PrintCurrentInternalFrame() +{ + _ASSERTE(m_iInternalFrameCur < m_cInternalFramesActual); + + HRESULT Status; + + CORDB_ADDRESS address; + IfFailRet(m_rgpInternalFrame2[m_iInternalFrameCur]->GetAddress(&address)); + + ToRelease<ICorDebugInternalFrame> pInternalFrame; + IfFailRet(m_rgpInternalFrame2[m_iInternalFrameCur]->QueryInterface(IID_ICorDebugInternalFrame, (LPVOID *) &pInternalFrame)); + + CorDebugInternalFrameType type; + IfFailRet(pInternalFrame->GetFrameType(&type)); + + LPCSTR szFrameType = NULL; + switch(type) + { + default: + szFrameType = "Unknown internal frame."; + break; + + case STUBFRAME_M2U: + szFrameType = "Managed to Unmanaged transition"; + break; + + case STUBFRAME_U2M: + szFrameType = "Unmanaged to Managed transition"; + break; + + case STUBFRAME_APPDOMAIN_TRANSITION: + szFrameType = "AppDomain transition"; + break; + + case STUBFRAME_LIGHTWEIGHT_FUNCTION: + szFrameType = "Lightweight function"; + break; + + case STUBFRAME_FUNC_EVAL: + szFrameType = "Function evaluation"; + break; + + case STUBFRAME_INTERNALCALL: + szFrameType = "Internal call"; + break; + + case STUBFRAME_CLASS_INIT: + szFrameType = "Class initialization"; + break; + + case STUBFRAME_EXCEPTION: + szFrameType = "Exception"; + break; + + case STUBFRAME_SECURITY: + szFrameType = "Security"; + break; + + case STUBFRAME_JIT_COMPILATION: + szFrameType = "JIT Compilation"; + break; + } + + DMLOut("%p %s ", SOS_PTR(address), SOS_PTR(0)); + ExtOut("[%s: %p]\n", szFrameType, SOS_PTR(address)); + + return S_OK; +} diff --git a/src/ToolBox/SOS/Strike/util.h b/src/ToolBox/SOS/Strike/util.h new file mode 100644 index 0000000000..f444c9fcb2 --- /dev/null +++ b/src/ToolBox/SOS/Strike/util.h @@ -0,0 +1,3292 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ==++== +// + +// +// ==--== +#ifndef __util_h__ +#define __util_h__ + +#define LIMITED_METHOD_CONTRACT + +// So we can use the PAL_TRY_NAKED family of macros without dependencies on utilcode. +inline void RestoreSOToleranceState() {} + +#include <cor.h> +#include <corsym.h> +#include <clrdata.h> +#include <palclr.h> +#include <metahost.h> +#include <new> + +#if !defined(FEATURE_PAL) +#include <dia2.h> +#endif + +#ifdef STRIKE +#if defined(_MSC_VER) +#pragma warning(disable:4200) +#pragma warning(default:4200) +#endif +#include "data.h" +#endif //STRIKE + +#include "cordebug.h" +#include "static_assert.h" + +typedef LPCSTR LPCUTF8; +typedef LPSTR LPUTF8; + +DECLARE_HANDLE(OBJECTHANDLE); + +struct IMDInternalImport; + +#if defined(_TARGET_WIN64_) +#define WIN64_8SPACES "" +#define WIN86_8SPACES " " +#define POINTERSIZE "16" +#define POINTERSIZE_HEX 16 +#define POINTERSIZE_BYTES 8 +#define POINTERSIZE_TYPE "I64" +#else +#define WIN64_8SPACES " " +#define WIN86_8SPACES "" +#define POINTERSIZE "8" +#define POINTERSIZE_HEX 8 +#define POINTERSIZE_BYTES 4 +#define POINTERSIZE_TYPE "I32" +#endif + +#if defined(_MSC_VER) +#pragma warning(disable:4510 4512 4610) +#endif + +#ifndef _ASSERTE +#ifdef _DEBUG +#define _ASSERTE(expr) \ + do { if (!(expr) ) { ExtErr("_ASSERTE fired:\n\t%s\n", #expr); if (IsDebuggerPresent()) DebugBreak(); } } while (0) +#else +#define _ASSERTE(x) +#endif +#endif // ASSERTE + +#ifdef _DEBUG +#define ASSERT_CHECK(expr, msg, reason) \ + do { if (!(expr) ) { ExtOut(reason); ExtOut(msg); ExtOut(#expr); DebugBreak(); } } while (0) +#endif + +// PREFIX macros - Begin + +// SOS does not have support for Contracts. Therefore we needed to duplicate +// some of the PREFIX infrastructure from inc\check.h in here. + +// Issue - PREFast_:510 v4.51 does not support __assume(0) +#if (defined(_MSC_VER) && !defined(_PREFAST_)) || defined(_PREFIX_) +#if defined(_AMD64_) +// Empty methods that consist of UNREACHABLE() result in a zero-sized declspec(noreturn) method +// which causes the pdb file to make the next method declspec(noreturn) as well, thus breaking BBT +// Remove when we get a VC compiler that fixes VSW 449170 +# define __UNREACHABLE() DebugBreak(); __assume(0); +#else +# define __UNREACHABLE() __assume(0) +#endif +#else +#define __UNREACHABLE() do { } while(true) +#endif + + +#if defined(_PREFAST_) || defined(_PREFIX_) +#define COMPILER_ASSUME_MSG(_condition, _message) if (!(_condition)) __UNREACHABLE(); +#else + +#if defined(DACCESS_COMPILE) +#define COMPILER_ASSUME_MSG(_condition, _message) do { } while (0) +#else + +#if defined(_DEBUG) +#define COMPILER_ASSUME_MSG(_condition, _message) \ + ASSERT_CHECK(_condition, _message, "Compiler optimization assumption invalid") +#else +#define COMPILER_ASSUME_MSG(_condition, _message) __assume(_condition) +#endif // _DEBUG + +#endif // DACCESS_COMPILE + +#endif // _PREFAST_ || _PREFIX_ + +#define PREFIX_ASSUME(_condition) \ + COMPILER_ASSUME_MSG(_condition, "") + +// PREFIX macros - End + +class MethodTable; + +#define MD_NOT_YET_LOADED ((DWORD_PTR)-1) +/* + * HANDLES + * + * The default type of handle is a strong handle. + * + */ +#define HNDTYPE_DEFAULT HNDTYPE_STRONG +#define HNDTYPE_WEAK_DEFAULT HNDTYPE_WEAK_LONG +#define HNDTYPE_WEAK_SHORT (0) +#define HNDTYPE_WEAK_LONG (1) +#define HNDTYPE_STRONG (2) +#define HNDTYPE_PINNED (3) +#define HNDTYPE_VARIABLE (4) +#define HNDTYPE_REFCOUNTED (5) +#define HNDTYPE_DEPENDENT (6) +#define HNDTYPE_ASYNCPINNED (7) +#define HNDTYPE_SIZEDREF (8) +#define HNDTYPE_WEAK_WINRT (9) + +// Anything above this we consider abnormal and stop processing heap information +const int nMaxHeapSegmentCount = 1000; + +class BaseObject +{ + MethodTable *m_pMethTab; +}; + + +const BYTE gElementTypeInfo[] = { +#define TYPEINFO(e,ns,c,s,g,ia,ip,if,im,gv) s, +#include "cortypeinfo.h" +#undef TYPEINFO +}; + +typedef struct tagLockEntry +{ + tagLockEntry *pNext; // next entry + tagLockEntry *pPrev; // prev entry + DWORD dwULockID; + DWORD dwLLockID; // owning lock + WORD wReaderLevel; // reader nesting level +} LockEntry; + +#define MAX_CLASSNAME_LENGTH 1024 + +enum EEFLAVOR {UNKNOWNEE, MSCOREE, MSCORWKS, MSCOREND}; + +#include "sospriv.h" +extern IXCLRDataProcess *g_clrData; +extern ISOSDacInterface *g_sos; + +#include "dacprivate.h" + +interface ICorDebugProcess; +extern ICorDebugProcess * g_pCorDebugProcess; + +// This class is templated for easy modification. We may need to update the CachedString +// or related classes to use WCHAR instead of char in the future. +template <class T, int count, int size> +class StaticData +{ +public: + StaticData() + { + for (int i = 0; i < count; ++i) + InUse[i] = false; + } + + // Whether the individual data pointers in the cache are in use. + bool InUse[count]; + + // The actual data itself. + T Data[count][size]; + + // The number of arrays in the cache. + static const int Count; + + // The size of each individual array. + static const int Size; +}; + +class CachedString +{ +public: + CachedString(); + CachedString(const CachedString &str); + ~CachedString(); + + const CachedString &operator=(const CachedString &str); + + // Returns the capacity of this string. + size_t GetStrLen() const + { + return mSize; + } + + // Returns a mutable character pointer. Be sure not to write past the + // length of this string. + inline operator char *() + { + return mPtr; + } + + // Returns a const char representation of this string. + inline operator const char *() const + { + return GetPtr(); + } + + // To ensure no AV's, any time a constant pointer is requested, we will + // return an empty string "" if we hit an OOM. This will only happen + // if we hit an OOM and do not check for it before using the string. + // If you request a non-const char pointer out of this class, it may be + // null (see operator char *). + inline const char *GetPtr() const + { + if (!mPtr || IsOOM()) + return ""; + + return mPtr; + } + + // Returns true if we ran out of memory trying to allocate the string + // or the refcount. + bool IsOOM() const + { + return mIndex == -2; + } + + // allocate a string of the specified size. this will Clear() any + // previously allocated string. call IsOOM() to check for failure. + void Allocate(int size); + +private: + // Copies rhs into this string. + void Copy(const CachedString &rhs); + + // Clears this string, releasing any underlying memory. + void Clear(); + + // Creates a new string. + void Create(); + + // Sets an out of memory state. + void SetOOM(); + +private: + char *mPtr; + + // The reference count. This may be null if there is only one copy + // of this string. + mutable unsigned int *mRefCount; + + // mIndex contains the index of the cached pointer we are using, or: + // ~0 - poison value we initialize it to for debugging purposes + // -1 - mPtr points to a pointer we have new'ed + // -2 - We hit an oom trying to allocate either mCount or mPtr + int mIndex; + + // contains the size of current string + int mSize; + +private: + static StaticData<char, 4, 1024> cache; +}; + +// Things in this namespace should not be directly accessed/called outside of +// the output-related functions. +namespace Output +{ + extern unsigned int g_bSuppressOutput; + extern unsigned int g_Indent; + extern unsigned int g_DMLEnable; + extern bool g_bDbgOutput; + extern bool g_bDMLExposed; + + inline bool IsOutputSuppressed() + { return g_bSuppressOutput > 0; } + + inline void ResetIndent() + { g_Indent = 0; } + + inline void SetDebugOutputEnabled(bool enabled) + { g_bDbgOutput = enabled; } + + inline bool IsDebugOutputEnabled() + { return g_bDbgOutput; } + + inline void SetDMLExposed(bool exposed) + { g_bDMLExposed = exposed; } + + inline bool IsDMLExposed() + { return g_bDMLExposed; } + + enum FormatType + { + DML_None, + DML_MethodTable, + DML_MethodDesc, + DML_EEClass, + DML_Module, + DML_IP, + DML_Object, + DML_Domain, + DML_Assembly, + DML_ThreadID, + DML_ValueClass, + DML_DumpHeapMT, + DML_ListNearObj, + DML_ThreadState, + DML_PrintException, + DML_RCWrapper, + DML_CCWrapper, + DML_ManagedVar, + }; + + /**********************************************************************\ + * This function builds a DML string for a ValueClass. If DML is * + * enabled, this function returns a DML string based on the format * + * type. Otherwise this returns a string containing only the hex value * + * of addr. * + * * + * Params: * + * mt - the method table of the ValueClass * + * addr - the address of the ValueClass * + * type - the format type to use to output this object * + * fill - whether or not to pad the hex value with zeros * + * * + \**********************************************************************/ + CachedString BuildVCValue(CLRDATA_ADDRESS mt, CLRDATA_ADDRESS addr, FormatType type, bool fill = true); + + + /**********************************************************************\ + * This function builds a DML string for an object. If DML is enabled, * + * this function returns a DML string based on the format type. * + * Otherwise this returns a string containing only the hex value of * + * addr. * + * * + * Params: * + * addr - the address of the object * + * type - the format type to use to output this object * + * fill - whether or not to pad the hex value with zeros * + * * + \**********************************************************************/ + CachedString BuildHexValue(CLRDATA_ADDRESS addr, FormatType type, bool fill = true); + + /**********************************************************************\ + * This function builds a DML string for an managed variable name. * + * If DML is enabled, this function returns a DML string that will * + * enable the expansion of that managed variable using the !ClrStack * + * command to display the variable's fields, otherwise it will just * + * return the variable's name as a string. + * * + * Params: * + * expansionName - the current variable expansion string * + * frame - the frame that contains the variable of interest * + * simpleName - simple name of the managed variable * + * * + \**********************************************************************/ + CachedString BuildManagedVarValue(__in_z LPCWSTR expansionName, ULONG frame, __in_z LPCWSTR simpleName, FormatType type); + CachedString BuildManagedVarValue(__in_z LPCWSTR expansionName, ULONG frame, int indexInArray, FormatType type); //used for array indices (simpleName = "[<indexInArray>]") +} + +class NoOutputHolder +{ +public: + NoOutputHolder(BOOL bSuppress = TRUE); + ~NoOutputHolder(); + +private: + BOOL mSuppress; +}; + +class EnableDMLHolder +{ +public: + EnableDMLHolder(BOOL enable); + ~EnableDMLHolder(); + +private: + BOOL mEnable; +}; + +size_t CountHexCharacters(CLRDATA_ADDRESS val); + +// Normal output. +void DMLOut(PCSTR format, ...); /* Prints out DML strings. */ +void IfDMLOut(PCSTR format, ...); /* Prints given DML string ONLY if DML is enabled; prints nothing otherwise. */ +void ExtOut(PCSTR Format, ...); /* Prints out to ExtOut (no DML). */ +void ExtWarn(PCSTR Format, ...); /* Prints out to ExtWarn (no DML). */ +void ExtErr(PCSTR Format, ...); /* Prints out to ExtErr (no DML). */ +void ExtDbgOut(PCSTR Format, ...); /* Prints out to ExtOut in a checked build (no DML). */ +void WhitespaceOut(int count); /* Prints out "count" number of spaces in the output. */ + +// Change indent for ExtOut +inline void IncrementIndent() { Output::g_Indent++; } +inline void DecrementIndent() { if (Output::g_Indent > 0) Output::g_Indent--; } +inline void ExtOutIndent() { WhitespaceOut(Output::g_Indent << 2); } + +// DML Generation Methods +#define DMLListNearObj(addr) Output::BuildHexValue(addr, Output::DML_ListNearObj).GetPtr() +#define DMLDumpHeapMT(addr) Output::BuildHexValue(addr, Output::DML_DumpHeapMT).GetPtr() +#define DMLMethodTable(addr) Output::BuildHexValue(addr, Output::DML_MethodTable).GetPtr() +#define DMLMethodDesc(addr) Output::BuildHexValue(addr, Output::DML_MethodDesc).GetPtr() +#define DMLClass(addr) Output::BuildHexValue(addr, Output::DML_EEClass).GetPtr() +#define DMLModule(addr) Output::BuildHexValue(addr, Output::DML_Module).GetPtr() +#define DMLIP(ip) Output::BuildHexValue(ip, Output::DML_IP).GetPtr() +#define DMLObject(addr) Output::BuildHexValue(addr, Output::DML_Object).GetPtr() +#define DMLDomain(addr) Output::BuildHexValue(addr, Output::DML_Domain).GetPtr() +#define DMLAssembly(addr) Output::BuildHexValue(addr, Output::DML_Assembly).GetPtr() +#define DMLThreadID(id) Output::BuildHexValue(id, Output::DML_ThreadID, false).GetPtr() +#define DMLValueClass(mt, addr) Output::BuildVCValue(mt, addr, Output::DML_ValueClass).GetPtr() +#define DMLRCWrapper(addr) Output::BuildHexValue(addr, Output::DML_RCWrapper).GetPtr() +#define DMLCCWrapper(addr) Output::BuildHexValue(addr, Output::DML_CCWrapper).GetPtr() +#define DMLManagedVar(expansionName,frame,simpleName) Output::BuildManagedVarValue(expansionName, frame, simpleName, Output::DML_ManagedVar).GetPtr() + +bool IsDMLEnabled(); + + +#ifndef SOS_Assert +#define SOS_Assert(x) +#endif + +void ConvertToLower(__out_ecount(len) char *buffer, size_t len); + +extern const char * const DMLFormats[]; +int GetHex(CLRDATA_ADDRESS addr, __out_ecount(len) char *out, size_t len, bool fill); + +// A simple string class for mutable strings. We cannot use STL, so this is a stand in replacement +// for std::string (though it doesn't use the same interface). +template <class T, size_t (__cdecl *LEN)(const T *), errno_t (__cdecl *COPY)(T *, size_t, const T * _Src)> +class BaseString +{ +public: + BaseString() + : mStr(0), mSize(0), mLength(0) + { + const size_t size = 64; + + mStr = new T[size]; + mSize = size; + mStr[0] = 0; + } + + BaseString(const T *str) + : mStr(0), mSize(0), mLength(0) + { + CopyFrom(str, LEN(str)); + } + + BaseString(const BaseString<T, LEN, COPY> &rhs) + : mStr(0), mSize(0), mLength(0) + { + *this = rhs; + } + + ~BaseString() + { + Clear(); + } + + const BaseString<T, LEN, COPY> &operator=(const BaseString<T, LEN, COPY> &rhs) + { + Clear(); + CopyFrom(rhs.mStr, rhs.mLength); + return *this; + } + + const BaseString<T, LEN, COPY> &operator=(const T *str) + { + Clear(); + CopyFrom(str, LEN(str)); + return *this; + } + + const BaseString<T, LEN, COPY> &operator +=(const T *str) + { + size_t len = LEN(str); + CopyFrom(str, len); + return *this; + } + + const BaseString<T, LEN, COPY> &operator +=(const BaseString<T, LEN, COPY> &str) + { + CopyFrom(str.mStr, str.mLength); + return *this; + } + + BaseString<T, LEN, COPY> operator+(const T *str) const + { + return BaseString<T, LEN, COPY>(mStr, mLength, str, LEN(str)); + } + + BaseString<T, LEN, COPY> operator+(const BaseString<T, LEN, COPY> &str) const + { + return BaseString<T, LEN, COPY>(mStr, mLength, str.mStr, str.mLength); + } + + operator const T *() const + { + return mStr; + } + + const T *c_str() const + { + return mStr; + } + + size_t GetLength() const + { + return mLength; + } + +private: + BaseString(const T * str1, size_t len1, const T * str2, size_t len2) + : mStr(0), mSize(0), mLength(0) + { + const size_t size = len1 + len2 + 1 + ((len1 + len2) >> 1); + mStr = new T[size]; + mSize = size; + + CopyFrom(str1, len1); + CopyFrom(str2, len2); + } + + void Clear() + { + mLength = 0; + mSize = 0; + if (mStr) + { + delete [] mStr; + mStr = 0; + } + } + + void CopyFrom(const T *str, size_t len) + { + if (mLength + len + 1 >= mSize) + Resize(mLength + len + 1); + + COPY(mStr+mLength, mSize-mLength, str); + mLength += len; + } + + void Resize(size_t size) + { + /* We always resize at least one half bigger than we need. When CopyFrom requests a resize + * it asks for the exact size that's needed to concatenate strings. However in practice + * it's common to add multiple strings together in a row, e.g.: + * String foo = "One " + "Two " + "Three " + "Four " + "\n"; + * Ensuring the size of the string is bigger than we need, and that the minimum size is 64, + * we will cut down on a lot of needless resizes at the cost of a few bytes wasted in some + * cases. + */ + size += size >> 1; + if (size < 64) + size = 64; + + T *newStr = new T[size]; + + if (mStr) + { + COPY(newStr, size, mStr); + delete [] mStr; + } + else + { + newStr[0] = 0; + } + + mStr = newStr; + mSize = size; + } +private: + T *mStr; + size_t mSize, mLength; +}; + +typedef BaseString<char, strlen, strcpy_s> String; +typedef BaseString<WCHAR, _wcslen, wcscpy_s> WString; + + +template<class T> +void Flatten(__out_ecount(len) T *data, unsigned int len) +{ + for (unsigned int i = 0; i < len; ++i) + if (data[i] < 32 || (data[i] > 126 && data[i] <= 255)) + data[i] = '.'; + data[len] = 0; +} + +void Flatten(__out_ecount(len) char *data, unsigned int len); + +/* Formats for the Format class. We support the following formats: + * Pointer - Same as %p. + * Hex - Same as %x (same as %p, but does not output preceding zeros. + * PrefixHex - Same as %x, but prepends 0x. + * Decimal - Same as %d. + * Strings and wide strings don't use this. + */ +class Formats +{ +public: + enum Format + { + Default, + Pointer, + Hex, + PrefixHex, + Decimal, + }; +}; + +enum Alignment +{ + AlignLeft, + AlignRight +}; + +namespace Output +{ + /* Defines how a value will be printed. This class understands how to format + * and print values according to the format and DML settings provided. + * The raw templated class handles the pointer/integer case. Support for + * character arrays and wide character arrays are handled by template + * specializations. + * + * Note that this class is not used directly. Instead use the typedefs and + * macros which define the type of data you are outputing (for example ObjectPtr, + * MethodTablePtr, etc). + */ + template <class T> + class Format + { + public: + Format(T value) + : mValue(value), mFormat(Formats::Default), mDml(Output::DML_None) + { + } + + Format(T value, Formats::Format format, Output::FormatType dmlType) + : mValue(value), mFormat(format), mDml(dmlType) + { + } + + Format(const Format<T> &rhs) + : mValue(rhs.mValue), mFormat(rhs.mFormat), mDml(rhs.mDml) + { + } + + /* Prints out the value according to the Format and DML settings provided. + */ + void Output() const + { + if (IsDMLEnabled() && mDml != Output::DML_None) + { + const int len = GetDMLWidth(mDml); + char *buffer = (char*)alloca(len); + + BuildDML(buffer, len, (CLRDATA_ADDRESS)mValue, mFormat, mDml); + DMLOut(buffer); + } + else + { + if (mFormat == Formats::Default || mFormat == Formats::Pointer) + { + ExtOut("%p", SOS_PTR(mValue)); + } + else + { + const char *format = NULL; + if (mFormat == Formats::PrefixHex) + { + format = "0x%x"; + } + else if (mFormat == Formats::Hex) + { + format = "%x"; + } + else if (mFormat == Formats::Decimal) + { + format = "%d"; + } + + ExtOut(format, (__int32)mValue); + } + + } + } + + /* Prints out the value based on a specified width and alignment. + * Params: + * align - Whether the output should be left or right justified. + * width - The output width to fill. + * Note: + * This function guarantees that exactly width will be printed out (so if width is 24, + * exactly 24 characters will be printed), even if the output wouldn't normally fit + * in the space provided. This function makes no guarantees as to what part of the + * data will be printed in the case that width isn't wide enough. + */ + void OutputColumn(Alignment align, int width) const + { + bool leftAlign = align == AlignLeft; + if (IsDMLEnabled() && mDml != Output::DML_None) + { + const int len = GetDMLColWidth(mDml, width); + char *buffer = (char*)alloca(len); + + BuildDMLCol(buffer, len, (CLRDATA_ADDRESS)mValue, mFormat, mDml, leftAlign, width); + DMLOut(buffer); + } + else + { + int precision = GetPrecision(); + if (mFormat == Formats::Default || mFormat == Formats::Pointer) + { + if (precision > width) + precision = width; + + ExtOut(leftAlign ? "%-*.*p" : "%*.*p", width, precision, SOS_PTR(mValue)); + } + else + { + const char *format = NULL; + if (mFormat == Formats::PrefixHex) + { + format = leftAlign ? "0x%-*.*x" : "0x%*.*x"; + width -= 2; + } + else if (mFormat == Formats::Hex) + { + format = leftAlign ? "%-*.*x" : "%*.*x"; + } + else if (mFormat == Formats::Decimal) + { + format = leftAlign ? "%-*.*d" : "%*.*d"; + } + + if (precision > width) + precision = width; + + ExtOut(format, width, precision, (__int32)mValue); + } + } + } + + /* Converts this object into a Wide char string. This allows you to write the following code: + * WString foo = L"bar " + ObjectPtr(obj); + * Where ObjectPtr is a subclass/typedef of this Format class. + */ + operator WString() const + { + String str = *this; + const char *cstr = (const char *)str; + + int len = MultiByteToWideChar(CP_ACP, 0, cstr, -1, NULL, 0); + WCHAR *buffer = (WCHAR *)alloca(len*sizeof(WCHAR)); + + MultiByteToWideChar(CP_ACP, 0, cstr, -1, buffer, len); + + return WString(buffer); + } + + /* Converts this object into a String object. This allows you to write the following code: + * String foo = "bar " + ObjectPtr(obj); + * Where ObjectPtr is a subclass/typedef of this Format class. + */ + operator String() const + { + if (IsDMLEnabled() && mDml != Output::DML_None) + { + const int len = GetDMLColWidth(mDml, 0); + char *buffer = (char*)alloca(len); + + BuildDMLCol(buffer, len, (CLRDATA_ADDRESS)mValue, mFormat, mDml, false, 0); + return buffer; + } + else + { + char buffer[64]; + if (mFormat == Formats::Default || mFormat == Formats::Pointer) + { + sprintf_s(buffer, _countof(buffer), "%p", (int *)(SIZE_T)mValue); + ConvertToLower(buffer, _countof(buffer)); + } + else + { + const char *format = NULL; + if (mFormat == Formats::PrefixHex) + format = "0x%x"; + else if (mFormat == Formats::Hex) + format = "%x"; + else if (mFormat == Formats::Decimal) + format = "%d"; + + sprintf_s(buffer, _countof(buffer), format, (__int32)mValue); + ConvertToLower(buffer, _countof(buffer)); + } + + return buffer; + } + } + + private: + int GetPrecision() const + { + if (mFormat == Formats::Hex || mFormat == Formats::PrefixHex) + { + ULONGLONG val = mValue; + int count = 0; + while (val) + { + val >>= 4; + count++; + } + + if (count == 0) + count = 1; + + return count; + } + + else if (mFormat == Formats::Decimal) + { + T val = mValue; + int count = (val > 0) ? 0 : 1; + while (val) + { + val /= 10; + count++; + } + + return count; + } + + // mFormat == Formats::Pointer + return sizeof(int*)*2; + } + + static inline void BuildDML(__out_ecount(len) char *result, int len, CLRDATA_ADDRESS value, Formats::Format format, Output::FormatType dmlType) + { + BuildDMLCol(result, len, value, format, dmlType, true, 0); + } + + static int GetDMLWidth(Output::FormatType dmlType) + { + return GetDMLColWidth(dmlType, 0); + } + + static void BuildDMLCol(__out_ecount(len) char *result, int len, CLRDATA_ADDRESS value, Formats::Format format, Output::FormatType dmlType, bool leftAlign, int width) + { + char hex[64]; + int count = GetHex(value, hex, _countof(hex), format != Formats::Hex); + int i = 0; + + if (!leftAlign) + { + for (; i < width - count; ++i) + result[i] = ' '; + + result[i] = 0; + } + + int written = sprintf_s(result+i, len - i, DMLFormats[dmlType], hex, hex); + + SOS_Assert(written != -1); + if (written != -1) + { + for (i = i + written; i < width; ++i) + result[i] = ' '; + + result[i] = 0; + } + } + + static int GetDMLColWidth(Output::FormatType dmlType, int width) + { + return 1 + 4*sizeof(int*) + (int)strlen(DMLFormats[dmlType]) + width; + } + + private: + T mValue; + Formats::Format mFormat; + Output::FormatType mDml; + }; + + /* Format class used for strings. + */ + template <> + class Format<const char *> + { + public: + Format(const char *value) + : mValue(value) + { + } + + Format(const Format<const char *> &rhs) + : mValue(rhs.mValue) + { + } + + void Output() const + { + if (IsDMLEnabled()) + DMLOut("%s", mValue); + else + ExtOut("%s", mValue); + } + + void OutputColumn(Alignment align, int width) const + { + int precision = (int)strlen(mValue); + + if (precision > width) + precision = width; + + const char *format = align == AlignLeft ? "%-*.*s" : "%*.*s"; + + if (IsDMLEnabled()) + DMLOut(format, width, precision, mValue); + else + ExtOut(format, width, precision, mValue); + } + + private: + const char *mValue; + }; + + /* Format class for wide char strings. + */ + template <> + class Format<const WCHAR *> + { + public: + Format(const WCHAR *value) + : mValue(value) + { + } + + Format(const Format<const WCHAR *> &rhs) + : mValue(rhs.mValue) + { + } + + void Output() const + { + if (IsDMLEnabled()) + DMLOut("%S", mValue); + else + ExtOut("%S", mValue); + } + + void OutputColumn(Alignment align, int width) const + { + int precision = (int)_wcslen(mValue); + if (precision > width) + precision = width; + + const char *format = align == AlignLeft ? "%-*.*S" : "%*.*S"; + + if (IsDMLEnabled()) + DMLOut(format, width, precision, mValue); + else + ExtOut(format, width, precision, mValue); + } + + private: + const WCHAR *mValue; + }; + + + template <class T> + void InternalPrint(const T &t) + { + Format<T>(t).Output(); + } + + template <class T> + void InternalPrint(const Format<T> &t) + { + t.Output(); + } + + inline void InternalPrint(const char t[]) + { + Format<const char *>(t).Output(); + } +} + +#define DefineFormatClass(name, format, dml) \ + template <class T> \ + Output::Format<T> name(T value) \ + { return Output::Format<T>(value, format, dml); } + +DefineFormatClass(EEClassPtr, Formats::Pointer, Output::DML_EEClass); +DefineFormatClass(ObjectPtr, Formats::Pointer, Output::DML_Object); +DefineFormatClass(ExceptionPtr, Formats::Pointer, Output::DML_PrintException); +DefineFormatClass(ModulePtr, Formats::Pointer, Output::DML_Module); +DefineFormatClass(MethodDescPtr, Formats::Pointer, Output::DML_MethodDesc); +DefineFormatClass(AppDomainPtr, Formats::Pointer, Output::DML_Domain); +DefineFormatClass(ThreadState, Formats::Hex, Output::DML_ThreadState); +DefineFormatClass(ThreadID, Formats::Hex, Output::DML_ThreadID); +DefineFormatClass(RCWrapper, Formats::Pointer, Output::DML_RCWrapper); +DefineFormatClass(CCWrapper, Formats::Pointer, Output::DML_CCWrapper); +DefineFormatClass(InstructionPtr, Formats::Pointer, Output::DML_IP); +DefineFormatClass(NativePtr, Formats::Pointer, Output::DML_None); + +DefineFormatClass(Decimal, Formats::Decimal, Output::DML_None); +DefineFormatClass(Pointer, Formats::Pointer, Output::DML_None); +DefineFormatClass(PrefixHex, Formats::PrefixHex, Output::DML_None); +DefineFormatClass(Hex, Formats::Hex, Output::DML_None); + +#undef DefineFormatClass + +template <class T0> +void Print(const T0 &val0) +{ + Output::InternalPrint(val0); +} + +template <class T0, class T1> +void Print(const T0 &val0, const T1 &val1) +{ + Output::InternalPrint(val0); + Output::InternalPrint(val1); +} + +template <class T0> +void PrintLn(const T0 &val0) +{ + Output::InternalPrint(val0); + ExtOut("\n"); +} + +template <class T0, class T1> +void PrintLn(const T0 &val0, const T1 &val1) +{ + Output::InternalPrint(val0); + Output::InternalPrint(val1); + ExtOut("\n"); +} + +template <class T0, class T1, class T2> +void PrintLn(const T0 &val0, const T1 &val1, const T2 &val2) +{ + Output::InternalPrint(val0); + Output::InternalPrint(val1); + Output::InternalPrint(val2); + ExtOut("\n"); +} + + +/* This class handles the formatting for output which is in a table format. To use this class you define + * how the table is formatted by setting the number of columns in the table, the default column width, + * the default column alignment, the indentation (whitespace) for the table, and the amount of padding + * (whitespace) between each column. Once this has been setup, you output rows at a time or individual + * columns to build the output instead of manually tabbing out space. + * Also note that this class was built to work with the Format class. When outputing data, use the + * predefined output types to specify the format (such as ObjectPtr, MethodDescPtr, Decimal, etc). This + * tells the TableOutput class how to display the data, and where applicable, it automatically generates + * the appropriate DML output. See the DefineFormatClass macro. + */ +class TableOutput +{ +public: + + TableOutput() + : mColumns(0), mDefaultWidth(0), mIndent(0), mPadding(0), mCurrCol(0), mDefaultAlign(AlignLeft), + mWidths(0), mAlignments(0) + { + } + /* Constructor. + * Params: + * numColumns - the number of columns the table has + * defaultColumnWidth - the default width of each column + * alignmentDefault - whether columns are by default left aligned or right aligned + * indent - the amount of whitespace to prefix at the start of the row (in characters) + * padding - the amount of whitespace to place between each column (in characters) + */ + TableOutput(int numColumns, int defaultColumnWidth, Alignment alignmentDefault = AlignLeft, int indent = 0, int padding = 1) + : mColumns(numColumns), mDefaultWidth(defaultColumnWidth), mIndent(indent), mPadding(padding), mCurrCol(0), mDefaultAlign(alignmentDefault), + mWidths(0), mAlignments(0) + { + } + + ~TableOutput() + { + Clear(); + } + + /* See the documentation for the constructor. + */ + void ReInit(int numColumns, int defaultColumnWidth, Alignment alignmentDefault = AlignLeft, int indent = 0, int padding = 1); + + /* Sets the amount of whitespace to prefix at the start of the row (in characters). + */ + void SetIndent(int indent) + { + SOS_Assert(indent >= 0); + + mIndent = indent; + } + + /* Sets the exact widths for the the given columns. + * Params: + * columns - the number of columns you are providing the width for, starting at the first column + * ... - an int32 for each column (given by the number of columns in the first parameter). + * Example: + * If you have 5 columns in the table, you can set their widths like so: + * tableOutput.SetWidths(5, 2, 3, 5, 7, 13); + * Note: + * It's fine to pass a value for "columns" less than the number of columns in the table. This + * is useful when you set the default column width to be correct for most of the table, and need + * to make a minor adjustment to a few. + */ + void SetWidths(int columns, ...); + + /* Individually sets a column to the given width. + * Params: + * col - the column to set, 0 indexed + * width - the width of the column (note this must be non-negative) + */ + void SetColWidth(int col, int width); + + /* Individually sets the column alignment. + * Params: + * col - the column to set, 0 indexed + * align - the new alignment (left or right) for the column + */ + void SetColAlignment(int col, Alignment align); + + + /* The WriteRow family of functions allows you to write an entire row of the table at once. + * The common use case for the TableOutput class is to individually output each column after + * calculating what the value should contain. However, this would be tedious if you already + * knew the contents of the entire row which usually happenes when you are printing out the + * header for the table. To use this, simply pass each column as an individual parameter, + * for example: + * tableOutput.WriteRow("First Column", "Second Column", Decimal(3), PrefixHex(4), "Fifth Column"); + */ + template <class T0, class T1> + void WriteRow(T0 t0, T1 t1) + { + WriteColumn(0, t0); + WriteColumn(1, t1); + } + + template <class T0, class T1, class T2> + void WriteRow(T0 t0, T1 t1, T2 t2) + { + WriteColumn(0, t0); + WriteColumn(1, t1); + WriteColumn(2, t2); + } + + + template <class T0, class T1, class T2, class T3> + void WriteRow(T0 t0, T1 t1, T2 t2, T3 t3) + { + WriteColumn(0, t0); + WriteColumn(1, t1); + WriteColumn(2, t2); + WriteColumn(3, t3); + } + + + template <class T0, class T1, class T2, class T3, class T4> + void WriteRow(T0 t0, T1 t1, T2 t2, T3 t3, T4 t4) + { + WriteColumn(0, t0); + WriteColumn(1, t1); + WriteColumn(2, t2); + WriteColumn(3, t3); + WriteColumn(4, t4); + } + + template <class T0, class T1, class T2, class T3, class T4, class T5> + void WriteRow(T0 t0, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5) + { + WriteColumn(0, t0); + WriteColumn(1, t1); + WriteColumn(2, t2); + WriteColumn(3, t3); + WriteColumn(4, t4); + WriteColumn(5, t5); + } + + template <class T0, class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9> + void WriteRow(T0 t0, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8, T9 t9) + { + WriteColumn(0, t0); + WriteColumn(1, t1); + WriteColumn(2, t2); + WriteColumn(3, t3); + WriteColumn(4, t4); + WriteColumn(5, t5); + WriteColumn(6, t6); + WriteColumn(7, t7); + WriteColumn(8, t8); + WriteColumn(9, t9); + } + + /* The WriteColumn family of functions is used to output individual columns in the table. + * The intent is that the bulk of the table will be generated in a loop like so: + * while (condition) { + * int value1 = CalculateFirstColumn(); + * table.WriteColumn(0, value1); + * + * String value2 = CalculateSecondColumn(); + * table.WriteColumn(1, value2); + * } + * Params: + * col - the column to write, 0 indexed + * t - the value to write + * Note: + * You should generally use the specific instances of the Format class to generate output. + * For example, use the "Decimal", "Pointer", "ObjectPtr", etc. When passing data to this + * function. This tells the Table class how to display the value. + */ + template <class T> + void WriteColumn(int col, const Output::Format<T> &t) + { + SOS_Assert(col >= 0); + SOS_Assert(col < mColumns); + + if (col != mCurrCol) + OutputBlankColumns(col); + + if (col == 0) + OutputIndent(); + + bool lastCol = col == mColumns - 1; + + if (!lastCol) + t.OutputColumn(GetColAlign(col), GetColumnWidth(col)); + else + t.Output(); + + ExtOut(lastCol ? "\n" : GetWhitespace(mPadding)); + + if (lastCol) + mCurrCol = 0; + else + mCurrCol = col+1; + } + + template <class T> + void WriteColumn(int col, T t) + { + WriteColumn(col, Output::Format<T>(t)); + } + + void WriteColumn(int col, const String &str) + { + WriteColumn(col, Output::Format<const char *>(str)); + } + + void WriteColumn(int col, const WString &str) + { + WriteColumn(col, Output::Format<const WCHAR *>(str)); + } + + void WriteColumn(int col, __in_z WCHAR *str) + { + WriteColumn(col, Output::Format<const WCHAR *>(str)); + } + + void WriteColumn(int col, const WCHAR *str) + { + WriteColumn(col, Output::Format<const WCHAR *>(str)); + } + + inline void WriteColumn(int col, __in_z char *str) + { + WriteColumn(col, Output::Format<const char *>(str)); + } + + /* Writes a column using a printf style format. You cannot use the Format class with + * this function to specify how the output should look, use printf style formatting + * with the appropriate parameters instead. + */ + void WriteColumnFormat(int col, const char *fmt, ...) + { + SOS_Assert(strstr(fmt, "%S") == NULL); + + char result[128]; + + va_list list; + va_start(list, fmt); + vsprintf_s(result, _countof(result), fmt, list); + va_end(list); + + WriteColumn(col, result); + } + + void WriteColumnFormat(int col, const WCHAR *fmt, ...) + { + WCHAR result[128]; + + va_list list; + va_start(list, fmt); + vswprintf_s(result, _countof(result), fmt, list); + va_end(list); + + WriteColumn(col, result); + } + + /* This function is a shortcut for writing the next column. (That is, the one after the + * one you just wrote.) + */ + template <class T> + void WriteColumn(T t) + { + WriteColumn(mCurrCol, t); + } + +private: + void Clear(); + void AllocWidths(); + int GetColumnWidth(int col); + Alignment GetColAlign(int col); + const char *GetWhitespace(int amount); + void OutputBlankColumns(int col); + void OutputIndent(); + +private: + int mColumns, mDefaultWidth, mIndent, mPadding, mCurrCol; + Alignment mDefaultAlign; + int *mWidths; + Alignment *mAlignments; +}; + +HRESULT GetMethodDefinitionsFromName(DWORD_PTR ModulePtr, IXCLRDataModule* mod, const char* name, IXCLRDataMethodDefinition **ppMethodDefinitions, int numMethods, int *numMethodsNeeded); +HRESULT GetMethodDescsFromName(DWORD_PTR ModulePtr, IXCLRDataModule* mod, const char* name, DWORD_PTR **pOut, int *numMethodDescs); + +HRESULT FileNameForModule (DacpModuleData *pModule, __out_ecount (MAX_LONGPATH) WCHAR *fileName); +HRESULT FileNameForModule (DWORD_PTR pModuleAddr, __out_ecount (MAX_LONGPATH) WCHAR *fileName); +void IP2MethodDesc (DWORD_PTR IP, DWORD_PTR &methodDesc, JITTypes &jitType, + DWORD_PTR &gcinfoAddr); +const char *ElementTypeName (unsigned type); +void DisplayFields (CLRDATA_ADDRESS cdaMT, DacpMethodTableData *pMTD, DacpMethodTableFieldData *pMTFD, + DWORD_PTR dwStartAddr = 0, BOOL bFirst=TRUE, BOOL bValueClass=FALSE); +int GetObjFieldOffset(CLRDATA_ADDRESS cdaObj, __in_z LPCWSTR wszFieldName, BOOL bFirst=TRUE); +int GetObjFieldOffset(CLRDATA_ADDRESS cdaObj, CLRDATA_ADDRESS cdaMT, __in_z LPCWSTR wszFieldName, BOOL bFirst=TRUE); + +BOOL IsValidToken(DWORD_PTR ModuleAddr, mdTypeDef mb); +void NameForToken_s(DacpModuleData *pModule, mdTypeDef mb, __out_ecount (capacity_mdName) WCHAR *mdName, size_t capacity_mdName, + bool bClassName=true); +void NameForToken_s(DWORD_PTR ModuleAddr, mdTypeDef mb, __out_ecount (capacity_mdName) WCHAR *mdName, size_t capacity_mdName, + bool bClassName=true); +HRESULT NameForToken_s(mdTypeDef mb, IMetaDataImport *pImport, __out_ecount (capacity_mdName) WCHAR *mdName, size_t capacity_mdName, + bool bClassName); +HRESULT NameForTokenNew_s(mdTypeDef mb, IMDInternalImport *pImport, __out_ecount (capacity_mdName) WCHAR *mdName, size_t capacity_mdName, + bool bClassName); + +void vmmap(); +void vmstat(); + +#ifndef FEATURE_PAL +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Support for managed stack tracing +// + +DWORD_PTR GetDebuggerJitInfo(DWORD_PTR md); + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#endif // FEATURE_PAL + +template <typename SCALAR> +inline +int bitidx(SCALAR bitflag) +{ + for (int idx = 0; idx < static_cast<int>(sizeof(bitflag))*8; ++idx) + { + if (bitflag & (1 << idx)) + { + _ASSERTE((bitflag & (~(1 << idx))) == 0); + return idx; + } + } + return -1; +} + +HRESULT +DllsName( + ULONG_PTR addrContaining, + __out_ecount (MAX_LONGPATH) WCHAR *dllName + ); + +inline +BOOL IsElementValueType (CorElementType cet) +{ + return (cet >= ELEMENT_TYPE_BOOLEAN && cet <= ELEMENT_TYPE_R8) + || cet == ELEMENT_TYPE_VALUETYPE || cet == ELEMENT_TYPE_I || cet == ELEMENT_TYPE_U; +} + + +#define safemove(dst, src) \ +SafeReadMemory (TO_TADDR(src), &(dst), sizeof(dst), NULL) + +extern "C" PDEBUG_DATA_SPACES g_ExtData; + +template <class T> +class ArrayHolder +{ +public: + ArrayHolder(T *ptr) + : mPtr(ptr) + { + } + + ~ArrayHolder() + { + Clear(); + } + + ArrayHolder(const ArrayHolder &rhs) + { + mPtr = const_cast<ArrayHolder *>(&rhs)->Detach(); + } + + ArrayHolder &operator=(T *ptr) + { + Clear(); + mPtr = ptr; + return *this; + } + + const T &operator[](int i) const + { + return mPtr[i]; + } + + T &operator[](int i) + { + return mPtr[i]; + } + + operator const T *() const + { + return mPtr; + } + + operator T *() + { + return mPtr; + } + + T **operator&() + { + return &mPtr; + } + + T *GetPtr() + { + return mPtr; + } + + T *Detach() + { + T *ret = mPtr; + mPtr = NULL; + return ret; + } + +private: + void Clear() + { + if (mPtr) + { + delete [] mPtr; + mPtr = NULL; + } + } + +private: + T *mPtr; +}; + + + +// This class acts a smart pointer which calls the Release method on any object +// you place in it when the ToRelease class falls out of scope. You may use it +// just like you would a standard pointer to a COM object (including if (foo), +// if (!foo), if (foo == 0), etc) except for two caveats: +// 1. This class never calls AddRef and it always calls Release when it +// goes out of scope. +// 2. You should never use & to try to get a pointer to a pointer unless +// you call Release first, or you will leak whatever this object contains +// prior to updating its internal pointer. +template<class T> +class ToRelease +{ +public: + ToRelease() + : m_ptr(NULL) + {} + + ToRelease(T* ptr) + : m_ptr(ptr) + {} + + ~ToRelease() + { + Release(); + } + + void operator=(T *ptr) + { + Release(); + + m_ptr = ptr; + } + + T* operator->() + { + return m_ptr; + } + + operator T*() + { + return m_ptr; + } + + T** operator&() + { + return &m_ptr; + } + + T* GetPtr() const + { + return m_ptr; + } + + T* Detach() + { + T* pT = m_ptr; + m_ptr = NULL; + return pT; + } + + void Release() + { + if (m_ptr != NULL) + { + m_ptr->Release(); + m_ptr = NULL; + } + } + +private: + T* m_ptr; +}; + +struct ModuleInfo +{ + ULONG64 baseAddr; + ULONG64 size; + BOOL hasPdb; +}; +extern ModuleInfo moduleInfo[]; + +BOOL InitializeHeapData(); +BOOL IsServerBuild (); +UINT GetMaxGeneration(); +UINT GetGcHeapCount(); +BOOL GetGcStructuresValid(); + +ULONG GetILSize(DWORD_PTR ilAddr); // REturns 0 if error occurs +HRESULT DecodeILFromAddress(IMetaDataImport *pImport, TADDR ilAddr); +void DecodeIL(IMetaDataImport *pImport, BYTE *buffer, ULONG bufSize); +void DecodeDynamicIL(BYTE *data, ULONG Size, DacpObjectData& tokenArray); + +BOOL IsRetailBuild (size_t base); +EEFLAVOR GetEEFlavor (); +HRESULT InitCorDebugInterface(); +VOID UninitCorDebugInterface(); +#ifndef FEATURE_PAL +BOOL GetEEVersion(VS_FIXEDFILEINFO *pFileInfo); +BOOL GetSOSVersion(VS_FIXEDFILEINFO *pFileInfo); +#endif + +BOOL IsDumpFile (); + +// IsMiniDumpFile will return true if 1) we are in +// a small format minidump, and g_InMinidumpSafeMode is true. +extern BOOL g_InMinidumpSafeMode; + +BOOL IsMiniDumpFile(); +void ReportOOM(); + +BOOL SafeReadMemory (TADDR offset, PVOID lpBuffer, ULONG cb, PULONG lpcbBytesRead); +#if !defined(_TARGET_WIN64_) && !defined(_ARM64_) +// on 64-bit platforms TADDR and CLRDATA_ADDRESS are identical +inline BOOL SafeReadMemory (CLRDATA_ADDRESS offset, PVOID lpBuffer, ULONG cb, PULONG lpcbBytesRead) +{ return SafeReadMemory(TO_TADDR(offset), lpBuffer, cb, lpcbBytesRead); } +#endif + +BOOL NameForMD_s (DWORD_PTR pMD, __out_ecount (capacity_mdName) WCHAR *mdName, size_t capacity_mdName); +BOOL NameForMT_s (DWORD_PTR MTAddr, __out_ecount (capacity_mdName) WCHAR *mdName, size_t capacity_mdName); + +WCHAR *CreateMethodTableName(TADDR mt, TADDR cmt = NULL); + +void isRetAddr(DWORD_PTR retAddr, DWORD_PTR* whereCalled); +DWORD_PTR GetValueFromExpression (___in __in_z const char *const str); + +enum ModuleHeapType +{ + ModuleHeapType_ThunkHeap, + ModuleHeapType_LookupTableHeap +}; + +HRESULT PrintDomainHeapInfo(const char *name, CLRDATA_ADDRESS adPtr, DWORD_PTR *size, DWORD_PTR *wasted = 0); +DWORD_PTR PrintModuleHeapInfo(DWORD_PTR *moduleList, int count, ModuleHeapType type, DWORD_PTR *wasted = 0); +void PrintHeapSize(DWORD_PTR total, DWORD_PTR wasted); +void DomainInfo(DacpAppDomainData *pDomain); +void AssemblyInfo(DacpAssemblyData *pAssembly); +DWORD_PTR LoaderHeapInfo(CLRDATA_ADDRESS pLoaderHeapAddr, DWORD_PTR *wasted = 0); +DWORD_PTR JitHeapInfo(); +DWORD_PTR VSDHeapInfo(CLRDATA_ADDRESS appDomain, DWORD_PTR *wasted = 0); + +DWORD GetNumComponents(TADDR obj); + +struct GenUsageStat +{ + size_t allocd; + size_t freed; + size_t unrooted; +}; + +struct HeapUsageStat +{ + GenUsageStat genUsage[4]; // gen0, 1, 2, LOH +}; + +extern DacpUsefulGlobalsData g_special_usefulGlobals; +BOOL GCHeapUsageStats(const DacpGcHeapDetails& heap, BOOL bIncUnreachable, HeapUsageStat *hpUsage); + +class HeapStat +{ +protected: + struct Node + { + DWORD_PTR data; + DWORD count; + size_t totalSize; + Node* left; + Node* right; + Node () + : data(0), count(0), totalSize(0), left(NULL), right(NULL) + { + } + }; + BOOL bHasStrings; + Node *head; + BOOL fLinear; +public: + HeapStat () + : bHasStrings(FALSE), head(NULL), fLinear(FALSE) + {} + ~HeapStat() + { + Delete(); + } + // TODO: Change the aSize argument to size_t when we start supporting + // TODO: object sizes above 4GB + void Add (DWORD_PTR aData, DWORD aSize); + void Sort (); + void Print (const char* label = NULL); + void Delete (); + void HasStrings(BOOL abHasStrings) + { + bHasStrings = abHasStrings; + } +private: + int CompareData(DWORD_PTR n1, DWORD_PTR n2); + void SortAdd (Node *&root, Node *entry); + void LinearAdd (Node *&root, Node *entry); + void ReverseLeftMost (Node *root); + void Linearize(); +}; + +class CGCDesc; + +// The information MethodTableCache returns. +struct MethodTableInfo +{ + bool IsInitialized() { return BaseSize != 0; } + + DWORD BaseSize; // Caching BaseSize and ComponentSize for a MethodTable + DWORD ComponentSize; // here has HUGE perf benefits in heap traversals. + BOOL bContainsPointers; + DWORD_PTR* GCInfoBuffer; // Start of memory of GC info + CGCDesc* GCInfo; // Just past GC info (which is how it is stored) + bool ArrayOfVC; +}; + +class MethodTableCache +{ +protected: + + struct Node + { + DWORD_PTR data; // This is the key (the method table pointer) + MethodTableInfo info; // The info associated with this MethodTable + Node* left; + Node* right; + Node (DWORD_PTR aData) : data(aData), left(NULL), right(NULL) + { + info.BaseSize = 0; + info.ComponentSize = 0; + info.bContainsPointers = false; + info.GCInfo = NULL; + info.ArrayOfVC = false; + info.GCInfoBuffer = NULL; + } + }; + Node *head; +public: + MethodTableCache () + : head(NULL) + {} + ~MethodTableCache() { Clear(); } + + // Always succeeds, if it is not present it adds an empty Info struct and returns that + // Thus you must call 'IsInitialized' on the returned value before using it + MethodTableInfo* Lookup(DWORD_PTR aData); + + void Clear (); +private: + int CompareData(DWORD_PTR n1, DWORD_PTR n2); + void ReverseLeftMost (Node *root); +}; + +extern MethodTableCache g_special_mtCache; + +struct DumpArrayFlags +{ + DWORD_PTR startIndex; + DWORD_PTR Length; + BOOL bDetail; + LPSTR strObject; + BOOL bNoFieldsForElement; + + DumpArrayFlags () + : startIndex(0), Length((DWORD_PTR)-1), bDetail(FALSE), strObject (0), bNoFieldsForElement(FALSE) + {} + ~DumpArrayFlags () + { + if (strObject) + delete [] strObject; + } +}; //DumpArrayFlags + + + +// ----------------------------------------------------------------------- + +#define BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX 0x08000000 +#define BIT_SBLK_FINALIZER_RUN 0x40000000 +#define BIT_SBLK_SPIN_LOCK 0x10000000 +#define SBLK_MASK_LOCK_THREADID 0x000003FF // special value of 0 + 1023 thread ids +#define SBLK_MASK_LOCK_RECLEVEL 0x0000FC00 // 64 recursion levels +#define SBLK_APPDOMAIN_SHIFT 16 // shift right this much to get appdomain index +#define SBLK_MASK_APPDOMAININDEX 0x000007FF // 2048 appdomain indices +#define SBLK_RECLEVEL_SHIFT 10 // shift right this much to get recursion level +#define BIT_SBLK_IS_HASHCODE 0x04000000 +#define MASK_HASHCODE ((1<<HASHCODE_BITS)-1) +#define SYNCBLOCKINDEX_BITS 26 +#define MASK_SYNCBLOCKINDEX ((1<<SYNCBLOCKINDEX_BITS)-1) + +HRESULT GetMTOfObject(TADDR obj, TADDR *mt); + +struct needed_alloc_context +{ + BYTE* alloc_ptr; // starting point for next allocation + BYTE* alloc_limit; // ending point for allocation region/quantum +}; + +struct AllocInfo +{ + needed_alloc_context *array; + int num; // number of allocation contexts in array + + AllocInfo() + : array(NULL) + , num(0) + {} + void Init() + { + extern void GetAllocContextPtrs(AllocInfo *pallocInfo); + GetAllocContextPtrs(this); + } + ~AllocInfo() + { + if (array != NULL) + delete[] array; + } +}; + +struct GCHandleStatistics +{ + HeapStat hs; + + DWORD strongHandleCount; + DWORD pinnedHandleCount; + DWORD asyncPinnedHandleCount; + DWORD refCntHandleCount; + DWORD weakLongHandleCount; + DWORD weakShortHandleCount; + DWORD variableCount; + DWORD sizedRefCount; + DWORD dependentCount; + DWORD weakWinRTHandleCount; + DWORD unknownHandleCount; + GCHandleStatistics() + : strongHandleCount(0), pinnedHandleCount(0), asyncPinnedHandleCount(0), refCntHandleCount(0), + weakLongHandleCount(0), weakShortHandleCount(0), variableCount(0), sizedRefCount(0), + dependentCount(0), weakWinRTHandleCount(0), unknownHandleCount(0) + {} + ~GCHandleStatistics() + { + hs.Delete(); + } +}; + +struct SegmentLookup +{ + DacpHeapSegmentData *m_segments; + int m_iSegmentsSize; + int m_iSegmentCount; + + SegmentLookup(); + ~SegmentLookup(); + + void Clear(); + BOOL AddSegment(DacpHeapSegmentData *pData); + CLRDATA_ADDRESS GetHeap(CLRDATA_ADDRESS object, BOOL& bFound); +}; + +class GCHeapSnapshot +{ +private: + BOOL m_isBuilt; + DacpGcHeapDetails *m_heapDetails; + DacpGcHeapData m_gcheap; + SegmentLookup m_segments; + + BOOL AddSegments(DacpGcHeapDetails& details); +public: + GCHeapSnapshot(); + + BOOL Build(); + void Clear(); + BOOL IsBuilt() { return m_isBuilt; } + + DacpGcHeapData *GetHeapData() { return &m_gcheap; } + + int GetHeapCount() { return m_gcheap.HeapCount; } + + DacpGcHeapDetails *GetHeap(CLRDATA_ADDRESS objectPointer); + int GetGeneration(CLRDATA_ADDRESS objectPointer); + + +}; +extern GCHeapSnapshot g_snapshot; + +BOOL IsSameModuleName (const char *str1, const char *str2); +BOOL IsModule (DWORD_PTR moduleAddr); +BOOL IsMethodDesc (DWORD_PTR value); +BOOL IsMethodTable (DWORD_PTR value); +BOOL IsStringObject (size_t obj); +BOOL IsObjectArray (DWORD_PTR objPointer); +BOOL IsObjectArray (DacpObjectData *pData); + +/* Returns a list of all modules in the process. + * Params: + * name - The name of the module you would like. If mName is NULL the all modules are returned. + * numModules - The number of modules in the array returned. + * Returns: + * An array of modules whose length is *numModules, NULL if an error occurred. Note that if this + * function succeeds but finds no modules matching the name given, this function returns a valid + * array, but *numModules will equal 0. + * Note: + * You must clean up the return value of this array by calling delete [] on it, or using the + * ArrayHolder class. + */ +DWORD_PTR *ModuleFromName(__in_opt LPSTR name, int *numModules); +void GetInfoFromName(DWORD_PTR ModuleAddr, const char* name); +void GetInfoFromModule (DWORD_PTR ModuleAddr, ULONG token, DWORD_PTR *ret=NULL); + + +typedef void (*VISITGCHEAPFUNC)(DWORD_PTR objAddr,size_t Size,DWORD_PTR methodTable,LPVOID token); +BOOL GCHeapsTraverse(VISITGCHEAPFUNC pFunc, LPVOID token, BOOL verify=true); + +///////////////////////////////////////////////////////////////////////////////////////////////////////// + +struct strobjInfo +{ + size_t methodTable; + DWORD m_StringLength; +}; + +// Just to make figuring out which fill pointer element matches a generation +// a bit less confusing. This gen_segment function is ported from gc.cpp. +inline unsigned int gen_segment (int gen) +{ + return (DAC_NUMBERGENERATIONS - gen - 1); +} + +inline CLRDATA_ADDRESS SegQueue(DacpGcHeapDetails& heapDetails, int seg) +{ + return heapDetails.finalization_fill_pointers[seg - 1]; +} + +inline CLRDATA_ADDRESS SegQueueLimit(DacpGcHeapDetails& heapDetails, int seg) +{ + return heapDetails.finalization_fill_pointers[seg]; +} + +#define FinalizerListSeg (DAC_NUMBERGENERATIONS+1) +#define CriticalFinalizerListSeg (DAC_NUMBERGENERATIONS) + +void GatherOneHeapFinalization(DacpGcHeapDetails& heapDetails, HeapStat *stat, BOOL bAllReady, BOOL bShort); + +CLRDATA_ADDRESS GetAppDomainForMT(CLRDATA_ADDRESS mtPtr); +CLRDATA_ADDRESS GetAppDomain(CLRDATA_ADDRESS objPtr); +void GCHeapInfo(const DacpGcHeapDetails &heap, DWORD_PTR &total_size); +BOOL GCObjInHeap(TADDR taddrObj, const DacpGcHeapDetails &heap, + TADDR_SEGINFO& trngSeg, int& gen, TADDR_RANGE& allocCtx, BOOL &bLarge); + +BOOL VerifyObject(const DacpGcHeapDetails &heap, const DacpHeapSegmentData &seg, DWORD_PTR objAddr, DWORD_PTR MTAddr, size_t objSize, + BOOL bVerifyMember); +BOOL VerifyObject(const DacpGcHeapDetails &heap, DWORD_PTR objAddr, DWORD_PTR MTAddr, size_t objSize, + BOOL bVerifyMember); + +BOOL IsMTForFreeObj(DWORD_PTR pMT); +void DumpStackObjectsHelper (TADDR StackTop, TADDR StackBottom, BOOL verifyFields); + + +enum ARGTYPE {COBOOL,COSIZE_T,COHEX,COSTRING}; +struct CMDOption +{ + const char* name; + void *vptr; + ARGTYPE type; + BOOL hasValue; + BOOL hasSeen; +}; +struct CMDValue +{ + void *vptr; + ARGTYPE type; +}; +BOOL GetCMDOption(const char *string, CMDOption *option, size_t nOption, + CMDValue *arg, size_t maxArg, size_t *nArg); + +void DumpMDInfo(DWORD_PTR dwStartAddr, CLRDATA_ADDRESS dwRequestedIP = 0, BOOL fStackTraceFormat = FALSE); +void DumpMDInfoFromMethodDescData(DacpMethodDescData * pMethodDescData, BOOL fStackTraceFormat); +void GetDomainList(DWORD_PTR *&domainList, int &numDomain); +HRESULT GetThreadList(DWORD_PTR **threadList, int *numThread); +CLRDATA_ADDRESS GetCurrentManagedThread(); // returns current managed thread if any +void GetAllocContextPtrs(AllocInfo *pallocInfo); + +void ReloadSymbolWithLineInfo(); + +size_t FunctionType (size_t EIP); + +size_t Align (size_t nbytes); +// Aligns large objects +size_t AlignLarge (size_t nbytes); + +ULONG OSPageSize (); +size_t NextOSPageAddress (size_t addr); + +// This version of objectsize reduces the lookup of methodtables in the DAC. +// It uses g_special_mtCache for it's work. +BOOL GetSizeEfficient(DWORD_PTR dwAddrCurrObj, + DWORD_PTR dwAddrMethTable, BOOL bLarge, size_t& s, BOOL& bContainsPointers); + +// ObjSize now uses the methodtable cache for its work too. +size_t ObjectSize (DWORD_PTR obj, BOOL fIsLargeObject=FALSE); +size_t ObjectSize(DWORD_PTR obj, DWORD_PTR mt, BOOL fIsValueClass, BOOL fIsLargeObject=FALSE); + +void CharArrayContent(TADDR pos, ULONG num, bool widechar); +void StringObjectContent (size_t obj, BOOL fLiteral=FALSE, const int length=-1); // length=-1: dump everything in the string object. + +UINT FindAllPinnedAndStrong (DWORD_PTR handlearray[],UINT arraySize); +void PrintNotReachableInRange(TADDR rngStart, TADDR rngEnd, BOOL bExcludeReadyForFinalization, + HeapStat* stat, BOOL bShort); + +const char *EHTypeName(EHClauseType et); + +template<typename T> +inline const LPCSTR GetTransparency(const T &t) +{ + if (!t.bHasCriticalTransparentInfo) + { + return "Not calculated"; + } + else if (t.bIsCritical && !t.bIsTreatAsSafe) + { + return "Critical"; + } + else if (t.bIsCritical) + { + return "Safe critical"; + } + else + { + return "Transparent"; + } +} + +struct StringHolder +{ + LPSTR data; + StringHolder() : data(NULL) { } + ~StringHolder() { if(data) delete [] data; } +}; + + +ULONG DebuggeeType(); + +inline BOOL IsKernelDebugger () +{ + return DebuggeeType() == DEBUG_CLASS_KERNEL; +} + +void ResetGlobals(void); +HRESULT LoadClrDebugDll(void); +extern "C" void UnloadClrDebugDll(void); + +extern IMetaDataImport* MDImportForModule (DacpModuleData *pModule); +extern IMetaDataImport* MDImportForModule (DWORD_PTR pModule); + +//***************************************************************************** +// +// **** CQuickBytes +// This helper class is useful for cases where 90% of the time you allocate 512 +// or less bytes for a data structure. This class contains a 512 byte buffer. +// Alloc() will return a pointer to this buffer if your allocation is small +// enough, otherwise it asks the heap for a larger buffer which is freed for +// you. No mutex locking is required for the small allocation case, making the +// code run faster, less heap fragmentation, etc... Each instance will allocate +// 520 bytes, so use accordinly. +// +//***************************************************************************** +template <DWORD SIZE, DWORD INCREMENT> +class CQuickBytesBase +{ +public: + CQuickBytesBase() : + pbBuff(0), + iSize(0), + cbTotal(SIZE) + { } + + void Destroy() + { + if (pbBuff) + { + delete[] (BYTE*)pbBuff; + pbBuff = 0; + } + } + + void *Alloc(SIZE_T iItems) + { + iSize = iItems; + if (iItems <= SIZE) + { + cbTotal = SIZE; + return (&rgData[0]); + } + else + { + if (pbBuff) + delete[] (BYTE*)pbBuff; + pbBuff = new BYTE[iItems]; + cbTotal = pbBuff ? iItems : 0; + return (pbBuff); + } + } + + // This is for conformity to the CQuickBytesBase that is defined by the runtime so + // that we can use it inside of some GC code that SOS seems to include as well. + // + // The plain vanilla "Alloc" version on this CQuickBytesBase doesn't throw either, + // so we'll just forward the call. + void *AllocNoThrow(SIZE_T iItems) + { + return Alloc(iItems); + } + + HRESULT ReSize(SIZE_T iItems) + { + void *pbBuffNew; + if (iItems <= cbTotal) + { + iSize = iItems; + return NOERROR; + } + + pbBuffNew = new BYTE[iItems + INCREMENT]; + if (!pbBuffNew) + return E_OUTOFMEMORY; + if (pbBuff) + { + memcpy(pbBuffNew, pbBuff, cbTotal); + delete[] (BYTE*)pbBuff; + } + else + { + _ASSERTE(cbTotal == SIZE); + memcpy(pbBuffNew, rgData, SIZE); + } + cbTotal = iItems + INCREMENT; + iSize = iItems; + pbBuff = pbBuffNew; + return NOERROR; + + } + + operator PVOID() + { return ((pbBuff) ? pbBuff : &rgData[0]); } + + void *Ptr() + { return ((pbBuff) ? pbBuff : &rgData[0]); } + + SIZE_T Size() + { return (iSize); } + + SIZE_T MaxSize() + { return (cbTotal); } + + void *pbBuff; + SIZE_T iSize; // number of bytes used + SIZE_T cbTotal; // total bytes allocated in the buffer + // use UINT64 to enforce the alignment of the memory + UINT64 rgData[(SIZE+sizeof(UINT64)-1)/sizeof(UINT64)]; +}; + +#define CQUICKBYTES_BASE_SIZE 512 +#define CQUICKBYTES_INCREMENTAL_SIZE 128 + +class CQuickBytesNoDtor : public CQuickBytesBase<CQUICKBYTES_BASE_SIZE, CQUICKBYTES_INCREMENTAL_SIZE> +{ +}; + +class CQuickBytes : public CQuickBytesNoDtor +{ +public: + CQuickBytes() { } + + ~CQuickBytes() + { + Destroy(); + } +}; + +template <DWORD CQUICKBYTES_BASE_SPECIFY_SIZE> +class CQuickBytesNoDtorSpecifySize : public CQuickBytesBase<CQUICKBYTES_BASE_SPECIFY_SIZE, CQUICKBYTES_INCREMENTAL_SIZE> +{ +}; + +template <DWORD CQUICKBYTES_BASE_SPECIFY_SIZE> +class CQuickBytesSpecifySize : public CQuickBytesNoDtorSpecifySize<CQUICKBYTES_BASE_SPECIFY_SIZE> +{ +public: + CQuickBytesSpecifySize() { } + + ~CQuickBytesSpecifySize() + { + CQuickBytesNoDtorSpecifySize<CQUICKBYTES_BASE_SPECIFY_SIZE>::Destroy(); + } +}; + + +#define STRING_SIZE 10 +class CQuickString : public CQuickBytesBase<STRING_SIZE, STRING_SIZE> +{ +public: + CQuickString() { } + + ~CQuickString() + { + Destroy(); + } + + void *Alloc(SIZE_T iItems) + { + return CQuickBytesBase<STRING_SIZE, STRING_SIZE>::Alloc(iItems*sizeof(WCHAR)); + } + + HRESULT ReSize(SIZE_T iItems) + { + return CQuickBytesBase<STRING_SIZE, STRING_SIZE>::ReSize(iItems * sizeof(WCHAR)); + } + + SIZE_T Size() + { + return CQuickBytesBase<STRING_SIZE, STRING_SIZE>::Size() / sizeof(WCHAR); + } + + SIZE_T MaxSize() + { + return CQuickBytesBase<STRING_SIZE, STRING_SIZE>::MaxSize() / sizeof(WCHAR); + } + + WCHAR* String() + { + return (WCHAR*) Ptr(); + } + +}; + +enum GetSignatureStringResults +{ + GSS_SUCCESS, + GSS_ERROR, + GSS_INSUFFICIENT_DATA, +}; + +GetSignatureStringResults GetMethodSignatureString (PCCOR_SIGNATURE pbSigBlob, ULONG ulSigBlob, DWORD_PTR dwModuleAddr, CQuickBytes *sigString); +GetSignatureStringResults GetSignatureString (PCCOR_SIGNATURE pbSigBlob, ULONG ulSigBlob, DWORD_PTR dwModuleAddr, CQuickBytes *sigString); +void GetMethodName(mdMethodDef methodDef, IMetaDataImport * pImport, CQuickBytes *fullName); + +#ifndef _TARGET_WIN64_ +#define itoa_s_ptr _itoa_s +#define itow_s_ptr _itow_s +#define itoa_ptr _itoa +#define itow_ptr _itow +#else +#define itoa_s_ptr _i64toa_s +#define itow_s_ptr _i64tow_s +#define itoa_ptr _i64toa +#define itow_ptr _i64tow +#endif + +#ifdef FEATURE_PAL +extern "C" +int _itoa_s( int inValue, char* outBuffer, size_t inDestBufferSize, int inRadix ); +extern "C" +int _ui64toa_s( unsigned __int64 inValue, char* outBuffer, size_t inDestBufferSize, int inRadix ); +#endif // FEATURE_PAL + +struct MemRange +{ + MemRange (ULONG64 s = NULL, size_t l = 0, MemRange * n = NULL) + : start(s), len (l), next (n) + {} + + bool InRange (ULONG64 addr) + { + return addr >= start && addr < start + len; + } + + ULONG64 start; + size_t len; + MemRange * next; +}; //struct MemRange + +#ifndef FEATURE_PAL + +class StressLogMem +{ +private: + // use a linked list for now, could be optimazied later + MemRange * list; + + void AddRange (ULONG64 s, size_t l) + { + list = new MemRange (s, l, list); + } + +public: + StressLogMem () : list (NULL) + {} + ~StressLogMem (); + bool Init (ULONG64 stressLogAddr, IDebugDataSpaces* memCallBack); + bool IsInStressLog (ULONG64 addr); +}; //class StressLogMem + +// An adapter class that DIA consumes so that it can read PE data from the an image +// This implementation gets the backing data from the image loaded in debuggee memory +// that has been layed out identical to the disk format (ie not seperated by section) +class PEOffsetMemoryReader : IDiaReadExeAtOffsetCallback +{ +public: + PEOffsetMemoryReader(TADDR moduleBaseAddress); + + // IUnknown implementation + HRESULT __stdcall QueryInterface(REFIID riid, VOID** ppInterface); + ULONG __stdcall AddRef(); + ULONG __stdcall Release(); + + // IDiaReadExeAtOffsetCallback implementation + HRESULT __stdcall ReadExecutableAt(DWORDLONG fileOffset, DWORD cbData, DWORD* pcbData, BYTE data[]); + +private: + TADDR m_moduleBaseAddress; + volatile ULONG m_refCount; +}; + +// An adapter class that DIA consumes so that it can read PE data from the an image +// This implementation gets the backing data from the image loaded in debuggee memory +// that has been layed out in LoadLibrary format +class PERvaMemoryReader : IDiaReadExeAtRVACallback +{ +public: + PERvaMemoryReader(TADDR moduleBaseAddress); + + // IUnknown implementation + HRESULT __stdcall QueryInterface(REFIID riid, VOID** ppInterface); + ULONG __stdcall AddRef(); + ULONG __stdcall Release(); + + // IDiaReadExeAtOffsetCallback implementation + HRESULT __stdcall ReadExecutableAtRVA(DWORD relativeVirtualAddress, DWORD cbData, DWORD* pcbData, BYTE data[]); + +private: + TADDR m_moduleBaseAddress; + volatile ULONG m_refCount; +}; + +#endif // !FEATURE_PAL + +static const char *SymbolReaderDllName = "SOS.NETCore"; +static const char *SymbolReaderClassName = "SOS.SymbolReader"; + +typedef int (*ReadMemoryDelegate)(ULONG64, char *, int); +typedef ULONG64 (*LoadSymbolsForModuleDelegate)(const char*, BOOL, ULONG64, int, ULONG64, int, ReadMemoryDelegate); +typedef void (*DisposeDelegate)(ULONG64); +typedef BOOL (*ResolveSequencePointDelegate)(ULONG64, const char*, unsigned int, unsigned int*, unsigned int*); +typedef BOOL (*GetLocalVariableName)(ULONG64, int, int, BSTR*); +typedef BOOL (*GetLineByILOffsetDelegate)(ULONG64, mdMethodDef, ULONG64, ULONG *, BSTR*); + +class SymbolReader +{ +private: +#ifndef FEATURE_PAL + ISymUnmanagedReader* m_pSymReader; +#endif + ULONG64 m_symbolReaderHandle; + + static LoadSymbolsForModuleDelegate loadSymbolsForModuleDelegate; + static DisposeDelegate disposeDelegate; + static ResolveSequencePointDelegate resolveSequencePointDelegate; + static GetLocalVariableName getLocalVariableNameDelegate; + static GetLineByILOffsetDelegate getLineByILOffsetDelegate; + static HRESULT PrepareSymbolReader(); + + HRESULT GetNamedLocalVariable(___in ISymUnmanagedScope* pScope, ___in ICorDebugILFrame* pILFrame, ___in mdMethodDef methodToken, ___in ULONG localIndex, + __out_ecount(paramNameLen) WCHAR* paramName, ___in ULONG paramNameLen, ___out ICorDebugValue** ppValue); + HRESULT LoadSymbolsForWindowsPDB(___in IMetaDataImport* pMD, ___in ULONG64 peAddress, __in_z WCHAR* pModuleName, ___in BOOL isFileLayout); + HRESULT LoadSymbolsForPortablePDB(__in_z WCHAR* pModuleName, ___in BOOL isInMemory, ___in BOOL isFileLayout, ___in ULONG64 peAddress, ___in ULONG64 peSize, + ___in ULONG64 inMemoryPdbAddress, ___in ULONG64 inMemoryPdbSize); + +public: + SymbolReader() + { +#ifndef FEATURE_PAL + m_pSymReader = NULL; +#endif + m_symbolReaderHandle = 0; + } + + ~SymbolReader() + { +#ifndef FEATURE_PAL + if(m_pSymReader != NULL) + { + m_pSymReader->Release(); + m_pSymReader = NULL; + } +#endif + if (m_symbolReaderHandle != 0) + { + disposeDelegate(m_symbolReaderHandle); + m_symbolReaderHandle = 0; + } + } + + HRESULT LoadSymbols(___in IMetaDataImport* pMD, ___in ICorDebugModule* pModule); + HRESULT LoadSymbols(___in IMetaDataImport* pMD, ___in IXCLRDataModule* pModule); + HRESULT GetLineByILOffset(___in mdMethodDef MethodToken, ___in ULONG64 IlOffset, ___out ULONG *pLinenum, __out_ecount(cchFileName) WCHAR* pwszFileName, ___in ULONG cchFileName); + HRESULT GetNamedLocalVariable(___in ICorDebugFrame * pFrame, ___in ULONG localIndex, __out_ecount(paramNameLen) WCHAR* paramName, ___in ULONG paramNameLen, ___out ICorDebugValue** ppValue); + HRESULT ResolveSequencePoint(__in_z WCHAR* pFilename, ___in ULONG32 lineNumber, ___in TADDR mod, ___out mdMethodDef* ___out pToken, ___out ULONG32* pIlOffset); +}; + +HRESULT +GetLineByOffset( + ___in ULONG64 IP, + ___out ULONG *pLinenum, + __out_ecount(cchFileName) WCHAR* pwszFileName, + ___in ULONG cchFileName); + +/// X86 Context +#define X86_SIZE_OF_80387_REGISTERS 80 +#define X86_MAXIMUM_SUPPORTED_EXTENSION 512 + +typedef struct { + DWORD ControlWord; + DWORD StatusWord; + DWORD TagWord; + DWORD ErrorOffset; + DWORD ErrorSelector; + DWORD DataOffset; + DWORD DataSelector; + BYTE RegisterArea[X86_SIZE_OF_80387_REGISTERS]; + DWORD Cr0NpxState; +} X86_FLOATING_SAVE_AREA; + +typedef struct { + + DWORD ContextFlags; + DWORD Dr0; + DWORD Dr1; + DWORD Dr2; + DWORD Dr3; + DWORD Dr6; + DWORD Dr7; + + X86_FLOATING_SAVE_AREA FloatSave; + + DWORD SegGs; + DWORD SegFs; + DWORD SegEs; + DWORD SegDs; + + DWORD Edi; + DWORD Esi; + DWORD Ebx; + DWORD Edx; + DWORD Ecx; + DWORD Eax; + + DWORD Ebp; + DWORD Eip; + DWORD SegCs; + DWORD EFlags; + DWORD Esp; + DWORD SegSs; + + BYTE ExtendedRegisters[X86_MAXIMUM_SUPPORTED_EXTENSION]; + +} X86_CONTEXT; + +typedef struct { + ULONGLONG Low; + LONGLONG High; +} M128A_XPLAT; + + +/// AMD64 Context +typedef struct { + WORD ControlWord; + WORD StatusWord; + BYTE TagWord; + BYTE Reserved1; + WORD ErrorOpcode; + DWORD ErrorOffset; + WORD ErrorSelector; + WORD Reserved2; + DWORD DataOffset; + WORD DataSelector; + WORD Reserved3; + DWORD MxCsr; + DWORD MxCsr_Mask; + M128A_XPLAT FloatRegisters[8]; + +#if defined(_WIN64) + M128A_XPLAT XmmRegisters[16]; + BYTE Reserved4[96]; +#else + M128A_XPLAT XmmRegisters[8]; + BYTE Reserved4[220]; + + DWORD Cr0NpxState; +#endif + +} AMD64_XMM_SAVE_AREA32; + +typedef struct { + + DWORD64 P1Home; + DWORD64 P2Home; + DWORD64 P3Home; + DWORD64 P4Home; + DWORD64 P5Home; + DWORD64 P6Home; + + DWORD ContextFlags; + DWORD MxCsr; + + WORD SegCs; + WORD SegDs; + WORD SegEs; + WORD SegFs; + WORD SegGs; + WORD SegSs; + DWORD EFlags; + + DWORD64 Dr0; + DWORD64 Dr1; + DWORD64 Dr2; + DWORD64 Dr3; + DWORD64 Dr6; + DWORD64 Dr7; + + DWORD64 Rax; + DWORD64 Rcx; + DWORD64 Rdx; + DWORD64 Rbx; + DWORD64 Rsp; + DWORD64 Rbp; + DWORD64 Rsi; + DWORD64 Rdi; + DWORD64 R8; + DWORD64 R9; + DWORD64 R10; + DWORD64 R11; + DWORD64 R12; + DWORD64 R13; + DWORD64 R14; + DWORD64 R15; + + DWORD64 Rip; + + union { + AMD64_XMM_SAVE_AREA32 FltSave; + struct { + M128A_XPLAT Header[2]; + M128A_XPLAT Legacy[8]; + M128A_XPLAT Xmm0; + M128A_XPLAT Xmm1; + M128A_XPLAT Xmm2; + M128A_XPLAT Xmm3; + M128A_XPLAT Xmm4; + M128A_XPLAT Xmm5; + M128A_XPLAT Xmm6; + M128A_XPLAT Xmm7; + M128A_XPLAT Xmm8; + M128A_XPLAT Xmm9; + M128A_XPLAT Xmm10; + M128A_XPLAT Xmm11; + M128A_XPLAT Xmm12; + M128A_XPLAT Xmm13; + M128A_XPLAT Xmm14; + M128A_XPLAT Xmm15; + } DUMMYSTRUCTNAME; + } DUMMYUNIONNAME; + + M128A_XPLAT VectorRegister[26]; + DWORD64 VectorControl; + + DWORD64 DebugControl; + DWORD64 LastBranchToRip; + DWORD64 LastBranchFromRip; + DWORD64 LastExceptionToRip; + DWORD64 LastExceptionFromRip; + +} AMD64_CONTEXT; + +typedef struct{ + __int64 LowPart; + __int64 HighPart; +} FLOAT128_XPLAT; + + +/// ARM Context +#define ARM_MAX_BREAKPOINTS_CONST 8 +#define ARM_MAX_WATCHPOINTS_CONST 4 +typedef struct { + + DWORD ContextFlags; + + DWORD R0; + DWORD R1; + DWORD R2; + DWORD R3; + DWORD R4; + DWORD R5; + DWORD R6; + DWORD R7; + DWORD R8; + DWORD R9; + DWORD R10; + DWORD R11; + DWORD R12; + + DWORD Sp; + DWORD Lr; + DWORD Pc; + DWORD Cpsr; + + DWORD Fpscr; + union { + M128A_XPLAT Q[16]; + ULONGLONG D[32]; + DWORD S[32]; + } DUMMYUNIONNAME; + + DWORD Bvr[ARM_MAX_BREAKPOINTS_CONST]; + DWORD Bcr[ARM_MAX_BREAKPOINTS_CONST]; + DWORD Wvr[ARM_MAX_WATCHPOINTS_CONST]; + DWORD Wcr[ARM_MAX_WATCHPOINTS_CONST]; + +} ARM_CONTEXT; + +// On ARM this mask is or'ed with the address of code to get an instruction pointer +#ifndef THUMB_CODE +#define THUMB_CODE 1 +#endif + +///ARM64 Context +#define ARM64_MAX_BREAKPOINTS 8 +#define ARM64_MAX_WATCHPOINTS 2 +typedef struct { + + DWORD ContextFlags; + DWORD Cpsr; // NZVF + DAIF + CurrentEL + SPSel + union { + struct { + DWORD64 X0; + DWORD64 X1; + DWORD64 X2; + DWORD64 X3; + DWORD64 X4; + DWORD64 X5; + DWORD64 X6; + DWORD64 X7; + DWORD64 X8; + DWORD64 X9; + DWORD64 X10; + DWORD64 X11; + DWORD64 X12; + DWORD64 X13; + DWORD64 X14; + DWORD64 X15; + DWORD64 X16; + DWORD64 X17; + DWORD64 X18; + DWORD64 X19; + DWORD64 X20; + DWORD64 X21; + DWORD64 X22; + DWORD64 X23; + DWORD64 X24; + DWORD64 X25; + DWORD64 X26; + DWORD64 X27; + DWORD64 X28; + }; + + DWORD64 X[29]; + }; + + DWORD64 Fp; + DWORD64 Lr; + DWORD64 Sp; + DWORD64 Pc; + + + M128A_XPLAT V[32]; + DWORD Fpcr; + DWORD Fpsr; + + DWORD Bcr[ARM64_MAX_BREAKPOINTS]; + DWORD64 Bvr[ARM64_MAX_BREAKPOINTS]; + DWORD Wcr[ARM64_MAX_WATCHPOINTS]; + DWORD64 Wvr[ARM64_MAX_WATCHPOINTS]; + +} ARM64_CONTEXT; + +typedef struct _CROSS_PLATFORM_CONTEXT { + + _CROSS_PLATFORM_CONTEXT() {} + + union { + X86_CONTEXT X86Context; + AMD64_CONTEXT Amd64Context; + ARM_CONTEXT ArmContext; + ARM64_CONTEXT Arm64Context; + }; + +} CROSS_PLATFORM_CONTEXT, *PCROSS_PLATFORM_CONTEXT; + + + +WString BuildRegisterOutput(const SOSStackRefData &ref, bool printObj = true); +WString MethodNameFromIP(CLRDATA_ADDRESS methodDesc, BOOL bSuppressLines = FALSE, BOOL bAssemblyName = FALSE, BOOL bDisplacement = FALSE); +HRESULT GetGCRefs(ULONG osID, SOSStackRefData **ppRefs, unsigned int *pRefCnt, SOSStackRefError **ppErrors, unsigned int *pErrCount); +WString GetFrameFromAddress(TADDR frameAddr, IXCLRDataStackWalk *pStackwalk = NULL, BOOL bAssemblyName = FALSE); + +/* This cache is used to read data from the target process if the reads are known + * to be sequential. + */ +class LinearReadCache +{ +public: + LinearReadCache(ULONG pageSize = 0x10000); + ~LinearReadCache(); + + /* Reads an address out of the target process, caching the page of memory read. + * Params: + * addr - The address to read out of the target process. + * t - A pointer to the data to stuff it in. We will read sizeof(T) data + * from the process and write it into the location t points to. This + * parameter must be non-null. + * Returns: + * True if the read succeeded. False if it did not, usually as a result + * of the memory simply not being present in the target process. + * Note: + * The state of *t is undefined if this function returns false. We may + * have written partial data to it if we return false, so you must + * absolutely NOT use it if Read returns false. + */ + template <class T> + bool Read(TADDR addr, T *t, bool update = true) + { + _ASSERTE(t); + + // Unfortunately the ctor can fail the alloc for the byte array. In this case + // we'll just fall back to non-cached reads. + if (mPage == NULL) + return MisalignedRead(addr, t); + + // Is addr on the current page? If not read the page of memory addr is on. + // If this fails, we will fall back to a raw read out of the process (which + // is what MisalignedRead does). + if ((addr < mCurrPageStart) || (addr - mCurrPageStart > mCurrPageSize)) + if (!update || !MoveToPage(addr)) + return MisalignedRead(addr, t); + + // If MoveToPage succeeds, we MUST be on the right page. + _ASSERTE(addr >= mCurrPageStart); + + // However, the amount of data requested may fall off of the page. In that case, + // fall back to MisalignedRead. + TADDR offset = addr - mCurrPageStart; + if (offset + sizeof(T) > mCurrPageSize) + return MisalignedRead(addr, t); + + // If we reach here we know we are on the right page of memory in the cache, and + // that the read won't fall off of the end of the page. +#ifdef _DEBUG + mReads++; +#endif + + *t = *reinterpret_cast<T*>(mPage+offset); + return true; + } + + void EnsureRangeInCache(TADDR start, unsigned int size) + { + if (mCurrPageStart == start) + { + if (size <= mCurrPageSize) + return; + + // Total bytes to read, don't overflow buffer. + unsigned int total = size + mCurrPageSize; + if (total + mCurrPageSize > mPageSize) + total = mPageSize-mCurrPageSize; + + // Read into the middle of the buffer, update current page size. + ULONG read = 0; + HRESULT hr = g_ExtData->ReadVirtual(mCurrPageStart+mCurrPageSize, mPage+mCurrPageSize, total, &read); + mCurrPageSize += read; + + if (hr != S_OK) + { + mCurrPageStart = 0; + mCurrPageSize = 0; + } + } + else + { + MoveToPage(start, size); + } + } + + void ClearStats() + { +#ifdef _DEBUG + mMisses = 0; + mReads = 0; + mMisaligned = 0; +#endif + } + + void PrintStats(const char *func) + { +#ifdef _DEBUG + char buffer[1024]; + sprintf_s(buffer, _countof(buffer), "Cache (%s): %d reads (%2.1f%% hits), %d misses (%2.1f%%), %d misaligned (%2.1f%%).\n", + func, mReads, 100*(mReads-mMisses)/(float)(mReads+mMisaligned), mMisses, + 100*mMisses/(float)(mReads+mMisaligned), mMisaligned, 100*mMisaligned/(float)(mReads+mMisaligned)); + OutputDebugStringA(buffer); +#endif + } + +private: + /* Sets the cache to the page specified by addr, or false if we could not move to + * that page. + */ + bool MoveToPage(TADDR addr, unsigned int size = 0x18); + + /* Attempts to read from the target process if the data is possibly hanging off + * the end of a page. + */ + template<class T> + inline bool MisalignedRead(TADDR addr, T *t) + { + ULONG fetched = 0; + HRESULT hr = g_ExtData->ReadVirtual(addr, (BYTE*)t, sizeof(T), &fetched); + + if (FAILED(hr) || fetched != sizeof(T)) + return false; + + mMisaligned++; + return true; + } + +private: + TADDR mCurrPageStart; + ULONG mPageSize, mCurrPageSize; + BYTE *mPage; + + int mMisses, mReads, mMisaligned; +}; + + +/////////////////////////////////////////////////////////////////////////////////////////// +// +// Methods for creating a database out of the gc heap and it's roots in xml format or CLRProfiler format +// + +#include <unordered_map> +#include <unordered_set> +#include <list> + +class TypeTree; +enum { FORMAT_XML=0, FORMAT_CLRPROFILER=1 }; +enum { TYPE_START=0,TYPE_TYPES=1,TYPE_ROOTS=2,TYPE_OBJECTS=3,TYPE_HIGHEST=4}; +class HeapTraverser +{ +private: + TypeTree *m_pTypeTree; + size_t m_curNID; + FILE *m_file; + int m_format; // from the enum above + size_t m_objVisited; // for UI updates + bool m_verify; + LinearReadCache mCache; + + std::unordered_map<TADDR, std::list<TADDR>> mDependentHandleMap; + +public: + HeapTraverser(bool verify); + ~HeapTraverser(); + + FILE *getFile() { return m_file; } + + BOOL Initialize(); + BOOL CreateReport (FILE *fp, int format); + +private: + // First all types are added to a tree + void insert(size_t mTable); + size_t getID(size_t mTable); + + // Functions for writing to the output file. + void PrintType(size_t ID,LPCWSTR name); + + void PrintObjectHead(size_t objAddr,size_t typeID,size_t Size); + void PrintObjectMember(size_t memberValue, bool dependentHandle); + void PrintObjectTail(); + + void PrintRootHead(); + void PrintRoot(LPCWSTR kind,size_t Value); + void PrintRootTail(); + + void PrintSection(int Type,BOOL bOpening); + + // Root and object member helper functions + void FindGCRootOnStacks(); + void PrintRefs(size_t obj, size_t methodTable, size_t size); + + // Callback functions used during traversals + static void GatherTypes(DWORD_PTR objAddr,size_t Size,DWORD_PTR methodTable, LPVOID token); + static void PrintHeap(DWORD_PTR objAddr,size_t Size,DWORD_PTR methodTable, LPVOID token); + static void PrintOutTree(size_t methodTable, size_t ID, LPVOID token); + void TraceHandles(); +}; + + +class GCRootImpl +{ +private: + struct MTInfo + { + TADDR MethodTable; + WCHAR *TypeName; + + TADDR *Buffer; + CGCDesc *GCDesc; + + bool ArrayOfVC; + bool ContainsPointers; + size_t BaseSize; + size_t ComponentSize; + + const WCHAR *GetTypeName() + { + if (!TypeName) + TypeName = CreateMethodTableName(MethodTable); + + if (!TypeName) + return W("<error>"); + + return TypeName; + } + + MTInfo() + : MethodTable(0), TypeName(0), Buffer(0), GCDesc(0), + ArrayOfVC(false), ContainsPointers(false), BaseSize(0), ComponentSize(0) + { + } + + ~MTInfo() + { + if (Buffer) + delete [] Buffer; + + if (TypeName) + delete [] TypeName; + } + }; + + struct RootNode + { + RootNode *Next; + RootNode *Prev; + TADDR Object; + MTInfo *MTInfo; + + bool FilledRefs; + bool FromDependentHandle; + RootNode *GCRefs; + + + const WCHAR *GetTypeName() + { + if (!MTInfo) + return W("<unknown>"); + + return MTInfo->GetTypeName(); + } + + RootNode() + : Next(0), Prev(0) + { + Clear(); + } + + void Clear() + { + if (Next && Next->Prev == this) + Next->Prev = NULL; + + if (Prev && Prev->Next == this) + Prev->Next = NULL; + + Next = 0; + Prev = 0; + Object = 0; + MTInfo = 0; + FilledRefs = false; + FromDependentHandle = false; + GCRefs = 0; + } + + void Remove(RootNode *&list) + { + RootNode *curr_next = Next; + + // We've already considered this object, remove it. + if (Prev == NULL) + { + // If we've filtered out the head, update it. + list = curr_next; + + if (curr_next) + curr_next->Prev = NULL; + } + else + { + // Otherwise remove the current item from the list + Prev->Next = curr_next; + + if (curr_next) + curr_next->Prev = Prev; + } + } + }; + +public: + static void GetDependentHandleMap(std::unordered_map<TADDR, std::list<TADDR>> &map); + +public: + // Finds all objects which root "target" and prints the path from the root + // to "target". If all is true, all possible paths to the object are printed. + // If all is false, only completely unique paths will be printed. + int PrintRootsForObject(TADDR obj, bool all, bool noStacks); + + // Finds a path from root to target if it exists and prints it out. Returns + // true if it found a path, false otherwise. + bool PrintPathToObject(TADDR root, TADDR target); + + // Calculates the size of the closure of objects kept alive by root. + size_t ObjSize(TADDR root); + + // Walks each root, printing out the total amount of memory held alive by it. + void ObjSize(); + + // Returns the set of all live objects in the process. + const std::unordered_set<TADDR> &GetLiveObjects(bool excludeFQ = false); + + // See !FindRoots. + int FindRoots(int gen, TADDR target); + +private: + // typedefs + typedef void (*ReportCallback)(TADDR root, RootNode *path, bool printHeader); + + // Book keeping and debug. + void ClearAll(); + void ClearNodes(); + void ClearSizeData(); + + // Printing roots + int PrintRootsOnHandleTable(int gen = -1); + int PrintRootsOnAllThreads(); + int PrintRootsOnThread(DWORD osThreadId); + int PrintRootsOnFQ(bool notReadyForFinalization = false); + int PrintRootsInOlderGen(); + int PrintRootsInRange(LinearReadCache &cache, TADDR start, TADDR stop, ReportCallback func, bool printHeader); + + // Calculate gc root + RootNode *FilterRoots(RootNode *&list); + RootNode *FindPathToTarget(TADDR root); + RootNode *GetGCRefs(RootNode *path, RootNode *node); + + void InitDependentHandleMap(); + + //Reporting: + void ReportOneHandlePath(const SOSHandleData &handle, RootNode *node, bool printHeader); + void ReportOnePath(DWORD thread, const SOSStackRefData &stackRef, RootNode *node, bool printThread, bool printFrame); + static void ReportOneFQEntry(TADDR root, RootNode *path, bool printHeader); + static void ReportOlderGenEntry(TADDR root, RootNode *path, bool printHeader); + void ReportSizeInfo(const SOSHandleData &handle, TADDR obj); + void ReportSizeInfo(DWORD thread, const SOSStackRefData &ref, TADDR obj); + + // Data reads: + TADDR ReadPointer(TADDR location); + TADDR ReadPointerCached(TADDR location); + + // Object/MT data: + MTInfo *GetMTInfo(TADDR mt); + DWORD GetComponents(TADDR obj, TADDR mt); + size_t GetSizeOfObject(TADDR obj, MTInfo *info); + + // RootNode management: + RootNode *NewNode(TADDR obj = 0, MTInfo *mtinfo = 0, bool fromDependent = false); + void DeleteNode(RootNode *node); + +private: + + bool mAll, // Print all roots or just unique roots? + mSize; // Print rooting information or total size info? + + std::list<RootNode*> mCleanupList; // A list of RootNode's we've newed up. This is only used to delete all of them later. + std::list<RootNode*> mRootNewList; // A list of unused RootNodes that are free to use instead of having to "new" up more. + + std::unordered_map<TADDR, MTInfo*> mMTs; // The MethodTable cache which maps from MT -> MethodTable data (size, gcdesc, string typename) + std::unordered_map<TADDR, RootNode*> mTargets; // The objects that we are searching for. + std::unordered_set<TADDR> mConsidered; // A hashtable of objects we've already visited. + std::unordered_map<TADDR, size_t> mSizes; // A mapping from object address to total size of data the object roots. + + std::unordered_map<TADDR, std::list<TADDR>> mDependentHandleMap; + + LinearReadCache mCache; // A linear cache which stops us from having to read from the target process more than 1-2 times per object. +}; + +// +// Helper class used for type-safe bitflags +// T - the enum type specifying the individual bit flags +// U - the underlying/storage type +// Requirement: +// sizeof(T) <= sizeof(U) +// +template <typename T, typename U> +struct Flags +{ + typedef T UnderlyingType; + typedef U BitFlagEnumType; + + static_assert_no_msg(sizeof(BitFlagEnumType) <= sizeof(UnderlyingType)); + + Flags(UnderlyingType v) + : m_val(v) + { } + + Flags(BitFlagEnumType v) + : m_val(v) + { } + + Flags(const Flags& other) + : m_val(other.m_val) + { } + + Flags& operator = (const Flags& other) + { m_val = other.m_val; return *this; } + + Flags operator | (Flags other) const + { return Flags<T, U>(m_val | other._val); } + + void operator |= (Flags other) + { m_val |= other.m_val; } + + Flags operator & (Flags other) const + { return Flags<T, U>(m_val & other.m_val); } + + void operator &= (Flags other) + { m_val &= other.m_val; } + + Flags operator ^ (Flags other) const + { return Flags<T, U>(m_val ^ other._val); } + + void operator ^= (Flags other) + { m_val ^= other.m_val; } + + BOOL operator == (Flags other) const + { return m_val == other.m_val; } + + BOOL operator != (Flags other) const + { return m_val != other.m_val; } + + +private: + UnderlyingType m_val; +}; + +#ifndef FEATURE_PAL + +// Flags defining activation policy for COM objects +enum CIOptionsBits +{ + cciLatestFx = 0x01, // look in the most recent .NETFx installation + cciMatchFx = 0x02, // NYI: Look in the .NETFx installation matching the debuggee's runtime + cciAnyFx = 0x04, // look in any .NETFx installation + cciFxMask = 0x0f, + cciDbiColocated = 0x10, // NYI: Look next to the already loaded DBI module + cciDacColocated = 0x20, // Look next to the already loaded DAC module + cciDbgPath = 0x40, // Look in all folders in the debuggers symbols and binary path +}; + +typedef Flags<DWORD, CIOptionsBits> CIOptions; + +/**********************************************************************\ +* Routine Description: * +* * +* CreateInstanceCustom() provides a way to activate a COM object w/o * +* triggering the FeatureOnDemand dialog. In order to do this we * +* must avoid using the CoCreateInstance() API, which, on a machine * +* with v4+ installed and w/o v2, would trigger this. * +* CreateInstanceCustom() activates the requested COM object according * +* to the specified passed in CIOptions, in the following order * +* (skipping the steps not enabled in the CIOptions flags passed in): * +* 1. Attempt to activate the COM object using a framework install: * +* a. If the debugger machine has a V4+ shell shim use the shim * +* to activate the object * +* b. Otherwise simply call CoCreateInstance * +* 2. If unsuccessful attempt to activate looking for the dllName in * +* the same folder as the DAC was loaded from * +* 3. If unsuccessful attempt to activate the COM object looking in * +* every path specified in the debugger's .exepath and .sympath * +\**********************************************************************/ +HRESULT CreateInstanceCustom( + REFCLSID clsid, + REFIID iid, + LPCWSTR dllName, + CIOptions cciOptions, + void** ppItf); + + +//------------------------------------------------------------------------ +// A typesafe version of GetProcAddress +//------------------------------------------------------------------------ +template <typename T> +BOOL +GetProcAddressT( + ___in PCSTR FunctionName, + __in_opt PCWSTR DllName, + __inout T* OutFunctionPointer, + __inout HMODULE* InOutDllHandle + ) +{ + _ASSERTE(InOutDllHandle != NULL); + _ASSERTE(OutFunctionPointer != NULL); + + T FunctionPointer = NULL; + HMODULE DllHandle = *InOutDllHandle; + if (DllHandle == NULL) + { + DllHandle = LoadLibraryExW(DllName, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); + if (DllHandle != NULL) + *InOutDllHandle = DllHandle; + } + if (DllHandle != NULL) + { + FunctionPointer = (T) GetProcAddress(DllHandle, FunctionName); + } + *OutFunctionPointer = FunctionPointer; + return FunctionPointer != NULL; +} + + +#endif // FEATURE_PAL + +struct ImageInfo +{ + ULONG64 modBase; +}; + +// Helper class used in ClrStackFromPublicInterface() to keep track of explicit EE Frames +// (i.e., "internal frames") on the stack. Call Init() with the appropriate +// ICorDebugThread3, and this class will initialize itself with the set of internal +// frames. You can then call PrintPrecedingInternalFrames during your stack walk to +// have this class output any internal frames that "precede" (i.e., that are closer to +// the leaf than) the specified ICorDebugFrame. +class InternalFrameManager +{ +private: + // TODO: Verify constructor AND destructor is called for each array element + // TODO: Comment about hard-coding 1000 + ToRelease<ICorDebugInternalFrame2> m_rgpInternalFrame2[1000]; + ULONG32 m_cInternalFramesActual; + ULONG32 m_iInternalFrameCur; + +public: + InternalFrameManager(); + HRESULT Init(ICorDebugThread3 * pThread3); + HRESULT PrintPrecedingInternalFrames(ICorDebugFrame * pFrame); + +private: + HRESULT PrintCurrentInternalFrame(); +}; + +#endif // __util_h__ diff --git a/src/ToolBox/SOS/Strike/vm.cpp b/src/ToolBox/SOS/Strike/vm.cpp new file mode 100644 index 0000000000..e7e5701fc6 --- /dev/null +++ b/src/ToolBox/SOS/Strike/vm.cpp @@ -0,0 +1,732 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ==++== +// + +// +// ==--== +/*++ + +Module Name: + + vm.cxx + +Abstract: + + This module contains an NTSD debugger extension for dumping various + virtual memory statistics. + +Revision History: + +--*/ + +#ifndef FEATURE_PAL +#include <tchar.h> + + +#include "strike.h" +#include "util.h" +#include "gcinfo.h" +#include "disasm.h" +#include <dbghelp.h> + +#include "corhdr.h" +#include "cor.h" +#include "dacprivate.h" + + + +// +// Private constants. +// + +#define SMALL_REGION (64 * 1024) +#define MEDIUM_REGION (1 * 1024 * 1024) + +#define IS_SMALL(c) ((c) <= SMALL_REGION) +#define IS_MEDIUM(c) (((c) > SMALL_REGION) && ((c) <= MEDIUM_REGION)) +#define IS_LARGE(c) ((c) > MEDIUM_REGION) + +#define PRINTF_FORMAT_HEAD "%-7s %*s %*s %*s %*s %*s\n" +#define PRINTF_FORMAT "%-7s %*sK %*sK %*sK %*s %*sK\n" + +#define CCH_ULONGLONG_COMMAS _countof("18,446,744,073,709,551,616") +#define CCH_ULONGLONG_MINIMUM_COMMAS (CCH_ULONGLONG_COMMAS - 3) +#define CCH_ULONGLONG_BLOCKCOUNT_COMMAS sizeof("1,000,000") + + +// +// Private types. +// + +typedef struct _INDIVIDUAL_STAT +{ + SIZE_T MinimumSize; + SIZE_T MaximumSize; + SIZE_T TotalSize; + SIZE_T BlockCount; + +} INDIVIDUAL_STAT, *PINDIVIDUAL_STAT; + +typedef struct _VM_STATS +{ + INDIVIDUAL_STAT Summary; + INDIVIDUAL_STAT Small; + INDIVIDUAL_STAT Medium; + INDIVIDUAL_STAT Large; + +} VM_STATS, *PVM_STATS; + +typedef struct PROTECT_MASK +{ + DWORD Bit; + PSTR Name; + +} PROTECT_MASK, *PPROTECT_MASK; + + +// +// Private globals. +// + +PROTECT_MASK ProtectMasks[] = + { + { + PAGE_NOACCESS, + "NA" + }, + + { + PAGE_NOCACHE, + "NC" + }, + + { + PAGE_GUARD, + "G" + }, + + { + PAGE_READONLY, + "Rd" + }, + + { + PAGE_READWRITE, + "RdWr" + }, + + { + PAGE_WRITECOPY, + "WrCp" + }, + + { + PAGE_EXECUTE, + "Ex" + }, + + { + PAGE_EXECUTE_READ, + "ExRd" + }, + + { + PAGE_EXECUTE_READWRITE, + "ExRdWr" + }, + + { + PAGE_EXECUTE_WRITECOPY, + "ExWrCp" + } + }; + +#define NUM_PROTECT_MASKS (sizeof(ProtectMasks) / sizeof(ProtectMasks[0])) + +// +// Private functions. +// + + +PSTR +ULongLongToString( + IN ULONGLONG Value, + __out_ecount (CCH_ULONGLONG_COMMAS) OUT PSTR Buffer + ) +{ + + PSTR p1; + PSTR p2; + CHAR ch; + INT digit; + INT count; + BOOL needComma; + INT length; + + // + // Handling zero specially makes everything else a bit easier. + // + + if( Value == 0 ) { + Buffer[0] = '0'; + Buffer[1] = '\0'; + return Buffer; + } + + // + // Pull the least signifigant digits off the value and store them + // into the buffer. Note that this will store the digits in the + // reverse order. + // + + p1 = p2 = Buffer; + count = 3; + needComma = FALSE; + + while( Value != 0 ) { + + if( needComma ) { + *p1++ = ','; + needComma = FALSE; + } + + digit = (INT)( Value % 10 ); + Value = Value / 10; + + *p1++ = '0' + (CHAR) digit; + + count--; + if( count == 0 ) { + count = 3; + needComma = TRUE; + } + + } + + length = (INT)(((size_t)p1) - ((size_t)Buffer)); + + // + // Reverse the digits in the buffer. + // + + *p1-- = '\0'; + + while( p1 > p2 ) { + + ch = *p1; + *p1 = *p2; + *p2 = ch; + + p2++; + p1--; + + } + + return Buffer; + +} // ULongLongToString + +VOID +InitVmStats( + OUT PVM_STATS Stats + ) +{ + ZeroMemory( Stats, sizeof(*Stats) ); + Stats->Summary.MinimumSize = (SIZE_T)-1L; + Stats->Small.MinimumSize = (SIZE_T)-1L; + Stats->Medium.MinimumSize = (SIZE_T)-1L; + Stats->Large.MinimumSize = (SIZE_T)-1L; + +} // InitVmStats + +VOID +UpdateIndividualStat( + IN OUT PINDIVIDUAL_STAT Stat, + IN SIZE_T BlockSize + ) +{ + Stat->BlockCount++; + Stat->TotalSize += BlockSize; + + if( BlockSize > Stat->MaximumSize ) { + Stat->MaximumSize = BlockSize; + } + + if( BlockSize < Stat->MinimumSize ) { + Stat->MinimumSize = BlockSize; + } + +} // UpdateIndividualStat + +VOID +UpdateVmStats( + IN OUT PVM_STATS Stats, + IN SIZE_T BlockSize + ) +{ + UpdateIndividualStat( &Stats->Summary, BlockSize ); + + if( IS_SMALL(BlockSize) ) { + UpdateIndividualStat( &Stats->Small, BlockSize ); + } + + if( IS_MEDIUM(BlockSize) ) { + UpdateIndividualStat( &Stats->Medium, BlockSize ); + } + + if( IS_LARGE(BlockSize) ) { + UpdateIndividualStat( &Stats->Large, BlockSize ); + } + +} // UpdateVmStats + +VOID +PrintVmStatsHeader( + VOID + ) +{ + ExtOut( + PRINTF_FORMAT_HEAD, + "TYPE", + CCH_ULONGLONG_MINIMUM_COMMAS, + "MINIMUM", + CCH_ULONGLONG_COMMAS, + "MAXIMUM", + CCH_ULONGLONG_COMMAS, + "AVERAGE", + CCH_ULONGLONG_BLOCKCOUNT_COMMAS, + "BLK COUNT", + CCH_ULONGLONG_COMMAS, + "TOTAL" + ); + + ExtOut( + PRINTF_FORMAT_HEAD, + "~~~~", + CCH_ULONGLONG_MINIMUM_COMMAS, + "~~~~~~~", + CCH_ULONGLONG_COMMAS, + "~~~~~~~", + CCH_ULONGLONG_COMMAS, + "~~~~~~~", + CCH_ULONGLONG_BLOCKCOUNT_COMMAS, + "~~~~~~~~~", + CCH_ULONGLONG_COMMAS, + "~~~~~" + ); + +} // PrintVmStatsHeader + +#define BYTES_TO_K(x) (x/1024) + +VOID +PrintIndividualStat( + ___in __in_z IN PSTR Name, + IN PINDIVIDUAL_STAT Stat + ) +{ + SIZE_T average; + SIZE_T minsize; + CHAR minStr[CCH_ULONGLONG_COMMAS]; + CHAR maxStr[CCH_ULONGLONG_COMMAS]; + CHAR avgStr[CCH_ULONGLONG_COMMAS]; + CHAR countStr[CCH_ULONGLONG_COMMAS]; + CHAR totalStr[CCH_ULONGLONG_COMMAS]; + + if( Stat->BlockCount == 0 ) { + average = 0; + minsize = 0; + } else { + average = Stat->TotalSize / Stat->BlockCount; + minsize = Stat->MinimumSize; + } + + ExtOut( + PRINTF_FORMAT, + Name, + CCH_ULONGLONG_MINIMUM_COMMAS, + ULongLongToString( + (ULONGLONG)BYTES_TO_K(minsize), + minStr + ), + CCH_ULONGLONG_COMMAS, + ULongLongToString( + (ULONGLONG)BYTES_TO_K(Stat->MaximumSize), + maxStr + ), + CCH_ULONGLONG_COMMAS, + ULongLongToString( + (ULONGLONG)BYTES_TO_K(average), + avgStr + ), + CCH_ULONGLONG_BLOCKCOUNT_COMMAS, + ULongLongToString( + (ULONGLONG)Stat->BlockCount, + countStr + ), + CCH_ULONGLONG_COMMAS, + ULongLongToString( + (ULONGLONG)BYTES_TO_K(Stat->BlockCount * average), + totalStr + ) + ); + +} // PrintIndividualStat + + +VOID +PrintVmStats( + ___in __in_z IN PSTR Name, + IN PVM_STATS Stats + ) +{ + ExtOut( "%s:\n", Name ); + + PrintIndividualStat( "Small", &Stats->Small ); + PrintIndividualStat( "Medium", &Stats->Medium ); + PrintIndividualStat( "Large", &Stats->Large ); + PrintIndividualStat( "Summary", &Stats->Summary ); + + ExtOut( "\n" ); + +} // PrintVmStats + +PSTR +VmProtectToString( + IN DWORD Protect, + __out_ecount(capacity_Buffer) OUT PSTR Buffer, + size_t capacity_Buffer + ) +{ + INT i; + PPROTECT_MASK mask; + + Buffer[0] = '\0'; + + for( i = 0, mask = &ProtectMasks[0] ; + (i < NUM_PROTECT_MASKS) && (Protect != 0) ; + i++, mask++ ) { + if( mask->Bit & Protect ) { + Protect &= ~mask->Bit; + if( Buffer[0] != '\0' ) { + strcat_s(Buffer,capacity_Buffer, "|" ); + } + strcat_s( Buffer, capacity_Buffer, mask->Name ); + } + } + + if( Protect != 0 ) { + if( Buffer[0] != '\0' ) { + strcat_s( Buffer, capacity_Buffer, "|" ); + } + size_t len_Buffer = strlen(Buffer); + size_t cbSizeInBytes = 0; + if (!ClrSafeInt<size_t>::subtraction(capacity_Buffer, len_Buffer, cbSizeInBytes)) + { + ExtOut("<integer underflow>\n"); + return Buffer; + } + sprintf_s( Buffer + len_Buffer, cbSizeInBytes, "%08lx", Protect ); + } + + return Buffer; + +} // VmProtectToString + +PSTR +VmStateToString( + IN DWORD State, + __out_ecount(capacity_Buffer) OUT PSTR Buffer, + size_t capacity_Buffer + ) +{ + PSTR result; + CHAR invalidStr[sizeof("12345678")]; + + switch( State ) + { + case MEM_COMMIT: + result = "Commit"; + break; + + case MEM_RESERVE: + result = "Reserve"; + break; + + case MEM_FREE: + result = "Free"; + break; + + default: + sprintf_s(invalidStr,_countof(invalidStr), "%08lx", State ); + result = invalidStr; + break; + } + + strcpy_s( Buffer, capacity_Buffer, result ); + return Buffer; + +} // VmStateToString + +PSTR +VmTypeToString( + IN DWORD Type, + __out_ecount(capacity_Buffer) OUT PSTR Buffer, + size_t capacity_Buffer + ) +{ + PSTR result; + CHAR invalidStr[sizeof("12345678")]; + + switch( Type ) + { + case MEM_PRIVATE: + result = "Private"; + break; + + case MEM_MAPPED: + result = "Mapped"; + break; + + case MEM_IMAGE: + result = "Image"; + break; + + case 0: + result = ""; + break; + + default: + sprintf_s(invalidStr,_countof(invalidStr), "%08lx", Type ); + result = invalidStr; + break; + } + strcpy_s( Buffer,capacity_Buffer, result ); + return Buffer; + +} // VmTypeToString + +/************************************************************ + * Dump Virtual Memory Info + ************************************************************/ + +void vmstat() + +/*++ + +Routine Description: + + This function is called as an NTSD extension to format and dump + virtual memory statistics. + +Arguments: + +Return Value: + + None. + +--*/ + +{ + + NTSTATUS status; + ULONG64 address; + MEMORY_BASIC_INFORMATION64 memInfo; + VM_STATS freeStats; + VM_STATS reserveStats; + VM_STATS commitStats; + VM_STATS privateStats; + VM_STATS mappedStats; + VM_STATS imageStats; + + // + // Setup. + // + + InitVmStats( &freeStats ); + InitVmStats( &reserveStats ); + InitVmStats( &commitStats ); + InitVmStats( &privateStats ); + InitVmStats( &mappedStats ); + InitVmStats( &imageStats ); + + address = 0; + + // + // Scan the virtual address space. + // + + for( ; ; ) { + status = g_ExtData2->QueryVirtual(address, &memInfo); + + if( !NT_SUCCESS(status) ) { + break; + } + + // + // Interpret the memory state. + // + + SIZE_T regionSize = (SIZE_T) memInfo.RegionSize; + + switch( memInfo.State ) { + + case MEM_FREE: + UpdateVmStats( &freeStats, regionSize ); + break; + + case MEM_RESERVE: + UpdateVmStats( &reserveStats, regionSize ); + break; + + case MEM_COMMIT: + UpdateVmStats( &commitStats, regionSize ); + break; + } + + // + // Interpret the memory type. + // + + switch( memInfo.Type ) { + case MEM_PRIVATE: + UpdateVmStats( &privateStats, regionSize ); + break; + + case MEM_MAPPED: + UpdateVmStats( &mappedStats, regionSize ); + break; + + case MEM_IMAGE: + UpdateVmStats( &imageStats, regionSize ); + break; + } + + // + // Advance to the next block. + // + + address += memInfo.RegionSize; + } + + // + // Dump it. + // + + PrintVmStatsHeader(); + PrintVmStats( "Free", &freeStats ); + PrintVmStats( "Reserve", &reserveStats ); + PrintVmStats( "Commit", &commitStats ); + PrintVmStats( "Private", &privateStats ); + PrintVmStats( "Mapped", &mappedStats ); + PrintVmStats( "Image", &imageStats ); + +} // DECLARE_API( vmstat ) + + +void vmmap() + +/*++ + +Routine Description: + + This function is called as an NTSD extension to format and dump + the debugee's virtual memory address space. + +Arguments: + +Return Value: + + None. + +--*/ + +{ + + NTSTATUS status; + ULONG64 address; + MEMORY_BASIC_INFORMATION64 memInfo; + CHAR protectStr[32]; + CHAR aprotectStr[32]; + CHAR stateStr[16]; + CHAR typeStr[16]; + + // + // Setup. + // + + address = 0; + + ExtOut( + "%-*s %-*s %-*s %-13s %-13s %-8s %-8s\n", + sizeof(PVOID) * 2, + "Start", + sizeof(PVOID) * 2, + "Stop", + sizeof(PVOID) * 2, + "Length", + "AllocProtect", + "Protect", + "State", + "Type" + ); + + // + // Scan the virtual address space. + // + + for( ; ; ) { + + if (IsInterrupt()) + break; + + status = g_ExtData2->QueryVirtual(address, &memInfo); + + if( !NT_SUCCESS(status) ) { + break; + } + + // + // Dump the current entry. + // + + ExtOut( + "%p-%p %p %-13s %-13s %-8s %-8s\n", + SOS_PTR(memInfo.BaseAddress), + SOS_PTR(((ULONG_PTR)memInfo.BaseAddress + memInfo.RegionSize - 1)), + SOS_PTR(memInfo.RegionSize), + VmProtectToString( memInfo.AllocationProtect, aprotectStr, _countof(aprotectStr) ), + VmProtectToString( memInfo.Protect, protectStr, _countof(protectStr) ), + VmStateToString( memInfo.State, stateStr, _countof(stateStr) ), + VmTypeToString( memInfo.Type, typeStr , _countof(typeStr)) + ); + + // + // Advance to the next block. + // + + address += memInfo.RegionSize; + } + +} // DECLARE_API( vmmap ) + +#else + +#include <rotor_pal.h> +#include <assert.h> + +void vmstat() +{ + assert(false); +} + +void vmmap() +{ + + assert(false); +} + +#endif // #ifndef FEATURE_PAL diff --git a/src/ToolBox/SOS/Strike/xplat/.gitmirror b/src/ToolBox/SOS/Strike/xplat/.gitmirror new file mode 100644 index 0000000000..f507630f94 --- /dev/null +++ b/src/ToolBox/SOS/Strike/xplat/.gitmirror @@ -0,0 +1 @@ +Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror.
\ No newline at end of file diff --git a/src/ToolBox/SOS/Strike/xplat/dbgeng.h b/src/ToolBox/SOS/Strike/xplat/dbgeng.h new file mode 100644 index 0000000000..b4562271a6 --- /dev/null +++ b/src/ToolBox/SOS/Strike/xplat/dbgeng.h @@ -0,0 +1,485 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +//---------------------------------------------------------------------------- +// +// Debugger engine interface subset implemented with ILLDBServices +// +//---------------------------------------------------------------------------- + +#ifndef __DBGENG_H__ +#define __DBGENG_H__ + +#include <unknwn.h> +#include <rpc.h> +#include <lldbservices.h> + +#ifdef __cplusplus +extern "C" { +#endif + +class DebugClient +{ +private: + LONG m_ref; + ILLDBServices *m_lldbservices; + +public: + DebugClient(ILLDBServices *lldbservices) : + m_ref(1), + m_lldbservices(lldbservices) + { + m_lldbservices->AddRef(); + } + + //---------------------------------------------------------------------------- + // IUnknown + //---------------------------------------------------------------------------- + + HRESULT + QueryInterface( + REFIID InterfaceId, + PVOID* Interface); + + ULONG AddRef(); + + ULONG Release(); + + //---------------------------------------------------------------------------- + // IDebugControl2 + //---------------------------------------------------------------------------- + + // Checks for a user interrupt, such a Ctrl-C + // or stop button. + // This method is reentrant. + HRESULT + GetInterrupt() + { + return m_lldbservices->GetInterrupt(); + } + + // Sends output through clients + // output callbacks if the mask is allowed + // by the current output control mask and + // according to the output distribution + // settings. + HRESULT + Output( + ULONG mask, + PCSTR format, + ...) + { + va_list args; + va_start (args, format); + HRESULT result = m_lldbservices->OutputVaList(mask, format, args); + va_end (args); + return result; + } + + HRESULT + OutputVaList( + ULONG mask, + PCSTR format, + va_list args) + { + char str[4096]; + int length = PAL__vsnprintf(str, sizeof(str), format, args); + if (length > 0) + { + return Output(mask, "%s", str); + } + return E_FAIL; + } + + // The following methods allow direct control + // over the distribution of the given output + // for situations where something other than + // the default is desired. These methods require + // extra work in the engine so they should + // only be used when necessary. + HRESULT + ControlledOutput( + ULONG outputControl, + ULONG mask, + PCSTR format, + ...) + { + va_list args; + va_start (args, format); + HRESULT result = ControlledOutputVaList(outputControl, mask, format, args); + va_end (args); + return result; + } + + HRESULT + ControlledOutputVaList( + ULONG outputControl, + ULONG mask, + PCSTR format, + va_list args) + { + return OutputVaList(mask, format, args); + } + + // Returns information about the debuggee such + // as user vs. kernel, dump vs. live, etc. + HRESULT + GetDebuggeeType( + PULONG debugClass, + PULONG qualifier) + { + return m_lldbservices->GetDebuggeeType(debugClass, qualifier); + } + + // Returns the page size for the currently executing + // processor context. The page size may vary between + // processor types. + HRESULT + GetPageSize( + PULONG size) + { + return m_lldbservices->GetPageSize(size); + } + + HRESULT + GetExecutingProcessorType( + PULONG type) + { + return m_lldbservices->GetExecutingProcessorType(type); + } + + HRESULT + Execute( + ULONG outputControl, + PCSTR command, + ULONG flags) + { + return m_lldbservices->Execute(outputControl, command, flags); + } + + HRESULT + GetLastEventInformation( + PULONG type, + PULONG processId, + PULONG threadId, + PVOID extraInformation, + ULONG extraInformationSize, + PULONG extraInformationUsed, + PSTR description, + ULONG descriptionSize, + PULONG descriptionUsed) + { + return m_lldbservices->GetLastEventInformation(type, processId, threadId, extraInformation, + extraInformationSize, extraInformationUsed, description, descriptionSize, descriptionUsed); + } + + HRESULT + Disassemble( + ULONG64 offset, + ULONG flags, + PSTR buffer, + ULONG bufferSize, + PULONG disassemblySize, + PULONG64 endOffset) + { + return m_lldbservices->Disassemble(offset, flags, buffer, bufferSize, disassemblySize, endOffset); + } + + //---------------------------------------------------------------------------- + // IDebugControl4 + //---------------------------------------------------------------------------- + + // Stack tracing with a full initial context + // and full context return for each frame. + // The FrameContextsSize parameter is the total + // byte size of FrameContexts. FrameContextsEntrySize + // gives the byte size of each entry in + // FrameContexts. + HRESULT + GetContextStackTrace( + PVOID startContext, + ULONG startContextSize, + PDEBUG_STACK_FRAME frames, + ULONG framesSize, + PVOID frameContexts, + ULONG frameContextsSize, + ULONG frameContextsEntrySize, + PULONG framesFilled) + { + return m_lldbservices->GetContextStackTrace(startContext, startContextSize, frames, + framesSize, frameContexts, frameContextsSize, frameContextsEntrySize, framesFilled); + } + + //---------------------------------------------------------------------------- + // IDebugDataSpaces + //---------------------------------------------------------------------------- + + HRESULT + ReadVirtual( + ULONG64 offset, + PVOID buffer, + ULONG bufferSize, + PULONG bytesRead) + { + return m_lldbservices->ReadVirtual(offset, buffer, bufferSize, bytesRead); + } + + HRESULT + WriteVirtual( + ULONG64 offset, + PVOID buffer, + ULONG bufferSize, + PULONG bytesWritten) + { + return m_lldbservices->WriteVirtual(offset, buffer, bufferSize, bytesWritten); + } + + //---------------------------------------------------------------------------- + // IDebugSymbols + //---------------------------------------------------------------------------- + + HRESULT + GetSymbolOptions( + PULONG options) + { + return m_lldbservices->GetSymbolOptions(options); + } + + HRESULT + GetNameByOffset( + ULONG64 offset, + PSTR nameBuffer, + ULONG nameBufferSize, + PULONG nameSize, + PULONG64 displacement) + { + return m_lldbservices->GetNameByOffset(offset, nameBuffer, nameBufferSize, nameSize, displacement); + } + + HRESULT + GetNumberModules( + PULONG loaded, + PULONG unloaded) + { + return m_lldbservices->GetNumberModules(loaded, unloaded); + } + + HRESULT GetModuleByIndex( + ULONG index, + PULONG64 base) + { + return m_lldbservices->GetModuleByIndex(index, base); + } + + HRESULT + GetModuleByModuleName( + PCSTR name, + ULONG startIndex, + PULONG index, + PULONG64 base) + { + return m_lldbservices->GetModuleByModuleName(name, startIndex, index, base); + } + + HRESULT + GetModuleByOffset( + ULONG64 offset, + ULONG startIndex, + PULONG index, + PULONG64 base) + { + return m_lldbservices->GetModuleByOffset(offset, startIndex, index, base); + } + + HRESULT + GetModuleNames( + ULONG index, + ULONG64 base, + PSTR imageNameBuffer, + ULONG imageNameBufferSize, + PULONG imageNameSize, + PSTR moduleNameBuffer, + ULONG moduleNameBufferSize, + PULONG moduleNameSize, + PSTR loadedImageNameBuffer, + ULONG loadedImageNameBufferSize, + PULONG loadedImageNameSize) + { + return m_lldbservices->GetModuleNames(index, base, imageNameBuffer, imageNameBufferSize, imageNameSize, moduleNameBuffer, + moduleNameBufferSize, moduleNameSize, loadedImageNameBuffer, loadedImageNameBufferSize, loadedImageNameSize); + } + + HRESULT + GetLineByOffset( + ULONG64 offset, + PULONG line, + PSTR fileBuffer, + ULONG fileBufferSize, + PULONG fileSize, + PULONG64 displacement) + { + return m_lldbservices->GetLineByOffset(offset, line, fileBuffer, fileBufferSize, fileSize, displacement); + } + + HRESULT + GetSourceFileLineOffsets( + PCSTR file, + PULONG64 buffer, + ULONG bufferLines, + PULONG fileLines) + { + return m_lldbservices->GetSourceFileLineOffsets(file, buffer, bufferLines, fileLines); + } + + // Uses the given file path and the source path + // information to try and locate an existing file. + // The given file path is merged with elements + // of the source path and checked for existence. + // If a match is found the element used is returned. + // A starting element can be specified to restrict + // the search to a subset of the path elements; + // this can be useful when checking for multiple + // matches along the source path. + // The returned element can be 1, indicating + // the file was found directly and not on the path. + HRESULT + FindSourceFile( + ULONG startElement, + PCSTR file, + ULONG flags, + PULONG foundElement, + PSTR buffer, + ULONG bufferSize, + PULONG foundSize) + { + return m_lldbservices->FindSourceFile(startElement, file, flags, foundElement, buffer, bufferSize, foundSize); + } + + //---------------------------------------------------------------------------- + // IDebugSystemObjects + //---------------------------------------------------------------------------- + + HRESULT + GetCurrentProcessId( + PULONG id) + { + return m_lldbservices->GetCurrentProcessId(id); + } + + HRESULT + GetCurrentThreadId( + PULONG id) + { + return m_lldbservices->GetCurrentThreadId(id); + } + + HRESULT + SetCurrentThreadId( + ULONG id) + { + return m_lldbservices->SetCurrentThreadId(id); + } + + HRESULT + GetCurrentThreadSystemId( + PULONG sysId) + { + return m_lldbservices->GetCurrentThreadSystemId(sysId); + } + + HRESULT + GetThreadIdBySystemId( + ULONG sysId, + PULONG threadId) + { + return m_lldbservices->GetThreadIdBySystemId(sysId, threadId); + } + + HRESULT + GetThreadContextById( + /* in */ ULONG32 threadID, + /* in */ ULONG32 contextFlags, + /* in */ ULONG32 contextSize, + /* out */ PBYTE context) + { + return m_lldbservices->GetThreadContextById(threadID, contextFlags, contextSize, context); + } + + //---------------------------------------------------------------------------- + // IDebugRegisters + //---------------------------------------------------------------------------- + + HRESULT + GetValueByName( + PCSTR name, + PDWORD_PTR debugValue) + { + return m_lldbservices->GetValueByName(name, debugValue); + } + + HRESULT + GetInstructionOffset( + PULONG64 offset) + { + return m_lldbservices->GetInstructionOffset(offset); + } + + HRESULT + GetStackOffset( + PULONG64 offset) + { + return m_lldbservices->GetStackOffset(offset); + } + + HRESULT + GetFrameOffset( + PULONG64 offset) + { + return m_lldbservices->GetFrameOffset(offset); + } +}; + +MIDL_INTERFACE("d4366723-44df-4bed-8c7e-4c05424f4588") +IDebugControl2 : DebugClient +{ +}; + +MIDL_INTERFACE("94e60ce9-9b41-4b19-9fc0-6d9eb35272b3") +IDebugControl4 : DebugClient +{ +}; + +MIDL_INTERFACE("88f7dfab-3ea7-4c3a-aefb-c4e8106173aa") +IDebugDataSpaces : DebugClient +{ +}; + +MIDL_INTERFACE("8c31e98c-983a-48a5-9016-6fe5d667a950") +IDebugSymbols : DebugClient +{ +}; + +MIDL_INTERFACE("6b86fe2c-2c4f-4f0c-9da2-174311acc327") +IDebugSystemObjects : DebugClient +{ +}; + +MIDL_INTERFACE("ce289126-9e84-45a7-937e-67bb18691493") +IDebugRegisters : DebugClient +{ +}; + +typedef interface ILLDBServices* PDEBUG_CLIENT; +typedef interface IDebugControl2* PDEBUG_CONTROL2; +typedef interface IDebugControl4* PDEBUG_CONTROL4; +typedef interface IDebugDataSpaces* PDEBUG_DATA_SPACES; +typedef interface IDebugSymbols* PDEBUG_SYMBOLS; +typedef interface IDebugSystemObjects* PDEBUG_SYSTEM_OBJECTS; +typedef interface IDebugRegisters* PDEBUG_REGISTERS; + +#ifdef __cplusplus +}; +#endif + +#endif // #ifndef __DBGENG_H__ diff --git a/src/ToolBox/SOS/Strike/xplat/dbghelp.h b/src/ToolBox/SOS/Strike/xplat/dbghelp.h new file mode 100644 index 0000000000..7086b335fd --- /dev/null +++ b/src/ToolBox/SOS/Strike/xplat/dbghelp.h @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/* + +Module Name: + + dbghelp.h + +Abstract: + + Dummy include file for LLDB sos extension. + +Revision History: + +--*/
\ No newline at end of file diff --git a/src/ToolBox/SOS/Strike/xplat/wdbgexts.h b/src/ToolBox/SOS/Strike/xplat/wdbgexts.h new file mode 100644 index 0000000000..d5a1a31c0a --- /dev/null +++ b/src/ToolBox/SOS/Strike/xplat/wdbgexts.h @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*++ + +Module Name: + + wdbgexts.h + +Abstract: + + Dummy include file for LLDB sos extension. + +Environment: + +Revision History: + +--*/ + +#ifndef _WDBGEXTS_ +#define _WDBGEXTS_ + +#if _MSC_VER > 1000 +#pragma once +#endif + +#endif // _WDBGEXTS_ |