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-f1 |