From 4b4aad7217d3292650e77eec2cf4c198ea9c3b4b Mon Sep 17 00:00:00 2001 From: Jiyoung Yun Date: Wed, 23 Nov 2016 19:09:09 +0900 Subject: Imported Upstream version 1.1.0 --- src/ToolBox/SOS/Strike/.gitmirror | 1 + src/ToolBox/SOS/Strike/ApolloNative.rc | 10 + src/ToolBox/SOS/Strike/CMakeLists.txt | 201 + src/ToolBox/SOS/Strike/EventCallbacks.cpp | 160 + src/ToolBox/SOS/Strike/EventCallbacks.h | 68 + src/ToolBox/SOS/Strike/ExpressionNode.cpp | 2178 ++++ src/ToolBox/SOS/Strike/ExpressionNode.h | 307 + src/ToolBox/SOS/Strike/Native.rc | 10 + src/ToolBox/SOS/Strike/SOS.nativeproj | 7 + src/ToolBox/SOS/Strike/SOS.sln | 76 + src/ToolBox/SOS/Strike/SOS.vcproj | 303 + src/ToolBox/SOS/Strike/UtilCode.h | 11 + src/ToolBox/SOS/Strike/WatchCmd.cpp | 331 + src/ToolBox/SOS/Strike/WatchCmd.h | 110 + src/ToolBox/SOS/Strike/apollososdocs.txt | 2727 +++++ src/ToolBox/SOS/Strike/data.h | 51 + src/ToolBox/SOS/Strike/datatarget.cpp | 215 + src/ToolBox/SOS/Strike/datatarget.h | 90 + src/ToolBox/SOS/Strike/dirs.proj | 20 + src/ToolBox/SOS/Strike/disasm.cpp | 1142 ++ src/ToolBox/SOS/Strike/disasm.h | 453 + src/ToolBox/SOS/Strike/disasmARM.cpp | 626 ++ src/ToolBox/SOS/Strike/disasmARM64.cpp | 392 + src/ToolBox/SOS/Strike/disasmX86.cpp | 1707 +++ src/ToolBox/SOS/Strike/dllsext.cpp | 278 + src/ToolBox/SOS/Strike/eeheap.cpp | 1913 ++++ src/ToolBox/SOS/Strike/exts.cpp | 435 + src/ToolBox/SOS/Strike/exts.h | 513 + src/ToolBox/SOS/Strike/gchist.cpp | 636 ++ src/ToolBox/SOS/Strike/gcroot.cpp | 2503 +++++ src/ToolBox/SOS/Strike/inc/.gitmirror | 1 + src/ToolBox/SOS/Strike/inc/dbgeng.h | 16122 +++++++++++++++++++++++++++ src/ToolBox/SOS/Strike/inc/dbghelp.h | 4540 ++++++++ src/ToolBox/SOS/Strike/inc/wdbgexts.h | 2807 +++++ src/ToolBox/SOS/Strike/metadata.cpp | 1041 ++ src/ToolBox/SOS/Strike/ntinfo.h | 193 + src/ToolBox/SOS/Strike/platformspecific.h | 195 + src/ToolBox/SOS/Strike/sildasm.cpp | 1090 ++ src/ToolBox/SOS/Strike/sos.cpp | 888 ++ src/ToolBox/SOS/Strike/sos.def | 231 + src/ToolBox/SOS/Strike/sos.h | 792 ++ src/ToolBox/SOS/Strike/sos.targets | 166 + src/ToolBox/SOS/Strike/sos_md.h | 926 ++ src/ToolBox/SOS/Strike/sos_stacktrace.h | 174 + src/ToolBox/SOS/Strike/sos_unixexports.src | 54 + src/ToolBox/SOS/Strike/sosdocs.txt | 2572 +++++ src/ToolBox/SOS/Strike/sosdocsunix.txt | 1713 +++ src/ToolBox/SOS/Strike/stressLogDump.cpp | 549 + src/ToolBox/SOS/Strike/strike.cpp | 14462 ++++++++++++++++++++++++ src/ToolBox/SOS/Strike/strike.h | 144 + src/ToolBox/SOS/Strike/util.cpp | 6975 ++++++++++++ src/ToolBox/SOS/Strike/util.h | 3292 ++++++ src/ToolBox/SOS/Strike/vm.cpp | 732 ++ src/ToolBox/SOS/Strike/xplat/.gitmirror | 1 + src/ToolBox/SOS/Strike/xplat/dbgeng.h | 485 + src/ToolBox/SOS/Strike/xplat/dbghelp.h | 17 + src/ToolBox/SOS/Strike/xplat/wdbgexts.h | 28 + 57 files changed, 77664 insertions(+) create mode 100644 src/ToolBox/SOS/Strike/.gitmirror create mode 100644 src/ToolBox/SOS/Strike/ApolloNative.rc create mode 100644 src/ToolBox/SOS/Strike/CMakeLists.txt create mode 100644 src/ToolBox/SOS/Strike/EventCallbacks.cpp create mode 100644 src/ToolBox/SOS/Strike/EventCallbacks.h create mode 100644 src/ToolBox/SOS/Strike/ExpressionNode.cpp create mode 100644 src/ToolBox/SOS/Strike/ExpressionNode.h create mode 100644 src/ToolBox/SOS/Strike/Native.rc create mode 100644 src/ToolBox/SOS/Strike/SOS.nativeproj create mode 100644 src/ToolBox/SOS/Strike/SOS.sln create mode 100644 src/ToolBox/SOS/Strike/SOS.vcproj create mode 100644 src/ToolBox/SOS/Strike/UtilCode.h create mode 100644 src/ToolBox/SOS/Strike/WatchCmd.cpp create mode 100644 src/ToolBox/SOS/Strike/WatchCmd.h create mode 100644 src/ToolBox/SOS/Strike/apollososdocs.txt create mode 100644 src/ToolBox/SOS/Strike/data.h create mode 100644 src/ToolBox/SOS/Strike/datatarget.cpp create mode 100644 src/ToolBox/SOS/Strike/datatarget.h create mode 100644 src/ToolBox/SOS/Strike/dirs.proj create mode 100644 src/ToolBox/SOS/Strike/disasm.cpp create mode 100644 src/ToolBox/SOS/Strike/disasm.h create mode 100644 src/ToolBox/SOS/Strike/disasmARM.cpp create mode 100644 src/ToolBox/SOS/Strike/disasmARM64.cpp create mode 100644 src/ToolBox/SOS/Strike/disasmX86.cpp create mode 100644 src/ToolBox/SOS/Strike/dllsext.cpp create mode 100644 src/ToolBox/SOS/Strike/eeheap.cpp create mode 100644 src/ToolBox/SOS/Strike/exts.cpp create mode 100644 src/ToolBox/SOS/Strike/exts.h create mode 100644 src/ToolBox/SOS/Strike/gchist.cpp create mode 100644 src/ToolBox/SOS/Strike/gcroot.cpp create mode 100644 src/ToolBox/SOS/Strike/inc/.gitmirror create mode 100644 src/ToolBox/SOS/Strike/inc/dbgeng.h create mode 100644 src/ToolBox/SOS/Strike/inc/dbghelp.h create mode 100644 src/ToolBox/SOS/Strike/inc/wdbgexts.h create mode 100644 src/ToolBox/SOS/Strike/metadata.cpp create mode 100644 src/ToolBox/SOS/Strike/ntinfo.h create mode 100644 src/ToolBox/SOS/Strike/platformspecific.h create mode 100644 src/ToolBox/SOS/Strike/sildasm.cpp create mode 100644 src/ToolBox/SOS/Strike/sos.cpp create mode 100644 src/ToolBox/SOS/Strike/sos.def create mode 100644 src/ToolBox/SOS/Strike/sos.h create mode 100644 src/ToolBox/SOS/Strike/sos.targets create mode 100644 src/ToolBox/SOS/Strike/sos_md.h create mode 100644 src/ToolBox/SOS/Strike/sos_stacktrace.h create mode 100644 src/ToolBox/SOS/Strike/sos_unixexports.src create mode 100644 src/ToolBox/SOS/Strike/sosdocs.txt create mode 100644 src/ToolBox/SOS/Strike/sosdocsunix.txt create mode 100644 src/ToolBox/SOS/Strike/stressLogDump.cpp create mode 100644 src/ToolBox/SOS/Strike/strike.cpp create mode 100644 src/ToolBox/SOS/Strike/strike.h create mode 100644 src/ToolBox/SOS/Strike/util.cpp create mode 100644 src/ToolBox/SOS/Strike/util.h create mode 100644 src/ToolBox/SOS/Strike/vm.cpp create mode 100644 src/ToolBox/SOS/Strike/xplat/.gitmirror create mode 100644 src/ToolBox/SOS/Strike/xplat/dbgeng.h create mode 100644 src/ToolBox/SOS/Strike/xplat/dbghelp.h create mode 100644 src/ToolBox/SOS/Strike/xplat/wdbgexts.h (limited to 'src/ToolBox/SOS/Strike') 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 +#include + +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(this); + AddRef(); + return S_OK; + } + else if(riid == __uuidof(IUnknown)) + { + *ppInterface = static_cast(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 '' +// 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 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 pType; + if(pTypeCast != NULL) + { + pType = pTypeCast; + pType->AddRef(); + } + else + { + BOOL isNull; + ToRelease pInnerValue; + IfFailRet(DereferenceAndUnboxValue(pValue, &pInnerValue, &isNull)); + ToRelease 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 pClass; + IfFailRet(pType->GetClass(&pClass)); + ToRelease pModule; + IfFailRet(pClass->GetModule(&pModule)); + ToRelease 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 + //ELEMENT_TYPE_GENERICINST = 0x15, // GENERICINST ... + //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 + //ELEMENT_TYPE_CMOD_REQD = 0x1F, // required C modifier : E_T_CMOD_REQD + //ELEMENT_TYPE_CMOD_OPT = 0x20, // optional C modifier : E_T_CMOD_OPT + //ELEMENT_TYPE_INTERNAL = 0x21, // INTERNAL + //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 pClass; + if(SUCCEEDED(pType->GetClass(&pClass)) && SUCCEEDED(pClass->GetToken(&typeDef))) + { + ToRelease pModule; + IfFailRet(pClass->GetModule(&pModule)); + + ToRelease pMDUnknown; + ToRelease 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 pFirstParameter; + if(SUCCEEDED(pType->GetFirstTypeParameter(&pFirstParameter))) + CalculateTypeName(pFirstParameter, typeName, typeNameLen); + else + swprintf_s(typeName, typeNameLen, L"\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 pTypeEnum; + if(SUCCEEDED(pType->EnumerateTypeParameters(&pTypeEnum))) + { + ULONG numTypes = 0; + ToRelease 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""); + + // 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 pType; + ToRelease 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 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 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 pClass; + ToRelease pType; + ToRelease pModule; + if(pTypeCast == NULL) + { + ToRelease 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 pMDUnknown; + ToRelease 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 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"", 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 pFieldType; + ToRelease 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 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 pReferenceValue; + Status = pInputValue->QueryInterface(IID_ICorDebugReferenceValue, (LPVOID*) &pReferenceValue); + if (SUCCEEDED(Status)) + { + BOOL isNull = FALSE; + IfFailRet(pReferenceValue->IsNull(&isNull)); + if(!isNull) + { + ToRelease pDereferencedValue; + IfFailRet(pReferenceValue->Dereference(&pDereferencedValue)); + return DereferenceAndUnboxValue(pDereferencedValue, ppOutputValue); + } + else + { + if(pIsNull != NULL) *pIsNull = TRUE; + *ppOutputValue = pInputValue; + (*ppOutputValue)->AddRef(); + return S_OK; + } + } + + ToRelease pBoxedValue; + Status = pInputValue->QueryInterface(IID_ICorDebugBoxValue, (LPVOID*) &pBoxedValue); + if (SUCCEEDED(Status)) + { + ToRelease 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 pValue; + if(FAILED(DereferenceAndUnboxValue(pInputValue, &pValue, NULL))) return FALSE; + + WCHAR baseTypeName[mdNameLen]; + ToRelease pValue2; + ToRelease pType; + ToRelease 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 pClass; + ToRelease pValue2; + ToRelease pType; + ToRelease 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 pMDUnknown; + ToRelease 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 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 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 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 rgbValue = new BYTE[cbSize]; + memset(rgbValue.GetPtr(), 0, cbSize * sizeof(BYTE)); + if(pInnerValue != NULL) + { + ToRelease 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""); + break; + + case ELEMENT_TYPE_FNPTR: + { + CORDB_ADDRESS addr = 0; + ToRelease pReferenceValue = NULL; + if(SUCCEEDED(pInnerValue->QueryInterface(IID_ICorDebugReferenceValue, (LPVOID*) &pReferenceValue))) + pReferenceValue->GetValue(&addr); + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"", 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 ... ... + // + // ELEMENT_TYPE_GENERICINST = 0x15, // GENERICINST ... + } + + 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 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 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 pModule; + Status = pFunction->GetModule(&pModule); + if (FAILED(Status)) + { + return; + } + ToRelease pMDUnknown; + Status = pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown); + if (FAILED(Status)) + { + return; + } + ToRelease 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 = +// pParsedValue = pParsedDefaultValue = NULL +// cchParsedDefaultValue = 0 +// The expression up to charactersParsed is a recognized value in the debuggee: +// pParsedValue = +// pParsedType = pParsedDefaultValue = NULL +// cchParsedDefaultValue = 0 +// The expression up to charactersParsed is a recognized default value stored in metadata: +// pParsedValue = NULL +// pParsedType = +// pParsedDefaultValue = +// cchParsedDefaultValue = +// +// +// 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 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 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 pType; + BOOL isNull = TRUE; + ToRelease 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 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 pClass; + IfFailRet(pType->GetClass(&pClass)); + ToRelease pModule; + IfFailRet(pClass->GetModule(&pModule)); + mdTypeDef currentTypeDef; + IfFailRet(pClass->GetToken(¤tTypeDef)); + + ToRelease pMDUnknown; + ToRelease 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 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 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 pBaseType = pType; + pBaseType->AddRef(); + + while(pBaseType != NULL) + { + // get the current base type class/token/MD + ToRelease pBaseClass; + IfFailRet(pBaseType->GetClass(&pBaseClass)); + ToRelease pBaseTypeModule; + IfFailRet(pBaseClass->GetModule(&pBaseTypeModule)); + mdTypeDef baseTypeDef; + IfFailRet(pBaseClass->GetToken(&baseTypeDef)); + ToRelease pBaseTypeMDUnknown; + ToRelease 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 pFieldValType = NULL; + ToRelease pFieldVal; + if (fieldAttr & fdStatic) + pBaseType->GetStaticFieldValue(fieldDef, pFrame, &pFieldVal); + else if(pInnerValue != NULL) + { + ToRelease 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 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 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 pFunction; + ToRelease pModule; + if(SUCCEEDED(pILFrame->GetFunction(&pFunction))) + { + IfFailRet(pFunction->GetModule(&pModule)); + } + ToRelease 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 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 pThread; + ToRelease pThread3; + ToRelease 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("\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 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 pAppDomainEnum; + IfFailRet(g_pCorDebugProcess->EnumerateAppDomains(&pAppDomainEnum)); + DWORD count; + IfFailRet(pAppDomainEnum->GetCount(&count)); + for(DWORD i = 0; i < count; i++) + { + ToRelease 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 pAssemblyEnum; + IfFailRet(pAppDomain->EnumerateAssemblies(&pAssemblyEnum)); + DWORD count; + IfFailRet(pAssemblyEnum->GetCount(&count)); + for(DWORD i = 0; i < count; i++) + { + ToRelease 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 pModuleEnum; + IfFailRet(pAssembly->EnumerateModules(&pModuleEnum)); + DWORD count; + IfFailRet(pModuleEnum->GetCount(&count)); + for(DWORD i = 0; i < count; i++) + { + ToRelease 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 pMDUnknown; + ToRelease 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 pClass; + IfFailRet(pModule->GetClassFromToken(typeDef, &pClass)); + ToRelease pClass2; + IfFailRet(pClass->QueryInterface(__uuidof(ICorDebugClass2), (void**)&pClass2)); + + // Convert from class to type - if generic then recursively resolve the generic + // parameter list + ArrayHolder> typeParams = NULL; + int countTypeParams = 0; + if(genericParamListStart != typeNameLen) + { + ToRelease pAssembly; + IfFailRet(pModule->GetAssembly(&pAssembly)); + ToRelease pDomain; + IfFailRet(pAssembly->GetAppDomain(&pDomain)); + + countTypeParams = 1; + for(int i = genericParamListStart+1; i < typeNameLen; i++) + { + if(pTypeName[i] == L',') countTypeParams++; + } + typeParams = new ToRelease[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 '' + // 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 pTypeCast; + + // for nodes that evaluate to a memory backed debuggee value, this is that value + ToRelease 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 pILFrame; + + // TODO: exactly which metadata is this supposed to be? try to get rid of this + ToRelease 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 pFoundValue; + ToRelease pFoundFrame; + ToRelease 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 +#include + +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 @@ + + + + + true + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 %S %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: + + +COMMAND: + +\\ + + + +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 " 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 rather than using the +.loadby shortcut). Within the Microsoft corpnet, we keep tagged versions +of mscordacwks.dll, with names like mscordacwks__.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 " 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] + + [] + +!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] + +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 ] + [-length ] + [-details] + [-nofields] + + +This command allows you to examine elements of an array object. +The arguments in detail: + -start : optional, only supported for single dimension array. + Specify from which index the command shows the elements. + -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 ] + [-max ] + [-thinlock] + [-startAtLowerBound] + [-mt ] + [-type ] + [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: + + + 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 []". + +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
+ +!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] + +!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 [] | [-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] [] + +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] + +!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: + + + + + ... + + + + + ... + + + + + + ... + + ... + + + +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 = . 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 + +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 , and if the +debugger is configured to load line number information. +\\ + +COMMAND: u. +!U [-gcinfo] [-ehinfo] [-n] | + +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. + + + ... + 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. + + + ... + c:\Code\prj.mini\exc.cs @ 38: + 001b00b0 8b0d3020ab03 mov ecx,dword ptr ds:[3AB2030h] ("Break in debugger. When done type 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 ( | ) + +!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 ( | ) + +!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] [] +!BPMD : +!BPMD -md +!BPMD -list +!BPMD -clear +!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 + + 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 + { + ... + public void F(T1 p1, T2 p2, T3 p3) + { ... } + } + + public class G1 { + // static method + static public void G(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 + { + void M1(T t); + } + + public class ExplicitItfImpl : IT1 + { + ... + void IT1.M1(U u) // this method's name is 'IT1.M1' + { ... } + } + + !bpmd bpmd.exe ExplicitItfImpl`1.IT1.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.M1 + + (note that the fully qualified type name for ExplicitItfImpl became + Outer+ExplicitItfImpl, using the '+' separator, while the method name + is Outer.IT1.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.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 [] + +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 +!Name2EE ! + +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 to search all loaded managed modules. + can also be the debugger's name for a module, such as +mscorlib or image00400000. + +The Windows Debugger syntax of ! 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 . +\\ + +COMMAND: syncblk. +!SyncBlk [-all | ] + +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] + +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 + +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 + +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 + +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 to find what that token maps to in every +loaded managed module. 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] + +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 + ...... + 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 + +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 + +This command dumps the signature of a method or field given by . 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 n) + + 0:000> !dumpsig 00000000`00bc2437 000007ff00043178 + [DEFAULT] [hasThis] __Canon (Class System.Collections.Generic.IEnumerable`1<__Canon>) + +\\ + +COMMAND: dumpsigelem. +!DumpSigElem + +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 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 | + | + | + /i + +!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 " 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 + +!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 | -gen any | + +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 + +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 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 + +!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 + +!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 ] [] + +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 + +!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 + +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 + +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 + +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 + +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 + +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 + +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 + +!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 <$ to execute the saved script. +\\ + +COMMAND: watch. +!Watch +!Watch -add +!Watch -remove +!Watch -save +!Watch -rename +!Watch [-filter ] [-expand [type_cast]] + +!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 [] 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 ` to the type name you would see +in VB or C#. For example: +System.Collections.Generic.Dictionary`2 + +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 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 @@ + + + + + true + false + true + true + + + + + + + + + + + + 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 +#include + +#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 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(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(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 + + +#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 ). + 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 , [PC +/- ]). 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 . 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 + ] 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 +// : 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