summaryrefslogtreecommitdiff
path: root/src/ToolBox/SOS/Strike
diff options
context:
space:
mode:
Diffstat (limited to 'src/ToolBox/SOS/Strike')
-rw-r--r--src/ToolBox/SOS/Strike/.gitmirror1
-rw-r--r--src/ToolBox/SOS/Strike/ApolloNative.rc10
-rw-r--r--src/ToolBox/SOS/Strike/CMakeLists.txt201
-rw-r--r--src/ToolBox/SOS/Strike/EventCallbacks.cpp160
-rw-r--r--src/ToolBox/SOS/Strike/EventCallbacks.h68
-rw-r--r--src/ToolBox/SOS/Strike/ExpressionNode.cpp2178
-rw-r--r--src/ToolBox/SOS/Strike/ExpressionNode.h307
-rw-r--r--src/ToolBox/SOS/Strike/Native.rc10
-rw-r--r--src/ToolBox/SOS/Strike/SOS.nativeproj7
-rw-r--r--src/ToolBox/SOS/Strike/SOS.sln76
-rw-r--r--src/ToolBox/SOS/Strike/SOS.vcproj303
-rw-r--r--src/ToolBox/SOS/Strike/UtilCode.h11
-rw-r--r--src/ToolBox/SOS/Strike/WatchCmd.cpp331
-rw-r--r--src/ToolBox/SOS/Strike/WatchCmd.h110
-rw-r--r--src/ToolBox/SOS/Strike/apollososdocs.txt2727
-rw-r--r--src/ToolBox/SOS/Strike/data.h51
-rw-r--r--src/ToolBox/SOS/Strike/datatarget.cpp215
-rw-r--r--src/ToolBox/SOS/Strike/datatarget.h90
-rw-r--r--src/ToolBox/SOS/Strike/dirs.proj20
-rw-r--r--src/ToolBox/SOS/Strike/disasm.cpp1142
-rw-r--r--src/ToolBox/SOS/Strike/disasm.h453
-rw-r--r--src/ToolBox/SOS/Strike/disasmARM.cpp626
-rw-r--r--src/ToolBox/SOS/Strike/disasmARM64.cpp392
-rw-r--r--src/ToolBox/SOS/Strike/disasmX86.cpp1707
-rw-r--r--src/ToolBox/SOS/Strike/dllsext.cpp278
-rw-r--r--src/ToolBox/SOS/Strike/eeheap.cpp1913
-rw-r--r--src/ToolBox/SOS/Strike/exts.cpp435
-rw-r--r--src/ToolBox/SOS/Strike/exts.h513
-rw-r--r--src/ToolBox/SOS/Strike/gchist.cpp636
-rw-r--r--src/ToolBox/SOS/Strike/gcroot.cpp2503
-rw-r--r--src/ToolBox/SOS/Strike/inc/.gitmirror1
-rw-r--r--src/ToolBox/SOS/Strike/inc/dbgeng.h16122
-rw-r--r--src/ToolBox/SOS/Strike/inc/dbghelp.h4540
-rw-r--r--src/ToolBox/SOS/Strike/inc/wdbgexts.h2807
-rw-r--r--src/ToolBox/SOS/Strike/metadata.cpp1041
-rw-r--r--src/ToolBox/SOS/Strike/ntinfo.h193
-rw-r--r--src/ToolBox/SOS/Strike/platformspecific.h195
-rw-r--r--src/ToolBox/SOS/Strike/sildasm.cpp1090
-rw-r--r--src/ToolBox/SOS/Strike/sos.cpp888
-rw-r--r--src/ToolBox/SOS/Strike/sos.def231
-rw-r--r--src/ToolBox/SOS/Strike/sos.h792
-rw-r--r--src/ToolBox/SOS/Strike/sos.targets166
-rw-r--r--src/ToolBox/SOS/Strike/sos_md.h926
-rw-r--r--src/ToolBox/SOS/Strike/sos_stacktrace.h174
-rw-r--r--src/ToolBox/SOS/Strike/sos_unixexports.src54
-rw-r--r--src/ToolBox/SOS/Strike/sosdocs.txt2572
-rw-r--r--src/ToolBox/SOS/Strike/sosdocsunix.txt1713
-rw-r--r--src/ToolBox/SOS/Strike/stressLogDump.cpp549
-rw-r--r--src/ToolBox/SOS/Strike/strike.cpp14462
-rw-r--r--src/ToolBox/SOS/Strike/strike.h144
-rw-r--r--src/ToolBox/SOS/Strike/util.cpp6975
-rw-r--r--src/ToolBox/SOS/Strike/util.h3292
-rw-r--r--src/ToolBox/SOS/Strike/vm.cpp732
-rw-r--r--src/ToolBox/SOS/Strike/xplat/.gitmirror1
-rw-r--r--src/ToolBox/SOS/Strike/xplat/dbgeng.h485
-rw-r--r--src/ToolBox/SOS/Strike/xplat/dbghelp.h17
-rw-r--r--src/ToolBox/SOS/Strike/xplat/wdbgexts.h28
57 files changed, 77664 insertions, 0 deletions
diff --git a/src/ToolBox/SOS/Strike/.gitmirror b/src/ToolBox/SOS/Strike/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/ToolBox/SOS/Strike/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/ToolBox/SOS/Strike/ApolloNative.rc b/src/ToolBox/SOS/Strike/ApolloNative.rc
new file mode 100644
index 0000000000..ba320af911
--- /dev/null
+++ b/src/ToolBox/SOS/Strike/ApolloNative.rc
@@ -0,0 +1,10 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#define FX_VER_FILEDESCRIPTION_STR "Microsoft NTSD extension for .NET Runtime\0"
+
+#include <fxver.h>
+#include <fxver.rc>
+
+DOCUMENTATION TEXT DISCARDABLE "apollososdocs.txt"
diff --git a/src/ToolBox/SOS/Strike/CMakeLists.txt b/src/ToolBox/SOS/Strike/CMakeLists.txt
new file mode 100644
index 0000000000..5d25865780
--- /dev/null
+++ b/src/ToolBox/SOS/Strike/CMakeLists.txt
@@ -0,0 +1,201 @@
+# Set the RPATH of sos so that it can find dependencies without needing to set LD_LIBRARY_PATH
+# For more information: http://www.cmake.org/Wiki/CMake_RPATH_handling.
+if (CORECLR_SET_RPATH)
+ set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
+ if(CLR_CMAKE_PLATFORM_DARWIN)
+ set(CMAKE_INSTALL_RPATH "@loader_path")
+ else()
+ set(CMAKE_INSTALL_RPATH "\$ORIGIN")
+ endif(CLR_CMAKE_PLATFORM_DARWIN)
+endif (CORECLR_SET_RPATH)
+
+if(CLR_CMAKE_PLATFORM_ARCH_AMD64)
+ add_definitions(-DSOS_TARGET_AMD64=1)
+ add_definitions(-D_TARGET_WIN64_=1)
+ add_definitions(-DDBG_TARGET_64BIT)
+ add_definitions(-DDBG_TARGET_WIN64=1)
+ if(WIN32)
+ add_definitions(-DSOS_TARGET_ARM64=1)
+ endif(WIN32)
+ remove_definitions(-D_TARGET_ARM64_=1)
+ add_definitions(-D_TARGET_AMD64_)
+ add_definitions(-DDBG_TARGET_AMD64)
+elseif(CLR_CMAKE_PLATFORM_ARCH_I386)
+ add_definitions(-DSOS_TARGET_X86=1)
+ add_definitions(-D_TARGET_X86_=1)
+ add_definitions(-DDBG_TARGET_32BIT)
+ add_definitions(-DSOS_TARGET_ARM=1)
+elseif(CLR_CMAKE_PLATFORM_ARCH_ARM)
+ add_definitions(-DSOS_TARGET_ARM=1)
+ add_definitions(-D_TARGET_WIN32_=1)
+ add_definitions(-D_TARGET_ARM_=1)
+ add_definitions(-DDBG_TARGET_32BIT)
+elseif(CLR_CMAKE_PLATFORM_ARCH_ARM64)
+ add_definitions(-DSOS_TARGET_ARM64=1)
+ add_definitions(-D_TARGET_WIN64_=1)
+ add_definitions(-DDBG_TARGET_64BIT)
+ add_definitions(-DDBG_TARGET_WIN64=1)
+endif()
+add_definitions(-DSTRIKE)
+
+remove_definitions(-DUNICODE)
+remove_definitions(-D_UNICODE)
+
+include_directories(BEFORE ${VM_DIR})
+include_directories(${CLR_DIR}/src/gcdump)
+include_directories(${CLR_DIR}/src/debug/shim)
+include_directories(${CLR_DIR}/src/coreclr/hosts/unixcoreruncommon)
+include_directories(${CLR_DIR}/src/coreclr/hosts/inc)
+
+if(WIN32)
+ include_directories(inc)
+ include_directories("$ENV{VSInstallDir}/DIA SDK/include")
+
+ add_definitions(-DUSE_STL)
+
+ #use static crt
+ add_definitions(-MT)
+
+ set(SOS_SOURCES
+ disasm.cpp
+ dllsext.cpp
+ eeheap.cpp
+ EventCallbacks.cpp
+ ExpressionNode.cpp
+ exts.cpp
+ gchist.cpp
+ gcroot.cpp
+ metadata.cpp
+ sildasm.cpp
+ sos.cpp
+ stressLogDump.cpp
+ strike.cpp
+ util.cpp
+ vm.cpp
+ WatchCmd.cpp
+ Native.rc
+ )
+
+ add_definitions(-DFX_VER_INTERNALNAME_STR=SOS.dll)
+
+ #Preprocess exports definition file
+ preprocess_def_file(${CMAKE_CURRENT_SOURCE_DIR}/sos.def ${CMAKE_CURRENT_BINARY_DIR}/sos.def)
+ list(APPEND SOS_SOURCES ${CMAKE_CURRENT_BINARY_DIR}/sos.def)
+
+ set(SOS_LIBRARY
+ corguids
+ debugshim
+ dbgutil
+ ${STATIC_MT_CRT_LIB}
+ ${STATIC_MT_CPP_LIB}
+ ${STATIC_MT_VCRT_LIB}
+ kernel32.lib
+ user32.lib
+ ole32.lib
+ oleaut32.lib
+ dbghelp.lib
+ uuid.lib
+ version.lib
+ dbgeng.lib
+ advapi32.lib
+ psapi.lib
+ ntdll.lib
+ )
+else(WIN32)
+ add_definitions(-DFEATURE_ENABLE_HARDWARE_EXCEPTIONS)
+ add_definitions(-DPAL_STDCPP_COMPAT=1)
+ add_compile_options(-Wno-null-arithmetic)
+ add_compile_options(-Wno-format)
+
+ include_directories(BEFORE xplat)
+ include_directories(BEFORE ../lldbplugin/inc)
+
+ add_compile_options(-fPIC)
+
+ set(SOS_SOURCES
+ disasm.cpp
+ datatarget.cpp
+ eeheap.cpp
+ exts.cpp
+ gchist.cpp
+ gcroot.cpp
+ metadata.cpp
+ sildasm.cpp
+ stressLogDump.cpp
+ strike.cpp
+ sos.cpp
+ util.cpp
+ ../../../coreclr/hosts/unixcoreruncommon/coreruncommon.cpp
+ )
+
+ set(SOS_LIBRARY
+ corguids
+ debugshim
+ dbgutil
+ # share the PAL in the dac module
+ mscordaccore
+ palrt
+ )
+
+ set(DEF_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/sos_unixexports.src)
+ set(EXPORTS_FILE ${CMAKE_CURRENT_BINARY_DIR}/sos.exports)
+ generate_exports_file(${DEF_SOURCES} ${EXPORTS_FILE})
+endif(WIN32)
+
+if(CLR_CMAKE_PLATFORM_ARCH_AMD64)
+ set(SOS_SOURCES_ARCH
+ disasmX86.cpp
+ )
+ if(WIN32)
+ list(APPEND
+ SOS_SOURCES_ARCH
+ disasmARM64.cpp
+ )
+ endif(WIN32)
+elseif(CLR_CMAKE_PLATFORM_ARCH_I386)
+ set(SOS_SOURCES_ARCH
+ disasmX86.cpp
+ disasmARM.cpp
+ )
+elseif(CLR_CMAKE_PLATFORM_ARCH_ARM)
+ set(SOS_SOURCES_ARCH
+ disasmARM.cpp
+ )
+elseif(CLR_CMAKE_PLATFORM_ARCH_ARM64)
+ set(SOS_SOURCES_ARCH
+ disasmARM64.cpp
+ )
+endif()
+
+list(APPEND SOS_SOURCES ${SOS_SOURCES_ARCH})
+
+if(CLR_CMAKE_PLATFORM_LINUX OR CLR_CMAKE_PLATFORM_FREEBSD OR CLR_CMAKE_PLATFORM_NETBSD)
+ # Add linker exports file option
+ set(EXPORTS_LINKER_OPTION -Wl,--version-script=${EXPORTS_FILE})
+endif(CLR_CMAKE_PLATFORM_LINUX OR CLR_CMAKE_PLATFORM_FREEBSD OR CLR_CMAKE_PLATFORM_NETBSD)
+
+if(CLR_CMAKE_PLATFORM_DARWIN)
+ # Add linker exports file option
+ set(EXPORTS_LINKER_OPTION -Wl,-exported_symbols_list,${EXPORTS_FILE})
+endif(CLR_CMAKE_PLATFORM_DARWIN)
+
+add_library_clr(sos SHARED ${SOS_SOURCES})
+
+if(CLR_CMAKE_PLATFORM_UNIX)
+ add_custom_target(sos_exports DEPENDS ${EXPORTS_FILE})
+ add_dependencies(sos sos_exports)
+
+ set_property(TARGET sos APPEND_STRING PROPERTY LINK_FLAGS ${EXPORTS_LINKER_OPTION})
+ set_property(TARGET sos APPEND_STRING PROPERTY LINK_DEPENDS ${EXPORTS_FILE})
+
+ add_dependencies(sos mscordaccore)
+endif(CLR_CMAKE_PLATFORM_UNIX)
+
+target_link_libraries(sos ${SOS_LIBRARY})
+
+# add the install targets
+install_clr(sos)
+
+if(NOT WIN32)
+ install(FILES sosdocsunix.txt DESTINATION .)
+endif(NOT WIN32)
diff --git a/src/ToolBox/SOS/Strike/EventCallbacks.cpp b/src/ToolBox/SOS/Strike/EventCallbacks.cpp
new file mode 100644
index 0000000000..0066dfa1e8
--- /dev/null
+++ b/src/ToolBox/SOS/Strike/EventCallbacks.cpp
@@ -0,0 +1,160 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#include "EventCallbacks.h"
+
+EventCallbacks::EventCallbacks(IDebugClient* pDebugClient) : m_refCount(1), m_pDebugClient(pDebugClient)
+{
+}
+
+EventCallbacks::~EventCallbacks()
+{
+ if(m_pDebugClient != NULL)
+ m_pDebugClient->Release();
+}
+
+ // IUnknown implementation
+HRESULT __stdcall EventCallbacks::QueryInterface(REFIID riid, VOID** ppInterface)
+{
+ if(riid == __uuidof(IDebugEventCallbacks))
+ {
+ *ppInterface = static_cast<IDebugEventCallbacks*>(this);
+ AddRef();
+ return S_OK;
+ }
+ else if(riid == __uuidof(IUnknown))
+ {
+ *ppInterface = static_cast<IUnknown*>(this);
+ AddRef();
+ return S_OK;
+ }
+ else
+ {
+ return E_NOINTERFACE;
+ }
+}
+
+ULONG __stdcall EventCallbacks::AddRef()
+{
+ return InterlockedIncrement((volatile LONG *) &m_refCount);
+}
+
+ULONG __stdcall EventCallbacks::Release()
+{
+ ULONG count = InterlockedDecrement((volatile LONG *) &m_refCount);
+ if(count == 0)
+ {
+ delete this;
+ }
+ return count;
+}
+
+// IDebugEventCallbacks implementation
+HRESULT __stdcall EventCallbacks::Breakpoint(PDEBUG_BREAKPOINT bp)
+{
+ return DEBUG_STATUS_NO_CHANGE;
+}
+
+HRESULT __stdcall EventCallbacks::ChangeDebuggeeState(ULONG Flags, ULONG64 Argument)
+{
+ return DEBUG_STATUS_NO_CHANGE;
+}
+
+HRESULT __stdcall EventCallbacks::ChangeEngineState(ULONG Flags, ULONG64 Argument)
+{
+ return DEBUG_STATUS_NO_CHANGE;
+}
+HRESULT __stdcall EventCallbacks::ChangeSymbolState(ULONG Flags, ULONG64 Argument)
+{
+ return DEBUG_STATUS_NO_CHANGE;
+}
+HRESULT __stdcall EventCallbacks::CreateProcess(ULONG64 ImageFileHandle,
+ ULONG64 Handle,
+ ULONG64 BaseOffset,
+ ULONG ModuleSize,
+ PCSTR ModuleName,
+ PCSTR ImageName,
+ ULONG CheckSum,
+ ULONG TimeDateStamp,
+ ULONG64 InitialThreadHandle,
+ ULONG64 ThreadDataOffset,
+ ULONG64 StartOffset)
+{
+ return DEBUG_STATUS_NO_CHANGE;
+}
+
+HRESULT __stdcall EventCallbacks::CreateThread(ULONG64 Handle,
+ ULONG64 DataOffset,
+ ULONG64 StartOffset)
+{
+ return DEBUG_STATUS_NO_CHANGE;
+}
+
+HRESULT __stdcall EventCallbacks::Exception(PEXCEPTION_RECORD64 Exception, ULONG FirstChance)
+{
+ return DEBUG_STATUS_NO_CHANGE;
+}
+
+HRESULT __stdcall EventCallbacks::ExitProcess(ULONG ExitCode)
+{
+ UninitCorDebugInterface();
+ return DEBUG_STATUS_NO_CHANGE;
+}
+
+HRESULT __stdcall EventCallbacks::ExitThread(ULONG ExitCode)
+{
+ return DEBUG_STATUS_NO_CHANGE;
+}
+
+HRESULT __stdcall EventCallbacks::GetInterestMask(PULONG Mask)
+{
+ *Mask = DEBUG_EVENT_LOAD_MODULE | DEBUG_EVENT_EXIT_PROCESS;
+ return S_OK;
+}
+
+extern BOOL g_fAllowJitOptimization;
+
+HRESULT __stdcall EventCallbacks::LoadModule(ULONG64 ImageFileHandle,
+ ULONG64 BaseOffset,
+ ULONG ModuleSize,
+ PCSTR ModuleName,
+ PCSTR ImageName,
+ ULONG CheckSum,
+ ULONG TimeDateStamp)
+{
+ HRESULT handleEventStatus = DEBUG_STATUS_NO_CHANGE;
+ ExtQuery(m_pDebugClient);
+
+ if (ModuleName != NULL && _stricmp(ModuleName, MAIN_CLR_MODULE_NAME_A) == 0)
+ {
+ // if we don't want the JIT to optimize, we should also disable optimized NGEN images
+ if(!g_fAllowJitOptimization)
+ {
+ // if we aren't succesful SetNGENCompilerFlags will print relevant error messages
+ // and then we need to stop the debugger so the user can intervene if desired
+ if(FAILED(SetNGENCompilerFlags(CORDEBUG_JIT_DISABLE_OPTIMIZATION)))
+ {
+ handleEventStatus = DEBUG_STATUS_BREAK;
+ }
+ }
+ }
+
+ ExtRelease();
+ return handleEventStatus;
+}
+
+HRESULT __stdcall EventCallbacks::SessionStatus(ULONG Status)
+{
+ return DEBUG_STATUS_NO_CHANGE;
+}
+
+HRESULT __stdcall EventCallbacks::SystemError(ULONG Error, ULONG Level)
+{
+ return DEBUG_STATUS_NO_CHANGE;
+}
+
+HRESULT __stdcall EventCallbacks::UnloadModule(PCSTR ImageBaseName, ULONG64 BaseOffset)
+{
+ return DEBUG_STATUS_NO_CHANGE;
+}
diff --git a/src/ToolBox/SOS/Strike/EventCallbacks.h b/src/ToolBox/SOS/Strike/EventCallbacks.h
new file mode 100644
index 0000000000..acd5e412d1
--- /dev/null
+++ b/src/ToolBox/SOS/Strike/EventCallbacks.h
@@ -0,0 +1,68 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#ifndef __EventCallbacks__
+#define __EventCallbacks__
+
+#include "exts.h"
+
+// A set of callbacks that are registered with windbg whenever SOS is loaded
+// Right now these callbacks only act on the module load event for CLR, but
+// feel free to add other event hooks as needed
+//
+// TODO: we should probably be using these callbacks to hook clrnotify exceptions
+// rather than attaching a user handler on the clrn event. That handler is both
+// visible to the user and could be accidentally erased by them.
+class EventCallbacks : IDebugEventCallbacks
+{
+public:
+ EventCallbacks(IDebugClient* pDebugClient);
+ ~EventCallbacks();
+
+ // IUnknown implementation
+ HRESULT __stdcall QueryInterface(REFIID riid, VOID** ppInterface);
+ ULONG __stdcall AddRef();
+ ULONG __stdcall Release();
+
+ // IDebugEventCallbacks implementation
+ HRESULT __stdcall Breakpoint(PDEBUG_BREAKPOINT bp);
+ HRESULT __stdcall ChangeDebuggeeState(ULONG Flags, ULONG64 Argument);
+ HRESULT __stdcall ChangeEngineState(ULONG Flags, ULONG64 Argument);
+ HRESULT __stdcall ChangeSymbolState(ULONG Flags, ULONG64 Argument);
+ HRESULT __stdcall CreateProcess(ULONG64 ImageFileHandle,
+ ULONG64 Handle,
+ ULONG64 BaseOffset,
+ ULONG ModuleSize,
+ PCSTR ModuleName,
+ PCSTR ImageName,
+ ULONG CheckSum,
+ ULONG TimeDateStamp,
+ ULONG64 InitialThreadHandle,
+ ULONG64 ThreadDataOffset,
+ ULONG64 StartOffset);
+ HRESULT __stdcall CreateThread(ULONG64 Handle,
+ ULONG64 DataOffset,
+ ULONG64 StartOffset);
+ HRESULT __stdcall Exception(PEXCEPTION_RECORD64 Exception, ULONG FirstChance);
+ HRESULT __stdcall ExitProcess(ULONG ExitCode);
+ HRESULT __stdcall ExitThread(ULONG ExitCode);
+ HRESULT __stdcall GetInterestMask(PULONG Mask);
+ HRESULT __stdcall LoadModule(ULONG64 ImageFileHandle,
+ ULONG64 BaseOffset,
+ ULONG ModuleSize,
+ PCSTR ModuleName,
+ PCSTR ImageName,
+ ULONG CheckSum,
+ ULONG TimeDateStamp);
+ HRESULT __stdcall SessionStatus(ULONG Status);
+ HRESULT __stdcall SystemError(ULONG Error, ULONG Level);
+ HRESULT __stdcall UnloadModule(PCSTR ImageBaseName, ULONG64 BaseOffset);
+
+
+private:
+ volatile ULONG m_refCount;
+ IDebugClient* m_pDebugClient;
+};
+
+#endif
diff --git a/src/ToolBox/SOS/Strike/ExpressionNode.cpp b/src/ToolBox/SOS/Strike/ExpressionNode.cpp
new file mode 100644
index 0000000000..6516823aaa
--- /dev/null
+++ b/src/ToolBox/SOS/Strike/ExpressionNode.cpp
@@ -0,0 +1,2178 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#include "ExpressionNode.h"
+
+
+#ifndef IfFailRet
+#define IfFailRet(EXPR) do { Status = (EXPR); if(FAILED(Status)) { return (Status); } } while (0)
+#endif
+
+// Returns the complete expression being evaluated to get the value for this node
+// The returned pointer is a string interior to this object - once you release
+// all references to this object the string is invalid.
+WCHAR* ExpressionNode::GetAbsoluteExpression() { return pAbsoluteExpression; }
+
+// Returns the sub expression that logically indicates how the parent expression
+// was built upon to reach this node. This relative value has no purpose other
+// than an identifier and to convey UI meaning to the user. At present typical values
+// are the name of type, a local, a parameter, a field, an array index, or '<basetype>'
+// for a baseclass casting operation
+// The returned pointer is a string interior to this object - once you release
+// all references to this object the string is invalid.
+WCHAR* ExpressionNode::GetRelativeExpression() { return pRelativeExpression; }
+
+// Returns a text representation of the type of value that this node refers to
+// It is possible this node doesn't evaluate to anything and therefore has no
+// type
+// The returned pointer is a string interior to this object - once you release
+// all references to this object the string is invalid.
+WCHAR* ExpressionNode::GetTypeName() { PopulateType(); return pTypeName; }
+
+// Returns a text representation of the value for this node. It is possible that
+// this node doesn't evaluate to anything and therefore has no value text.
+// The returned pointer is a string interior to this object - once you release
+// all references to this object the string is invalid.
+WCHAR* ExpressionNode::GetTextValue() { PopulateTextValue(); return pTextValue; }
+
+// If there is any error during the evaluation of this node's expression, it is
+// returned here.
+// The returned pointer is a string interior to this object - once you release
+// all references to this object the string is invalid.
+WCHAR* ExpressionNode::GetErrorMessage() { return pErrorMessage; }
+
+// Factory function for creating the expression node at the root of a tree
+HRESULT ExpressionNode::CreateExpressionNode(__in_z WCHAR* pExpression, ExpressionNode** ppExpressionNode)
+{
+ *ppExpressionNode = NULL;
+ HRESULT Status = CreateExpressionNodeHelper(pExpression,
+ pExpression,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ 0,
+ NULL,
+ ppExpressionNode);
+ if(FAILED(Status) && *ppExpressionNode == NULL)
+ {
+
+ WCHAR pErrorMessage[MAX_ERROR];
+ _snwprintf_s(pErrorMessage, MAX_ERROR, _TRUNCATE, L"Error 0x%x while parsing expression", Status);
+ *ppExpressionNode = new ExpressionNode(pExpression, pErrorMessage);
+ Status = S_OK;
+ if(*ppExpressionNode == NULL)
+ Status = E_OUTOFMEMORY;
+ }
+ return Status;
+}
+
+// Performs recursive expansion within the tree for nodes that are along the path to varToExpand.
+// Expansion involves calulating a set of child expressions from the current expression via
+// field dereferencing, array index dereferencing, or casting to a base type.
+// For example if a tree was rooted with expression 'foo.bar' and varToExpand is '(Baz)foo.bar[9]'
+// then 'foo.bar', 'foo.bar[9]', and '(Baz)foo.bar[9]' nodes would all be expanded.
+HRESULT ExpressionNode::Expand(__in_z WCHAR* varToExpand)
+{
+ if(!ShouldExpandVariable(varToExpand))
+ return S_FALSE;
+ if(pValue == NULL && pTypeCast == NULL)
+ return S_OK;
+
+ // if the node evaluates to a type, then the children are static fields of the type
+ if(pValue == NULL)
+ return ExpandFields(NULL, varToExpand);
+
+ // If the value is a null reference there is nothing to expand
+ HRESULT Status = S_OK;
+ BOOL isNull = TRUE;
+ ToRelease<ICorDebugValue> pInnerValue;
+ IfFailRet(DereferenceAndUnboxValue(pValue, &pInnerValue, &isNull));
+ if(isNull)
+ {
+ return S_OK;
+ }
+
+
+ CorElementType corElemType;
+ IfFailRet(pValue->GetType(&corElemType));
+ if (corElemType == ELEMENT_TYPE_SZARRAY)
+ {
+ //If its an array, add children representing the indexed elements
+ return ExpandSzArray(pInnerValue, varToExpand);
+ }
+ else if(corElemType == ELEMENT_TYPE_CLASS || corElemType == ELEMENT_TYPE_VALUETYPE)
+ {
+ // If its a class or struct (not counting string, array, or object) then add children representing
+ // the fields.
+ return ExpandFields(pInnerValue, varToExpand);
+ }
+ else
+ {
+ // nothing else expands
+ return S_OK;
+ }
+}
+
+// Standard depth first search tree traversal pattern with a callback
+VOID ExpressionNode::DFSVisit(ExpressionNodeVisitorCallback pFunc, VOID* pUserData, int depth)
+{
+ pFunc(this, depth, pUserData);
+ ExpressionNode* pCurChild = pChild;
+ while(pCurChild != NULL)
+ {
+ pCurChild->DFSVisit(pFunc, pUserData, depth+1);
+ pCurChild = pCurChild->pNextSibling;
+ }
+}
+
+
+// Creates a new expression with a given debuggee value and frame
+ExpressionNode::ExpressionNode(__in_z WCHAR* pExpression, ICorDebugValue* pValue, ICorDebugILFrame* pFrame)
+{
+ Init(pValue, NULL, pFrame);
+ _snwprintf_s(pAbsoluteExpression, MAX_EXPRESSION, _TRUNCATE, L"%s", pExpression);
+ _snwprintf_s(pRelativeExpression, MAX_EXPRESSION, _TRUNCATE, L"%s", pExpression);
+}
+
+// Creates a new expression that has an error and no value
+ExpressionNode::ExpressionNode(__in_z WCHAR* pExpression, __in_z WCHAR* pErrorMessage)
+{
+ Init(NULL, NULL, NULL);
+ _snwprintf_s(pAbsoluteExpression, MAX_EXPRESSION, _TRUNCATE, L"%s", pExpression);
+ _snwprintf_s(pRelativeExpression, MAX_EXPRESSION, _TRUNCATE, L"%s", pExpression);
+ _snwprintf_s(this->pErrorMessage, MAX_ERROR, _TRUNCATE, L"%s", pErrorMessage);
+}
+
+// Creates a new child expression
+ExpressionNode::ExpressionNode(__in_z WCHAR* pParentExpression, ChildKind ck, __in_z WCHAR* pRelativeExpression, ICorDebugValue* pValue, ICorDebugType* pType, ICorDebugILFrame* pFrame, UVCP_CONSTANT pDefaultValue, ULONG cchDefaultValue)
+{
+ Init(pValue, pType, pFrame);
+ if(ck == ChildKind_BaseClass)
+ {
+ _snwprintf_s(pAbsoluteExpression, MAX_EXPRESSION, _TRUNCATE, L"%s", pParentExpression);
+ }
+ else
+ {
+ _snwprintf_s(pAbsoluteExpression, MAX_EXPRESSION, _TRUNCATE, ck == ChildKind_Field ? L"%s.%s" : L"%s[%s]", pParentExpression, pRelativeExpression);
+ }
+ _snwprintf_s(this->pRelativeExpression, MAX_EXPRESSION, _TRUNCATE, ck == ChildKind_Index ? L"[%s]" : L"%s", pRelativeExpression);
+ this->pDefaultValue = pDefaultValue;
+ this->cchDefaultValue = cchDefaultValue;
+}
+
+// Common member initialization for the constructors
+VOID ExpressionNode::Init(ICorDebugValue* pValue, ICorDebugType* pTypeCast, ICorDebugILFrame* pFrame)
+{
+ this->pValue = pValue;
+ this->pTypeCast = pTypeCast;
+ this->pILFrame = pFrame;
+ pChild = NULL;
+ pNextSibling = NULL;
+ pTextValue[0] = 0;
+ pErrorMessage[0] = 0;
+ pAbsoluteExpression[0] = 0;
+ pRelativeExpression[0] = 0;
+ pTypeName[0] = 0;
+
+ pDefaultValue = NULL;
+ cchDefaultValue = 0;
+
+ // The ToRelease holders don't automatically AddRef
+ if(pILFrame != NULL)
+ pILFrame->AddRef();
+ if(pTypeCast != NULL)
+ pTypeCast->AddRef();
+ if(pValue != NULL)
+ {
+ pValue->AddRef();
+ PopulateMetaDataImport();
+ }
+}
+
+// Retreves the correct IMetaDataImport for the type represented in this node and stores it
+// in pMD.
+HRESULT ExpressionNode::PopulateMetaDataImport()
+{
+ if(pMD != NULL)
+ return S_OK;
+
+ HRESULT Status = S_OK;
+ ToRelease<ICorDebugType> pType;
+ if(pTypeCast != NULL)
+ {
+ pType = pTypeCast;
+ pType->AddRef();
+ }
+ else
+ {
+ BOOL isNull;
+ ToRelease<ICorDebugValue> pInnerValue;
+ IfFailRet(DereferenceAndUnboxValue(pValue, &pInnerValue, &isNull));
+ ToRelease<ICorDebugValue2> pValue2;
+ IfFailRet(pInnerValue->QueryInterface(IID_ICorDebugValue2, (LPVOID *) &pValue2));
+
+ IfFailRet(pValue2->GetExactType(&pType));
+
+ // for array, pointer, and byref types we can't directly get a class, we must unwrap first
+ CorElementType et;
+ IfFailRet(pType->GetType(&et));
+ while(et == ELEMENT_TYPE_ARRAY || et == ELEMENT_TYPE_SZARRAY || et == ELEMENT_TYPE_BYREF || et == ELEMENT_TYPE_PTR)
+ {
+ pType->GetFirstTypeParameter(&pType);
+ IfFailRet(pType->GetType(&et));
+ }
+ }
+ ToRelease<ICorDebugClass> pClass;
+ IfFailRet(pType->GetClass(&pClass));
+ ToRelease<ICorDebugModule> pModule;
+ IfFailRet(pClass->GetModule(&pModule));
+ ToRelease<IUnknown> pMDUnknown;
+ IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown));
+ IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD));
+ return Status;
+}
+
+// Determines the string representation of pType and stores it in typeName
+HRESULT ExpressionNode::CalculateTypeName(ICorDebugType * pType, __inout_ecount(typeNameLen) WCHAR* typeName, DWORD typeNameLen)
+{
+ HRESULT Status = S_OK;
+
+ CorElementType corElemType;
+ IfFailRet(pType->GetType(&corElemType));
+
+ switch (corElemType)
+ {
+ //List of unsupported CorElementTypes:
+ //ELEMENT_TYPE_END = 0x0,
+ //ELEMENT_TYPE_VAR = 0x13, // a class type variable VAR <U1>
+ //ELEMENT_TYPE_GENERICINST = 0x15, // GENERICINST <generic type> <argCnt> <arg1> ... <argn>
+ //ELEMENT_TYPE_TYPEDBYREF = 0x16, // TYPEDREF (it takes no args) a typed referece to some other type
+ //ELEMENT_TYPE_MVAR = 0x1e, // a method type variable MVAR <U1>
+ //ELEMENT_TYPE_CMOD_REQD = 0x1F, // required C modifier : E_T_CMOD_REQD <mdTypeRef/mdTypeDef>
+ //ELEMENT_TYPE_CMOD_OPT = 0x20, // optional C modifier : E_T_CMOD_OPT <mdTypeRef/mdTypeDef>
+ //ELEMENT_TYPE_INTERNAL = 0x21, // INTERNAL <typehandle>
+ //ELEMENT_TYPE_MAX = 0x22, // first invalid element type
+ //ELEMENT_TYPE_MODIFIER = 0x40,
+ //ELEMENT_TYPE_SENTINEL = 0x01 | ELEMENT_TYPE_MODIFIER, // sentinel for varargs
+ //ELEMENT_TYPE_PINNED = 0x05 | ELEMENT_TYPE_MODIFIER,
+ default:
+ swprintf_s(typeName, typeNameLen, L"(Unhandled CorElementType: 0x%x)\0", corElemType);
+ break;
+
+ case ELEMENT_TYPE_VALUETYPE:
+ case ELEMENT_TYPE_CLASS:
+ {
+ //Defaults in case we fail...
+ if(corElemType == ELEMENT_TYPE_VALUETYPE) swprintf_s(typeName, typeNameLen, L"struct\0");
+ else swprintf_s(typeName, typeNameLen, L"class\0");
+
+ mdTypeDef typeDef;
+ ToRelease<ICorDebugClass> pClass;
+ if(SUCCEEDED(pType->GetClass(&pClass)) && SUCCEEDED(pClass->GetToken(&typeDef)))
+ {
+ ToRelease<ICorDebugModule> pModule;
+ IfFailRet(pClass->GetModule(&pModule));
+
+ ToRelease<IUnknown> pMDUnknown;
+ ToRelease<IMetaDataImport> pMD;
+ IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown));
+ IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD));
+
+ if(SUCCEEDED(NameForToken_s(TokenFromRid(typeDef, mdtTypeDef), pMD, g_mdName, mdNameLen, false)))
+ swprintf_s(typeName, typeNameLen, L"%s\0", g_mdName);
+ }
+ AddGenericArgs(pType, typeName, typeNameLen);
+ }
+ break;
+ case ELEMENT_TYPE_VOID:
+ swprintf_s(typeName, typeNameLen, L"void\0");
+ break;
+ case ELEMENT_TYPE_BOOLEAN:
+ swprintf_s(typeName, typeNameLen, L"bool\0");
+ break;
+ case ELEMENT_TYPE_CHAR:
+ swprintf_s(typeName, typeNameLen, L"char\0");
+ break;
+ case ELEMENT_TYPE_I1:
+ swprintf_s(typeName, typeNameLen, L"signed byte\0");
+ break;
+ case ELEMENT_TYPE_U1:
+ swprintf_s(typeName, typeNameLen, L"byte\0");
+ break;
+ case ELEMENT_TYPE_I2:
+ swprintf_s(typeName, typeNameLen, L"short\0");
+ break;
+ case ELEMENT_TYPE_U2:
+ swprintf_s(typeName, typeNameLen, L"unsigned short\0");
+ break;
+ case ELEMENT_TYPE_I4:
+ swprintf_s(typeName, typeNameLen, L"int\0");
+ break;
+ case ELEMENT_TYPE_U4:
+ swprintf_s(typeName, typeNameLen, L"unsigned int\0");
+ break;
+ case ELEMENT_TYPE_I8:
+ swprintf_s(typeName, typeNameLen, L"long\0");
+ break;
+ case ELEMENT_TYPE_U8:
+ swprintf_s(typeName, typeNameLen, L"unsigned long\0");
+ break;
+ case ELEMENT_TYPE_R4:
+ swprintf_s(typeName, typeNameLen, L"float\0");
+ break;
+ case ELEMENT_TYPE_R8:
+ swprintf_s(typeName, typeNameLen, L"double\0");
+ break;
+ case ELEMENT_TYPE_OBJECT:
+ swprintf_s(typeName, typeNameLen, L"object\0");
+ break;
+ case ELEMENT_TYPE_STRING:
+ swprintf_s(typeName, typeNameLen, L"string\0");
+ break;
+ case ELEMENT_TYPE_I:
+ swprintf_s(typeName, typeNameLen, L"IntPtr\0");
+ break;
+ case ELEMENT_TYPE_U:
+ swprintf_s(typeName, typeNameLen, L"UIntPtr\0");
+ break;
+ case ELEMENT_TYPE_SZARRAY:
+ case ELEMENT_TYPE_ARRAY:
+ case ELEMENT_TYPE_BYREF:
+ case ELEMENT_TYPE_PTR:
+ {
+ // get a name for the type we are building from
+ ToRelease<ICorDebugType> pFirstParameter;
+ if(SUCCEEDED(pType->GetFirstTypeParameter(&pFirstParameter)))
+ CalculateTypeName(pFirstParameter, typeName, typeNameLen);
+ else
+ swprintf_s(typeName, typeNameLen, L"<unknown>\0");
+
+ // append the appropriate [], *, &
+ switch(corElemType)
+ {
+ case ELEMENT_TYPE_SZARRAY:
+ wcsncat_s(typeName, typeNameLen, L"[]", typeNameLen);
+ return S_OK;
+ case ELEMENT_TYPE_ARRAY:
+ {
+ ULONG32 rank = 0;
+ pType->GetRank(&rank);
+ wcsncat_s(typeName, typeNameLen, L"[", typeNameLen);
+ for(ULONG32 i = 0; i < rank - 1; i++)
+ {
+ // todo- could we print out exact boundaries?
+ wcsncat_s(typeName, typeNameLen, L",", typeNameLen);
+ }
+ wcsncat_s(typeName, typeNameLen, L"]", typeNameLen);
+ }
+ return S_OK;
+ case ELEMENT_TYPE_BYREF:
+ wcsncat_s(typeName, typeNameLen, L"&", typeNameLen);
+ return S_OK;
+ case ELEMENT_TYPE_PTR:
+ wcsncat_s(typeName, typeNameLen, L"*", typeNameLen);
+ return S_OK;
+ }
+ }
+ break;
+ case ELEMENT_TYPE_FNPTR:
+ swprintf_s(typeName, typeNameLen, L"*(...)");
+ break;
+ case ELEMENT_TYPE_TYPEDBYREF:
+ swprintf_s(typeName, typeNameLen, L"typedbyref");
+ break;
+ }
+ return S_OK;
+}
+
+
+// Appends angle brackets and the generic argument list to a type name
+HRESULT ExpressionNode::AddGenericArgs(ICorDebugType * pType, __inout_ecount(typeNameLen) WCHAR* typeName, DWORD typeNameLen)
+{
+ bool isFirst = true;
+ ToRelease<ICorDebugTypeEnum> pTypeEnum;
+ if(SUCCEEDED(pType->EnumerateTypeParameters(&pTypeEnum)))
+ {
+ ULONG numTypes = 0;
+ ToRelease<ICorDebugType> pCurrentTypeParam;
+
+ while(SUCCEEDED(pTypeEnum->Next(1, &pCurrentTypeParam, &numTypes)))
+ {
+ if(numTypes == 0) break;
+
+ if(isFirst)
+ {
+ isFirst = false;
+ wcsncat_s(typeName, typeNameLen, L"<", typeNameLen);
+ }
+ else wcsncat_s(typeName, typeNameLen, L",", typeNameLen);
+
+ WCHAR typeParamName[mdNameLen];
+ typeParamName[0] = L'\0';
+ CalculateTypeName(pCurrentTypeParam, typeParamName, mdNameLen);
+ wcsncat_s(typeName, typeNameLen, typeParamName, typeNameLen);
+ }
+ if(!isFirst)
+ wcsncat_s(typeName, typeNameLen, L">", typeNameLen);
+ }
+
+ return S_OK;
+}
+
+// Determines the text name for the type of this node and caches it
+HRESULT ExpressionNode::PopulateType()
+{
+ HRESULT Status = S_OK;
+ if(pTypeName[0] != 0)
+ return S_OK;
+
+ //default value
+ swprintf_s(pTypeName, MAX_EXPRESSION, L"<unknown>");
+
+ // if we are displaying this type as a specific sub-type, use that
+ if(pTypeCast != NULL)
+ return CalculateTypeName(pTypeCast, pTypeName, MAX_EXPRESSION);
+
+ // if there is no value then either we succesfully already determined the type
+ // name, or this node has no value or type and thus no type name.
+ if(pValue == NULL)
+ return S_OK;
+
+ // get the type from the value and then calculate a name based on that
+ ToRelease<ICorDebugType> pType;
+ ToRelease<ICorDebugValue2> pValue2;
+ if(SUCCEEDED(pValue->QueryInterface(IID_ICorDebugValue2, (void**) &pValue2)) && SUCCEEDED(pValue2->GetExactType(&pType)))
+ return CalculateTypeName(pType, pTypeName, MAX_EXPRESSION);
+
+ return S_OK;
+}
+
+// Node expansion helpers
+
+// Inserts a new child at the end of the linked list of children
+// PERF: This has O(N) insert time but these lists should never be large
+VOID ExpressionNode::AddChild(ExpressionNode* pNewChild)
+{
+ if(pChild == NULL)
+ pChild = pNewChild;
+ else
+ {
+ ExpressionNode* pCursor = pChild;
+ while(pCursor->pNextSibling != NULL)
+ pCursor = pCursor->pNextSibling;
+ pCursor->pNextSibling = pNewChild;
+ }
+}
+
+// Helper that determines if the current node is on the path of nodes represented by
+// expression varToExpand
+BOOL ExpressionNode::ShouldExpandVariable(__in_z WCHAR* varToExpand)
+{
+ if(pAbsoluteExpression == NULL || varToExpand == NULL) return FALSE;
+
+ // if there is a cast operation, move past it
+ WCHAR* pEndCast = _wcschr(varToExpand, L')');
+ varToExpand = (pEndCast == NULL) ? varToExpand : pEndCast+1;
+
+ size_t varToExpandLen = _wcslen(varToExpand);
+ size_t currentExpansionLen = _wcslen(pAbsoluteExpression);
+ if(currentExpansionLen > varToExpandLen) return FALSE;
+ if(currentExpansionLen < varToExpandLen &&
+ varToExpand[currentExpansionLen] != L'.' &&
+ varToExpand[currentExpansionLen] != L'[')
+ return FALSE;
+ if(_wcsncmp(pAbsoluteExpression, varToExpand, currentExpansionLen) != 0) return FALSE;
+
+ return TRUE;
+}
+
+// Expands this array node by creating child nodes with expressions refering to individual array elements
+HRESULT ExpressionNode::ExpandSzArray(ICorDebugValue* pInnerValue, __in_z WCHAR* varToExpand)
+{
+ HRESULT Status = S_OK;
+ ToRelease<ICorDebugArrayValue> pArrayValue;
+ IfFailRet(pInnerValue->QueryInterface(IID_ICorDebugArrayValue, (LPVOID*) &pArrayValue));
+
+ ULONG32 nRank;
+ IfFailRet(pArrayValue->GetRank(&nRank));
+ if (nRank != 1)
+ {
+ _snwprintf_s(pErrorMessage, MAX_ERROR, _TRUNCATE, L"Multi-dimensional arrays NYI");
+ return E_UNEXPECTED;
+ }
+
+ ULONG32 cElements;
+ IfFailRet(pArrayValue->GetCount(&cElements));
+
+ //TODO: do we really want all the elements? This could be huge!
+ for (ULONG32 i=0; i < cElements; i++)
+ {
+ WCHAR index[20];
+ swprintf_s(index, 20, L"%d", i);
+
+ ToRelease<ICorDebugValue> pElementValue;
+ IfFailRet(pArrayValue->GetElementAtPosition(i, &pElementValue));
+ ExpressionNode* pExpr = new ExpressionNode(pAbsoluteExpression, ChildKind_Index, index, pElementValue, NULL, pILFrame);
+ AddChild(pExpr);
+ pExpr->Expand(varToExpand);
+ }
+ return S_OK;
+}
+
+// Expands this struct/class node by creating child nodes with expressions refering to individual field values
+// and one node for the basetype value
+HRESULT ExpressionNode::ExpandFields(ICorDebugValue* pInnerValue, __in_z WCHAR* varToExpand)
+{
+ HRESULT Status = S_OK;
+
+ mdTypeDef currentTypeDef;
+ ToRelease<ICorDebugClass> pClass;
+ ToRelease<ICorDebugType> pType;
+ ToRelease<ICorDebugModule> pModule;
+ if(pTypeCast == NULL)
+ {
+ ToRelease<ICorDebugValue2> pValue2;
+ IfFailRet(pInnerValue->QueryInterface(IID_ICorDebugValue2, (LPVOID *) &pValue2));
+ IfFailRet(pValue2->GetExactType(&pType));
+ }
+ else
+ {
+ pType = pTypeCast;
+ pType->AddRef();
+ }
+ IfFailRet(pType->GetClass(&pClass));
+ IfFailRet(pClass->GetModule(&pModule));
+ IfFailRet(pClass->GetToken(&currentTypeDef));
+
+ ToRelease<IUnknown> pMDUnknown;
+ ToRelease<IMetaDataImport> pMD;
+ IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown));
+ IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD));
+
+ // If the current type has a base type that isn't object, enum, or ValueType then add a node for the base type
+ WCHAR baseTypeName[mdNameLen] = L"\0";
+ ToRelease<ICorDebugType> pBaseType;
+ ExpressionNode* pBaseTypeNode = NULL;
+ if(SUCCEEDED(pType->GetBase(&pBaseType)) && pBaseType != NULL && SUCCEEDED(CalculateTypeName(pBaseType, baseTypeName, mdNameLen)))
+ {
+ if(_wcsncmp(baseTypeName, L"System.Enum", 11) == 0)
+ return S_OK;
+ else if(_wcsncmp(baseTypeName, L"System.Object", 13) != 0 && _wcsncmp(baseTypeName, L"System.ValueType", 16) != 0)
+ {
+ pBaseTypeNode = new ExpressionNode(pAbsoluteExpression, ChildKind_BaseClass, L"<baseclass>", pInnerValue, pBaseType, pILFrame);
+ AddChild(pBaseTypeNode);
+ }
+ }
+
+ // add nodes for all the fields in this object
+ ULONG numFields = 0;
+ HCORENUM fEnum = NULL;
+ mdFieldDef fieldDef;
+ BOOL fieldExpanded = FALSE;
+ while(SUCCEEDED(pMD->EnumFields(&fEnum, currentTypeDef, &fieldDef, 1, &numFields)) && numFields != 0)
+ {
+ mdTypeDef classDef = 0;
+ ULONG nameLen = 0;
+ DWORD fieldAttr = 0;
+ WCHAR mdName[mdNameLen];
+ WCHAR typeName[mdNameLen];
+ CorElementType fieldDefaultValueEt;
+ UVCP_CONSTANT pDefaultValue;
+ ULONG cchDefaultValue;
+ if(SUCCEEDED(pMD->GetFieldProps(fieldDef, &classDef, mdName, mdNameLen, &nameLen, &fieldAttr, NULL, NULL, (DWORD*)&fieldDefaultValueEt, &pDefaultValue, &cchDefaultValue)))
+ {
+ ToRelease<ICorDebugType> pFieldType;
+ ToRelease<ICorDebugValue> pFieldVal;
+
+ // static fields (of any kind - AppDomain, thread, context, RVA)
+ if (fieldAttr & fdStatic)
+ {
+ pType->GetStaticFieldValue(fieldDef, pILFrame, &pFieldVal);
+ }
+ // non-static fields on an object instance
+ else if(pInnerValue != NULL)
+ {
+ ToRelease<ICorDebugObjectValue> pObjValue;
+ if (SUCCEEDED(pInnerValue->QueryInterface(IID_ICorDebugObjectValue, (LPVOID*) &pObjValue)))
+ pObjValue->GetFieldValue(pClass, fieldDef, &pFieldVal);
+ }
+ // skip over non-static fields on static types
+ else
+ {
+ continue;
+ }
+
+ // we didn't get a value yet and there is default value available
+ // need to calculate the type because there won't be a ICorDebugValue to derive it from
+ if(pFieldVal == NULL && pDefaultValue != NULL)
+ {
+ FindTypeFromElementType(fieldDefaultValueEt, &pFieldType);
+ }
+
+ ExpressionNode* pNewChildNode = new ExpressionNode(pAbsoluteExpression, ChildKind_Field, mdName, pFieldVal, pFieldType, pILFrame, pDefaultValue, cchDefaultValue);
+ AddChild(pNewChildNode);
+ if(pNewChildNode->Expand(varToExpand) != S_FALSE)
+ fieldExpanded = TRUE;
+ }
+ }
+ pMD->CloseEnum(fEnum);
+
+ // Only recurse to expand the base type if all of these hold:
+ // 1) base type exists
+ // 2) no field was expanded
+ // 3) the non-casting portion of the varToExpand doesn't match the current expression
+ // OR the cast exists and doesn't match
+
+ if(pBaseTypeNode == NULL) return Status;
+ if(fieldExpanded) return Status;
+
+ WCHAR* pEndCast = _wcschr(varToExpand, L')');
+ WCHAR* pNonCast = (pEndCast == NULL) ? varToExpand : pEndCast+1;
+ if(_wcscmp(pNonCast, pAbsoluteExpression) != 0)
+ {
+ pBaseTypeNode->Expand(varToExpand);
+ return Status;
+ }
+
+ if(varToExpand[0] == L'(' && pEndCast != NULL)
+ {
+ int cchCastTypeName = ((int)(pEndCast-1)-(int)varToExpand)/2;
+ PopulateType();
+ if(_wcslen(pTypeName) != (cchCastTypeName) ||
+ _wcsncmp(varToExpand+1, pTypeName, cchCastTypeName) != 0)
+ {
+ pBaseTypeNode->Expand(varToExpand);
+ return Status;
+ }
+ }
+
+ return Status;
+}
+
+
+// Value Population functions
+
+//Helper for unwrapping values
+HRESULT ExpressionNode::DereferenceAndUnboxValue(ICorDebugValue * pInputValue, ICorDebugValue** ppOutputValue, BOOL * pIsNull)
+{
+ HRESULT Status = S_OK;
+ *ppOutputValue = NULL;
+ if(pIsNull != NULL) *pIsNull = FALSE;
+
+ ToRelease<ICorDebugReferenceValue> pReferenceValue;
+ Status = pInputValue->QueryInterface(IID_ICorDebugReferenceValue, (LPVOID*) &pReferenceValue);
+ if (SUCCEEDED(Status))
+ {
+ BOOL isNull = FALSE;
+ IfFailRet(pReferenceValue->IsNull(&isNull));
+ if(!isNull)
+ {
+ ToRelease<ICorDebugValue> pDereferencedValue;
+ IfFailRet(pReferenceValue->Dereference(&pDereferencedValue));
+ return DereferenceAndUnboxValue(pDereferencedValue, ppOutputValue);
+ }
+ else
+ {
+ if(pIsNull != NULL) *pIsNull = TRUE;
+ *ppOutputValue = pInputValue;
+ (*ppOutputValue)->AddRef();
+ return S_OK;
+ }
+ }
+
+ ToRelease<ICorDebugBoxValue> pBoxedValue;
+ Status = pInputValue->QueryInterface(IID_ICorDebugBoxValue, (LPVOID*) &pBoxedValue);
+ if (SUCCEEDED(Status))
+ {
+ ToRelease<ICorDebugObjectValue> pUnboxedValue;
+ IfFailRet(pBoxedValue->GetObject(&pUnboxedValue));
+ return DereferenceAndUnboxValue(pUnboxedValue, ppOutputValue);
+ }
+ *ppOutputValue = pInputValue;
+ (*ppOutputValue)->AddRef();
+ return S_OK;
+}
+
+// Returns TRUE if the value derives from System.Enum
+BOOL ExpressionNode::IsEnum(ICorDebugValue * pInputValue)
+{
+ ToRelease<ICorDebugValue> pValue;
+ if(FAILED(DereferenceAndUnboxValue(pInputValue, &pValue, NULL))) return FALSE;
+
+ WCHAR baseTypeName[mdNameLen];
+ ToRelease<ICorDebugValue2> pValue2;
+ ToRelease<ICorDebugType> pType;
+ ToRelease<ICorDebugType> pBaseType;
+
+ if(FAILED(pValue->QueryInterface(IID_ICorDebugValue2, (LPVOID *) &pValue2))) return FALSE;
+ if(FAILED(pValue2->GetExactType(&pType))) return FALSE;
+ if(FAILED(pType->GetBase(&pBaseType)) || pBaseType == NULL) return FALSE;
+ if(FAILED(CalculateTypeName(pBaseType, baseTypeName, mdNameLen))) return FALSE;
+
+ return (_wcsncmp(baseTypeName, L"System.Enum", 11) == 0);
+}
+
+// Calculates the value text for nodes that have enum values
+HRESULT ExpressionNode::PopulateEnumValue(ICorDebugValue* pEnumValue, BYTE* enumValue)
+{
+ HRESULT Status = S_OK;
+
+ mdTypeDef currentTypeDef;
+ ToRelease<ICorDebugClass> pClass;
+ ToRelease<ICorDebugValue2> pValue2;
+ ToRelease<ICorDebugType> pType;
+ ToRelease<ICorDebugModule> pModule;
+ IfFailRet(pEnumValue->QueryInterface(IID_ICorDebugValue2, (LPVOID *) &pValue2));
+ IfFailRet(pValue2->GetExactType(&pType));
+ IfFailRet(pType->GetClass(&pClass));
+ IfFailRet(pClass->GetModule(&pModule));
+ IfFailRet(pClass->GetToken(&currentTypeDef));
+
+ ToRelease<IUnknown> pMDUnknown;
+ ToRelease<IMetaDataImport> pMD;
+ IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown));
+ IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD));
+
+
+ //First, we need to figure out the underlying enum type so that we can correctly type cast the raw values of each enum constant
+ //We get that from the non-static field of the enum variable (I think the field is called __value or something similar)
+ ULONG numFields = 0;
+ HCORENUM fEnum = NULL;
+ mdFieldDef fieldDef;
+ CorElementType enumUnderlyingType = ELEMENT_TYPE_END;
+ while(SUCCEEDED(pMD->EnumFields(&fEnum, currentTypeDef, &fieldDef, 1, &numFields)) && numFields != 0)
+ {
+ DWORD fieldAttr = 0;
+ PCCOR_SIGNATURE pSignatureBlob = NULL;
+ ULONG sigBlobLength = 0;
+ if(SUCCEEDED(pMD->GetFieldProps(fieldDef, NULL, NULL, 0, NULL, &fieldAttr, &pSignatureBlob, &sigBlobLength, NULL, NULL, NULL)))
+ {
+ if((fieldAttr & fdStatic) == 0)
+ {
+ CorSigUncompressCallingConv(pSignatureBlob);
+ enumUnderlyingType = CorSigUncompressElementType(pSignatureBlob);
+ break;
+ }
+ }
+ }
+ pMD->CloseEnum(fEnum);
+
+
+ //Now that we know the underlying enum type, let's decode the enum variable into OR-ed, human readable enum contants
+ fEnum = NULL;
+ bool isFirst = true;
+ ULONG64 remainingValue = *((ULONG64*)enumValue);
+ WCHAR* pTextValueCursor = pTextValue;
+ DWORD cchTextValueCursor = MAX_EXPRESSION;
+ while(SUCCEEDED(pMD->EnumFields(&fEnum, currentTypeDef, &fieldDef, 1, &numFields)) && numFields != 0)
+ {
+ ULONG nameLen = 0;
+ DWORD fieldAttr = 0;
+ WCHAR mdName[mdNameLen];
+ WCHAR typeName[mdNameLen];
+ UVCP_CONSTANT pRawValue = NULL;
+ ULONG rawValueLength = 0;
+ if(SUCCEEDED(pMD->GetFieldProps(fieldDef, NULL, mdName, mdNameLen, &nameLen, &fieldAttr, NULL, NULL, NULL, &pRawValue, &rawValueLength)))
+ {
+ DWORD enumValueRequiredAttributes = fdPublic | fdStatic | fdLiteral | fdHasDefault;
+ if((fieldAttr & enumValueRequiredAttributes) != enumValueRequiredAttributes)
+ continue;
+
+ ULONG64 currentConstValue = 0;
+ switch (enumUnderlyingType)
+ {
+ case ELEMENT_TYPE_CHAR:
+ case ELEMENT_TYPE_I1:
+ currentConstValue = (ULONG64)(*((CHAR*)pRawValue));
+ break;
+ case ELEMENT_TYPE_U1:
+ currentConstValue = (ULONG64)(*((BYTE*)pRawValue));
+ break;
+ case ELEMENT_TYPE_I2:
+ currentConstValue = (ULONG64)(*((SHORT*)pRawValue));
+ break;
+ case ELEMENT_TYPE_U2:
+ currentConstValue = (ULONG64)(*((USHORT*)pRawValue));
+ break;
+ case ELEMENT_TYPE_I4:
+ currentConstValue = (ULONG64)(*((INT32*)pRawValue));
+ break;
+ case ELEMENT_TYPE_U4:
+ currentConstValue = (ULONG64)(*((UINT32*)pRawValue));
+ break;
+ case ELEMENT_TYPE_I8:
+ currentConstValue = (ULONG64)(*((LONG*)pRawValue));
+ break;
+ case ELEMENT_TYPE_U8:
+ currentConstValue = (ULONG64)(*((ULONG*)pRawValue));
+ break;
+ case ELEMENT_TYPE_I:
+ currentConstValue = (ULONG64)(*((int*)pRawValue));
+ break;
+ case ELEMENT_TYPE_U:
+ case ELEMENT_TYPE_R4:
+ case ELEMENT_TYPE_R8:
+ // Technically U and the floating-point ones are options in the CLI, but not in the CLS or C#, so these are NYI
+ default:
+ currentConstValue = 0;
+ }
+
+ if((currentConstValue == remainingValue) || ((currentConstValue != 0) && ((currentConstValue & remainingValue) == currentConstValue)))
+ {
+ remainingValue &= ~currentConstValue;
+ DWORD charsCopied = 0;
+ if(isFirst)
+ {
+ charsCopied = _snwprintf_s(pTextValueCursor, cchTextValueCursor, _TRUNCATE, L"= %s", mdName);
+ isFirst = false;
+ }
+ else
+ charsCopied = _snwprintf_s(pTextValueCursor, cchTextValueCursor, _TRUNCATE, L" | %s", mdName);
+
+ // if an error or truncation occurred, stop copying
+ if(charsCopied == -1)
+ {
+ cchTextValueCursor = 0;
+ pTextValueCursor = NULL;
+ }
+ else
+ {
+ // charsCopied is the number of characters copied, not counting the terminating null
+ // this advances the cursor to point right at the terminating null so that future copies
+ // will concatenate the string
+ pTextValueCursor += charsCopied;
+ cchTextValueCursor -= charsCopied;
+ }
+ }
+ }
+ }
+ pMD->CloseEnum(fEnum);
+
+ return Status;
+}
+
+// Helper that caches the textual value for nodes that evaluate to a string object
+HRESULT ExpressionNode::GetDebuggeeStringValue(ICorDebugValue* pInputValue, __inout_ecount(cchBuffer) WCHAR* wszBuffer, DWORD cchBuffer)
+{
+ HRESULT Status;
+
+ ToRelease<ICorDebugStringValue> pStringValue;
+ IfFailRet(pInputValue->QueryInterface(IID_ICorDebugStringValue, (LPVOID*) &pStringValue));
+
+ ULONG32 cchValueReturned;
+ IfFailRet(pStringValue->GetString(cchBuffer, &cchValueReturned, wszBuffer));
+
+ return S_OK;
+}
+
+// Retrieves the string value for a constant
+HRESULT ExpressionNode::GetConstantStringValue(__inout_ecount(cchBuffer) WCHAR* wszBuffer, DWORD cchBuffer)
+{
+ // The string encoded in metadata isn't null-terminated
+ // so we need to copy it to a null terminated buffer
+ DWORD copyLen = cchDefaultValue;
+ if(copyLen > cchBuffer-1)
+ copyLen = cchDefaultValue;
+
+ wcsncpy_s(wszBuffer, cchBuffer, (WCHAR*)pDefaultValue, copyLen);
+ return S_OK;
+}
+
+// Helper that caches the textual value for nodes that evaluate to array objects
+HRESULT ExpressionNode::PopulateSzArrayValue(ICorDebugValue* pInputValue)
+{
+ HRESULT Status = S_OK;
+
+ ToRelease<ICorDebugArrayValue> pArrayValue;
+ IfFailRet(pInputValue->QueryInterface(IID_ICorDebugArrayValue, (LPVOID*) &pArrayValue));
+
+ ULONG32 nRank;
+ IfFailRet(pArrayValue->GetRank(&nRank));
+ if (nRank != 1)
+ {
+ _snwprintf_s(pErrorMessage, MAX_EXPRESSION, _TRUNCATE, L"Multi-dimensional arrays NYI");
+ return E_UNEXPECTED;
+ }
+
+ ULONG32 cElements;
+ IfFailRet(pArrayValue->GetCount(&cElements));
+
+ if (cElements == 0)
+ _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"(empty)");
+ else if (cElements == 1)
+ _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"(1 element)");
+ else
+ _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"(%d elements)", cElements);
+
+ return S_OK;
+}
+
+// Helper that caches the textual value for nodes of any type
+HRESULT ExpressionNode::PopulateTextValueHelper()
+{
+ HRESULT Status = S_OK;
+
+ BOOL isNull = TRUE;
+ ToRelease<ICorDebugValue> pInnerValue;
+ CorElementType corElemType;
+ ULONG32 cbSize = 0;
+ if(pValue != NULL)
+ {
+ IfFailRet(DereferenceAndUnboxValue(pValue, &pInnerValue, &isNull));
+
+ if(isNull)
+ {
+ _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= null");
+ return S_OK;
+ }
+ IfFailRet(pInnerValue->GetSize(&cbSize));
+ IfFailRet(pInnerValue->GetType(&corElemType));
+ }
+ else if(pDefaultValue != NULL)
+ {
+ if(pTypeCast == NULL)
+ {
+ // this shouldn't happen, but just print nothing if it does
+ return S_OK;
+ }
+ // This works around an irritating issue in ICorDebug. For default values
+ // we have to construct the ICorDebugType ourselves, however ICorDebug
+ // doesn't allow type construction using the correct element types. The
+ // caller must past CLASS or VALUETYPE even when a more specific short
+ // form element type is applicable. That means that later, here, we get
+ // back the wrong answer. To work around this we format the type as a
+ // string, and check it against all the known types. That allows us determine
+ // everything except VALUETYPE/CLASS. Thankfully that distinction is the
+ // one piece of data ICorDebugType will tell us if needed.
+ if(FAILED(GetCanonicalElementTypeForTypeName(GetTypeName(), &corElemType)))
+ {
+ pTypeCast->GetType(&corElemType);
+ }
+
+ switch(corElemType)
+ {
+ case ELEMENT_TYPE_BOOLEAN:
+ case ELEMENT_TYPE_I1:
+ case ELEMENT_TYPE_U1:
+ cbSize = 1;
+ break;
+
+ case ELEMENT_TYPE_CHAR:
+ case ELEMENT_TYPE_I2:
+ case ELEMENT_TYPE_U2:
+ cbSize = 2;
+ break;
+
+ case ELEMENT_TYPE_I:
+ case ELEMENT_TYPE_U:
+ case ELEMENT_TYPE_I4:
+ case ELEMENT_TYPE_U4:
+ case ELEMENT_TYPE_R4:
+ cbSize = 4;
+ break;
+
+ case ELEMENT_TYPE_I8:
+ case ELEMENT_TYPE_U8:
+ case ELEMENT_TYPE_R8:
+ cbSize = 8;
+ break;
+ }
+ }
+
+ if (corElemType == ELEMENT_TYPE_STRING)
+ {
+ WCHAR buffer[MAX_EXPRESSION];
+ buffer[0] = L'\0';
+ if(pInnerValue != NULL)
+ GetDebuggeeStringValue(pInnerValue, buffer, MAX_EXPRESSION);
+ else
+ GetConstantStringValue(buffer, MAX_EXPRESSION);
+ _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= \"%s\"", buffer);
+ }
+ else if (corElemType == ELEMENT_TYPE_SZARRAY)
+ {
+ return PopulateSzArrayValue(pInnerValue);
+ }
+
+
+ ArrayHolder<BYTE> rgbValue = new BYTE[cbSize];
+ memset(rgbValue.GetPtr(), 0, cbSize * sizeof(BYTE));
+ if(pInnerValue != NULL)
+ {
+ ToRelease<ICorDebugGenericValue> pGenericValue;
+ IfFailRet(pInnerValue->QueryInterface(IID_ICorDebugGenericValue, (LPVOID*) &pGenericValue));
+ IfFailRet(pGenericValue->GetValue((LPVOID) &(rgbValue[0])));
+ }
+ else
+ {
+ memcpy((LPVOID) &(rgbValue[0]), pDefaultValue, cbSize);
+ }
+
+ //TODO: this should really be calculated from the type
+ if(pInnerValue != NULL && IsEnum(pInnerValue))
+ {
+ Status = PopulateEnumValue(pInnerValue, rgbValue);
+ return Status;
+ }
+
+ switch (corElemType)
+ {
+ default:
+ _snwprintf_s(pErrorMessage, MAX_ERROR, _TRUNCATE, L"Unhandled CorElementType: 0x%x", corElemType);
+ Status = E_FAIL;
+ break;
+
+ case ELEMENT_TYPE_PTR:
+ _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"<pointer>");
+ break;
+
+ case ELEMENT_TYPE_FNPTR:
+ {
+ CORDB_ADDRESS addr = 0;
+ ToRelease<ICorDebugReferenceValue> pReferenceValue = NULL;
+ if(SUCCEEDED(pInnerValue->QueryInterface(IID_ICorDebugReferenceValue, (LPVOID*) &pReferenceValue)))
+ pReferenceValue->GetValue(&addr);
+ _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"<function pointer 0x%x>", addr);
+ }
+ break;
+
+ case ELEMENT_TYPE_VALUETYPE:
+ case ELEMENT_TYPE_CLASS:
+ case ELEMENT_TYPE_OBJECT:
+ ULONG64 pointer;
+ if(pInnerValue != NULL && SUCCEEDED(pInnerValue->GetAddress(&pointer)))
+ _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"@ 0x%p", (void *) pointer);
+ break;
+
+ case ELEMENT_TYPE_BOOLEAN:
+ _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %s", rgbValue[0] == 0 ? L"false" : L"true");
+ break;
+
+ case ELEMENT_TYPE_CHAR:
+ _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= '%C'", *(WCHAR *) &(rgbValue[0]));
+ break;
+
+ case ELEMENT_TYPE_I1:
+ _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %d", *(char*) &(rgbValue[0]));
+ break;
+
+ case ELEMENT_TYPE_U1:
+ _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %d", *(unsigned char*) &(rgbValue[0]));
+ break;
+
+ case ELEMENT_TYPE_I2:
+ _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %hd", *(short*) &(rgbValue[0]));
+ break;
+
+ case ELEMENT_TYPE_U2:
+ _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %hu", *(unsigned short*) &(rgbValue[0]));
+ break;
+
+ case ELEMENT_TYPE_I:
+ _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %d", *(int*) &(rgbValue[0]));
+ break;
+
+ case ELEMENT_TYPE_U:
+ _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %u", *(unsigned int*) &(rgbValue[0]));
+ break;
+
+ case ELEMENT_TYPE_I4:
+ _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %d", *(int*) &(rgbValue[0]));
+ break;
+
+ case ELEMENT_TYPE_U4:
+ _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %u", *(unsigned int*) &(rgbValue[0]));
+ break;
+
+ case ELEMENT_TYPE_I8:
+ _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %I64d", *(__int64*) &(rgbValue[0]));
+ break;
+
+ case ELEMENT_TYPE_U8:
+ _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %I64u", *(unsigned __int64*) &(rgbValue[0]));
+ break;
+
+ case ELEMENT_TYPE_R4:
+ _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %f", (double) *(float*) &(rgbValue[0]));
+ break;
+
+ case ELEMENT_TYPE_R8:
+ _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"%f", *(double*) &(rgbValue[0]));
+ break;
+
+ // TODO: The following corElementTypes are not yet implemented here. Array
+ // might be interesting to add, though the others may be of rather limited use:
+ // ELEMENT_TYPE_ARRAY = 0x14, // MDARRAY <type> <rank> <bcount> <bound1> ... <lbcount> <lb1> ...
+ //
+ // ELEMENT_TYPE_GENERICINST = 0x15, // GENERICINST <generic type> <argCnt> <arg1> ... <argn>
+ }
+
+ return Status;
+}
+
+// Caches the textual value of this node
+HRESULT ExpressionNode::PopulateTextValue()
+{
+ if(pErrorMessage[0] != 0)
+ return E_UNEXPECTED;
+ if(pValue == NULL && pDefaultValue == NULL)
+ return S_OK;
+ HRESULT Status = PopulateTextValueHelper();
+ if(FAILED(Status) && pErrorMessage[0] == 0)
+ {
+ _snwprintf_s(pErrorMessage, MAX_ERROR, _TRUNCATE, L"Error in PopulateTextValueHelper: 0x%x", Status);
+ }
+ return Status;
+}
+
+
+// Expression parsing and search
+
+//Callback that searches a frame to determine if it contains a local variable or parameter of a given name
+VOID ExpressionNode::EvaluateExpressionFrameScanCallback(ICorDebugFrame* pFrame, VOID* pUserData)
+{
+ EvaluateExpressionFrameScanData* pData = (EvaluateExpressionFrameScanData*)pUserData;
+
+ // we already found what we were looking for, just continue
+ if(pData->pFoundValue != NULL)
+ return;
+
+ // if any of these fail we just continue on
+ // querying for ILFrame will frequently fail because many frames aren't IL
+ ToRelease<ICorDebugILFrame> pILFrame;
+ HRESULT Status = pFrame->QueryInterface(IID_ICorDebugILFrame, (LPVOID*) &pILFrame);
+ if (FAILED(Status))
+ {
+ return;
+ }
+ // we need to save off the first frame we find regardless of whether we find the
+ // local or not. We might need this frame later for static field lookup.
+ if(pData->pFirstFrame == NULL)
+ {
+ pData->pFirstFrame = pILFrame;
+ pData->pFirstFrame->AddRef();
+ }
+ // not all IL frames map to an assembly (ex. LCG)
+ ToRelease<ICorDebugFunction> pFunction;
+ Status = pFrame->GetFunction(&pFunction);
+ if (FAILED(Status))
+ {
+ return;
+ }
+ // from here down shouldn't generally fail, but just in case
+ mdMethodDef methodDef;
+ Status = pFunction->GetToken(&methodDef);
+ if (FAILED(Status))
+ {
+ return;
+ }
+ ToRelease<ICorDebugModule> pModule;
+ Status = pFunction->GetModule(&pModule);
+ if (FAILED(Status))
+ {
+ return;
+ }
+ ToRelease<IUnknown> pMDUnknown;
+ Status = pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown);
+ if (FAILED(Status))
+ {
+ return;
+ }
+ ToRelease<IMetaDataImport> pMD;
+ Status = pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD);
+ if (FAILED(Status))
+ {
+ return;
+ }
+
+ pData->pFoundFrame = pILFrame;
+ pData->pFoundFrame->AddRef();
+ // Enumerate all the parameters
+ EnumerateParameters(pMD, methodDef, pILFrame, EvaluateExpressionVariableScanCallback, pUserData);
+ // Enumerate all the locals
+ EnumerateLocals(pMD, methodDef, pILFrame, EvaluateExpressionVariableScanCallback, pUserData);
+
+ // if we didn't find it in this frame then clear the frame back out
+ if(pData->pFoundValue == NULL)
+ {
+ pData->pFoundFrame = NULL;
+ }
+
+ return;
+}
+
+//Callback checks to see if a given local/parameter has name pName
+VOID ExpressionNode::EvaluateExpressionVariableScanCallback(ICorDebugValue* pValue, __in_z WCHAR* pName, __out_z WCHAR* pErrorMessage, VOID* pUserData)
+{
+ EvaluateExpressionFrameScanData* pData = (EvaluateExpressionFrameScanData*)pUserData;
+ if(_wcscmp(pName, pData->pIdentifier) == 0)
+ {
+ // found it
+ pData->pFoundValue = pValue;
+ pValue->AddRef();
+ }
+ return;
+}
+
+//Factory method that recursively parses pExpression and create an ExpressionNode
+// pExpression - the entire expression being parsed
+// pExpressionRemainder - the portion of the expression that remains to be parsed in this
+// recursive invocation
+// charactersParsed - the number of characters that have been parsed from pExpression
+// so far (todo: this is basically the difference between remainder and
+// full expression, do we need it?)
+// pParsedValue - A debuggee value that should be used as the context for interpreting
+// pExpressionRemainder
+// pParsedType - A debuggee type that should be used as the context for interpreting
+// pExpressionRemainder.
+// pParsedDefaultValue - A fixed value from metadata that should be used as context for
+// interpretting pExpressionRemainder
+// cchParsedDefaultValue- Size of pParsedDefaultValue
+// pFrame - A debuggee IL frame that disambiguates the thread and context needed
+// to evaluate a thread-static or context-static value
+// ppExpressionNode - OUT - the resulting expression node
+//
+//
+// Valid combinations of state comming into this method:
+// The expression up to charactersParsed isn't recognized yet:
+// pParsedValue = pParsedType = pParsedDefaultValue = NULL
+// cchParsedDefaultValue = 0
+// The expression up to charactersParsed is a recognized type:
+// pParsedType = <parsed type>
+// pParsedValue = pParsedDefaultValue = NULL
+// cchParsedDefaultValue = 0
+// The expression up to charactersParsed is a recognized value in the debuggee:
+// pParsedValue = <parsed value>
+// pParsedType = pParsedDefaultValue = NULL
+// cchParsedDefaultValue = 0
+// The expression up to charactersParsed is a recognized default value stored in metadata:
+// pParsedValue = NULL
+// pParsedType = <type calculated from metadata>
+// pParsedDefaultValue = <value from metadata>
+// cchParsedDefaultValue = <size of metadata value>
+//
+//
+// REFACTORING NOTE: This method is very similar (but not identical) to the expansion logic
+// in ExpressionNode. The primary difference is that the nodes expand all
+// fields/indices whereas this function only expands along a precise route.
+// If the ExpressionNode code where enhanced to support expanding precisely
+// large portions of this function could be disposed of. As soon as the function
+// matched the initial name it could create an ExpressionNode and then use the
+// ExpressionNode expansion functions to drill down to the actual node required.
+// Also need to make sure the nodes manage lifetime ok when a parent is destroyed
+// but a child node is still referenced.
+HRESULT ExpressionNode::CreateExpressionNodeHelper(__in_z WCHAR* pExpression,
+ __in_z WCHAR* pExpressionParseRemainder,
+ DWORD charactersParsed,
+ ICorDebugValue* pParsedValue,
+ ICorDebugType* pParsedType,
+ UVCP_CONSTANT pParsedDefaultValue,
+ ULONG cchParsedDefaultValue,
+ ICorDebugILFrame* pFrame,
+ ExpressionNode** ppExpressionNode)
+{
+ HRESULT Status = S_OK;
+ WCHAR* pExpressionCursor = pExpressionParseRemainder;
+ DWORD currentCharsParsed = charactersParsed;
+ WCHAR pIdentifier[mdNameLen];
+ pIdentifier[0] = 0;
+ BOOL isArray = FALSE;
+ WCHAR pResultBuffer[MAX_EXPRESSION];
+
+ // Get the next name from the expression string
+ if(FAILED(Status = ParseNextIdentifier(&pExpressionCursor, pIdentifier, mdNameLen, pResultBuffer, MAX_EXPRESSION, &currentCharsParsed, &isArray)))
+ {
+ *ppExpressionNode = new ExpressionNode(pExpression, pResultBuffer);
+ if(*ppExpressionNode == NULL)
+ return E_OUTOFMEMORY;
+ else
+ return S_OK;
+ }
+
+ // we've gone as far as we need, nothing left to parse
+ if(Status == S_FALSE)
+ {
+ ToRelease<ICorDebugValue> pValue;
+ *ppExpressionNode = new ExpressionNode(pExpression, ChildKind_BaseClass, pExpression, pParsedValue, pParsedType, pFrame, pParsedDefaultValue, cchParsedDefaultValue);
+ if(*ppExpressionNode == NULL)
+ return E_OUTOFMEMORY;
+ else
+ return S_OK;
+ }
+ // if we are just starting and have no context then we need to search locals/parameters/type names
+ else if(pParsedValue == NULL && pParsedType == NULL)
+ {
+ // the first identifier must be a name, not an indexing expression
+ if(isArray)
+ {
+ *ppExpressionNode = new ExpressionNode(pExpression, L"Expression must begin with a local variable, parameter, or fully qualified type name");
+ return S_OK;
+ }
+
+ // scan for root on stack
+ EvaluateExpressionFrameScanData data;
+ data.pIdentifier = pIdentifier;
+ data.pFoundValue = NULL;
+ data.pFoundFrame = NULL;
+ data.pFirstFrame = NULL;
+ data.pErrorMessage = pResultBuffer;
+ data.cchErrorMessage = MAX_EXPRESSION;
+ EnumerateFrames(EvaluateExpressionFrameScanCallback, (VOID*) &data);
+
+ if(data.pFoundValue != NULL)
+ {
+ // found the root, now recurse along the expression
+ return CreateExpressionNodeHelper(pExpression, pExpressionCursor, currentCharsParsed, data.pFoundValue, NULL, NULL, 0, data.pFoundFrame, ppExpressionNode);
+ }
+
+ // didn't find it - search the type table for a matching name
+ WCHAR pName[MAX_EXPRESSION];
+ while(true)
+ {
+ wcsncpy_s(pName, MAX_EXPRESSION, pExpression, currentCharsParsed);
+ ToRelease<ICorDebugType> pType;
+ if(SUCCEEDED(FindTypeByName(pName, &pType)))
+ return CreateExpressionNodeHelper(pExpression, pExpressionCursor, currentCharsParsed, NULL, pType, NULL, 0, data.pFirstFrame, ppExpressionNode);
+
+ if(FAILED(Status = ParseNextIdentifier(&pExpressionCursor, pIdentifier, mdNameLen, pResultBuffer, MAX_EXPRESSION, &currentCharsParsed, &isArray)))
+ {
+ *ppExpressionNode = new ExpressionNode(pExpression, pResultBuffer);
+ return S_OK;
+ }
+ else if(Status == S_FALSE)
+ {
+ break;
+ }
+ }
+
+ WCHAR errorMessage[MAX_ERROR];
+ swprintf_s(errorMessage, MAX_ERROR, L"No expression prefix could not be matched to an existing type, parameter, or local");
+ *ppExpressionNode = new ExpressionNode(pExpression, errorMessage);
+ return S_OK;
+ }
+
+ // we've got some context from an earlier portion of the search, now just need to continue
+ // by dereferencing and indexing until we reach the end of the expression
+
+ // Figure out the type, module, and metadata from our context information
+ ToRelease<ICorDebugType> pType;
+ BOOL isNull = TRUE;
+ ToRelease<ICorDebugValue> pInnerValue = NULL;
+ if(pParsedValue != NULL)
+ {
+ IfFailRet(DereferenceAndUnboxValue(pParsedValue, &pInnerValue, &isNull));
+
+ if(isNull)
+ {
+ WCHAR parsedExpression[MAX_EXPRESSION];
+ wcsncpy_s(parsedExpression, MAX_EXPRESSION, pExpression, charactersParsed);
+ WCHAR errorMessage[MAX_ERROR];
+ swprintf_s(errorMessage, MAX_ERROR, L"Dereferencing \'%s\' throws NullReferenceException", parsedExpression);
+ *ppExpressionNode = new ExpressionNode(pExpression, errorMessage);
+ return S_OK;
+ }
+
+ ToRelease<ICorDebugValue2> pValue2;
+ IfFailRet(pInnerValue->QueryInterface(IID_ICorDebugValue2, (LPVOID *) &pValue2));
+ IfFailRet(pValue2->GetExactType(&pType));
+ CorElementType et;
+ IfFailRet(pType->GetType(&et));
+ while(et == ELEMENT_TYPE_ARRAY || et == ELEMENT_TYPE_SZARRAY || et == ELEMENT_TYPE_BYREF || et == ELEMENT_TYPE_PTR)
+ {
+ pType->GetFirstTypeParameter(&pType);
+ IfFailRet(pType->GetType(&et));
+ }
+ }
+ else
+ {
+ pType = pParsedType;
+ pType->AddRef();
+ }
+ ToRelease<ICorDebugClass> pClass;
+ IfFailRet(pType->GetClass(&pClass));
+ ToRelease<ICorDebugModule> pModule;
+ IfFailRet(pClass->GetModule(&pModule));
+ mdTypeDef currentTypeDef;
+ IfFailRet(pClass->GetToken(&currentTypeDef));
+
+ ToRelease<IUnknown> pMDUnknown;
+ ToRelease<IMetaDataImport> pMD;
+ IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown));
+ IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD));
+
+
+ // if we are searching along and this is an array index dereference
+ if(isArray)
+ {
+ ToRelease<ICorDebugArrayValue> pArrayValue;
+ if(pInnerValue == NULL || FAILED(Status = pInnerValue->QueryInterface(IID_ICorDebugArrayValue, (LPVOID*) &pArrayValue)))
+ {
+ WCHAR errorMessage[MAX_ERROR];
+ swprintf_s(errorMessage, MAX_ERROR, L"Index notation only supported for instances of an array type");
+ *ppExpressionNode = new ExpressionNode(pExpression, errorMessage);
+ return S_OK;
+ }
+
+ ULONG32 nRank;
+ IfFailRet(pArrayValue->GetRank(&nRank));
+ if (nRank != 1)
+ {
+ WCHAR errorMessage[MAX_ERROR];
+ swprintf_s(errorMessage, MAX_ERROR, L"Multi-dimensional arrays NYI");
+ *ppExpressionNode = new ExpressionNode(pExpression, errorMessage);
+ return S_OK;
+ }
+
+ int index = -1;
+ if(swscanf_s(pIdentifier, L"%d", &index) != 1)
+ {
+ WCHAR errorMessage[MAX_ERROR];
+ swprintf_s(errorMessage, MAX_ERROR, L"Failed to parse expression, missing or invalid index expression at character %d", charactersParsed+1);
+ *ppExpressionNode = new ExpressionNode(pExpression, errorMessage);
+ return S_OK;
+ }
+
+ ULONG32 cElements;
+ IfFailRet(pArrayValue->GetCount(&cElements));
+ if(index < 0 || (ULONG32)index >= cElements)
+ {
+ WCHAR errorMessage[MAX_ERROR];
+ swprintf_s(errorMessage, MAX_ERROR, L"Index is out of range for this array");
+ *ppExpressionNode = new ExpressionNode(pExpression, errorMessage);
+ return S_OK;
+ }
+
+ ToRelease<ICorDebugValue> pElementValue;
+ IfFailRet(pArrayValue->GetElementAtPosition(index, &pElementValue));
+ return CreateExpressionNodeHelper(pExpression, pExpressionCursor, currentCharsParsed, pElementValue, NULL, NULL, 0, pFrame, ppExpressionNode);
+ }
+ // if we are searching along and this is field dereference
+ else
+ {
+ ToRelease<ICorDebugType> pBaseType = pType;
+ pBaseType->AddRef();
+
+ while(pBaseType != NULL)
+ {
+ // get the current base type class/token/MD
+ ToRelease<ICorDebugClass> pBaseClass;
+ IfFailRet(pBaseType->GetClass(&pBaseClass));
+ ToRelease<ICorDebugModule> pBaseTypeModule;
+ IfFailRet(pBaseClass->GetModule(&pBaseTypeModule));
+ mdTypeDef baseTypeDef;
+ IfFailRet(pBaseClass->GetToken(&baseTypeDef));
+ ToRelease<IUnknown> pBaseTypeMDUnknown;
+ ToRelease<IMetaDataImport> pBaseTypeMD;
+ IfFailRet(pBaseTypeModule->GetMetaDataInterface(IID_IMetaDataImport, &pBaseTypeMDUnknown));
+ IfFailRet(pBaseTypeMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pBaseTypeMD));
+
+
+ // iterate through all fields at this level of the class hierarchy
+ ULONG numFields = 0;
+ HCORENUM fEnum = NULL;
+ mdFieldDef fieldDef;
+ while(SUCCEEDED(pMD->EnumFields(&fEnum, baseTypeDef, &fieldDef, 1, &numFields)) && numFields != 0)
+ {
+ ULONG nameLen = 0;
+ DWORD fieldAttr = 0;
+ WCHAR mdName[mdNameLen];
+ WCHAR typeName[mdNameLen];
+ CorElementType fieldDefaultValueEt;
+ UVCP_CONSTANT pDefaultValue;
+ ULONG cchDefaultValue;
+ if(SUCCEEDED(pBaseTypeMD->GetFieldProps(fieldDef, NULL, mdName, mdNameLen, &nameLen, &fieldAttr, NULL, NULL, (DWORD*)&fieldDefaultValueEt, &pDefaultValue, &cchDefaultValue)) &&
+ _wcscmp(mdName, pIdentifier) == 0)
+ {
+ ToRelease<ICorDebugType> pFieldValType = NULL;
+ ToRelease<ICorDebugValue> pFieldVal;
+ if (fieldAttr & fdStatic)
+ pBaseType->GetStaticFieldValue(fieldDef, pFrame, &pFieldVal);
+ else if(pInnerValue != NULL)
+ {
+ ToRelease<ICorDebugObjectValue> pObjValue;
+ if (SUCCEEDED(pInnerValue->QueryInterface(IID_ICorDebugObjectValue, (LPVOID*) &pObjValue)))
+ pObjValue->GetFieldValue(pBaseClass, fieldDef, &pFieldVal);
+ }
+
+ // we didn't get a value yet and there is default value available
+ // need to calculate the type because there won't be a ICorDebugValue to derive it from
+ if(pFieldVal == NULL && pDefaultValue != NULL)
+ {
+ FindTypeFromElementType(fieldDefaultValueEt, &pFieldValType);
+ }
+ else
+ {
+ // if we aren't using default value, make sure it is cleared out
+ pDefaultValue = NULL;
+ cchDefaultValue = 0;
+ }
+
+ // if we still don't have a value, check if we are trying to get an instance field from a static type
+ if(pInnerValue == NULL && pFieldVal == NULL && pDefaultValue == NULL)
+ {
+ WCHAR pObjectTypeName[MAX_EXPRESSION];
+ CalculateTypeName(pBaseType, pObjectTypeName, MAX_EXPRESSION);
+ WCHAR errorMessage[MAX_ERROR];
+ swprintf_s(errorMessage, MAX_ERROR, L"Can not evaluate instance field \'%s\' from static type \'%s\'", pIdentifier, pObjectTypeName);
+ *ppExpressionNode = new ExpressionNode(pExpression, errorMessage);
+ return S_OK;
+ }
+ return CreateExpressionNodeHelper(pExpression, pExpressionCursor, currentCharsParsed, pFieldVal, pFieldValType, pDefaultValue, cchDefaultValue, pFrame, ppExpressionNode);
+ }
+ }
+
+ //advance to next base type
+ ICorDebugType* pTemp = NULL;
+ pBaseType->GetBase(&pTemp);
+ pBaseType = pTemp;
+ }
+
+ WCHAR pObjectTypeName[MAX_EXPRESSION];
+ CalculateTypeName(pType, pObjectTypeName, MAX_EXPRESSION);
+ WCHAR errorMessage[MAX_ERROR];
+ swprintf_s(errorMessage, MAX_ERROR, L"Field \'%s\' does not exist in type \'%s\'", pIdentifier, pObjectTypeName);
+ *ppExpressionNode = new ExpressionNode(pExpression, errorMessage);
+ return S_OK;
+ }
+
+ return Status;
+}
+
+// Splits apart a C#-like expression and determines the first identifier in the string and updates expression to point
+// at the remaining unparsed portion
+HRESULT ExpressionNode::ParseNextIdentifier(__in_z WCHAR** expression, __inout_ecount(cchIdentifierName) WCHAR* identifierName, DWORD cchIdentifierName, __inout_ecount(cchErrorMessage) WCHAR* errorMessage, DWORD cchErrorMessage, DWORD* charactersParsed, BOOL* isArrayIndex)
+{
+
+ // This algorithm is best understood as a two stage process. The first stage splits
+ // the expression into two chunks an identifier and a remaining expression. The second stage
+ // normalizes the identifier. The splitting algorithm doesn't care if identifiers are well-formed
+ // at all, we do some error checking in the 2nd stage though. For the splitting stage, an identifier is
+ // any first character, followed by as many characters as possible that aren't a '.' or a '['.
+ // In the 2nd stage any '.' character is removed from the front (the only place it could be)
+ // and enclosing braces are removed. An error is recorded if the identifier ends 0 length or the
+ // opening bracket isn't matched. Here is an example showing how we would parse an expression
+ // which is deliberately not very well formed. Each line is the result of calling this function once... it
+ // takes many calls to break the entire expression down.
+ //
+ // expression 1st stage identifier 2nd stage identifier
+ // foo.bar[18..f[1.][2][[
+ // .bar[18..f[1.][2][[ foo foo
+ // [18..f[1.][2][[ .bar bar
+ // ..f[1.][2][[ [18 error no ]
+ // .f[1.][2][[ . error 0-length
+ // [1.][2][[ .f f
+ // .][2][[ [1 error no ]
+ // [2][[ .] ] (we don't error check legal CLI identifier name characters)
+ // [[ [2] 2
+ // [ [ error no ]
+ // [ error no ]
+
+ // not an error, just the end of the expression
+ if(*expression == NULL || **expression == 0)
+ return S_FALSE;
+
+ WCHAR* expressionStart = *expression;
+ DWORD currentCharsParsed = *charactersParsed;
+ DWORD identifierLen = (DWORD) _wcscspn(expressionStart, L".[");
+ // if the first character was a . or [ skip over it. Note that we don't
+ // do this always in case the first WCHAR was part of a surrogate pair
+ if(identifierLen == 0)
+ {
+ identifierLen = (DWORD) _wcscspn(expressionStart+1, L".[") + 1;
+ }
+
+ *expression += identifierLen;
+ *charactersParsed += identifierLen;
+
+ // done with the first stage splitting, on to 2nd stage
+
+ // a . should be followed by field name
+ if(*expressionStart == L'.')
+ {
+ if(identifierLen == 1) // 0-length after .
+ {
+ swprintf_s(errorMessage, cchErrorMessage, L"Failed to parse expression, missing name after character %d", currentCharsParsed+1);
+ return E_FAIL;
+ }
+ if(identifierLen-1 >= cchIdentifierName)
+ {
+ swprintf_s(errorMessage, cchErrorMessage, L"Failed to parse expression, name at character %d is too long", currentCharsParsed+2);
+ return E_FAIL;
+ }
+ *isArrayIndex = FALSE;
+ wcsncpy_s(identifierName, cchIdentifierName, expressionStart+1, identifierLen-1);
+ return S_OK;
+ }
+ // an open bracket should be followed by a decimal value and then a closing bracket
+ else if(*expressionStart == L'[')
+ {
+ if(*(expressionStart+identifierLen-1) != L']')
+ {
+ swprintf_s(errorMessage, cchErrorMessage, L"Failed to parse expression, missing or invalid index expression at character %d", currentCharsParsed+1);
+ return E_FAIL;
+ }
+ if(identifierLen <= 2) // 0-length between []
+ {
+ swprintf_s(errorMessage, cchErrorMessage, L"Failed to parse expression, missing index after character %d", currentCharsParsed+1);
+ return E_FAIL;
+ }
+ if(identifierLen-2 >= cchIdentifierName)
+ {
+ swprintf_s(errorMessage, cchErrorMessage, L"Failed to parse expression, index at character %d is too large", currentCharsParsed+2);
+ return E_FAIL;
+ }
+ *isArrayIndex = TRUE;
+ wcsncpy_s(identifierName, cchIdentifierName, expressionStart+1, identifierLen-2);
+ return S_OK;
+ }
+ else // no '.' or '[', this is an initial name
+ {
+ if(identifierLen == 0) // 0-length
+ {
+ swprintf_s(errorMessage, cchErrorMessage, L"Failed to parse expression, missing name after character %d", currentCharsParsed+1);
+ return E_FAIL;
+ }
+ if(identifierLen >= cchIdentifierName)
+ {
+ swprintf_s(errorMessage, cchErrorMessage, L"Failed to parse expression, name at character %d is too long", currentCharsParsed+1);
+ return E_FAIL;
+ }
+ *isArrayIndex = FALSE;
+ wcsncpy_s(identifierName, cchIdentifierName, expressionStart, identifierLen);
+ return S_OK;
+ }
+}
+
+
+// Iterate through all parameters in the ILFrame calling the callback function for each of them
+HRESULT ExpressionNode::EnumerateParameters(IMetaDataImport * pMD,
+ mdMethodDef methodDef,
+ ICorDebugILFrame * pILFrame,
+ VariableEnumCallback pCallback,
+ VOID* pUserData)
+{
+ HRESULT Status = S_OK;
+
+ ULONG cParams = 0;
+ ToRelease<ICorDebugValueEnum> pParamEnum;
+ IfFailRet(pILFrame->EnumerateArguments(&pParamEnum));
+ IfFailRet(pParamEnum->GetCount(&cParams));
+ DWORD methAttr = 0;
+ IfFailRet(pMD->GetMethodProps(methodDef, NULL, NULL, 0, NULL, &methAttr, NULL, NULL, NULL, NULL));
+ for (ULONG i=0; i < cParams; i++)
+ {
+ ULONG paramNameLen = 0;
+ mdParamDef paramDef;
+ WCHAR paramName[mdNameLen] = L"\0";
+
+ if(i == 0 && (methAttr & mdStatic) == 0)
+ swprintf_s(paramName, mdNameLen, L"this\0");
+ else
+ {
+ int idx = ((methAttr & mdStatic) == 0)? i : (i + 1);
+ if(SUCCEEDED(pMD->GetParamForMethodIndex(methodDef, idx, &paramDef)))
+ pMD->GetParamProps(paramDef, NULL, NULL, paramName, mdNameLen, &paramNameLen, NULL, NULL, NULL, NULL);
+ }
+ if(_wcslen(paramName) == 0)
+ swprintf_s(paramName, mdNameLen, L"param_%d\0", i);
+
+ ToRelease<ICorDebugValue> pValue;
+ ULONG cArgsFetched;
+ WCHAR pErrorMessage[MAX_ERROR] = L"\0";
+ HRESULT hr = pParamEnum->Next(1, &pValue, &cArgsFetched);
+ if (FAILED(hr))
+ {
+ swprintf_s(pErrorMessage, MAX_ERROR, L" + (Error 0x%x retrieving parameter '%S')\n", hr, paramName);
+ }
+ if (hr == S_FALSE)
+ {
+ break;
+ }
+ pCallback(pValue, paramName, pErrorMessage, pUserData);
+ }
+
+ return Status;
+}
+
+// Enumerate all locals in the given ILFrame, calling the callback method for each of them
+HRESULT ExpressionNode::EnumerateLocals(IMetaDataImport * pMD,
+ mdMethodDef methodDef,
+ ICorDebugILFrame * pILFrame,
+ VariableEnumCallback pCallback,
+ VOID* pUserData)
+{
+ HRESULT Status = S_OK;
+ ULONG cLocals = 0;
+ ToRelease<ICorDebugFunction> pFunction;
+ ToRelease<ICorDebugModule> pModule;
+ if(SUCCEEDED(pILFrame->GetFunction(&pFunction)))
+ {
+ IfFailRet(pFunction->GetModule(&pModule));
+ }
+ ToRelease<ICorDebugValueEnum> pLocalsEnum;
+ IfFailRet(pILFrame->EnumerateLocalVariables(&pLocalsEnum));
+ IfFailRet(pLocalsEnum->GetCount(&cLocals));
+ if (cLocals > 0)
+ {
+ SymbolReader symReader;
+ bool symbolsAvailable = false;
+ if(pModule != NULL && SUCCEEDED(symReader.LoadSymbols(pMD, pModule)))
+ symbolsAvailable = true;
+
+ for (ULONG i=0; i < cLocals; i++)
+ {
+ ULONG paramNameLen = 0;
+ WCHAR paramName[mdNameLen] = L"\0";
+ WCHAR pErrorMessage[MAX_ERROR] = L"\0";
+ ToRelease<ICorDebugValue> pValue;
+ HRESULT hr = S_OK;
+ if(symbolsAvailable)
+ hr = symReader.GetNamedLocalVariable(pILFrame, i, paramName, mdNameLen, &pValue);
+ else
+ {
+ ULONG cArgsFetched;
+ hr = pLocalsEnum->Next(1, &pValue, &cArgsFetched);
+ }
+ if(_wcslen(paramName) == 0)
+ swprintf_s(paramName, mdNameLen, L"local_%d\0", i);
+
+ if (FAILED(hr))
+ {
+ swprintf_s(pErrorMessage, MAX_ERROR, L" + (Error 0x%x retrieving local variable '%S')\n", hr, paramName);
+ }
+ else if (hr == S_FALSE)
+ {
+ break;
+ }
+ pCallback(pValue, paramName, pErrorMessage, pUserData);
+ }
+ }
+
+ return Status;
+}
+
+// Iterates over all frames on the current thread's stack, calling the callback function for each of them
+HRESULT ExpressionNode::EnumerateFrames(FrameEnumCallback pCallback, VOID* pUserData)
+{
+ HRESULT Status = S_OK;
+ ToRelease<ICorDebugThread> pThread;
+ ToRelease<ICorDebugThread3> pThread3;
+ ToRelease<ICorDebugStackWalk> pStackWalk;
+ ULONG ulThreadID = 0;
+ g_ExtSystem->GetCurrentThreadSystemId(&ulThreadID);
+
+ IfFailRet(g_pCorDebugProcess->GetThread(ulThreadID, &pThread));
+ IfFailRet(pThread->QueryInterface(IID_ICorDebugThread3, (LPVOID *) &pThread3));
+ IfFailRet(pThread3->CreateStackWalk(&pStackWalk));
+
+ InternalFrameManager internalFrameManager;
+ IfFailRet(internalFrameManager.Init(pThread3));
+
+ int currentFrame = -1;
+
+ for (Status = S_OK; ; Status = pStackWalk->Next())
+ {
+ currentFrame++;
+
+ if (Status == CORDBG_S_AT_END_OF_STACK)
+ {
+ break;
+ }
+ IfFailRet(Status);
+
+ if (IsInterrupt())
+ {
+ ExtOut("<interrupted>\n");
+ break;
+ }
+
+ CROSS_PLATFORM_CONTEXT context;
+ ULONG32 cbContextActual;
+ if ((Status=pStackWalk->GetContext(
+ DT_CONTEXT_FULL,
+ sizeof(context),
+ &cbContextActual,
+ (BYTE *)&context))!=S_OK)
+ {
+ ExtOut("GetFrameContext failed: %lx\n",Status);
+ break;
+ }
+
+ ToRelease<ICorDebugFrame> pFrame;
+ IfFailRet(pStackWalk->GetFrame(&pFrame));
+ if (Status == S_FALSE)
+ {
+ Status = S_OK;
+ continue;
+ }
+
+ pCallback(pFrame, pUserData);
+ }
+
+ return Status;
+}
+
+// Determines the corresponding ICorDebugType for a given primitive type
+HRESULT ExpressionNode::FindTypeFromElementType(CorElementType et, ICorDebugType** ppType)
+{
+ HRESULT Status;
+ switch (et)
+ {
+ default:
+ Status = E_FAIL;
+ break;
+
+ case ELEMENT_TYPE_BOOLEAN:
+ Status = FindTypeByName(L"System.Boolean", ppType);
+ break;
+
+ case ELEMENT_TYPE_CHAR:
+ Status = FindTypeByName(L"System.Char", ppType);
+ break;
+
+ case ELEMENT_TYPE_I1:
+ Status = FindTypeByName(L"System.SByte", ppType);
+ break;
+
+ case ELEMENT_TYPE_U1:
+ Status = FindTypeByName(L"System.Byte", ppType);
+ break;
+
+ case ELEMENT_TYPE_I2:
+ Status = FindTypeByName(L"System.Short", ppType);
+ break;
+
+ case ELEMENT_TYPE_U2:
+ Status = FindTypeByName(L"System.UShort", ppType);
+ break;
+
+ case ELEMENT_TYPE_I:
+ Status = FindTypeByName(L"System.Int32", ppType);
+ break;
+
+ case ELEMENT_TYPE_U:
+ Status = FindTypeByName(L"System.UInt32", ppType);
+ break;
+
+ case ELEMENT_TYPE_I4:
+ Status = FindTypeByName(L"System.Int32", ppType);
+ break;
+
+ case ELEMENT_TYPE_U4:
+ Status = FindTypeByName(L"System.UInt32", ppType);
+ break;
+
+ case ELEMENT_TYPE_I8:
+ Status = FindTypeByName(L"System.Int64", ppType);
+ break;
+
+ case ELEMENT_TYPE_U8:
+ Status = FindTypeByName(L"System.UInt64", ppType);
+ break;
+
+ case ELEMENT_TYPE_R4:
+ Status = FindTypeByName(L"System.Single", ppType);
+ break;
+
+ case ELEMENT_TYPE_R8:
+ Status = FindTypeByName(L"System.Double", ppType);
+ break;
+
+ case ELEMENT_TYPE_OBJECT:
+ Status = FindTypeByName(L"System.Object", ppType);
+ break;
+
+ case ELEMENT_TYPE_STRING:
+ Status = FindTypeByName(L"System.String", ppType);
+ break;
+ }
+ return Status;
+}
+
+// Gets the appropriate element type encoding for well-known fully qualified type names
+// This doesn't work for arbitrary types, just types that have CorElementType short forms.
+HRESULT ExpressionNode::GetCanonicalElementTypeForTypeName(__in_z WCHAR* pTypeName, CorElementType *et)
+{
+ //Sadly ICorDebug deliberately prevents creating ICorDebugType instances
+ //that use canonical short form element types... seems like an issue to me.
+
+ if(_wcscmp(pTypeName, L"System.String")==0)
+ {
+ *et = ELEMENT_TYPE_STRING;
+ }
+ else if(_wcscmp(pTypeName, L"System.Object")==0)
+ {
+ *et = ELEMENT_TYPE_OBJECT;
+ }
+ else if(_wcscmp(pTypeName, L"System.Void")==0)
+ {
+ *et = ELEMENT_TYPE_VOID;
+ }
+ else if(_wcscmp(pTypeName, L"System.Boolean")==0)
+ {
+ *et = ELEMENT_TYPE_BOOLEAN;
+ }
+ else if(_wcscmp(pTypeName, L"System.Char")==0)
+ {
+ *et = ELEMENT_TYPE_CHAR;
+ }
+ else if(_wcscmp(pTypeName, L"System.Byte")==0)
+ {
+ *et = ELEMENT_TYPE_U1;
+ }
+ else if(_wcscmp(pTypeName, L"System.Sbyte")==0)
+ {
+ *et = ELEMENT_TYPE_I1;
+ }
+ else if(_wcscmp(pTypeName, L"System.Int16")==0)
+ {
+ *et = ELEMENT_TYPE_I2;
+ }
+ else if(_wcscmp(pTypeName, L"System.UInt16")==0)
+ {
+ *et = ELEMENT_TYPE_U2;
+ }
+ else if(_wcscmp(pTypeName, L"System.UInt32")==0)
+ {
+ *et = ELEMENT_TYPE_U4;
+ }
+ else if(_wcscmp(pTypeName, L"System.Int32")==0)
+ {
+ *et = ELEMENT_TYPE_I4;
+ }
+ else if(_wcscmp(pTypeName, L"System.UInt64")==0)
+ {
+ *et = ELEMENT_TYPE_U8;
+ }
+ else if(_wcscmp(pTypeName, L"System.Int64")==0)
+ {
+ *et = ELEMENT_TYPE_I8;
+ }
+ else if(_wcscmp(pTypeName, L"System.Single")==0)
+ {
+ *et = ELEMENT_TYPE_R4;
+ }
+ else if(_wcscmp(pTypeName, L"System.Double")==0)
+ {
+ *et = ELEMENT_TYPE_R8;
+ }
+ else if(_wcscmp(pTypeName, L"System.IntPtr")==0)
+ {
+ *et = ELEMENT_TYPE_U;
+ }
+ else if(_wcscmp(pTypeName, L"System.UIntPtr")==0)
+ {
+ *et = ELEMENT_TYPE_I;
+ }
+ else if(_wcscmp(pTypeName, L"System.TypedReference")==0)
+ {
+ *et = ELEMENT_TYPE_TYPEDBYREF;
+ }
+ else
+ {
+ return E_FAIL; // can't tell from a name whether it should be valuetype or class
+ }
+ return S_OK;
+}
+
+// Searches the debuggee for any ICorDebugType that matches the given fully qualified name
+// This will search across all AppDomains and Assemblies
+HRESULT ExpressionNode::FindTypeByName(__in_z WCHAR* pTypeName, ICorDebugType** ppType)
+{
+ HRESULT Status = S_OK;
+ ToRelease<ICorDebugAppDomainEnum> pAppDomainEnum;
+ IfFailRet(g_pCorDebugProcess->EnumerateAppDomains(&pAppDomainEnum));
+ DWORD count;
+ IfFailRet(pAppDomainEnum->GetCount(&count));
+ for(DWORD i = 0; i < count; i++)
+ {
+ ToRelease<ICorDebugAppDomain> pAppDomain;
+ DWORD countFetched = 0;
+ IfFailRet(pAppDomainEnum->Next(1, &pAppDomain, &countFetched));
+ Status = FindTypeByName(pAppDomain, pTypeName, ppType);
+ if(SUCCEEDED(Status))
+ break;
+ }
+
+ return Status;
+}
+
+// Searches the debuggee for any ICorDebugType that matches the given fully qualified name
+// This will search across all Assemblies in the given AppDomain
+HRESULT ExpressionNode::FindTypeByName(ICorDebugAppDomain* pAppDomain, __in_z WCHAR* pTypeName, ICorDebugType** ppType)
+{
+ HRESULT Status = S_OK;
+ ToRelease<ICorDebugAssemblyEnum> pAssemblyEnum;
+ IfFailRet(pAppDomain->EnumerateAssemblies(&pAssemblyEnum));
+ DWORD count;
+ IfFailRet(pAssemblyEnum->GetCount(&count));
+ for(DWORD i = 0; i < count; i++)
+ {
+ ToRelease<ICorDebugAssembly> pAssembly;
+ DWORD countFetched = 0;
+ IfFailRet(pAssemblyEnum->Next(1, &pAssembly, &countFetched));
+ Status = FindTypeByName(pAssembly, pTypeName, ppType);
+ if(SUCCEEDED(Status))
+ break;
+ }
+
+ return Status;
+}
+
+// Searches the assembly for any ICorDebugType that matches the given fully qualified name
+HRESULT ExpressionNode::FindTypeByName(ICorDebugAssembly* pAssembly, __in_z WCHAR* pTypeName, ICorDebugType** ppType)
+{
+ HRESULT Status = S_OK;
+ ToRelease<ICorDebugModuleEnum> pModuleEnum;
+ IfFailRet(pAssembly->EnumerateModules(&pModuleEnum));
+ DWORD count;
+ IfFailRet(pModuleEnum->GetCount(&count));
+ for(DWORD i = 0; i < count; i++)
+ {
+ ToRelease<ICorDebugModule> pModule;
+ DWORD countFetched = 0;
+ IfFailRet(pModuleEnum->Next(1, &pModule, &countFetched));
+ Status = FindTypeByName(pModule, pTypeName, ppType);
+ if(SUCCEEDED(Status))
+ break;
+ }
+
+ return Status;
+}
+
+// Searches a given module for any ICorDebugType that matches the given fully qualified type name
+HRESULT ExpressionNode::FindTypeByName(ICorDebugModule* pModule, __in_z WCHAR* pTypeName, ICorDebugType** ppType)
+{
+ HRESULT Status = S_OK;
+ ToRelease<IUnknown> pMDUnknown;
+ ToRelease<IMetaDataImport> pMD;
+ IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown));
+ IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD));
+
+ // If the name contains a generic argument list, extract the type name from
+ // before the list
+ WCHAR rootName[mdNameLen];
+ WCHAR* pRootName = NULL;
+ int typeNameLen = (int) _wcslen(pTypeName);
+ int genericParamListStart = (int) _wcscspn(pTypeName, L"<");
+ if(genericParamListStart != typeNameLen)
+ {
+ if(pTypeName[typeNameLen-1] != L'>' || genericParamListStart > mdNameLen)
+ {
+ return E_FAIL; // mal-formed type name
+ }
+ else
+ {
+ wcsncpy_s(rootName, mdNameLen, pTypeName, genericParamListStart);
+ pRootName = rootName;
+ }
+ }
+ else
+ {
+ pRootName = pTypeName;
+ }
+
+ // Convert from name to token to ICorDebugClass
+ mdTypeDef typeDef;
+ IfFailRet(pMD->FindTypeDefByName(pRootName, NULL, &typeDef));
+ DWORD flags;
+ ULONG nameLen;
+ mdToken tkExtends;
+ IfFailRet(pMD->GetTypeDefProps(typeDef, NULL, 0, &nameLen, &flags, &tkExtends));
+ BOOL isValueType;
+ IfFailRet(IsTokenValueTypeOrEnum(tkExtends, pMD, &isValueType));
+ CorElementType et = isValueType ? ELEMENT_TYPE_VALUETYPE : ELEMENT_TYPE_CLASS;
+ ToRelease<ICorDebugClass> pClass;
+ IfFailRet(pModule->GetClassFromToken(typeDef, &pClass));
+ ToRelease<ICorDebugClass2> pClass2;
+ IfFailRet(pClass->QueryInterface(__uuidof(ICorDebugClass2), (void**)&pClass2));
+
+ // Convert from class to type - if generic then recursively resolve the generic
+ // parameter list
+ ArrayHolder<ToRelease<ICorDebugType>> typeParams = NULL;
+ int countTypeParams = 0;
+ if(genericParamListStart != typeNameLen)
+ {
+ ToRelease<ICorDebugAssembly> pAssembly;
+ IfFailRet(pModule->GetAssembly(&pAssembly));
+ ToRelease<ICorDebugAppDomain> pDomain;
+ IfFailRet(pAssembly->GetAppDomain(&pDomain));
+
+ countTypeParams = 1;
+ for(int i = genericParamListStart+1; i < typeNameLen; i++)
+ {
+ if(pTypeName[i] == L',') countTypeParams++;
+ }
+ typeParams = new ToRelease<ICorDebugType>[countTypeParams];
+
+ WCHAR* pCurName = pTypeName + genericParamListStart+1;
+ for(int i = 0; i < countTypeParams; i++)
+ {
+ WCHAR typeParamName[mdNameLen];
+ WCHAR* pNextComma = _wcschr(pCurName, L',');
+ int len = (pNextComma != NULL) ? (int)(pNextComma - pCurName) : (int)_wcslen(pCurName)-1;
+ if(len > mdNameLen)
+ return E_FAIL;
+ wcsncpy_s(typeParamName, mdNameLen, pCurName, len);
+ FindTypeByName(pDomain, typeParamName, &(typeParams[i]));
+ pCurName = pNextComma+1;
+ }
+ }
+ IfFailRet(pClass2->GetParameterizedType(et, countTypeParams, &(typeParams[0]), ppType));
+
+ return Status;
+}
+
+// Checks whether the given token is or refers to type System.ValueType or System.Enum
+HRESULT ExpressionNode::IsTokenValueTypeOrEnum(mdToken token, IMetaDataImport* pMetadata, BOOL* pResult)
+{
+ // This isn't a 100% correct check because we aren't verifying the module portion of the
+ // type identity. Arbitrary assemblies could define a type named System.ValueType or System.Enum.
+ // If that happens this code will get the answer wrong... we just assume that happens so rarely
+ // that it isn't worth doing all the overhead of assembly resolution to deal with
+
+ HRESULT Status = S_OK;
+ CorTokenType type = (CorTokenType)(token & 0xFF000000);
+
+ // only need enough space to hold either System.ValueType or System.Enum
+ //System.ValueType -> 16 characters
+ //System.Enum -> 11 characters
+ WCHAR nameBuffer[17];
+ nameBuffer[0] = L'\0';
+
+ if(type == mdtTypeRef)
+ {
+ ULONG chTypeDef;
+ pMetadata->GetTypeRefProps(token, NULL, NULL, 0, &chTypeDef);
+ if(chTypeDef > _countof(nameBuffer))
+ {
+ *pResult = FALSE;
+ return Status;
+ }
+ IfFailRet(pMetadata->GetTypeRefProps(token, NULL, nameBuffer, _countof(nameBuffer), &chTypeDef));
+ }
+ else if(type == mdtTypeDef)
+ {
+ ULONG chTypeDef;
+ pMetadata->GetTypeDefProps(token, NULL, 0, &chTypeDef, NULL, NULL);
+ if(chTypeDef > _countof(nameBuffer))
+ {
+ *pResult = FALSE;
+ return Status;
+ }
+ IfFailRet(pMetadata->GetTypeDefProps(token, nameBuffer, _countof(nameBuffer), &chTypeDef, NULL, NULL));
+ }
+
+ if(_wcscmp(nameBuffer, L"System.ValueType") == 0 ||
+ _wcscmp(nameBuffer, L"System.Enum") == 0)
+ {
+ *pResult = TRUE;
+ }
+ else
+ {
+ *pResult = FALSE;
+ }
+ return Status;
+}
diff --git a/src/ToolBox/SOS/Strike/ExpressionNode.h b/src/ToolBox/SOS/Strike/ExpressionNode.h
new file mode 100644
index 0000000000..507a8a53d3
--- /dev/null
+++ b/src/ToolBox/SOS/Strike/ExpressionNode.h
@@ -0,0 +1,307 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+#ifndef _EXPRESSION_NODE_
+#define _EXPRESSION_NODE_
+
+#ifdef FEATURE_PAL
+#error This file isn't designed to build in PAL
+#endif
+
+#include "strike.h"
+#include "sos.h"
+#include "util.h"
+
+#define MAX_EXPRESSION 500
+#define MAX_ERROR 500
+
+
+// Represents one node in a tree of expressions and sub-expressions
+// These nodes are used in the !watch expandable expression tree
+// Each node consists of a string based C#-like expression and its
+// evaluation within the current context of the debuggee.
+//
+// These nodes are also intended for eventual use in ClrStack -i expression tree
+// but ClrStack -i hasn't yet been refactored to use them
+//
+// Each node can evaluate to:
+// nothing - if an error occurs during expression parsing or the expression
+// names don't match to anything in the debuggee
+// a debuggee value - these are values that are backed in memory of the debuggee
+// (ICorDebugValue objects) or build time constants which are
+// stored in the assembly metadata.
+// a debuggee type - instead of refering to a particular instance of a type (the
+// value case above), nodes can directly refer to a type definition
+// represented by an ICorDebugType object
+class ExpressionNode
+{
+public:
+
+ typedef VOID (*ExpressionNodeVisitorCallback)(ExpressionNode* pExpressionNode, int depth, VOID* pUserData);
+
+ // Returns the complete expression being evaluated to get the value for this node
+ // The returned pointer is a string interior to this object - once you release
+ // all references to this object the string is invalid.
+ WCHAR* GetAbsoluteExpression();
+
+ // Returns the sub expression that logically indicates how the parent expression
+ // was built upon to reach this node. This relative value has no purpose other
+ // than an identifier and to convey UI meaning to the user. At present typical values
+ // are the name of type, a local, a parameter, a field, an array index, or '<basetype>'
+ // for a baseclass casting operation
+ // The returned pointer is a string interior to this object - once you release
+ // all references to this object the string is invalid.
+ WCHAR* GetRelativeExpression();
+
+ // Returns a text representation of the type of value that this node refers to
+ // It is possible this node doesn't evaluate to anything and therefore has no
+ // type
+ // The returned pointer is a string interior to this object - once you release
+ // all references to this object the string is invalid.
+ WCHAR* GetTypeName();
+
+ // Returns a text representation of the value for this node. It is possible that
+ // this node doesn't evaluate to anything and therefore has no value text.
+ // The returned pointer is a string interior to this object - once you release
+ // all references to this object the string is invalid.
+ WCHAR* GetTextValue();
+
+ // If there is any error during the evaluation of this node's expression, it is
+ // returned here.
+ // The returned pointer is a string interior to this object - once you release
+ // all references to this object the string is invalid.
+ WCHAR* GetErrorMessage();
+
+ // Factory function for creating the expression node at the root of a tree
+ static HRESULT CreateExpressionNode(__in_z WCHAR* pExpression, ExpressionNode** ppExpressionNode);
+
+ // Performs recursive expansion within the tree for nodes that are along the path to varToExpand.
+ // Expansion involves calulating a set of child expressions from the current expression via
+ // field dereferencing, array index dereferencing, or casting to a base type.
+ // For example if a tree was rooted with expression 'foo.bar' and varToExpand is '(Baz)foo.bar[9]'
+ // then 'foo.bar', 'foo.bar[9]', and '(Baz)foo.bar[9]' nodes would all be expanded.
+ HRESULT Expand(__in_z WCHAR* varToExpand);
+
+ // Standard depth first search tree traversal pattern with a callback
+ VOID DFSVisit(ExpressionNodeVisitorCallback pFunc, VOID* pUserData, int depth=0);
+
+private:
+ // for nodes that evaluate to a type, this is that type
+ // for nodes that evaluate to a debuggee value, this is the type of that
+ // value or one of its base types. It represents the type the value should
+ // displayed and expanded as.
+ ToRelease<ICorDebugType> pTypeCast;
+
+ // for nodes that evaluate to a memory backed debuggee value, this is that value
+ ToRelease<ICorDebugValue> pValue;
+
+ // if this node gets expanded and it has thread-static or context-static sub-fields,
+ // this frame disambiguates which thread and context to use.
+ ToRelease<ICorDebugILFrame> pILFrame;
+
+ // TODO: exactly which metadata is this supposed to be? try to get rid of this
+ ToRelease<IMetaDataImport> pMD;
+
+ // PERF: this could be a lot more memory efficient
+ WCHAR pTextValue[MAX_EXPRESSION];
+ WCHAR pErrorMessage[MAX_ERROR];
+ WCHAR pAbsoluteExpression[MAX_EXPRESSION];
+ WCHAR pRelativeExpression[MAX_EXPRESSION];
+ WCHAR pTypeName[MAX_EXPRESSION];
+
+ // if this value represents a build time constant debuggee value, this is a pointer
+ // to the value data stored in metadata and its size
+ UVCP_CONSTANT pDefaultValue;
+ ULONG cchDefaultValue;
+
+ // Pointer in a linked list of sibling nodes that all share the same parent
+ ExpressionNode* pNextSibling;
+ // Pointer to the first child node of this node, other children can be found
+ // by following the child's sibling list.
+ ExpressionNode* pChild;
+
+ typedef VOID (*VariableEnumCallback)(ICorDebugValue* pValue, WCHAR* pName, WCHAR* pErrorMessage, VOID* pUserData);
+ typedef VOID (*FrameEnumCallback)(ICorDebugFrame* pFrame, VOID* pUserData);
+
+ // Indicates how a child node was derived from its parent
+ enum ChildKind
+ {
+ ChildKind_Field,
+ ChildKind_Index,
+ ChildKind_BaseClass
+ };
+
+ // Creates a new expression with a given debuggee value and frame
+ ExpressionNode(__in_z WCHAR* pExpression, ICorDebugValue* pValue, ICorDebugILFrame* pFrame);
+
+ // Creates a new expression that has an error and no value
+ ExpressionNode(__in_z WCHAR* pExpression, __in_z WCHAR* pErrorMessage);
+
+ // Creates a new child expression
+ ExpressionNode(__in_z WCHAR* pParentExpression, ChildKind ck, __in_z WCHAR* pRelativeExpression, ICorDebugValue* pValue, ICorDebugType* pType, ICorDebugILFrame* pFrame, UVCP_CONSTANT pDefaultValue = NULL, ULONG cchDefaultValue = 0);
+
+ // Common member initialization for the constructors
+ VOID Init(ICorDebugValue* pValue, ICorDebugType* pTypeCast, ICorDebugILFrame* pFrame);
+
+ // Retreves the correct IMetaDataImport for the type represented in this node and stores it
+ // in pMD.
+ HRESULT PopulateMetaDataImport();
+
+ // Determines the string representation of pType and stores it in typeName
+ static HRESULT CalculateTypeName(ICorDebugType * pType, __inout_ecount(typeNameLen) WCHAR* typeName, DWORD typeNameLen);
+
+
+ // Appends angle brackets and the generic argument list to a type name
+ static HRESULT AddGenericArgs(ICorDebugType * pType, __inout_ecount(typeNameLen) WCHAR* typeName, DWORD typeNameLen);
+
+ // Determines the text name for the type of this node and caches it
+ HRESULT PopulateType();
+
+ // Node expansion helpers
+
+ // Inserts a new child at the end of the linked list of children
+ // PERF: This has O(N) insert time but these lists should never be large
+ VOID AddChild(ExpressionNode* pNewChild);
+
+ // Helper that determines if the current node is on the path of nodes represented by
+ // expression varToExpand
+ BOOL ShouldExpandVariable(__in_z WCHAR* varToExpand);
+
+ // Expands this array node by creating child nodes with expressions refering to individual array elements
+ HRESULT ExpandSzArray(ICorDebugValue* pInnerValue, __in_z WCHAR* varToExpand);
+
+ // Expands this struct/class node by creating child nodes with expressions refering to individual field values
+ // and one node for the basetype value
+ HRESULT ExpandFields(ICorDebugValue* pInnerValue, __in_z WCHAR* varToExpand);
+
+ // Value Population functions
+
+ //Helper for unwrapping values
+ static HRESULT DereferenceAndUnboxValue(ICorDebugValue * pInputValue, ICorDebugValue** ppOutputValue, BOOL * pIsNull = NULL);
+
+ // Returns TRUE if the value derives from System.Enum
+ static BOOL IsEnum(ICorDebugValue * pInputValue);
+
+ // Calculates the value text for nodes that have enum values
+ HRESULT PopulateEnumValue(ICorDebugValue* pEnumValue, BYTE* enumValue);
+
+ // Helper that fetches the text value of a string ICorDebugValue
+ HRESULT GetDebuggeeStringValue(ICorDebugValue* pInputValue, __inout_ecount(cchBuffer) WCHAR* wszBuffer, DWORD cchBuffer);
+
+ // Helper that fetches the text value of a string build-time literal
+ HRESULT GetConstantStringValue(__inout_ecount(cchBuffer) WCHAR* wszBuffer, DWORD cchBuffer);
+
+ // Helper that caches the textual value for nodes that evaluate to array objects
+ HRESULT PopulateSzArrayValue(ICorDebugValue* pInputValue);
+
+ // Helper that caches the textual value for nodes of any type
+ HRESULT PopulateTextValueHelper();
+
+ // Caches the textual value of this node
+ HRESULT PopulateTextValue();
+
+
+ // Expression parsing and search
+
+ // In/Out parameters for the EvaluateExpressionFrameScanCallback
+ typedef struct _EvaluateExpressionFrameScanData
+ {
+ WCHAR* pIdentifier;
+ ToRelease<ICorDebugValue> pFoundValue;
+ ToRelease<ICorDebugILFrame> pFoundFrame;
+ ToRelease<ICorDebugILFrame> pFirstFrame;
+ WCHAR* pErrorMessage;
+ DWORD cchErrorMessage;
+ } EvaluateExpressionFrameScanData;
+
+ //Callback that searches a frame to determine if it contains a local variable or parameter of a given name
+ static VOID EvaluateExpressionFrameScanCallback(ICorDebugFrame* pFrame, VOID* pUserData);
+
+ //Callback checks to see if a given local/parameter has name pName
+ static VOID EvaluateExpressionVariableScanCallback(ICorDebugValue* pValue, __in_z WCHAR* pName, __out_z WCHAR* pErrorMessage, VOID* pUserData);
+
+ //Factory method that recursively parses pExpression and create an ExpressionNode
+ // pExpression - the entire expression being parsed
+ // pExpressionRemainder - the portion of the expression that remains to be parsed in this
+ // recursive invocation
+ // charactersParsed - the number of characters that have been parsed from pExpression
+ // so far (todo: this is basically the difference between remainder and
+ // full expression, do we need it?)
+ // pParsedValue - A debuggee value that should be used as the context for interpreting
+ // pExpressionRemainder
+ // pParsedType - A debuggee type that should be used as the context for interpreting
+ // pExpressionRemainder.
+ // pParsedDefaultValue - A fixed value from metadata that should be used as context for
+ // interpretting pExpressionRemainder
+ // cchParsedDefaultValue- Size of pParsedDefaultValue
+ // pFrame - A debuggee IL frame that disambiguates the thread and context needed
+ // to evaluate a thread-static or context-static value
+ // ppExpressionNode - OUT - the resulting expression node
+ //
+ //
+ static HRESULT CreateExpressionNodeHelper(__in_z WCHAR* pExpression,
+ __in_z WCHAR* pExpressionParseRemainder,
+ DWORD charactersParsed,
+ ICorDebugValue* pParsedValue,
+ ICorDebugType* pParsedType,
+ UVCP_CONSTANT pParsedDefaultValue,
+ ULONG cchParsedDefaultValue,
+ ICorDebugILFrame* pFrame,
+ ExpressionNode** ppExpressionNode);
+
+ // Splits apart a C#-like expression and determines the first identifier in the string and updates expression to point
+ // at the remaining unparsed portion
+ static HRESULT ParseNextIdentifier(__in_z WCHAR** expression,
+ __inout_ecount(cchIdentifierName) WCHAR* identifierName,
+ DWORD cchIdentifierName,
+ __inout_ecount(cchErrorMessage) WCHAR* errorMessage,
+ DWORD cchErrorMessage,
+ DWORD* charactersParsed,
+ BOOL* isArrayIndex);
+
+
+ // Iterate through all parameters in the ILFrame calling the callback function for each of them
+ static HRESULT EnumerateParameters(IMetaDataImport * pMD,
+ mdMethodDef methodDef,
+ ICorDebugILFrame * pILFrame,
+ VariableEnumCallback pCallback,
+ VOID* pUserData);
+
+ // Enumerate all locals in the given ILFrame, calling the callback method for each of them
+ static HRESULT EnumerateLocals(IMetaDataImport * pMD,
+ mdMethodDef methodDef,
+ ICorDebugILFrame * pILFrame,
+ VariableEnumCallback pCallback,
+ VOID* pUserData);
+
+ // Iterates over all frames on the current thread's stack, calling the callback function for each of them
+ static HRESULT EnumerateFrames(FrameEnumCallback pCallback, VOID* pUserData);
+
+ // Determines the corresponding ICorDebugType for a given primitive type
+ static HRESULT FindTypeFromElementType(CorElementType et, ICorDebugType** ppType);
+
+ // Gets the appropriate element type encoding for well-known fully qualified type names
+ // This doesn't work for arbitrary types, just types that have CorElementType short forms.
+ static HRESULT GetCanonicalElementTypeForTypeName(__in_z WCHAR* pTypeName, CorElementType *et);
+
+ // Searches the debuggee for any ICorDebugType that matches the given fully qualified name
+ // This will search across all AppDomains and Assemblies
+ static HRESULT FindTypeByName(__in_z WCHAR* pTypeName, ICorDebugType** ppType);
+
+ // Searches the debuggee for any ICorDebugType that matches the given fully qualified name
+ // This will search across all Assemblies in the given AppDomain
+ static HRESULT FindTypeByName(ICorDebugAppDomain* pAppDomain, __in_z WCHAR* pTypeName, ICorDebugType** ppType);
+
+ // Searches the assembly for any ICorDebugType that matches the given fully qualified name
+ static HRESULT FindTypeByName(ICorDebugAssembly* pAssembly, __in_z WCHAR* pTypeName, ICorDebugType** ppType);
+
+ // Searches a given module for any ICorDebugType that matches the given fully qualified type name
+ static HRESULT FindTypeByName(ICorDebugModule* pModule, __in_z WCHAR* pTypeName, ICorDebugType** ppType);
+
+ // Checks whether the given token is or refers to type System.ValueType or System.Enum
+ static HRESULT IsTokenValueTypeOrEnum(mdToken token, IMetaDataImport* pMetadata, BOOL* pResult);
+};
+
+#endif
diff --git a/src/ToolBox/SOS/Strike/Native.rc b/src/ToolBox/SOS/Strike/Native.rc
new file mode 100644
index 0000000000..179ddfd24a
--- /dev/null
+++ b/src/ToolBox/SOS/Strike/Native.rc
@@ -0,0 +1,10 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#define FX_VER_FILEDESCRIPTION_STR "Microsoft NTSD extension for .NET Runtime\0"
+
+#include <fxver.h>
+#include <fxver.rc>
+
+DOCUMENTATION TEXT DISCARDABLE "sosdocs.txt"
diff --git a/src/ToolBox/SOS/Strike/SOS.nativeproj b/src/ToolBox/SOS/Strike/SOS.nativeproj
new file mode 100644
index 0000000000..4c0fc7616f
--- /dev/null
+++ b/src/ToolBox/SOS/Strike/SOS.nativeproj
@@ -0,0 +1,7 @@
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\xplat\SetHostLocal.props"/>
+ <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\src\toolbox\sos\strike\sos.targets" />
+ <PropertyGroup>
+ <BuildCoreBinaries>true</BuildCoreBinaries>
+ </PropertyGroup>
+</Project>
diff --git a/src/ToolBox/SOS/Strike/SOS.sln b/src/ToolBox/SOS/Strike/SOS.sln
new file mode 100644
index 0000000000..08f4a64836
--- /dev/null
+++ b/src/ToolBox/SOS/Strike/SOS.sln
@@ -0,0 +1,76 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2013
+VisualStudioVersion = 12.0.31101.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SOS", "SOS.vcxproj", "{3941DEDB-8183-4F82-9193-5EC0D5B6D4A6}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mscordacwks", "..\..\..\mscordacwks.vcproj", "{C5716445-C233-4491-85A4-31B75731DD95}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mscordbi", "..\..\..\mscordbi.vcproj", "{95A6AE03-EC45-4450-93DB-9B21890F79E7}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ amd64chk|Win32 = amd64chk|Win32
+ amd64dbg|Win32 = amd64dbg|Win32
+ amd64ret|Win32 = amd64ret|Win32
+ Debug|Win32 = Debug|Win32
+ Release|Win32 = Release|Win32
+ x86chk|Win32 = x86chk|Win32
+ x86dbg|Win32 = x86dbg|Win32
+ x86ret|Win32 = x86ret|Win32
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {3941DEDB-8183-4F82-9193-5EC0D5B6D4A6}.amd64chk|Win32.ActiveCfg = amd64chk|Win32
+ {3941DEDB-8183-4F82-9193-5EC0D5B6D4A6}.amd64chk|Win32.Build.0 = amd64chk|Win32
+ {3941DEDB-8183-4F82-9193-5EC0D5B6D4A6}.amd64dbg|Win32.ActiveCfg = amd64dbg|Win32
+ {3941DEDB-8183-4F82-9193-5EC0D5B6D4A6}.amd64dbg|Win32.Build.0 = amd64dbg|Win32
+ {3941DEDB-8183-4F82-9193-5EC0D5B6D4A6}.amd64ret|Win32.ActiveCfg = amd64ret|Win32
+ {3941DEDB-8183-4F82-9193-5EC0D5B6D4A6}.amd64ret|Win32.Build.0 = amd64ret|Win32
+ {3941DEDB-8183-4F82-9193-5EC0D5B6D4A6}.Debug|Win32.ActiveCfg = x86chk|Win32
+ {3941DEDB-8183-4F82-9193-5EC0D5B6D4A6}.Debug|Win32.Build.0 = x86chk|Win32
+ {3941DEDB-8183-4F82-9193-5EC0D5B6D4A6}.Release|Win32.ActiveCfg = x86chk|Win32
+ {3941DEDB-8183-4F82-9193-5EC0D5B6D4A6}.Release|Win32.Build.0 = x86chk|Win32
+ {3941DEDB-8183-4F82-9193-5EC0D5B6D4A6}.x86chk|Win32.ActiveCfg = x86chk|Win32
+ {3941DEDB-8183-4F82-9193-5EC0D5B6D4A6}.x86chk|Win32.Build.0 = x86chk|Win32
+ {3941DEDB-8183-4F82-9193-5EC0D5B6D4A6}.x86dbg|Win32.ActiveCfg = x86dbg|Win32
+ {3941DEDB-8183-4F82-9193-5EC0D5B6D4A6}.x86dbg|Win32.Build.0 = x86dbg|Win32
+ {3941DEDB-8183-4F82-9193-5EC0D5B6D4A6}.x86ret|Win32.ActiveCfg = x86ret|Win32
+ {3941DEDB-8183-4F82-9193-5EC0D5B6D4A6}.x86ret|Win32.Build.0 = x86ret|Win32
+ {C5716445-C233-4491-85A4-31B75731DD95}.amd64chk|Win32.ActiveCfg = Debug|Win32
+ {C5716445-C233-4491-85A4-31B75731DD95}.amd64chk|Win32.Build.0 = Debug|Win32
+ {C5716445-C233-4491-85A4-31B75731DD95}.amd64dbg|Win32.ActiveCfg = Debug|Win32
+ {C5716445-C233-4491-85A4-31B75731DD95}.amd64dbg|Win32.Build.0 = Debug|Win32
+ {C5716445-C233-4491-85A4-31B75731DD95}.amd64ret|Win32.ActiveCfg = Debug|Win32
+ {C5716445-C233-4491-85A4-31B75731DD95}.amd64ret|Win32.Build.0 = Debug|Win32
+ {C5716445-C233-4491-85A4-31B75731DD95}.Debug|Win32.ActiveCfg = Debug|Win32
+ {C5716445-C233-4491-85A4-31B75731DD95}.Debug|Win32.Build.0 = Debug|Win32
+ {C5716445-C233-4491-85A4-31B75731DD95}.Release|Win32.ActiveCfg = Debug|Win32
+ {C5716445-C233-4491-85A4-31B75731DD95}.Release|Win32.Build.0 = Debug|Win32
+ {C5716445-C233-4491-85A4-31B75731DD95}.x86chk|Win32.ActiveCfg = x86chk|Win32
+ {C5716445-C233-4491-85A4-31B75731DD95}.x86chk|Win32.Build.0 = x86chk|Win32
+ {C5716445-C233-4491-85A4-31B75731DD95}.x86dbg|Win32.ActiveCfg = Debug|Win32
+ {C5716445-C233-4491-85A4-31B75731DD95}.x86dbg|Win32.Build.0 = Debug|Win32
+ {C5716445-C233-4491-85A4-31B75731DD95}.x86ret|Win32.ActiveCfg = Debug|Win32
+ {C5716445-C233-4491-85A4-31B75731DD95}.x86ret|Win32.Build.0 = Debug|Win32
+ {95A6AE03-EC45-4450-93DB-9B21890F79E7}.amd64chk|Win32.ActiveCfg = amd64chk|Win32
+ {95A6AE03-EC45-4450-93DB-9B21890F79E7}.amd64chk|Win32.Build.0 = amd64chk|Win32
+ {95A6AE03-EC45-4450-93DB-9B21890F79E7}.amd64dbg|Win32.ActiveCfg = amd64dbg|Win32
+ {95A6AE03-EC45-4450-93DB-9B21890F79E7}.amd64dbg|Win32.Build.0 = amd64dbg|Win32
+ {95A6AE03-EC45-4450-93DB-9B21890F79E7}.amd64ret|Win32.ActiveCfg = amd64ret|Win32
+ {95A6AE03-EC45-4450-93DB-9B21890F79E7}.amd64ret|Win32.Build.0 = amd64ret|Win32
+ {95A6AE03-EC45-4450-93DB-9B21890F79E7}.Debug|Win32.ActiveCfg = x86chk|Win32
+ {95A6AE03-EC45-4450-93DB-9B21890F79E7}.Debug|Win32.Build.0 = x86chk|Win32
+ {95A6AE03-EC45-4450-93DB-9B21890F79E7}.Release|Win32.ActiveCfg = amd64ret|Win32
+ {95A6AE03-EC45-4450-93DB-9B21890F79E7}.Release|Win32.Build.0 = amd64ret|Win32
+ {95A6AE03-EC45-4450-93DB-9B21890F79E7}.x86chk|Win32.ActiveCfg = x86chk|Win32
+ {95A6AE03-EC45-4450-93DB-9B21890F79E7}.x86chk|Win32.Build.0 = x86chk|Win32
+ {95A6AE03-EC45-4450-93DB-9B21890F79E7}.x86dbg|Win32.ActiveCfg = x86dbg|Win32
+ {95A6AE03-EC45-4450-93DB-9B21890F79E7}.x86dbg|Win32.Build.0 = x86dbg|Win32
+ {95A6AE03-EC45-4450-93DB-9B21890F79E7}.x86ret|Win32.ActiveCfg = x86ret|Win32
+ {95A6AE03-EC45-4450-93DB-9B21890F79E7}.x86ret|Win32.Build.0 = x86ret|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/src/ToolBox/SOS/Strike/SOS.vcproj b/src/ToolBox/SOS/Strike/SOS.vcproj
new file mode 100644
index 0000000000..aff5e7cc60
--- /dev/null
+++ b/src/ToolBox/SOS/Strike/SOS.vcproj
@@ -0,0 +1,303 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="9.00"
+ Name="SOS"
+ ProjectGUID="{3941DEDB-8183-4F82-9193-5EC0D5B6D4A6}"
+ Keyword="MakeFileProj"
+ TargetFrameworkVersion="131072"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="x86chk|Win32"
+ OutputDirectory="$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="0"
+ >
+ <Tool
+ Name="VCNMakeTool"
+ BuildCommandLine="$(ProjectDir)..\..\..\..\bin\runjs buildSOS /quickBuild=$(ProjectPath)/buildArch:x86/buildType:chk"
+ ReBuildCommandLine="$(ProjectDir)..\..\..\..\bin\runjs buildSOS /buildArgs:-c/buildArch:x86/buildType:chk"
+ CleanCommandLine=""
+ Output="$(ProjectDir)\obj2c\i386\SOS.dll"
+ PreprocessorDefinitions="_X86_=1;i386=1"
+ IncludeSearchPath="inc\"
+ ForcedIncludes=""
+ AssemblySearchPath=""
+ ForcedUsingAssemblies=""
+ CompileAsManaged=""
+ />
+ </Configuration>
+ <Configuration
+ Name="x86dbg|Win32"
+ OutputDirectory="$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="0"
+ >
+ <Tool
+ Name="VCNMakeTool"
+ BuildCommandLine="$(ProjectDir)..\..\..\..\bin\runjs buildSOS /quickBuild=$(ProjectPath)/buildArch:x86/buildType:dbg"
+ ReBuildCommandLine="$(ProjectDir)..\..\..\..\bin\runjs buildSOS /buildArgs:-c/buildArch:x86/buildType:dbg"
+ CleanCommandLine=""
+ Output="$(ProjectDir)\obj2d\i386\SOS.dll"
+ PreprocessorDefinitions="_X86_=1;i386=1"
+ IncludeSearchPath="inc\"
+ ForcedIncludes=""
+ AssemblySearchPath=""
+ ForcedUsingAssemblies=""
+ CompileAsManaged=""
+ />
+ </Configuration>
+ <Configuration
+ Name="x86ret|Win32"
+ OutputDirectory="$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="0"
+ >
+ <Tool
+ Name="VCNMakeTool"
+ BuildCommandLine="$(ProjectDir)..\..\..\..\bin\runjs buildSOS /quickBuild=$(ProjectPath)/buildArch:x86/buildType:ret"
+ ReBuildCommandLine="$(ProjectDir)..\..\..\..\bin\runjs buildSOS /buildArgs:-c/buildArch:x86/buildType:ret"
+ CleanCommandLine=""
+ Output="$(ProjectDir)\obj2r\i386\SOS.dll"
+ PreprocessorDefinitions="_X86_=1;i386=1"
+ IncludeSearchPath="inc\"
+ ForcedIncludes=""
+ AssemblySearchPath=""
+ ForcedUsingAssemblies=""
+ CompileAsManaged=""
+ />
+ </Configuration>
+ <Configuration
+ Name="amd64chk|Win32"
+ OutputDirectory="$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="0"
+ >
+ <Tool
+ Name="VCNMakeTool"
+ BuildCommandLine="$(ProjectDir)..\..\..\..\bin\runjs buildSOS /quickBuild=$(ProjectPath)/buildArch:amd64/buildType:chk"
+ ReBuildCommandLine="$(ProjectDir)..\..\..\..\bin\runjs buildSOS /buildArgs:-c/buildArch:amd64/buildType:chk"
+ CleanCommandLine=""
+ Output="$(ProjectDir)\obj2c\amd64\SOS.dll"
+ PreprocessorDefinitions="_AMD64_=1;_WIN64=1;_DEBUG=1"
+ IncludeSearchPath="inc\"
+ ForcedIncludes=""
+ AssemblySearchPath=""
+ ForcedUsingAssemblies=""
+ CompileAsManaged=""
+ />
+ </Configuration>
+ <Configuration
+ Name="amd64dbg|Win32"
+ OutputDirectory="$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="0"
+ >
+ <Tool
+ Name="VCNMakeTool"
+ BuildCommandLine="$(ProjectDir)..\..\..\..\bin\runjs buildSOS /quickBuild=$(ProjectPath)/buildArch:amd64/buildType:dbg"
+ ReBuildCommandLine="$(ProjectDir)..\..\..\..\bin\runjs buildSOS /buildArgs:-c/buildArch:amd64/buildType:dbg"
+ CleanCommandLine=""
+ Output="$(ProjectDir)\obj2d\amd64\SOS.dll"
+ PreprocessorDefinitions="_AMD64_=1;_WIN64=1"
+ IncludeSearchPath="inc\"
+ ForcedIncludes=""
+ AssemblySearchPath=""
+ ForcedUsingAssemblies=""
+ CompileAsManaged=""
+ />
+ </Configuration>
+ <Configuration
+ Name="amd64ret|Win32"
+ OutputDirectory="$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="0"
+ >
+ <Tool
+ Name="VCNMakeTool"
+ BuildCommandLine="$(ProjectDir)..\..\..\..\bin\runjs buildSOS /quickBuild=$(ProjectPath)/buildArch:amd64/buildType:ret"
+ ReBuildCommandLine="$(ProjectDir)..\..\..\..\bin\runjs buildSOS /buildArgs:-c/buildArch:amd64/buildType:ret"
+ CleanCommandLine=""
+ Output="$(ProjectDir)\obj2r\amd64\SOS.dll"
+ PreprocessorDefinitions="_AMD64_=1;_WIN64=1"
+ IncludeSearchPath="inc\"
+ ForcedIncludes=""
+ AssemblySearchPath=""
+ ForcedUsingAssemblies=""
+ CompileAsManaged=""
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="Header Files"
+ Filter="h;hpp;hxx;hm;inl;inc;xsd"
+ UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
+ >
+ <File
+ RelativePath=".\data.h"
+ >
+ </File>
+ <File
+ RelativePath=".\inc\dbgeng.h"
+ >
+ </File>
+ <File
+ RelativePath=".\inc\dbghelp.h"
+ >
+ </File>
+ <File
+ RelativePath=".\disasm.h"
+ >
+ </File>
+ <File
+ RelativePath=".\exts.h"
+ >
+ </File>
+ <File
+ RelativePath=".\ntinfo.h"
+ >
+ </File>
+ <File
+ RelativePath=".\sos_md.h"
+ >
+ </File>
+ <File
+ RelativePath=".\sos_stacktrace.h"
+ >
+ </File>
+ <File
+ RelativePath=".\strike.h"
+ >
+ </File>
+ <File
+ RelativePath=".\symbol.h"
+ >
+ </File>
+ <File
+ RelativePath=".\util.h"
+ >
+ </File>
+ <File
+ RelativePath=".\UtilCode.h"
+ >
+ </File>
+ <File
+ RelativePath=".\inc\wdbgexts.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="Resource Files"
+ Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx"
+ UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"
+ >
+ <File
+ RelativePath=".\Native.rc"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="Source Files"
+ Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
+ UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
+ >
+ <File
+ RelativePath=".\disasm.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\disasmIA64.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\disasmX86.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\dllsext.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\eeheap.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\exts.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\gchist.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\gcroot.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\metadata.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\sildasm.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\sos.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\sos.def"
+ >
+ </File>
+ <File
+ RelativePath=".\sos.h"
+ >
+ </File>
+ <File
+ RelativePath=".\stressLogDump.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\strike.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\util.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\utilIA64.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\utilX86.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\vm.cpp"
+ >
+ </File>
+ </Filter>
+ <File
+ RelativePath=".\sosdocs.txt"
+ >
+ </File>
+ <File
+ RelativePath=".\sources"
+ >
+ </File>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/src/ToolBox/SOS/Strike/UtilCode.h b/src/ToolBox/SOS/Strike/UtilCode.h
new file mode 100644
index 0000000000..a002edc89e
--- /dev/null
+++ b/src/ToolBox/SOS/Strike/UtilCode.h
@@ -0,0 +1,11 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// ==++==
+//
+
+//
+// ==--==
+// An empty file so that gcdump.cpp does not include the one from other
+// places.
diff --git a/src/ToolBox/SOS/Strike/WatchCmd.cpp b/src/ToolBox/SOS/Strike/WatchCmd.cpp
new file mode 100644
index 0000000000..443f1dd6ef
--- /dev/null
+++ b/src/ToolBox/SOS/Strike/WatchCmd.cpp
@@ -0,0 +1,331 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#include "WatchCmd.h"
+
+#ifndef IfFailRet
+#define IfFailRet(EXPR) do { Status = (EXPR); if(FAILED(Status)) { return (Status); } } while (0)
+#endif
+
+_PersistList::~_PersistList()
+{
+ PersistWatchExpression* pCur = pHeadExpr;
+ while(pCur != NULL)
+ {
+ PersistWatchExpression* toDelete = pCur;
+ pCur = pCur->pNext;
+ delete toDelete;
+ }
+}
+
+WatchCmd::WatchCmd() :
+pExpressionListHead(NULL)
+{ }
+WatchCmd::~WatchCmd()
+{
+ Clear();
+ PersistList* pCur = pPersistListHead;
+ while(pCur != NULL)
+ {
+ PersistList* toDelete = pCur;
+ pCur = pCur->pNext;
+ delete toDelete;
+ }
+}
+
+// Deletes all current watch expressions from the watch list
+// (does not delete persisted watch lists though)
+HRESULT WatchCmd::Clear()
+{
+ WatchExpression* pCurrent = pExpressionListHead;
+ while(pCurrent != NULL)
+ {
+ WatchExpression* toDelete = pCurrent;
+ pCurrent = pCurrent->pNext;
+ delete toDelete;
+ }
+ pExpressionListHead = NULL;
+ return S_OK;
+}
+
+// Adds a new expression to the active watch list
+HRESULT WatchCmd::Add(__in_z WCHAR* pExpression)
+{
+ WatchExpression* pExpr = new WatchExpression;
+ if(pExpr == NULL)
+ return E_OUTOFMEMORY;
+ wcsncpy_s(pExpr->pExpression, MAX_EXPRESSION, pExpression, _TRUNCATE);
+ pExpr->pNext = NULL;
+
+ WatchExpression** ppCurrent = &pExpressionListHead;
+ while(*ppCurrent != NULL)
+ ppCurrent = &((*ppCurrent)->pNext);
+ *ppCurrent = pExpr;
+ return S_OK;
+}
+
+// removes an expression at the given index in the active watch list
+HRESULT WatchCmd::Remove(int index)
+{
+ HRESULT Status = S_FALSE;
+ WatchExpression** ppCurrent = &pExpressionListHead;
+ for(int i=1; *ppCurrent != NULL; i++)
+ {
+ if(i == index)
+ {
+ WatchExpression* toDelete = *ppCurrent;
+ *ppCurrent = (*ppCurrent)->pNext;
+ delete toDelete;
+ Status = S_OK;
+ break;
+ }
+ ppCurrent = &((*ppCurrent)->pNext);
+
+ }
+ return Status;
+}
+
+// Evaluates and prints a tree version of the active watch list
+// The tree will be expanded along the nodes in expansionPath
+// Optionally the list is filtered to only show differences from pFilterName (the name of a persisted watch list)
+HRESULT WatchCmd::Print(int expansionIndex, __in_z WCHAR* expansionPath, __in_z WCHAR* pFilterName)
+{
+ HRESULT Status = S_OK;
+ INIT_API_EE();
+ INIT_API_DAC();
+ EnableDMLHolder dmlHolder(TRUE);
+ IfFailRet(InitCorDebugInterface());
+
+ PersistList* pFilterList = NULL;
+ if(pFilterName != NULL)
+ {
+ pFilterList = pPersistListHead;
+ while(pFilterList != NULL)
+ {
+ if(_wcscmp(pFilterList->pName, pFilterName)==0)
+ break;
+ pFilterList = pFilterList->pNext;
+ }
+ }
+
+ PersistWatchExpression* pHeadFilterExpr = (pFilterList != NULL) ? pFilterList->pHeadExpr : NULL;
+
+ WatchExpression* pExpression = pExpressionListHead;
+ int index = 1;
+ while(pExpression != NULL)
+ {
+ ExpressionNode* pResult = NULL;
+ if(FAILED(Status = ExpressionNode::CreateExpressionNode(pExpression->pExpression, &pResult)))
+ {
+ ExtOut(" %d) Error: HRESULT 0x%x while evaluating expression \'%S\'", index, Status, pExpression->pExpression);
+ }
+ else
+ {
+ //check for matching absolute expression
+ PersistWatchExpression* pCurFilterExpr = pHeadFilterExpr;
+ while(pCurFilterExpr != NULL)
+ {
+ if(_wcscmp(pCurFilterExpr->pExpression, pResult->GetAbsoluteExpression())==0)
+ break;
+ pCurFilterExpr = pCurFilterExpr->pNext;
+ }
+
+ // check for matching persist evaluation on the matching expression
+ BOOL print = TRUE;
+ if(pCurFilterExpr != NULL)
+ {
+ WCHAR pCurPersistResult[MAX_EXPRESSION];
+ FormatPersistResult(pCurPersistResult, MAX_EXPRESSION, pResult);
+ if(_wcscmp(pCurPersistResult, pCurFilterExpr->pPersistResult)==0)
+ {
+ print = FALSE;
+ }
+ }
+
+ //expand and print
+ if(print)
+ {
+ if(index == expansionIndex)
+ pResult->Expand(expansionPath);
+ PrintCallbackData data;
+ data.index = index;
+ WCHAR pCommand[MAX_EXPRESSION];
+ swprintf_s(pCommand, MAX_EXPRESSION, L"!watch -expand %d", index);
+ data.pCommand = pCommand;
+ pResult->DFSVisit(EvalPrintCallback, (VOID*)&data);
+ }
+ delete pResult;
+ }
+ pExpression = pExpression->pNext;
+ index++;
+ }
+ return Status;
+}
+
+// Deletes an persisted watch list by name
+HRESULT WatchCmd::RemoveList(__in_z WCHAR* pListName)
+{
+ PersistList** ppList = &pPersistListHead;
+ while(*ppList != NULL)
+ {
+ if(_wcscmp((*ppList)->pName, pListName) == 0)
+ {
+ PersistList* toDelete = *ppList;
+ *ppList = (*ppList)->pNext;
+ delete toDelete;
+ return S_OK;
+ }
+ ppList = &((*ppList)->pNext);
+ }
+ return S_FALSE;
+}
+
+// Renames a previously saved persisted watch list
+HRESULT WatchCmd::RenameList(__in_z WCHAR* pOldName, __in_z WCHAR* pNewName)
+{
+ if(_wcscmp(pOldName, pNewName)==0)
+ return S_OK;
+ PersistList** ppList = &pPersistListHead;
+ while(*ppList != NULL)
+ {
+ if(_wcscmp((*ppList)->pName, pOldName) == 0)
+ {
+ PersistList* pListToChangeName = *ppList;
+ RemoveList(pNewName);
+ wcsncpy_s(pListToChangeName->pName, MAX_EXPRESSION, pNewName, _TRUNCATE);
+ return S_OK;
+ }
+ ppList = &((*ppList)->pNext);
+ }
+ return S_FALSE;
+}
+
+// Saves the active watch list together with the current evaluations as
+// a new persisted watch list
+HRESULT WatchCmd::SaveList(__in_z WCHAR* pSaveName)
+{
+ HRESULT Status = S_OK;
+ INIT_API_EE();
+ INIT_API_DAC();
+ IfFailRet(InitCorDebugInterface());
+
+ RemoveList(pSaveName);
+ PersistList* pList = new PersistList();
+ wcsncpy_s(pList->pName, MAX_EXPRESSION, pSaveName, _TRUNCATE);
+ pList->pHeadExpr = NULL;
+ PersistCallbackData data;
+ data.ppNext = &(pList->pHeadExpr);
+ WatchExpression* pExpression = pExpressionListHead;
+ while(pExpression != NULL)
+ {
+ ExpressionNode* pResult = NULL;
+ if(SUCCEEDED(Status = ExpressionNode::CreateExpressionNode(pExpression->pExpression, &pResult)))
+ {
+ pResult->DFSVisit(PersistCallback, (VOID*)&data);
+ delete pResult;
+ }
+ pExpression = pExpression->pNext;
+ }
+
+ pList->pNext = pPersistListHead;
+ pPersistListHead = pList;
+ return Status;
+}
+
+// Saves the current watch list to file as a sequence of commands that will
+// recreate the list
+HRESULT WatchCmd::SaveListToFile(FILE* pFile)
+{
+ WatchExpression* pExpression = pExpressionListHead;
+ while(pExpression != NULL)
+ {
+ fprintf_s(pFile, "!watch -a %S\n", pExpression->pExpression);
+ pExpression = pExpression->pNext;
+ }
+ return S_OK;
+}
+
+// Escapes characters that would be interpretted as DML markup, namely angle brackets
+// that often appear in generic type names
+VOID WatchCmd::DmlEscape(__in_ecount(cchInput) WCHAR* pInput, int cchInput, __in_ecount(cchOutput) WCHAR* pEscapedOutput, int cchOutput)
+{
+ pEscapedOutput[0] = L'\0';
+ for(int i = 0; i < cchInput; i++)
+ {
+ if(pInput[i] == L'<')
+ {
+ if(0 != wcscat_s(pEscapedOutput, cchOutput, L"&lt;")) return;
+ pEscapedOutput += 4;
+ cchOutput -= 4;
+ }
+ else if(pInput[i] == L'>')
+ {
+ if(0 != wcscat_s(pEscapedOutput, cchOutput, L"&gt;")) return;
+ pEscapedOutput += 4;
+ cchOutput -= 4;
+ }
+ else if(cchOutput > 1)
+ {
+ pEscapedOutput[0] = pInput[i];
+ pEscapedOutput[1] = '\0';
+ pEscapedOutput++;
+ cchOutput--;
+ }
+ if(pInput[i] == L'\0' || cchOutput == 1) break;
+ }
+}
+
+// A DFS traversal callback for the expression node tree that prints it
+VOID WatchCmd::EvalPrintCallback(ExpressionNode* pExpressionNode, int depth, VOID* pUserData)
+{
+ PrintCallbackData* pData = (PrintCallbackData*)pUserData;
+ for(int i = 0; i < depth; i++) ExtOut(" ");
+ if(depth == 0)
+ ExtOut(" %d) ", pData->index);
+ else
+ ExtOut(" |- ");
+ if(pExpressionNode->GetErrorMessage()[0] != 0)
+ {
+ ExtOut("%S (%S)\n", pExpressionNode->GetRelativeExpression(), pExpressionNode->GetErrorMessage());
+ }
+ else
+ {
+ // names can have '<' and '>' in them, need to escape
+ WCHAR pEscapedTypeName[MAX_EXPRESSION];
+ DmlEscape(pExpressionNode->GetTypeName(), (int)_wcslen(pExpressionNode->GetTypeName()), pEscapedTypeName, MAX_EXPRESSION);
+ WCHAR pRelativeExpression[MAX_EXPRESSION];
+ DmlEscape(pExpressionNode->GetRelativeExpression(), (int)_wcslen(pExpressionNode->GetRelativeExpression()), pRelativeExpression, MAX_EXPRESSION);
+ DMLOut("%S <exec cmd=\"%S (%S)%S\">%S</exec> %S\n", pEscapedTypeName, pData->pCommand, pEscapedTypeName, pExpressionNode->GetAbsoluteExpression(), pRelativeExpression, pExpressionNode->GetTextValue());
+ }
+}
+
+// A DFS traversal callback for the expression node tree that saves all the values into a new
+// persisted watch list
+VOID WatchCmd::PersistCallback(ExpressionNode* pExpressionNode, int depth, VOID* pUserData)
+{
+ PersistCallbackData* pData = (PersistCallbackData*)pUserData;
+ if(depth != 0)
+ return;
+
+ PersistWatchExpression* pPersistExpr = new PersistWatchExpression();
+ wcsncpy_s(pPersistExpr->pExpression, MAX_EXPRESSION, pExpressionNode->GetAbsoluteExpression(), _TRUNCATE);
+ FormatPersistResult(pPersistExpr->pPersistResult, MAX_EXPRESSION, pExpressionNode);
+ pPersistExpr->pNext = NULL;
+ *(pData->ppNext) = pPersistExpr;
+ pData->ppNext = &(pPersistExpr->pNext);
+}
+
+// Determines how the value of an expression node is saved as a persisted result. This effectively determines
+// the definition of equality when determining if an expression has changed value
+VOID WatchCmd::FormatPersistResult(__inout_ecount(cchPersistResult) WCHAR* pPersistResult, DWORD cchPersistResult, ExpressionNode* pExpressionNode)
+{
+ if(pExpressionNode->GetErrorMessage()[0] != 0)
+ {
+ _snwprintf_s(pPersistResult, MAX_EXPRESSION, _TRUNCATE, L"%s (%s)\n", pExpressionNode->GetRelativeExpression(), pExpressionNode->GetErrorMessage());
+ }
+ else
+ {
+ _snwprintf_s(pPersistResult, MAX_EXPRESSION, _TRUNCATE, L"%s %s %s\n", pExpressionNode->GetTypeName(), pExpressionNode->GetRelativeExpression(), pExpressionNode->GetTextValue());
+ }
+}
diff --git a/src/ToolBox/SOS/Strike/WatchCmd.h b/src/ToolBox/SOS/Strike/WatchCmd.h
new file mode 100644
index 0000000000..a34e391b79
--- /dev/null
+++ b/src/ToolBox/SOS/Strike/WatchCmd.h
@@ -0,0 +1,110 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#ifndef _WATCH_CMD_
+#define _WATCH_CMD_
+
+#ifdef FEATURE_PAL
+#error This file not designed for use with FEATURE_PAL
+#endif
+
+#include "ExpressionNode.h"
+#include "windows.h"
+
+// A linked list node for watch expressions
+typedef struct _WatchExpression
+{
+ WCHAR pExpression[MAX_EXPRESSION];
+ _WatchExpression* pNext;
+
+} WatchExpression;
+
+// A linked list node that stores both the watch expression and a persisted result
+// of the evaluation at some point in the past
+typedef struct _PersistWatchExpression
+{
+ WCHAR pExpression[MAX_EXPRESSION];
+ WCHAR pPersistResult[MAX_EXPRESSION];
+ _PersistWatchExpression* pNext;
+
+} PersistWatchExpression;
+
+// A named list of persisted watch expressions, each of which has an expression and
+// a saved value
+typedef struct _PersistList
+{
+ ~_PersistList();
+ WCHAR pName[MAX_EXPRESSION];
+ PersistWatchExpression* pHeadExpr;
+ _PersistList* pNext;
+} PersistList;
+
+// An API for the functionality in the !watch command
+class WatchCmd
+{
+public:
+ WatchCmd();
+ ~WatchCmd();
+
+ // Deletes all current watch expressions from the watch list
+ // (does not delete persisted watch lists though)
+ HRESULT Clear();
+
+ // Adds a new expression to the active watch list
+ HRESULT Add(__in_z WCHAR* pExpression);
+
+ // removes an expression at the given index in the active watch list
+ HRESULT Remove(int index);
+
+ // Evaluates and prints a tree version of the active watch list
+ // The tree will be expanded along the nodes in expansionPath
+ // Optionally the list is filtered to only show differences from pFilterName (the name of a persisted watch list)
+ HRESULT Print(int expansionIndex, __in_z WCHAR* expansionPath, __in_z WCHAR* pFilterName);
+
+ // Deletes an persisted watch list by name
+ HRESULT RemoveList(__in_z WCHAR* pListName);
+
+ // Renames a previously saved persisted watch list
+ HRESULT RenameList(__in_z WCHAR* pOldName, __in_z WCHAR* pNewName);
+
+ // Saves the active watch list together with the current evaluations as
+ // a new persisted watch list
+ HRESULT SaveList(__in_z WCHAR* pSaveName);
+
+ // Saves the current watch list to file as a sequence of commands that will
+ // recreate the list
+ HRESULT SaveListToFile(FILE* pFile);
+
+private:
+ WatchExpression* pExpressionListHead;
+ PersistList* pPersistListHead;
+
+ // Escapes characters that would be interpretted as DML markup, namely angle brackets
+ // that often appear in generic type names
+ static VOID DmlEscape(__in_z WCHAR* pInput, int cchInput, __inout_ecount(cchOutput) WCHAR* pEscapedOutput, int cchOutput);
+
+ typedef struct _PrintCallbackData
+ {
+ int index;
+ WCHAR* pCommand;
+ } PrintCallbackData;
+
+ // A DFS traversal callback for the expression node tree that prints it
+ static VOID EvalPrintCallback(ExpressionNode* pExpressionNode, int depth, VOID* pUserData);
+
+ typedef struct _PersistCallbackData
+ {
+ PersistWatchExpression** ppNext;
+ } PersistCallbackData;
+
+ // A DFS traversal callback for the expression node tree that saves all the values into a new
+ // persisted watch list
+ static VOID PersistCallback(ExpressionNode* pExpressionNode, int depth, VOID* pUserData);
+
+ // Determines how the value of an expression node is saved as a persisted result. This effectively determines
+ // the definition of equality when determining if an expression has changed value
+ static VOID FormatPersistResult(__inout_ecount(cchPersistResult) WCHAR* pPersistResult, DWORD cchPersistResult, ExpressionNode* pExpressionNode);
+};
+
+#endif
diff --git a/src/ToolBox/SOS/Strike/apollososdocs.txt b/src/ToolBox/SOS/Strike/apollososdocs.txt
new file mode 100644
index 0000000000..71fefcf4d7
--- /dev/null
+++ b/src/ToolBox/SOS/Strike/apollososdocs.txt
@@ -0,0 +1,2727 @@
+-------------------------------------------------------------------------------
+NOTE: THIS FILE CONTAINS SOS DOCUMENTATION. THE FORMAT OF THE FILE IS:
+
+<optional comments>
+COMMAND: <cmd name, all lower case>
+<descriptive text of the command>
+\\ <these are two backslashes, immediately followed by a newline>
+
+<repeat the sequence above>
+
+The first command is "contents" which is the general help screen. The rest
+correspond to SOS command names. This file is embedded as a resource in the SOS
+binary. Be sure to list any new commands here.
+-------------------------------------------------------------------------------
+
+
+
+COMMAND: contents.
+SOS is a debugger extension DLL designed to aid in the debugging of managed
+programs. Functions are listed by category, then roughly in order of
+importance. Shortcut names for popular functions are listed in parenthesis.
+Type "!help <functionname>" for detailed info on that function.
+
+Object Inspection Examining code and stacks
+----------------------------- -----------------------------
+DumpObj (do) Threads
+DumpArray (da) ThreadState
+DumpStackObjects (dso) IP2MD
+DumpHeap U
+DumpVC DumpStack
+GCRoot EEStack
+ObjSize CLRStack
+FinalizeQueue GCInfo
+PrintException (pe) EHInfo
+TraverseHeap BPMD
+Watch COMState
+ StopOnCatch
+ SuppressJitOptimization
+
+Examining CLR data structures Diagnostic Utilities
+----------------------------- -----------------------------
+DumpDomain VerifyHeap
+EEHeap VerifyObj
+Name2EE FindRoots
+SyncBlk HeapStat
+DumpMT GCWhere
+DumpClass ListNearObj (lno)
+DumpMD GCHandles
+Token2EE GCHandleLeaks
+EEVersion FinalizeQueue (fq)
+DumpModule FindAppDomain
+ThreadPool SaveModule
+DumpAssembly ProcInfo
+DumpSigElem StopOnException (soe)
+DumpRuntimeTypes DumpLog
+DumpSig VMMap
+RCWCleanupList VMStat
+DumpIL MinidumpMode
+DumpRCW AnalyzeOOM (ao)
+DumpCCW
+
+Examining the GC history Other
+----------------------------- -----------------------------
+HistInit FAQ
+HistRoot SaveState
+HistObj
+HistObjFind
+HistClear
+\\
+
+COMMAND: faq.
+>> Where can I get the right version of SOS for my build?
+
+If you are running version 1.1 or 2.0 of the CLR, SOS.DLL is installed in the
+same directory as the main CLR dll (CLR.DLL). Newer versions of the
+Windows Debugger provide a command to make it easy to load the right copy of
+SOS.DLL:
+
+ ".loadby sos clr"
+
+That will load the SOS extension DLL from the same place that CLR.DLL is
+loaded in the process. You shouldn't attempt to use a version of SOS.DLL that
+doesn't match the version of CLR.DLL. You can find the version of
+CLR.DLL by running
+
+ "lmvm clr"
+
+in the debugger. Note that if you are running CoreCLR (e.g. Silverlight)
+then you should replace "clr" with "coreclr".
+
+If you are using a dump file created on another machine, it is a little bit
+more complex. You need to make sure the mscordacwks.dll file that came with
+that install is on your symbol path, and you need to load the corresponding
+version of sos.dll (typing .load <full path to sos.dll> rather than using the
+.loadby shortcut). Within the Microsoft corpnet, we keep tagged versions
+of mscordacwks.dll, with names like mscordacwks_<architecture>_<version>.dll
+that the Windows Debugger can load. If you have the correct symbol path to the
+binaries for that version of the Runtime, the Windows Debugger will load the
+correct mscordacwks.dll file.
+
+>> I have a chicken and egg problem. I want to use SOS commands, but the CLR
+ isn't loaded yet. What can I do?
+
+In the debugger at startup you can type:
+
+ "sxe clrn"
+
+Let the program run, and it will stop with the notice
+
+ "CLR notification: module 'mscorlib' loaded"
+
+At this time you can use SOS commands. To turn off spurious notifications,
+type:
+
+ "sxd clrn"
+
+>> I got the following error message. Now what?
+
+ 0:000> .loadby sos clr
+ 0:000> !DumpStackObjects
+ Failed to find runtime DLL (clr.dll), 0x80004005
+ Extension commands need clr.dll in order to have something to do.
+ 0:000>
+
+This means that the CLR is not loaded yet, or has been unloaded. You need to
+wait until your managed program is running in order to use these commands. If
+you have just started the program a good way to do this is to type
+
+ bp clr!EEStartup "g @$ra"
+
+in the debugger, and let it run. After the function EEStartup is finished,
+there will be a minimal managed environment for executing SOS commands.
+
+>> I have a partial memory minidump, and !DumpObj doesn't work. Why?
+
+In order to run SOS commands, many CLR data structures need to be traversed.
+When creating a minidump without full memory, special functions are called at
+dump creation time to bring those structures into the minidump, and allow a
+minimum set of SOS debugging commands to work. At this time, those commands
+that can provide full or partial output are:
+
+CLRStack
+Threads
+Help
+PrintException
+EEVersion
+
+For a minidump created with this minimal set of functionality in mind, you
+will get an error message when running any other commands. A full memory dump
+(obtained with ".dump /ma <filename>" in the Windows Debugger) is often the
+best way to debug a managed program at this level.
+
+>> What other tools can I use to find my bug?
+
+Turn on Managed Debugging Assistants. These enable additional runtime diagnostics,
+particularly in the area of PInvoke/Interop. Adam Nathan has written some great
+information about that:
+
+http://blogs.msdn.com/adam_nathan/
+
+>> Does SOS support DML?
+
+Yes. SOS respects the .prefer_dml option in the debugger. If this setting is
+turned on, then SOS will output DML by default. Alternatively, you may leave
+it off and add /D to the beginning of a command to get DML based output for it.
+Not all SOS commands support DML output.
+
+\\
+
+COMMAND: stoponexception.
+!StopOnException [-derived]
+ [-create | -create2]
+ <Exception>
+ [<Pseudo-register number>]
+
+!StopOnException helps when you want the Windows Debugger to stop on a
+particular managed exception, say a System.OutOfMemoryException, but continue
+running if other exceptions are thrown. The command can be used in two ways:
+
+1) When you just want to stop on one particular CLR exception
+
+ At the debugger prompt, anytime after loading SOS, type:
+
+ !StopOnException -create System.OutOfMemoryException 1
+
+ The pseudo-register number (1) indicates that SOS can use register $t1 for
+ maintaining the breakpoint. The -create parameter allows SOS to go ahead
+ and set up the breakpoint as a first-chance exception. -create2 would set
+ it up as a 2nd-chance exception.
+
+2) When you need more complex logic for stopping on a CLR exception
+
+ !StopOnException can be used purely as a predicate in a larger expression.
+ If you type:
+
+ !StopOnException System.OutOfMemoryException 3
+
+ then register $t3 will be set to 1 if the last thrown exception on the
+ current thread is a System.OutOfMemoryException. Otherwise, $t3 will be set
+ to 0. Using the Windows Debugger scripting language, you could chain
+ such calls together to stop on various exception types. You'll have to
+ manually create such predicates, for example:
+
+ sxe -c "!soe System.OutOfMemoryException 3;
+ !soe -derived System.IOException 4;
+ .if(@$t3==1 || @$t4==1) { .echo 'stop' } .else {g}"
+
+The -derived option will cause StopOnException to set the pseudo-register to
+1 even if the thrown exception type doesn't exactly match the exception type
+given, but merely derives from it. So, "-derived System.Exception" would catch
+every exception in the System.Exception heirarchy.
+
+The pseudo-register number is optional. If you don't pass a number, SOS will
+use pseudo-register $t1.
+
+Note that !PrintException with no parameters will print out the last thrown
+exception on the current thread (if any). You can use !soe as a shortcut for
+!StopOnException.
+\\
+
+COMMAND: minidumpmode.
+!MinidumpMode <0 or 1>
+
+Minidumps created with ".dump /m" or ".dump" have a very small set of
+CLR-specific data, just enough to run a subset of SOS commands correctly. You
+are able to run other SOS commands, but they may fail with unexpected errors
+because required areas of memory are not mapped in or only partially mapped
+in. At this time, SOS cannot reliably detect if a dump file is of this type
+(for one thing, custom dump commands can map in additional memory, but there
+is no facility to read meta-information about this memory). You can turn this
+option on to protect against running unsafe commands against small minidumps.
+
+By default, MinidumpMode is 0, so there is no restriction on commands that will
+run against a minidump.
+\\
+
+COMMAND: dumpobj.
+!DumpObj [-nofields] <object address>
+
+This command allows you to examine the fields of an object, as well as learn
+important properties of the object such as the EEClass, the MethodTable, and
+the size.
+
+You might find an object pointer by running !DumpStackObjects and choosing
+from the resultant list. Here is a simple object:
+
+ 0:000> !DumpObj a79d40
+ Name: Customer
+ MethodTable: 009038ec
+ EEClass: 03ee1b84
+ Size: 20(0x14) bytes
+ (C:\pub\unittest.exe)
+ Fields:
+ MT Field Offset Type VT Attr Value Name
+ 009038ec 4000008 4 Customer 0 instance 00a79ce4 name
+ 009038ec 4000009 8 Bank 0 instance 00a79d2c bank
+
+Note that fields of type Customer and Bank are themselves objects, and you can
+run !DumpObj on them too. You could look at the field directly in memory using
+the offset given. "dd a79d40+8 l1" would allow you to look at the bank field
+directly. Be careful about using this to set memory breakpoints, since objects
+can move around in the garbage collected heap.
+
+What else can you do with an object? You might run !GCRoot, to determine what
+roots are keeping it alive. Or you can find all objects of that type with
+"!DumpHeap -type Customer".
+
+The column VT contains the value 1 if the field is a valuetype structure, and
+0 if the field contains a pointer to another object. For valuetypes, you can
+take the MethodTable pointer in the MT column, and the Value and pass them to
+the command !DumpVC.
+
+The abbreviation !do can be used for brevity.
+
+The arguments in detail:
+-nofields: do not print fields of the object, useful for objects like
+ String
+\\
+
+COMMAND: dumparray.
+!DumpArray
+ [-start <startIndex>]
+ [-length <length>]
+ [-details]
+ [-nofields]
+ <array object address>
+
+This command allows you to examine elements of an array object.
+The arguments in detail:
+ -start <startIndex>: optional, only supported for single dimension array.
+ Specify from which index the command shows the elements.
+ -length <length>: optional, only supported for single dimension array.
+ Specify how many elements to show.
+ -details: optional. Ask the command to print out details
+ of the element using !DumpObj and !DumpVC format.
+ -nofields: optional, only takes effect when -details is used. Do
+ not print fields of the elements. Useful for arrays of
+ objects like String
+
+ Example output:
+
+ 0:000> !dumparray -start 2 -length 3 -details 00ad28d0
+ Name: Value[]
+ MethodTable: 03e41044
+ EEClass: 03e40fc0
+ Size: 132(0x84) bytes
+ Array: Rank 1, Number of elements 10, Type VALUETYPE
+ Element Type: Value
+ [2] 00ad28f0
+ Name: Value
+ MethodTable 03e40f4c
+ EEClass: 03ef1698
+ Size: 20(0x14) bytes
+ (C:\bugs\225271\arraytest.exe)
+ Fields:
+ MT Field Offset Type Attr Value Name
+ 5b9a628c 4000001 0 System.Int32 instance 2 x
+ 5b9a628c 4000002 4 System.Int32 instance 4 y
+ 5b9a628c 4000003 8 System.Int32 instance 6 z
+ [3] 00ad28fc
+ Name: Value
+ MethodTable 03e40f4c
+ EEClass: 03ef1698
+ Size: 20(0x14) bytes
+ (C:\bugs\225271\arraytest.exe)
+ Fields:
+ MT Field Offset Type Attr Value Name
+ 5b9a628c 4000001 0 System.Int32 instance 3 x
+ 5b9a628c 4000002 4 System.Int32 instance 6 y
+ 5b9a628c 4000003 8 System.Int32 instance 9 z
+ [4] 00ad2908
+ Name: Value
+ MethodTable 03e40f4c
+ EEClass: 03ef1698
+ Size: 20(0x14) bytes
+ (C:\bugs\225271\arraytest.exe)
+ Fields:
+ MT Field Offset Type Attr Value Name
+ 5b9a628c 4000001 0 System.Int32 instance 4 x
+ 5b9a628c 4000002 4 System.Int32 instance 8 y
+ 5b9a628c 4000003 8 System.Int32 instance 12 z
+
+
+\\
+
+COMMAND: dumpstackobjects.
+!DumpStackObjects [-verify] [top stack [bottom stack]]
+
+This command will display any managed objects it finds within the bounds of
+the current stack. Combined with the stack tracing commands like K and
+!CLRStack, it is a good aid to determining the values of locals and
+parameters.
+
+If you use the -verify option, each non-static CLASS field of an object
+candidate is validated. This helps to eliminate false positives. It is not
+on by default because very often in a debugging scenario, you are
+interested in objects with invalid fields.
+
+The abbreviation !dso can be used for brevity.
+\\
+
+COMMAND: dumpheap.
+!DumpHeap [-stat]
+ [-strings]
+ [-short]
+ [-min <size>]
+ [-max <size>]
+ [-thinlock]
+ [-startAtLowerBound]
+ [-mt <MethodTable address>]
+ [-type <partial type name>]
+ [start [end]]
+
+!DumpHeap is a powerful command that traverses the garbage collected heap,
+collection statistics about objects. With it's various options, it can look for
+particular types, restrict to a range, or look for ThinLocks (see !SyncBlk
+documentation). Finally, it will provide a warning if it detects excessive
+fragmentation in the GC heap.
+
+When called without options, the output is first a list of objects in the heap,
+followed by a report listing all the types found, their size and number:
+
+ 0:000> !dumpheap
+ Address MT Size
+ 00a71000 0015cde8 12 Free
+ 00a7100c 0015cde8 12 Free
+ 00a71018 0015cde8 12 Free
+ 00a71024 5ba58328 68
+ 00a71068 5ba58380 68
+ 00a710ac 5ba58430 68
+ 00a710f0 5ba5dba4 68
+ ...
+ total 619 objects
+ Statistics:
+ MT Count TotalSize Class Name
+ 5ba7607c 1 12 System.Security.Permissions.HostProtectionResource
+ 5ba75d54 1 12 System.Security.Permissions.SecurityPermissionFlag
+ 5ba61f18 1 12 System.Collections.CaseInsensitiveComparer
+ ...
+ 0015cde8 6 10260 Free
+ 5ba57bf8 318 18136 System.String
+ ...
+
+"Free" objects are simply regions of space the garbage collector can use later.
+If 30% or more of the heap contains "Free" objects, the process may suffer from
+heap fragmentation. This is usually caused by pinning objects for a long time
+combined with a high rate of allocation. Here is example output where !DumpHeap
+provides a warning about fragmentation:
+
+ <After the Statistics section>
+ Fragmented blocks larger than 1MB:
+ Addr Size Followed by
+ 00a780c0 1.5MB 00bec800 System.Byte[]
+ 00da4e38 1.2MB 00ed2c00 System.Byte[]
+ 00f16df0 1.2MB 01044338 System.Byte[]
+
+The arguments in detail:
+
+-stat Restrict the output to the statistical type summary
+-strings Restrict the output to a statistical string value summary
+-short Limits output to just the address of each object. This allows you
+ to easily pipe output from the command to another debugger
+ command for automation.
+-min Ignore objects less than the size given in bytes
+-max Ignore objects larger than the size given in bytes
+-thinlock Report on any ThinLocks (an efficient locking scheme, see !SyncBlk
+ documentation for more info)
+-startAtLowerBound
+ Force heap walk to begin at lower bound of a supplied address range.
+ (During plan phase, the heap is often not walkable because objects
+ are being moved. In this case, DumpHeap may report spurious errors,
+ in particular bad objects. It may be possible to traverse more of
+ the heap after the reported bad object. Even if you specify an
+ address range, !DumpHeap will start its walk from the beginning of
+ the heap by default. If it finds a bad object before the specified
+ range, it will stop before displaying the part of the heap in which
+ you are interested. This switch will force !DumpHeap to begin its
+ walk at the specified lower bound. You must supply the address of a
+ good object as the lower bound for this to work. Display memory at
+ the address of the bad object to manually find the next method
+ table (use !dumpmt to verify). If the GC is currently in a call to
+ memcopy, You may also be able to find the next object's address by
+ adding the size to the start address given as parameters.)
+-mt List only those objects with the MethodTable given
+-type List only those objects whose type name is a substring match of the
+ string provided.
+start Begin listing from this address
+end Stop listing at this address
+
+A special note about -type: Often, you'd like to find not only Strings, but
+System.Object arrays that are constrained to contain Strings. ("new
+String[100]" actually creates a System.Object array, but it can only hold
+System.String object pointers). You can use -type in a special way to find
+these arrays. Just pass "-type System.String[]" and those Object arrays will
+be returned. More generally, "-type <Substring of interesting type>[]".
+
+The start/end parameters can be obtained from the output of !EEHeap -gc. For
+example, if you only want to list objects in the large heap segment:
+
+ 0:000> !eeheap -gc
+ Number of GC Heaps: 1
+ generation 0 starts at 0x00c32754
+ generation 1 starts at 0x00c32748
+ generation 2 starts at 0x00a71000
+ segment begin allocated size
+ 00a70000 00a71000 010443a8 005d33a8(6108072)
+ Large object heap starts at 0x01a71000
+ segment begin allocated size
+ 01a70000 01a71000 01a75000 0x00004000(16384)
+ Total Size 0x5d73a8(6124456)
+ ------------------------------
+ GC Heap Size 0x5d73a8(6124456)
+
+ 0:000> !dumpheap 1a71000 1a75000
+ Address MT Size
+ 01a71000 5ba88bd8 2064
+ 01a71810 0019fe48 2032 Free
+ 01a72000 5ba88bd8 4096
+ 01a73000 0019fe48 4096 Free
+ 01a74000 5ba88bd8 4096
+ total 5 objects
+ Statistics:
+ MT Count TotalSize Class Name
+ 0019fe48 2 6128 Free
+ 5ba88bd8 3 10256 System.Object[]
+ Total 5 objects
+
+Finally, if GC heap corruption is present, you may see an error like this:
+
+ 0:000> !dumpheap -stat
+ object 00a73d24: does not have valid MT
+ curr_object : 00a73d24
+ Last good object: 00a73d14
+ ----------------
+
+That indicates a serious problem. See the help for !VerifyHeap for more
+information on diagnosing the cause.
+\\
+
+COMMAND: dumpvc.
+!DumpVC <MethodTable address> <Address>
+
+!DumpVC allows you to examine the fields of a value class. In C#, this is a
+struct, and lives on the stack or within an Object on the GC heap. You need
+to know the MethodTable address to tell SOS how to interpret the fields, as
+a value class is not a first-class object with it's own MethodTable as the
+first field. For example:
+
+ 0:000> !DumpObj a79d98
+ Name: Mainy
+ MethodTable: 009032d8
+ EEClass: 03ee1424
+ Size: 28(0x1c) bytes
+ (C:\pub\unittest.exe)
+ Fields:
+ MT Field Offset Type Attr Value Name
+ 0090320c 4000010 4 VALUETYPE instance 00a79d9c m_valuetype
+ 009032d8 400000f 4 CLASS static 00a79d54 m_sExcep
+
+m_valuetype is a value type. The value in the MT column (0090320c) is the
+MethodTable for it, and the Value column provides the start address:
+
+ 0:000> !DumpVC 0090320c 00a79d9c
+ Name: Funny
+ MethodTable 0090320c
+ EEClass: 03ee14b8
+ Size: 28(0x1c) bytes
+ (C:\pub\unittest.exe)
+ Fields:
+ MT Field Offset Type Attr Value Name
+ 0090320c 4000001 0 CLASS instance 00a743d8 signature
+ 0090320c 4000002 8 System.Int32 instance 2345 m1
+ 0090320c 4000003 10 System.Boolean instance 1 b1
+ 0090320c 4000004 c System.Int32 instance 1234 m2
+ 0090320c 4000005 4 CLASS instance 00a79d98 backpointer
+
+!DumpVC is quite a specialized function. Some managed programs make heavy use
+of value classes, while others do not.
+\\
+
+COMMAND: gcroot.
+!GCRoot [-nostacks] <Object address>
+
+!GCRoot looks for references (or roots) to an object. These can exist in four
+places:
+
+ 1. On the stack
+ 2. Within a GC Handle
+ 3. In an object ready for finalization
+ 4. As a member of an object found in 1, 2 or 3 above.
+
+First, all stacks will be searched for roots, then handle tables, and finally
+the freachable queue of the finalizer. Some caution about the stack roots:
+!GCRoot doesn't attempt to determine if a stack root it encountered is valid
+or is old (discarded) data. You would have to use !CLRStack and !U to
+disassemble the frame that the local or argument value belongs to in order to
+determine if it is still in use.
+
+Because people often want to restrict the search to gc handles and freachable
+objects, there is a -nostacks option.
+\\
+
+COMMAND: objsize.
+!ObjSize [<Object address>] | [-aggregate] [-stat]
+
+With no parameters, !ObjSize lists the size of all objects found on managed
+threads. It also enumerates all GCHandles in the process, and totals the size
+of any objects pointed to by those handles. In calculating object size,
+!ObjSize includes the size of all child objects in addition to the parent.
+
+For example, !DumpObj lists a size of 20 bytes for this Customer object:
+
+ 0:000> !do a79d40
+ Name: Customer
+ MethodTable: 009038ec
+ EEClass: 03ee1b84
+ Size: 20(0x14) bytes
+ (C:\pub\unittest.exe)
+ Fields:
+ MT Field Offset Type Attr Value Name
+ 009038ec 4000008 4 CLASS instance 00a79ce4 name
+ 009038ec 4000009 8 CLASS instance 00a79d2c bank
+ 009038ec 400000a c System.Boolean instance 1 valid
+
+but !ObjSize lists 152 bytes:
+
+ 0:000> !ObjSize a79d40
+ sizeof(00a79d40) = 152 ( 0x98) bytes (Customer)
+
+This is because a Customer points to a Bank, has a name, and the Bank points to
+an Address string. You can use !ObjSize to identify any particularly large
+objects, such as a managed cache in a web server.
+
+While running ObjSize with no arguments may point to specific roots that hold
+onto large amounts of memory it does not provide information regarding the
+amount of managed memory that is still alive. This is due to the fact that a
+number of roots can share a common subgraph, and that part will be reported in
+the size of all the roots that reference the subgraph. The -aggregate argument
+is meant to answer this question:
+
+The -aggregate option can be used in conjunction with the -stat argument to get
+a detailed view of what are the types that are still rooted. Using !dumpheap
+-stat and !objsize -aggregate -stat one can determine what are the the objects
+that are not rooted any more and diagnose various memory issues.
+
+Sample output when using the -aggregate and -stat:
+
+ 0:003> !ObjSize -aggregate -stat
+ Scan Thread 0 OSTHread f70
+ Scan Thread 2 OSTHread ef8
+ Statistics:
+ MT Count TotalSize Class Name
+ 01e63768 1 12 Test+d
+ 01e63660 1 16 Test+c
+ 01e63548 1 16 Test+b
+ 01e632f8 1 16 Test+a
+ ...
+ 5b6c6d40 9 504 System.Collections.Hashtable
+ 5b6ebe28 3 552 System.Byte[]
+ 5b6ec0bc 9 1296 System.Collections.Hashtable+bucket[]
+ 5b6ec200 9 1436 System.Char[]
+ 5b6c447c 77 2468 System.String
+ 5b6ebd64 8 9020 System.Object[]
+ Total 203 objects
+ Total Memory Size 18332 (0x479c) bytes
+
+\\
+
+COMMAND: finalizequeue.
+!FinalizeQueue [-detail] | [-allReady] [-short]
+
+This command lists the objects registered for finalization. Here is output from
+a simple program:
+
+ 0:000> !finalizequeue
+ SyncBlocks to be cleaned up: 0
+ MTA Interfaces to be released: 0
+ STA Interfaces to be released: 1
+ generation 0 has 4 finalizable objects (0015bc90->0015bca0)
+ generation 1 has 0 finalizable objects (0015bc90->0015bc90)
+ generation 2 has 0 finalizable objects (0015bc90->0015bc90)
+ Ready for finalization 0 objects (0015bca0->0015bca0)
+ Statistics:
+ MT Count TotalSize Class Name
+ 5ba6cf78 1 24 Microsoft.Win32.SafeHandles.SafeFileHandle
+ 5ba5db04 1 68 System.Threading.Thread
+ 5ba73e28 2 112 System.IO.StreamWriter
+ Total 4 objects
+
+The GC heap is divided into generations, and objects are listed accordingly. We
+see that only generation 0 (the youngest generation) has any objects registered
+for finalization. The notation "(0015bc90->0015bca0)" means that if you look at
+memory in that range, you'll see the object pointers that are registered:
+
+0:000> dd 15bc90 15bca0-4
+0015bc90 00a743f4 00a79f00 00a7b3d8 00a7b47c
+
+You could run !DumpObj on any of those pointers to learn more. In this example,
+there are no objects ready for finalization, presumably because they still have
+roots (You can use !GCRoot to find out). The statistics section provides a
+higher-level summary of the objects registered for finalization. Note that
+objects ready for finalization are also included in the statistics (if any).
+
+Specifying -short will inhibit any display related to SyncBlocks or RCWs.
+
+The arguments in detail:
+
+-allReady Specifying this argument will allow for the display of all objects
+ that are ready for finalization, whether they are already marked by
+ the GC as such, or whether the next GC will. The objects that are
+ not in the "Ready for finalization" list are finalizable objects that
+ are no longer rooted. This option can be very expensive, as it
+ verifies whether all the objects in the finalizable queues are still
+ rooted or not.
+-short Limits the output to just the address of each object. If used in
+ conjunction with -allReady it enumerates all objects that have a
+ finalizer that are no longer rooted. If used independently it lists
+ all objects in the finalizable and "ready for finalization" queues.
+-detail Will display extra information on any SyncBlocks that need to be
+ cleaned up, and on any RuntimeCallableWrappers (RCWs) that await
+ cleanup. Both of these data structures are cached and cleaned up by
+ the finalizer thread when it gets a chance to run.
+\\
+
+COMMAND: printexception.
+!PrintException [-nested] [-lines] [<Exception object address>]
+
+This will format fields of any object derived from System.Exception. One of the
+more useful aspects is that it will format the _stackTrace field, which is a
+binary array. If _stackTraceString field is not filled in, that can be helpful
+for debugging. You can of course use !DumpObj on the same exception object to
+explore more fields.
+
+If called with no parameters, PrintException will look for the last outstanding
+exception on the current thread and print it. This will be the same exception
+that shows up in a run of !Threads.
+
+!PrintException will notify you if there are any nested exceptions on the
+current managed thread. (A nested exception occurs when you throw another
+exception within a catch handler already being called for another exception).
+If there are nested exceptions, you can re-run !PrintException with the
+"-nested" option to get full details on the nested exception objects. The
+!Threads command will also tell you which threads have nested exceptions.
+
+!PrintException can display source information if available, by specifying the
+-lines command line argument.
+
+The abbreviation !pe can be used for brevity.
+\\
+
+COMMAND: traverseheap.
+!TraverseHeap [-xml] [-verify] <filename>
+
+!TraverseHeap writes out a file in a format understood by the CLR Profiler.
+You can download the CLR Profiler from this link:
+
+http://www.microsoft.com/downloads/details.aspx?FamilyId=86CE6052-D7F4-4AEB-
+9B7A-94635BEEBDDA&displaylang=en
+
+It creates a graphical display of the GC heap to help you analyze the state of
+your application.
+
+If you pass the -verify option it will do more sanity checking of the heap
+as it dumps it. Use this option if heap corruption is suspected.
+
+If you pass the "-xml" flag, the file is instead written out in an easy to
+understand xml format:
+
+ <gcheap>
+ <types>
+ <type id="1" name="System.String">
+ ...
+ </types>
+ <roots>
+ <root kind="handle" address="0x00a73ff0"/>
+ <root kind="stack" address="0x0069f0e0"/>
+ ...
+ </roots>
+ <objects>
+ <object address="0x00b73030" typeid="1" size="300"/>
+ <object address="0x00b75054" typeid="5" size="20">
+ <member address="0x00b75088" />
+ ...
+ </object>
+ ...
+ </objects>
+ </gcheap>
+
+You can break into your process, load SOS, take a snapshot of your heap with
+this function, then continue.
+\\
+COMMAND: threadstate.
+!ThreadState value
+
+The !Threads command outputs, among other things, the state of the thread.
+This is a bit field which corresponds to various states the thread is in.
+To check the state of the thread, simply pass that bit field from the
+output of !Threads into !ThreadState.
+
+Example:
+ 0:003> !Threads
+ ThreadCount: 2
+ UnstartedThread: 0
+ BackgroundThread: 1
+ PendingThread: 0
+ DeadThread: 0
+ Hosted Runtime: no
+ PreEmptive GC Alloc Lock
+ ID OSID ThreadOBJ State GC Context Domain Count APT Exception
+ 0 1 250 0019b068 a020 Disabled 02349668:02349fe8 0015def0 0 MTA
+ 2 2 944 001a6020 b220 Enabled 00000000:00000000 0015def0 0 MTA (Finalizer)
+ 0:003> !ThreadState b220
+ Legal to Join
+ Background
+ CLR Owns
+ CoInitialized
+ In Multi Threaded Apartment
+
+Possible thread states:
+ Thread Abort Requested
+ GC Suspend Pending
+ User Suspend Pending
+ Debug Suspend Pending
+ GC On Transitions
+ Legal to Join
+ Yield Requested
+ Hijacked by the GC
+ Blocking GC for Stack Overflow
+ Background
+ Unstarted
+ Dead
+ CLR Owns
+ CoInitialized
+ In Single Threaded Apartment
+ In Multi Threaded Apartment
+ Reported Dead
+ Fully initialized
+ Task Reset
+ Sync Suspended
+ Debug Will Sync
+ Stack Crawl Needed
+ Suspend Unstarted
+ Aborted
+ Thread Pool Worker Thread
+ Interruptible
+ Interrupted
+ Completion Port Thread
+ Abort Initiated
+ Finalized
+ Failed to Start
+ Detached
+\\
+COMMAND: threads.
+!Threads [-live] [-special]
+
+!Threads lists all the mananaged threads in the process.
+
+-live: optional. Only print threads associated with a live thread.
+-special: optional. With this switch, the command will display all the special
+ threads created by CLR. Those threads might not be managed threads
+ so they might not be shown in the first part of the command's
+ output. Example of special threads include: GC threads (in
+ concurrent GC and server GC), Debugger helper threads, Finalizer
+ threads, AppDomain Unload threads, and Threadpool timer threads.
+
+Each thread has many attributes, many of which can be ignored. The important
+ones are discussed below:
+
+There are three ID columns:
+
+1) The debugger shorthand ID (When the runtime is hosted this column might
+ display the special string "<<<<" when this internal thread object is not
+ associated with any physical thread - this may happen when the host reuses
+ the runtime internal thread object)
+2) The CLR Thread ID
+3) The OS thread ID.
+
+If PreEmptiveGC is enabled for a thread, then a garbage collection
+can occur while that thread is running. For example, if you break in while
+a managed thread is making a PInvoke call to a Win32 function, that thread
+will be in PreEmptive GC mode.
+
+The Domain column indicates what AppDomain the thread is currently executing
+in. You can pass this value to !DumpDomain to find out more.
+
+The APT column gives the COM apartment mode.
+
+Exception will list the last thrown exception (if any) for the thread. More
+details can be obtained by passing the pointer value to !PrintException. If
+you get the notation "(nested exceptions)", you can get details on those
+exceptions by switching to the thread in question, and running
+"!PrintException -nested".
+\\
+
+COMMAND: clrstack.
+!CLRStack [-a] [-l] [-p] [-n]
+!CLRStack [-a] [-l] [-p] [-i] [variable name] [frame]
+
+CLRStack attempts to provide a true stack trace for managed code only. It is
+handy for clean, simple traces when debugging straightforward managed
+programs. The -p parameter will show arguments to the managed function. The
+-l parameter can be used to show information on local variables in a frame.
+SOS can't retrieve local names at this time, so the output for locals is in
+the format <local address> = <value>. The -a (all) parameter is a short-cut
+for -l and -p combined.
+
+If the debugger has the option SYMOPT_LOAD_LINES specified (either by the
+.lines or .symopt commands), SOS will look up the symbols for every managed
+frame and if successful will display the corresponding source file name and
+line number. The -n (No line numbers) parameter can be specified to disable
+this behavior.
+
+When you see methods with the name "[Frame:...", that indicates a transition
+between managed and unmanaged code. You could run !IP2MD on the return
+addresses in the call stack to get more information on each managed method.
+
+On x64 platforms, Transition Frames are not displayed at this time. To avoid
+heavy optimization of parameters and locals one can request the JIT compiler
+to not optimize functions in the managed app by creating a file myapp.ini
+(if your program is myapp.exe) in the same directory. Put the following lines
+in myapp.ini and re-run:
+
+[.NET Framework Debugging Control]
+GenerateTrackingInfo=1
+AllowOptimize=0
+
+The -i option is a new EXPERIMENTAL addition to CLRStack and will use the ICorDebug
+interfaces to display the managed stack and variables. With this option you can also
+view and expand arrays and fields for managed variables. If a stack frame number is
+specified in the command line, CLRStack will show you the parameters and/or locals
+only for that frame (provided you specify -l or -p or -a of course). If a variable
+name and a stack frame number are specified in the command line, CLRStack will show
+you the parameters and/or locals for that frame, and will also show you the fields
+for that variable name you specified. Here are some examples:
+ !CLRStack -i -a : This will show you all parameters and locals for all frames
+ !CLRStack -i -a 3 : This will show you all parameters and locals, for frame 3
+ !CLRStack -i var1 0 : This will show you the fields of 'var1' for frame 0
+ !CLRStack -i var1.abc 2 : This will show you the fields of 'var1', and expand
+ 'var1.abc' to show you the fields of the 'abc' field,
+ for frame 2.
+ !CLRStack -i var1.[basetype] 0 : This will show you the fields of 'var1', and
+ expand the base type of 'var1' to show you its
+ fields.
+ !CLRStack -i var1.[6] 0 : If 'var1' is an array, this will show you the element
+ at index 6 in the array, along with its fields
+The -i options uses DML output for a better debugging experience, so typically you
+should only need to execute "!CLRStack -i", and from there, click on the DML
+hyperlinks to inspect the different managed stack frames and managed variables.
+\\
+
+COMMAND: ip2md.
+!IP2MD <Code address>
+
+Given an address in managed JITTED code, IP2MD attempts to find the MethodDesc
+associated with it. For example, this output from K:
+
+ 0:000> K
+ ChildEBP RetAddr
+ 00a79c78 03ef02ab image00400000!Mainy.Top()+0xb
+ 00a79c78 03ef01a6 image00400000!Mainy.Level(Int32)+0xb
+ 00a79c78 5d3725a1 image00400000!Mainy.Main()+0xee
+ 0012ea04 5d512f59 clr!CallDescrWorkerInternal+0x30
+ 0012ee34 5d7946aa clr!CallDescrWorker+0x109
+
+ 0:000> !IP2MD 03ef01a6
+ MethodDesc: 00902f40
+ Method Name: Mainy.Main()
+ Class: 03ee1424
+ MethodTable: 009032d8
+ mdToken: 0600000d
+ Module: 001caa38
+ IsJitted: yes
+ CodeAddr: 03ef00b8
+ Transparency: Critical
+ Source file: c:\Code\prj.mini\exc.cs @ 39
+
+We have taken a return address into Mainy.Main, and discovered information
+about that method. You could run !U, !DumpMT, !DumpClass, !DumpMD, or
+!DumpModule on the fields listed to learn more.
+
+The "Source line" output will only be present if the debugger can find the
+symbols for the managed module containing the given <code address>, and if the
+debugger is configured to load line number information.
+\\
+
+COMMAND: u.
+!U [-gcinfo] [-ehinfo] [-n] <MethodDesc address> | <Code address>
+
+Presents an annotated disassembly of a managed method when given a MethodDesc
+pointer for the method, or a code address within the method body. Unlike the
+debugger "U" function, the entire method from start to finish is printed,
+with annotations that convert metadata tokens to names.
+
+ <example output>
+ ...
+ 03ef015d b901000000 mov ecx,0x1
+ 03ef0162 ff156477a25b call dword ptr [mscorlib_dll+0x3c7764 (5ba27764)] (System.Console.InitializeStdOutError(Boolean), mdToken: 06000713)
+ 03ef0168 a17c20a701 mov eax,[01a7207c] (Object: SyncTextWriter)
+ 03ef016d 89442414 mov [esp+0x14],eax
+
+If you pass the -gcinfo flag, you'll get inline display of the GCInfo for
+the method. You can also obtain this information with the !GCInfo command.
+
+If you pass the -ehinfo flag, you'll get inline display of exception info
+for the method. (Beginning and end of try/finally/catch handlers, etc.).
+You can also obtain this information with the !EHInfo command.
+
+If the debugger has the option SYMOPT_LOAD_LINES specified (either by the
+.lines or .symopt commands), and if symbols are available for the managed
+module containing the method being examined, the output of the command will
+include the source file name and line number corresponding to the
+disassembly. The -n (No line numbers) flag can be specified to disable this
+behavior.
+
+ <example output>
+ ...
+ c:\Code\prj.mini\exc.cs @ 38:
+ 001b00b0 8b0d3020ab03 mov ecx,dword ptr ds:[3AB2030h] ("Break in debugger. When done type <Enter> to continue: ")
+ 001b00b6 e8d5355951 call mscorlib_ni+0x8b3690 (51743690) (System.Console.Write(System.String), mdToken: 0600091b)
+ 001b00bb 90 nop
+
+ c:\Code\prj.mini\exc.cs @ 39:
+ 001b00bc e863cdc651 call mscorlib_ni+0xf8ce24 (51e1ce24) (System.Console.ReadLine(), mdToken: 060008f6)
+ >>> 001b00c1 90 nop
+ ...
+\\
+
+COMMAND: dumpstack.
+!DumpStack [-EE] [-n] [top stack [bottom stack]]
+
+[x86 and x64 documentation]
+
+This command provides a verbose stack trace obtained by "scraping." Therefore
+the output is very noisy and potentially confusing. The command is good for
+viewing the complete call stack when "kb" gets confused. For best results,
+make sure you have valid symbols.
+
+-EE will only show managed functions.
+
+If the debugger has the option SYMOPT_LOAD_LINES specified (either by the
+.lines or .symopt commands), SOS will look up the symbols for every managed
+frame and if successful will display the corresponding source file name and
+line number. The -n (No line numbers) parameter can be specified to disable
+this behavior.
+
+You can also pass a stack range to limit the output. Use the debugger
+extension !teb to get the top and bottom stack values.
+
+\\
+
+COMMAND: eestack.
+!EEStack [-short] [-EE]
+
+This command runs !DumpStack on all threads in the process. The -EE option is
+passed directly to !DumpStack. The -short option tries to narrow down the
+output to "interesting" threads only, which is defined by
+
+1) The thread has taken a lock.
+2) The thread has been "hijacked" in order to allow a garbage collection.
+3) The thread is currently in managed code.
+
+See the documentation for !DumpStack for more info.
+\\
+
+COMMAND: ehinfo.
+!EHInfo (<MethodDesc address> | <Code address>)
+
+!EHInfo shows the exception handling blocks in a jitted method. For each
+handler, it shows the type, including code addresses and offsets for the clause
+block and the handler block. For a TYPED handler, this would be the "try" and
+"catch" blocks respectively.
+
+Sample output:
+
+ 0:000> !ehinfo 33bbd3a
+ MethodDesc: 03310f68
+ Method Name: MainClass.Main()
+ Class: 03571358
+ MethodTable: 0331121c
+ mdToken: 0600000b
+ Module: 001e2fd8
+ IsJitted: yes
+ CodeAddr: 033bbca0
+ Transparency: Critical
+
+ EHHandler 0: TYPED catch(System.IO.FileNotFoundException)
+ Clause: [033bbd2b, 033bbd3c] [8b, 9c]
+ Handler: [033bbd3c, 033bbd50] [9c, b0]
+
+ EHHandler 1: FINALLY
+ Clause: [033bbd83, 033bbda3] [e3, 103]
+ Handler: [033bbda3, 033bbdc5] [103, 125]
+
+ EHHandler 2: TYPED catch(System.Exception)
+ Clause: [033bbd7a, 033bbdc5] [da, 125]
+ Handler: [033bbdc5, 033bbdd6] [125, 136]
+
+\\
+
+COMMAND: gcinfo.
+!GCInfo (<MethodDesc address> | <Code address>)
+
+!GCInfo is especially useful for CLR Devs who are trying to determine if there
+is a bug in the JIT Compiler. It parses the GCEncoding for a method, which is a
+compressed stream of data indicating when registers or stack locations contain
+managed objects. It is important to keep track of this information, because if
+a garbage collection occurs, the collector needs to know where roots are so it
+can update them with new object pointer values.
+
+Here is sample output where you can see the change in register state. Normally
+you would print this output out and read it alongside a disassembly of the
+method. For example, the notation "reg EDI becoming live" at offset 0x11 of the
+method might correspond to a "mov edi,ecx" statement.
+
+ 0:000> !gcinfo 5b68dbb8 (5b68dbb8 is the start of a JITTED method)
+ entry point 5b68dbb8
+ preJIT generated code
+ GC info 5b9f2f09
+ Method info block:
+ method size = 0036
+ prolog size = 19
+ epilog size = 8
+ epilog count = 1
+ epilog end = yes
+ saved reg. mask = 000B
+ ebp frame = yes
+ fully interruptible=yes
+ double align = no
+ security check = no
+ exception handlers = no
+ local alloc = no
+ edit & continue = no
+ varargs = no
+ argument count = 4
+ stack frame size = 1
+ untracked count = 5
+ var ptr tab count = 0
+ epilog at 002E
+ 36 D4 8C C7 AA |
+ 93 F3 40 05 |
+
+ Pointer table:
+ 14 | [EBP+14H] an untracked local
+ 10 | [EBP+10H] an untracked local
+ 0C | [EBP+0CH] an untracked local
+ 08 | [EBP+08H] an untracked local
+ 44 | [EBP-04H] an untracked local
+ F1 79 | 0011 reg EDI becoming live
+ 72 | 0013 reg ESI becoming live
+ 83 | 0016 push ptr 0
+ 8B | 0019 push ptr 1
+ 93 | 001C push ptr 2
+ 9B | 001F push ptr 3
+ 56 | 0025 reg EDX becoming live
+ 4A | 0027 reg ECX becoming live
+ 0E | 002D reg ECX becoming dead
+ 10 | 002D reg EDX becoming dead
+ E0 | 002D pop 4 ptrs
+ F0 31 | 0036 reg ESI becoming dead
+ 38 | 0036 reg EDI becoming dead
+ FF |
+
+This function is important for CLR Devs, but very difficult for anyone else to
+make sense of it. You would usually come to use it if you suspect a gc heap
+corruption bug caused by invalid GCEncoding for a particular method.
+\\
+
+COMMAND: comstate.
+!COMState
+
+!COMState lists the com apartment model for each thread, as well as a Context
+pointer if provided.
+\\
+
+COMMAND: bpmd.
+!BPMD [-nofuturemodule] <module name> <method name> [<il offset>]
+!BPMD <source file name>:<line number>
+!BPMD -md <MethodDesc>
+!BPMD -list
+!BPMD -clear <pending breakpoint number>
+!BPMD -clearall
+
+!BPMD provides managed breakpoint support. If it can resolve the method name
+to a loaded, jitted or ngen'd function it will create a breakpoint with "bp".
+If not then either the module that contains the method hasn't been loaded yet
+or the module is loaded, but the function is not jitted yet. In these cases,
+!bpmd asks the Windows Debugger to receive CLR Notifications, and waits to
+receive news of module loads and JITs, at which time it will try to resolve
+the function to a breakpoint. -nofuturemodule can be used to suppress
+creating a breakpoint against a module that has not yet been loaded.
+
+Management of the list of pending breakpoints can be done via !BPMD -list,
+!BPMD -clear, and !BPMD -clearall commands. !BPMD -list generates a list of
+all of the pending breakpoints. If the pending breakpoint has a non-zero
+module id, then that pending breakpoint is specific to function in that
+particular loaded module. If the pending breakpoint has a zero module id, then
+the breakpoint applies to modules that have not yet been loaded. Use
+!BPMD -clear or !BPMD -clearall to remove pending breakpoints from the list.
+
+This brings up a good question: "I want to set a breakpoint on the main
+method of my application. How can I do this?"
+
+ 1) If you know the full path to SOS, use this command and skip to step 6
+ .load <the full path to sos.dll>
+
+ 2) If you don't know the full path to sos, its usually next to clr.dll
+ You can wait for clr to load and then find it.
+ Start the debugger and type:
+ sxe -c "" clrn
+ 3) g
+ 4) You'll get the following notification from the debugger:
+ "CLR notification: module 'mscorlib' loaded"
+ 5) Now you can load SOS. Type
+ .loadby sos clr
+
+ 6) Add the breakpoint with command such as:
+ !bpmd myapp.exe MyApp.Main
+ 7) g
+ 8) You will stop at the start of MyApp.Main. If you type "bl" you will
+ see the breakpoint listed.
+
+You can specify breakpoints by file and line number if:
+ a) You have some version of .Net Framework installed on your machine. Any OS from
+ Vista onwards should have .Net Framework installed by default.
+ b) You have PDBs for the managed modules that need breakpoints, and your symbol
+ path points to those PDBs.
+This is often easier than module and method name syntax. For example:
+ !bpmd Demo.cs:15
+
+
+To correctly specify explicitly implemented methods make sure to retrieve the
+method name from the metadata, or from the output of the "!dumpmt -md" command.
+For example:
+
+ public interface I1
+ {
+ void M1();
+ }
+ public class ExplicitItfImpl : I1
+ {
+ ...
+ void I1.M1() // this method's name is 'I1.M1'
+ { ... }
+ }
+
+ !bpmd myapp.exe ExplicitItfImpl.I1.M1
+
+
+!BPMD works equally well with generic types. Adding a breakpoint on a generic
+type sets breakpoints on all already JIT-ted generic methods and sets a pending
+breakpoint for any instantiation that will be JIT-ted in the future.
+
+Example for generics:
+ Given the following two classes:
+
+ class G3<T1, T2, T3>
+ {
+ ...
+ public void F(T1 p1, T2 p2, T3 p3)
+ { ... }
+ }
+
+ public class G1<T> {
+ // static method
+ static public void G<W>(W w)
+ { ... }
+ }
+
+ One would issue the following commands to set breapoints on G3.F() and
+ G1.G():
+
+ !bpmd myapp.exe G3`3.F
+ !bpmd myapp.exe G1`1.G
+
+And for explicitly implemented methods on generic interfaces:
+ public interface IT1<T>
+ {
+ void M1(T t);
+ }
+
+ public class ExplicitItfImpl<U> : IT1<U>
+ {
+ ...
+ void IT1<U>.M1(U u) // this method's name is 'IT1<U>.M1'
+ { ... }
+ }
+
+ !bpmd bpmd.exe ExplicitItfImpl`1.IT1<U>.M1
+
+Additional examples:
+ If IT1 and ExplicitItfImpl are types declared inside another class,
+ Outer, the bpmd command would become:
+
+ !bpmd bpmd.exe Outer+ExplicitItfImpl`1.Outer.IT1<U>.M1
+
+ (note that the fully qualified type name for ExplicitItfImpl became
+ Outer+ExplicitItfImpl, using the '+' separator, while the method name
+ is Outer.IT1<U>.M1, using a '.' as the separator)
+
+ Furthermore, if the Outer class resides in a namespace, NS, the bpmd
+ command to use becomes:
+
+ !bpmd bpmd.exe NS.Outer+ExplicitItfImpl`1.NS.Outer.IT1<U>.M1
+
+!BPMD does not accept offsets nor parameters in the method name. You can add
+an IL offset as an optional parameter seperate from the name. If there are overloaded
+methods, !bpmd will set a breakpoint for all of them.
+
+In the case of hosted environments such as SQL, the module name may be
+complex, like 'price, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'.
+For this case, just be sure to surround the module name with single quotes,
+like:
+
+!bpmd 'price, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' Price.M2
+
+\\
+
+COMMAND: dumpdomain.
+!DumpDomain [<Domain address>]
+
+When called with no parameters, !DumpDomain will list all the AppDomains in the
+process. It enumerates each Assembly loaded into those AppDomains as well.
+In addition to your application domain, and any domains it might create, there
+are two special domains: the Shared Domain and the System Domain.
+
+Any Assembly pointer in the output can be passed to !DumpAssembly. Any Module
+pointer in the output can be passed to !DumpModule. Any AppDomain pointer can
+be passed to !DumpDomain to limit output only to that AppDomain. Other
+functions provide an AppDomain pointer as well, such as !Threads where it lists
+the current AppDomain for each thread.
+\\
+
+COMMAND: eeheap.
+!EEHeap [-gc] [-loader]
+
+!EEHeap enumerates process memory consumed by internal CLR data structures. You
+can limit the output by passing "-gc" or "-loader". All information will be
+displayed otherwise.
+
+The information for the Garbage Collector lists the ranges of each Segment in
+the managed heap. This can be useful if you believe you have an object pointer.
+If the pointer falls within a segment range given by "!EEHeap -gc", then you do
+have an object pointer, and can attempt to run "!DumpObj" on it.
+
+Here is output for a simple program:
+
+ 0:000> !eeheap -gc
+ Number of GC Heaps: 1
+ generation 0 starts at 0x00a71018
+ generation 1 starts at 0x00a7100c
+ generation 2 starts at 0x00a71000
+ segment begin allocated size
+ 00a70000 00a71000 00a7e01c 0000d01c(53276)
+ Large object heap starts at 0x01a71000
+ segment begin allocated size
+ 01a70000 01a71000 01a76000 0x00005000(20480)
+ Total Size 0x1201c(73756)
+ ------------------------------
+ GC Heap Size 0x1201c(73756)
+
+So the total size of the GC Heap is only 72K. On a large web server, with
+multiple processors, you can expect to see a GC Heap of 400MB or more. The
+Garbage Collector attempts to collect and reclaim memory only when required to
+by memory pressure for better performance. You can also see the notion of
+"generations," wherein the youngest objects live in generation 0, and
+long-lived objects eventually get "promoted" to generation 2.
+
+The loader output lists various private heaps associated with AppDomains. It
+also lists heaps associated with the JIT compiler, and heaps associated with
+Modules. For example:
+
+ 0:000> !EEHeap -loader
+ Loader Heap:
+ --------------------------------------
+ System Domain: 5e0662a0
+ LowFrequencyHeap:008f0000(00002000:00001000) Size: 0x00001000 bytes.
+ HighFrequencyHeap:008f2000(00008000:00001000) Size: 0x00001000 bytes.
+ StubHeap:008fa000(00002000:00001000) Size: 0x00001000 bytes.
+ Total size: 0x3000(12288)bytes
+ --------------------------------------
+ Shared Domain: 5e066970
+ LowFrequencyHeap:00920000(00002000:00001000) 03e30000(00010000:00003000) Size: 0x00004000 bytes.
+ Wasted: 0x00001000 bytes.
+ HighFrequencyHeap:00922000(00008000:00001000) Size: 0x00001000 bytes.
+ StubHeap:0092a000(00002000:00001000) Size: 0x00001000 bytes.
+ Total size: 0x6000(24576)bytes
+ --------------------------------------
+ Domain 1: 14f000
+ LowFrequencyHeap:00900000(00002000:00001000) 03ee0000(00010000:00003000) Size: 0x00004000 bytes.
+ Wasted: 0x00001000 bytes.
+ HighFrequencyHeap:00902000(00008000:00003000) Size: 0x00003000 bytes.
+ StubHeap:0090a000(00002000:00001000) Size: 0x00001000 bytes.
+ Total size: 0x8000(32768)bytes
+ --------------------------------------
+ Jit code heap:
+ Normal JIT:03ef0000(00010000:00002000) Size: 0x00002000 bytes.
+ Total size: 0x2000(8192)bytes
+ --------------------------------------
+ Module Thunk heaps:
+ Module 5ba22410: Size: 0x00000000 bytes.
+ Module 001c1320: Size: 0x00000000 bytes.
+ Module 001c03f0: Size: 0x00000000 bytes.
+ Module 001caa38: Size: 0x00000000 bytes.
+ Total size: 0x0(0)bytes
+ --------------------------------------
+ Module Lookup Table heaps:
+ Module 5ba22410:Size: 0x00000000 bytes.
+ Module 001c1320:Size: 0x00000000 bytes.
+ Module 001c03f0:Size: 0x00000000 bytes.
+ Module 001caa38:03ec0000(00010000:00002000) Size: 0x00002000 bytes.
+ Total size: 0x2000(8192)bytes
+ --------------------------------------
+ Total LoaderHeap size: 0x15000(86016)bytes
+ =======================================
+
+By using !EEHeap to keep track of the growth of these private heaps, we are
+able to rule out or include them as a source of a memory leak.
+\\
+
+COMMAND: name2ee.
+!Name2EE <module name> <type or method name>
+!Name2EE <module name>!<type or method name>
+
+This function allows you to turn a class name into a MethodTable and EEClass.
+It turns a method name into a MethodDesc. Here is an example for a method:
+
+ 0:000> !name2ee unittest.exe MainClass.Main
+ Module: 001caa38
+ Token: 0x0600000d
+ MethodDesc: 00902f40
+ Name: MainClass.Main()
+ JITTED Code Address: 03ef00b8
+
+and for a class:
+
+ 0:000> !name2ee unittest!MainClass
+ Module: 001caa38
+ Token: 0x02000005
+ MethodTable: 009032d8
+ EEClass: 03ee1424
+ Name: MainClass
+
+The module you are "browsing" with Name2EE needs to be loaded in the process.
+To get a type name exactly right, first browse the module with ILDASM. You
+can also pass * as the <module name> to search all loaded managed modules.
+<module name> can also be the debugger's name for a module, such as
+mscorlib or image00400000.
+
+The Windows Debugger syntax of <module>!<type> is also supported. You can
+use an asterisk on the left of the !, but the type on the right side needs
+to be fully qualified.
+
+If you are looking for a way to display a static field of a class (and you
+don't have an instance of the class, so !dumpobj won't help you), note that
+once you have the EEClass, you can run !DumpClass, which will display the
+value of all static fields.
+
+There is yet one more way to specify a module name. In the case of modules
+loaded from an assembly store (such as a SQL db) rather than disk, the
+module name will look like this:
+
+price, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+
+For this kind of module, simply use price as the module name:
+
+ 0:044> !name2ee price Price
+ Module: 10f028b0 (price, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null)
+ Token: 0x02000002
+ MethodTable: 11a47ae0
+ EEClass: 11a538c8
+ Name: Price
+
+Where are we getting these module names from? Run !DumpDomain to see a list of
+all loaded modules in all domains. And remember that you can browse all the
+types in a module with !DumpModule -mt <module pointer>.
+\\
+
+COMMAND: syncblk.
+!SyncBlk [-all | <syncblk number>]
+
+A SyncBlock is a holder for extra information that doesn't need to be created
+for every object. It can hold COM Interop data, HashCodes, and locking
+information for thread-safe operations.
+
+When called without arguments, !SyncBlk will print the list of SyncBlocks
+corresponding to objects that are owned by a thread. For example, a
+
+ lock(MyObject)
+ {
+ ....
+ }
+
+statement will set MyObject to be owned by the current thread. A SyncBlock will
+be created for MyObject, and the thread ownership information stored there
+(this is an oversimplification, see NOTE below). If another thread tries to
+execute the same code, they won't be able to enter the block until the first
+thread exits.
+
+This makes !SyncBlk useful for detecting managed deadlocks. Consider that the
+following code is executed by Threads A & B:
+
+ Resource r1 = new Resource();
+ Resource r2 = new Resource();
+
+ ...
+
+ lock(r1) lock(r2)
+ { {
+ lock(r2) lock(r1)
+ { {
+ ... ...
+ } }
+ } }
+
+This is a deadlock situation, as Thread A could take r1, and Thread B r2,
+leaving both threads with no option but to wait forever in the second lock
+statement. !SyncBlk will detect this with the following output:
+
+ 0:003> !syncblk
+ Index SyncBlock MonitorHeld Recursion Owning Thread Info SyncBlock Owner
+ 238 001e40ec 3 1 001e4e60 e04 3 00a7a194 Resource
+ 239 001e4124 3 1 001e5980 ab8 4 00a7a1a4 Resource
+
+It means that Thread e04 owns object 00a7a194, and Thread ab8 owns object
+00a7a1a4. Combine that information with the call stacks of the deadlock:
+
+(threads 3 and 4 have similar output)
+ 0:003> k
+ ChildEBP RetAddr
+ 0404ea04 77f5c524 SharedUserData!SystemCallStub+0x4
+ 0404ea08 77e75ee0 ntdll!NtWaitForMultipleObjects+0xc
+ 0404eaa4 5d9de9d6 KERNEL32!WaitForMultipleObjectsEx+0x12c
+ 0404eb38 5d9def80 clr!Thread::DoAppropriateAptStateWait+0x156
+ 0404ecc4 5d9dd8bb clr!Thread::DoAppropriateWaitWorker+0x360
+ 0404ed20 5da628dd clr!Thread::DoAppropriateWait+0xbb
+ 0404ede4 5da4e2e2 clr!CLREvent::Wait+0x29d
+ 0404ee70 5da4dd41 clr!AwareLock::EnterEpilog+0x132
+ 0404ef34 5da4efa3 clr!AwareLock::Enter+0x2c1
+ 0404f09c 5d767880 clr!AwareLock::Contention+0x483
+ 0404f1c4 03f00229 clr!JITutil_MonContention+0x2c0
+ 0404f1f4 5b6ef077 image00400000!Worker.Work()+0x79
+ ...
+
+By looking at the code corresponding to Worker.Work()+0x79 (run "!u 03f00229"),
+you can see that thread 3 is attempting to acquire the Resource 00a7a1a4, which
+is owned by thread 4.
+
+NOTE:
+It is not always the case that a SyncBlock will be created for every object
+that is locked by a thread. In version 2.0 of the CLR and above, a mechanism
+called a ThinLock will be used if there is not already a SyncBlock for the
+object in question. ThinLocks will not be reported by the !SyncBlk command.
+You can use "!DumpHeap -thinlock" to list objects locked in this way.
+\\
+
+COMMAND: dumpmt.
+!DumpMT [-MD] <MethodTable address>
+
+Examine a MethodTable. Each managed object has a MethodTable pointer at the
+start. If you pass the "-MD" flag, you'll also see a list of all the methods
+defined on the object.
+\\
+
+COMMAND: dumpclass.
+!DumpClass <EEClass address>
+
+The EEClass is a data structure associated with an object type. !DumpClass
+will show attributes, as well as list the fields of the type. The output is
+similar to !DumpObj. Although static field values will be displayed,
+non-static values won't because you need an instance of an object for that.
+
+You can get an EEClass to look at from !DumpMT, !DumpObj, !Name2EE, and
+!Token2EE among others.
+\\
+
+COMMAND: dumpmd.
+!DumpMD <MethodDesc address>
+
+This command lists information about a MethodDesc. You can use !IP2MD to turn
+a code address in a managed function into a MethodDesc:
+
+ 0:000> !dumpmd 902f40
+ Method Name: Mainy.Main()
+ Class: 03ee1424
+ MethodTable: 009032d8
+ mdToken: 0600000d
+ Module: 001caa78
+ IsJitted: yes
+ CodeAddr: 03ef00b8
+
+If IsJitted is "yes," you can run !U on the CodeAddr pointer to see a
+disassembly of the JITTED code. You can also call !DumpClass, !DumpMT,
+!DumpModule on the Class, MethodTable and Module fields above.
+\\
+
+COMMAND: token2ee.
+!Token2EE <module name> <token>
+
+This function allows you to turn a metadata token into a MethodTable or
+MethodDesc. Here is an example showing class tokens being resolved:
+
+ 0:000> !token2ee unittest.exe 02000003
+ Module: 001caa38
+ Token: 0x02000003
+ MethodTable: 0090375c
+ EEClass: 03ee1ae0
+ Name: Bank
+ 0:000> !token2ee image00400000 02000004
+ Module: 001caa38
+ Token: 0x02000004
+ MethodTable: 009038ec
+ EEClass: 03ee1b84
+ Name: Customer
+
+The module you are "browsing" with Token2EE needs to be loaded in the process.
+This function doesn't see much use, especially since a tool like ILDASM can
+show the mapping between metadata tokens and types/methods in a friendlier way.
+But it could be handy sometimes.
+
+You can pass "*" for <module name> to find what that token maps to in every
+loaded managed module. <module name> can also be the debugger's name for a
+module, such as mscorlib or image00400000.
+\\
+
+COMMAND: eeversion.
+!EEVersion
+
+This prints the Common Language Runtime version. It also tells you if the code
+is running in "Workstation" or "Server" mode, a distinction which affects the
+garbage collector. The most apparent difference in the debugger is that in
+"Server" mode there is one dedicated garbage collector thread per CPU.
+
+A handy supplement to this function is to also run "lm v m clr". That
+will provide more details about the CLR, including where clr.dll is
+loaded from.
+\\
+
+COMMAND: dumpmodule.
+!DumpModule [-mt] <Module address>
+
+You can get a Module address from !DumpDomain, !DumpAssembly and other
+functions. Here is sample output:
+
+ 0:000> !DumpModule 1caa50
+ Name: C:\pub\unittest.exe
+ Attributes: PEFile
+ Assembly: 001ca248
+ LoaderHeap: 001cab3c
+ TypeDefToMethodTableMap: 03ec0010
+ TypeRefToMethodTableMap: 03ec0024
+ MethodDefToDescMap: 03ec0064
+ FieldDefToDescMap: 03ec00a4
+ MemberRefToDescMap: 03ec00e8
+ FileReferencesMap: 03ec0128
+ AssemblyReferencesMap: 03ec012c
+ MetaData start address: 00402230 (1888 bytes)
+
+The Maps listed map metadata tokens to CLR data structures. Without going into
+too much detail, you can examine memory at those addresses to find the
+appropriate structures. For example, the TypeDefToMethodTableMap above can be
+examined:
+
+ 0:000> dd 3ec0010
+ 03ec0010 00000000 00000000 0090320c 0090375c
+ 03ec0020 009038ec ...
+
+This means TypeDef token 2 maps to a MethodTable with the value 0090320c. You
+can run !DumpMT to verify that. The MethodDefToDescMap takes a MethodDef token
+and maps it to a MethodDesc, which can be passed to !DumpMD.
+
+There is a new option "-mt", which will display the types defined in a module,
+and the types referenced by the module. For example:
+
+ 0:000> !dumpmodule -mt 1aa580
+ Name: C:\pub\unittest.exe
+ ...<etc>...
+ MetaData start address: 0040220c (1696 bytes)
+
+ Types defined in this module
+
+ MT TypeDef Name
+ --------------------------------------------------------------------------
+ 030d115c 0x02000002 Funny
+ 030d1228 0x02000003 Mainy
+
+ Types referenced in this module
+
+ MT TypeRef Name
+ --------------------------------------------------------------------------
+ 030b6420 0x01000001 System.ValueType
+ 030b5cb0 0x01000002 System.Object
+ 030fceb4 0x01000003 System.Exception
+ 0334e374 0x0100000c System.Console
+ 03167a50 0x0100000e System.Runtime.InteropServices.GCHandle
+ 0336a048 0x0100000f System.GC
+
+\\
+
+COMMAND: threadpool.
+!ThreadPool
+
+This command lists basic information about the ThreadPool, including the number
+of work requests in the queue, number of completion port threads, and number of
+timers.
+\\
+
+COMMAND: dumpassembly.
+!DumpAssembly <Assembly address>
+
+Example output:
+
+ 0:000> !dumpassembly 1ca248
+ Parent Domain: 0014f000
+ Name: C:\pub\unittest.exe
+ ClassLoader: 001ca060
+ Module Name
+ 001caa50 C:\pub\unittest.exe
+
+An assembly can consist of multiple modules, and those will be listed. You can
+get an Assembly address from the output of !DumpDomain.
+\\
+
+COMMAND: dumpruntimetypes.
+!DumpRuntimeTypes
+
+!DumpRuntimeTypes finds all System.RuntimeType objects in the gc heap and
+prints the type name and MethodTable they refer too. Sample output:
+
+ Address Domain MT Type Name
+ ------------------------------------------------------------------------------
+ a515f4 14a740 5baf8d28 System.TypedReference
+ a51608 14a740 5bb05764 System.Globalization.BaseInfoTable
+ a51958 14a740 5bb05b24 System.Globalization.CultureInfo
+ a51a44 14a740 5bb06298 System.Globalization.GlobalizationAssembly
+ a51de0 14a740 5bb069c8 System.Globalization.TextInfo
+ a56b98 14a740 5bb12d28 System.Security.Permissions.HostProtectionResource
+ a56bbc 14a740 5baf7248 System.Int32
+ a56bd0 14a740 5baf3fdc System.String
+ a56cfc 14a740 5baf36a4 System.ValueType
+ ...
+
+This command will print a "?" in the domain column if the type is loaded into multiple
+AppDomains. For example:
+
+ 0:000> !DumpRuntimeTypes
+ Address Domain MT Type Name
+ ------------------------------------------------------------------------------
+ 28435a0 ? 3f6a8c System.TypedReference
+ 28435b4 ? 214d6c System.ValueType
+ 28435c8 ? 216314 System.Enum
+ 28435dc ? 2147cc System.Object
+ 284365c ? 3cd57c System.IntPtr
+ 2843670 ? 3feaac System.Byte
+ 2843684 ? 23a544c System.IEquatable`1[[System.IntPtr, mscorlib]]
+ 2843784 ? 3c999c System.Int32
+ 2843798 ? 3caa04 System.IEquatable`1[[System.Int32, mscorlib]]
+
+\\
+
+COMMAND: dumpsig.
+!DumpSig <sigaddr> <moduleaddr>
+
+This command dumps the signature of a method or field given by <sigaddr>. This is
+useful when you are debugging parts of the runtime which returns a raw PCCOR_SIGNATURE
+structure and need to know what its contents are.
+
+Sample output for a method:
+ 0:000> !dumpsig 0x000007fe`ec20879d 0x000007fe`eabd1000
+ [DEFAULT] [hasThis] Void (Boolean,String,String)
+
+The first section of the output is the calling convention. This includes, but is not
+limited to, "[DEFAULT]", "[C]", "[STDCALL]", "[THISCALL]", and so on. The second
+portion of the output is either "[hasThis]" or "[explicit]" for whether the method
+is an instance method or a static method respectively. The third portion of the
+output is the return value (in this case a "void"). Finally, the method's arguments
+are printed as the final portion of the output.
+
+Sample output for a field:
+ 0:000> !dumpsig 0x000007fe`eb7fd8cd 0x000007fe`eabd1000
+ [FIELD] ValueClass System.RuntimeTypeHandle
+
+!DumpSig will also work with generics. Here is the output for the following
+function:
+ public A Test(IEnumerable<B> n)
+
+ 0:000> !dumpsig 00000000`00bc2437 000007ff00043178
+ [DEFAULT] [hasThis] __Canon (Class System.Collections.Generic.IEnumerable`1<__Canon>)
+
+\\
+
+COMMAND: dumpsigelem.
+!DumpSigElem <sigaddr> <moduleaddr>
+
+This command dumps a single element of a signature object. For most circumstances,
+you should use !DumpSig to look at individual signature objects, but if you find a
+signature that has been corrupted in some manner you can use !DumpSigElem to read out
+the valid portions of it.
+
+If we look at a valid signature object for a method we see the following:
+ 0:000> !dumpsig 0x000007fe`ec20879d 0x000007fe`eabd1000
+ [DEFAULT] [hasThis] Void (Boolean,String,String)
+
+We can look at the individual elements of this object by adding the offsets into the
+object which correspond to the return value and parameters:
+ 0:000> !dumpsigelem 0x000007fe`ec20879d+2 0x000007fe`eabd1000
+ Void
+ 0:000> !dumpsigelem 0x000007fe`ec20879d+3 0x000007fe`eabd1000
+ Boolean
+ 0:000> !dumpsigelem 0x000007fe`ec20879d+4 0x000007fe`eabd1000
+ String
+ 0:000> !dumpsigelem 0x000007fe`ec20879d+5 0x000007fe`eabd1000
+ String
+
+We can do something similar for fields. Here is the full signature of a field:
+ 0:000> !dumpsig 0x000007fe`eb7fd8cd 0x000007fe`eabd1000
+ [FIELD] ValueClass System.RuntimeTypeHandle
+
+Using !DumpSigElem we can find the type of the field by adding the offset of it (1) to
+the address of the signature:
+ 0:000> !dumpsigelem 0x000007fe`eb7fd8cd+1 0x000007fe`eabd1000
+ ValueClass System.RuntimeTypeHandle
+
+!DumpSigElem will also work with generics. Let a function be defined as follows:
+ public A Test(IEnumerable<B> n)
+
+The elements of this signature can be obtained by adding offsets into the signature
+when calling !DumpSigElem:
+
+ 0:000> !dumpsigelem 00000000`00bc2437+2 000007ff00043178
+ __Canon
+ 0:000> !dumpsigelem 00000000`00bc2437+4 000007ff00043178
+ Class System.Collections.Generic.IEnumerable`1<__Canon>
+
+The actual offsets that you should add are determined by the contents of the
+signature itself. By trial and error you should be able to find various elements
+of the signature.
+
+\\
+
+COMMAND: rcwcleanuplist.
+!RCWCleanupList [address]
+
+A RuntimeCallableWrapper is an internal CLR structure used to host COM objects
+which are exposed to managed code. This is exposed to managed code through the
+System.__ComObject class, and when objects of this type are collected, and a
+reference to the underlying COM object is no longer needed, the corresponding
+RCW is cleaned up. If you are trying to debug an issue related to one of these
+RCWs, then you can use the !RCWCleanupList function to display which COM objects
+will be released the next time a cleanup occurs.
+
+If given an address, this function will display the RCWCleanupList at that address.
+If no address is specified, it displays the default cleanup list, printing the
+wrapper, the context, and the thread of the object.
+
+Example:
+ 0:002> !rcwcleanuplist 001c04d0
+ RuntimeCallableWrappers (RCW) to be cleaned:
+ RCW CONTEXT THREAD Apartment
+ 1d54e0 192008 181180 STA
+ 1d4140 192178 0 MTA
+ 1dff50 192178 0 MTA
+ MTA Interfaces to be released: 2
+ STA Interfaces to be released: 1
+
+Note that CLR keeps track of which RCWs are bound to which managed objects through
+the SyncBlock of the object. As such, you can see more information about RCW
+objects through the !SyncBlk command. You can find more information about RCW
+cleanup through the !FinalizeQueue command.
+
+\\
+
+COMMAND: dumpil.
+!DumpIL <Managed DynamicMethod object> |
+ <DynamicMethodDesc pointer> |
+ <MethodDesc pointer> |
+ /i <IL pointer>
+
+!DumpIL prints the IL code associated with a managed method. We added this
+function specifically to debug DynamicMethod code which was constructed on
+the fly. Happily it works for non-dynamic code as well.
+
+You can use it in four ways:
+
+ 1) If you have a System.Reflection.Emit.DynamicMethod object, just pass
+ the pointer as the first argument.
+ 2) If you have a DynamicMethodDesc pointer you can use that to print the
+ IL associated with the dynamic method.
+ 3) If you have an ordinary MethodDesc, you can see the IL for that as well,
+ just pass it as the first argument.
+ 4) If you have a pointer directly to the IL, specify /i followed by the
+ the IL address. This is useful for writers of profilers that instrument
+ IL.
+
+
+Note that dynamic IL is constructed a bit differently. Rather than referring
+to metadata tokens, the IL points to objects in a managed object array. Here
+is a simple example of the output for a dynamic method:
+
+ 0:000> !dumpil b741dc
+ This is dynamic IL. Exception info is not reported at this time.
+ If a token is unresolved, run "!do <addr>" on the addr given
+ in parenthesis. You can also look at the token table yourself, by
+ running "!DumpArray 00b77388".
+
+ IL_0000: ldstr 70000002 "Inside invoked method "
+ IL_0005: call 6000003 System.Console.WriteLine(System.String)
+ IL_000a: ldc.i4.1
+ IL_000b: newarr 2000004 "System.Int32"
+ IL_0010: stloc.0
+ IL_0011: ldloc.0
+ IL_0012: ret
+
+\\
+
+COMMAND: verifyheap.
+!VerifyHeap
+
+!VerifyHeap is a diagnostic tool that checks the garbage collected heap for
+signs of corruption. It walks objects one by one in a pattern like this:
+
+ o = firstobject;
+ while(o != endobject)
+ {
+ o.ValidateAllFields();
+ o = (Object *) o + o.Size();
+ }
+
+If an error is found, !VerifyHeap will report it. I'll take a perfectly good
+object and corrupt it:
+
+ 0:000> !DumpObj a79d40
+ Name: Customer
+ MethodTable: 009038ec
+ EEClass: 03ee1b84
+ Size: 20(0x14) bytes
+ (C:\pub\unittest.exe)
+ Fields:
+ MT Field Offset Type Attr Value Name
+ 009038ec 4000008 4 CLASS instance 00a79ce4 name
+ 009038ec 4000009 8 CLASS instance 00a79d2c bank
+ 009038ec 400000a c System.Boolean instance 1 valid
+
+ 0:000> ed a79d40+4 01 (change the name field to the bogus pointer value 1)
+ 0:000> !VerifyHeap
+ object 01ee60dc: bad member 00000003 at 01EE6168
+ Last good object: 01EE60C4.
+
+If this gc heap corruption exists, there is a serious bug in your own code or
+in the CLR. In user code, an error in constructing PInvoke calls can cause
+this problem, and running with Managed Debugging Assistants is advised. If that
+possibility is eliminated, consider contacting Microsoft Product Support for
+help.
+
+\\
+
+COMMAND: verifyobj.
+!VerifyObj <object address>
+
+!VerifyObj is a diagnostic tool that checks the object that is passed as an
+argument for signs of corruption.
+
+ 0:002> !verifyobj 028000ec
+ object 0x28000ec does not have valid method table
+
+ 0:002> !verifyobj 0680017c
+ object 0x680017c: bad member 00000001 at 06800184
+
+\\
+
+COMMAND: findroots.
+!FindRoots -gen <N> | -gen any | <object address>
+
+The "-gen" form causes the debugger to break in the debuggee on the next
+collection of the specified generation. The effect is reset as soon as the
+break occurs, in other words, if you need to break on the next collection you
+would need to reissue the command.
+
+The last form of this command is meant to be used after the break caused by the
+other forms has occurred. Now the debuggee is in the right state for
+!FindRoots to be able to identify roots for objects from the current condemned
+generations.
+
+!FindRoots is a diagnostic command that is meant to answer the following
+question:
+
+"I see that GCs are happening, however my objects have still not been
+collected. Why? Who is holding onto them?"
+
+The process of answering the question would go something like this:
+
+1. Find out the generation of the object of interest using the !GCWhere
+command, say it is gen 1:
+ !GCWhere <object address>
+
+2. Instruct the runtime to stop the next time it collects that generation using
+the !FindRoots command:
+ !FindRoots -gen 1
+ g
+
+3. When the next GC starts, and has proceeded past the mark phase a CLR
+notification will cause a break in the debugger:
+ (fd0.ec4): CLR notification exception - code e0444143 (first chance)
+ CLR notification: GC - end of mark phase.
+ Condemned generation: 1.
+
+4. Now we can use the !FindRoots <object address> to find out the cross
+generational references to the object of interest. In other words, even if the
+object is not referenced by any "proper" root it may still be referenced by an
+older object (from an older generation), from a generation that has not yet been
+scheduled for collection. At this point !FindRoots will search those older
+generations too, and report those roots.
+ 0:002> !findroots 06808094
+ older generations::Root: 068012f8(AAA.Test+a)->
+ 06808094(AAA.Test+b)
+
+
+\\
+
+COMMAND: heapstat.
+!HeapStat [-inclUnrooted | -iu]
+
+This command shows the generation sizes for each heap and the total, how much free
+space there is in each generation on each heap. If the -inclUnrooted option is
+specified the report will include information about the managed objects from the
+GC heap that are not rooted anymore.
+
+Sample output:
+
+ 0:002> !heapstat
+ Heap Gen0 Gen1 Gen2 LOH
+ Heap0 177904 12 306956 8784
+ Heap1 159652 12 12 16
+ Total 337556 24 306968 8800
+
+ Free space: Percentage
+ Heap0 28 12 12 64 SOH: 0% LOH: 0%
+ Heap1 104 12 12 16 SOH: 0% LOH:100%
+ Total 132 24 24 80
+
+ 0:002> !heapstat -inclUnrooted
+ Heap Gen0 Gen1 Gen2 LOH
+ Heap0 177904 12 306956 8784
+ Heap1 159652 12 12 16
+ Total 337556 24 306968 8800
+
+ Free space: Percentage
+ Heap0 28 12 12 64 SOH: 0% LOH: 0%
+ Heap1 104 12 12 16 SOH: 0% LOH:100%
+ Total 132 24 24 80
+
+ Unrooted objects: Percentage
+ Heap0 152212 0 306196 0 SOH: 94% LOH: 0%
+ Heap1 155704 0 0 0 SOH: 97% LOH: 0%
+ Total 307916 0 306196 0
+
+The percentage column contains a breakout of free or unrooted bytes to total bytes.
+
+\\
+
+COMMAND: analyzeoom.
+!AnalyzeOOM
+
+!AnalyzeOOM displays the info of the last OOM occurred on an allocation request to
+the GC heap (in Server GC it displays OOM, if any, on each GC heap).
+
+To see the managed exception(s) use the !Threads command which will show you
+managed exception(s), if any, on each managed thread. If you do see an
+OutOfMemoryException exception you can use the !PrintException command on it.
+To get the full callstack use the "kb" command in the debugger for that thread.
+For example, to display thread 3's stack use ~3kb.
+
+OOM exceptions could be because of the following reasons:
+
+1) allocation request to GC heap
+ in which case you will see JIT_New* on the call stack because managed code called new.
+2) other runtime allocation failure
+ for example, failure to expand the finalize queue when GC.ReRegisterForFinalize is
+ called.
+3) some other code you use throws a managed OOM exception
+ for example, some .NET framework code converts a native OOM exception to managed
+ and throws it.
+
+The !AnalyzeOOM command aims to help you with investigating 1) which is the most
+difficult because it requires some internal info from GC. The only exception is
+we don't support allocating objects larger than 2GB on CLR v2.0 or prior. And this
+command will not display any managed OOM because we will throw OOM right away
+instead of even trying to allocate it on the GC heap.
+
+There are 2 legitimate scenarios where GC would return OOM to allocation requests -
+one is if the process is running out of VM space to reserve a segment; the other
+is if the system is running out physical memory (+ page file if you have one) so
+GC can not commit memory it needs. You can look at these scenarios by using performance
+counters or debugger commands. For example for the former scenario the "!address
+-summary" debugger command will show you the largest free region in the VM. For
+the latter scenario you can look at the "Memory\% Committed Bytes In Use" see
+if you are running low on commit space. One important thing to keep in mind is
+when you do this kind of memory analysis it could an aftereffect and doesn't
+completely agree with what this command tells you, in which case the command should
+be respected because it truly reflects what happened during GC.
+
+The other cases should be fairly obvious from the callstack.
+
+Sample output:
+
+0:011> !ao
+---------Heap 2 ---------
+Managed OOM occurred after GC #28 (Requested to allocate 1234 bytes)
+Reason: Didn't have enough memory to commit
+Detail: SOH: Didn't have enough memory to grow the internal GC datastructures (800000 bytes) -
+ on GC entry available commit space was 500 MB
+---------Heap 4 ---------
+Managed OOM occurred after GC #12 (Requested to allocate 100000 bytes)
+Reason: Didn't have enough memory to allocate an LOH segment
+Detail: LOH: Failed to reserve memory (16777216 bytes)
+
+\\
+
+COMMAND: gcwhere.
+!GCWhere <object address>
+
+!GCWhere displays the location in the GC heap of the argument passed in.
+
+ 0:002> !GCWhere 02800038
+ Address Gen Heap segment begin allocated size
+ 02800038 2 0 02800000 02800038 0282b740 12
+
+When the argument lies in the managed heap, but is not a valid *object* address
+the "size" is displayed as 0:
+
+ 0:002> !GCWhere 0280003c
+ Address Gen Heap segment begin allocated size
+ 0280003c 2 0 02800000 02800038 0282b740 0
+
+\\
+
+COMMAND: listnearobj.
+!ListNearObj <object address>
+
+!ListNearObj is a diagnostic tool that displays the object preceeding and
+succeeding the address passed in:
+
+The command looks for the address in the GC heap that looks like a valid
+beginning of a managed object (based on a valid method table) and the object
+following the argument address.
+
+ 0:002> !ListNearObj 028000ec
+ Before: 0x28000a4 72 (0x48 ) System.StackOverflowException
+ After: 0x2800134 72 (0x48 ) System.Threading.ThreadAbortException
+ Heap local consistency confirmed.
+
+ 0:002> !ListNearObj 028000f0
+ Before: 0x28000ec 72 (0x48 ) System.ExecutionEngineException
+ After: 0x2800134 72 (0x48 ) System.Threading.ThreadAbortException
+ Heap local consistency confirmed.
+
+The command considers the heap as "locally consistent" if:
+ prev_obj_addr + prev_obj_size = arg_addr && arg_obj + arg_size = next_obj_addr
+OR
+ prev_obj_addr + prev_obj_size = next_obj_addr
+
+When the condition is not satisfied:
+
+ 0:002> !lno 028000ec
+ Before: 0x28000a4 72 (0x48 ) System.StackOverflowException
+ After: 0x2800134 72 (0x48 ) System.Threading.ThreadAbortException
+ Heap local consistency not confirmed.
+
+\\
+
+COMMAND: dumplog.
+!DumpLog [-addr <addressOfStressLog>] [<Filename>]
+
+To aid in diagnosing hard-to-reproduce stress failures, the CLR team added an
+in-memory log capability. The idea was to avoid using locks or I/O which could
+disturb a fragile repro environment. The !DumpLog function allows you to write
+that log out to a file. If no Filename is specified, the file "Stresslog.txt"
+in the current directory is created.
+
+The optional argument addr allows one to specify a stress log other then the
+default one.
+
+ 0:000> !DumpLog
+ Attempting to dump Stress log to file 'StressLog.txt'
+ .................
+ SUCCESS: Stress log dumped
+
+To turn on the stress log, set the following registry keys under
+HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework:
+
+
+(DWORD) StressLog = 1
+(DWORD) LogFacility = 0xffffffbf (this is a bit mask, almost all logging is on.
+ This is also the default value if the key
+ isn't specified)
+(DWORD) StressLogSize = 65536 (this is the default value if the key isn't
+ specified)
+(DWORD) LogLevel = 6 (this is the default value if the key isn't
+ specified. The higher the number the more
+ detailed logs are generated. The maximum
+ value is decimal 10)
+
+StressLogSize is the size in bytes of the in-memory log allocated for each
+thread in the process. In the case above, each thread gets a 64K log. You
+could increase this to get more logging, but more memory will be required for
+this log in the process. For example, 20 threads with 524288 bytes per thread
+has a memory demand of 10 Megabytes. The stress log is circular so new entries
+will replace older ones on threads which have reached their buffer limit.
+
+The log facilities are defined as follows:
+ GC 0x00000001
+ GCINFO 0x00000002
+ STUBS 0x00000004
+ JIT 0x00000008
+ LOADER 0x00000010
+ METADATA 0x00000020
+ SYNC 0x00000040
+ EEMEM 0x00000080
+ GCALLOC 0x00000100
+ CORDB 0x00000200
+ CLASSLOADER 0x00000400
+ CORPROF 0x00000800
+ REMOTING 0x00001000
+ DBGALLOC 0x00002000
+ EH 0x00004000
+ ENC 0x00008000
+ ASSERT 0x00010000
+ VERIFIER 0x00020000
+ THREADPOOL 0x00040000
+ GCROOTS 0x00080000
+ INTEROP 0x00100000
+ MARSHALER 0x00200000
+ IJW 0x00400000
+ ZAP 0x00800000
+ STARTUP 0x01000000
+ APPDOMAIN 0x02000000
+ CODESHARING 0x04000000
+ STORE 0x08000000
+ SECURITY 0x10000000
+ LOCKS 0x20000000
+ BCL 0x40000000
+
+Here is some sample output:
+
+ 3560 9.981137099 : `SYNC` RareEnablePremptiveGC: entering.
+ Thread state = a030
+
+ 3560 9.981135033 : `GC`GCALLOC`GCROOTS` ========== ENDGC 4194 (gen = 2,
+ collect_classes = 0) ==========={
+
+ 3560 9.981125826 : `GC` Segment mem 00C61000 alloc
+ = 00D071F0 used 00D09254 committed 00D17000
+
+ 3560 9.981125726 : `GC` Generation 0 [00CED07C, 00000000
+ ] cur = 00000000
+
+ 3560 9.981125529 : `GC` Generation 1 [00CED070, 00000000
+ ] cur = 00000000
+
+ 3560 9.981125103 : `GC` Generation 2 [00C61000, 00000000
+ ] cur = 00000000
+
+ 3560 9.981124963 : `GC` GC Heap 00000000
+
+ 3560 9.980618994 : `GC`GCROOTS` GcScanHandles (Promotion Phase = 0)
+
+The first column is the OS thread ID for the thread appending to the log,
+the second column is the timestamp, the third is the facility category for the
+log entry, and the fourth contains the log message. The facility field is
+expressed as `facility1`facility2`facility3`. This facilitates the creation of
+filters for displaying only specific message categories. To make sense of this
+log, you would probably want the Shared Source CLI to find out exactly where
+the log comes from.
+\\
+
+COMMAND: findappdomain.
+!FindAppDomain <Object address>
+
+!FindAppDomain will attempt to resolve the AppDomain of an object. For example,
+using an Object Pointer from the output of !DumpStackObjects:
+
+ 0:000> !findappdomain 00a79d98
+ AppDomain: 0014f000
+ Name: unittest.exe
+ ID: 1
+
+You can find out more about the AppDomain with the !DumpDomain command. Not
+every object has enough clues about it's origin to determine the AppDomain.
+Objects with Finalizers are the easiest case, as the CLR needs to be able to
+call those when an AppDomain shuts down.
+\\
+
+COMMAND: savemodule.
+!SaveModule <Base address> <Filename>
+
+This command allows you to take a image loaded in memory and write it to a
+file. This is especially useful if you are debugging a full memory dump, and
+don't have the original DLLs or EXEs. This is most often used to save a managed
+binary to a file, so you can disassemble the code and browse types with ILDASM.
+
+The base address of an image can be found with the "LM" debugger command:
+
+ 0:000> lm
+ start end module name
+ 00400000 00408000 image00400000 (deferred)
+ 10200000 102ac000 MSVCR80D (deferred)
+ 5a000000 5a0b1000 mscoree (deferred)
+ 5a140000 5a29e000 clrjit (deferred)
+ 5b660000 5c440000 mscorlib_dll (deferred)
+ 5d1d0000 5e13c000 clr (deferred)
+ ...
+
+If I wanted to save a copy of clr.dll, I could run:
+
+ 0:000> !SaveModule 5d1d0000 c:\pub\out.tmp
+ 4 sections in file
+ section 0 - VA=1000, VASize=e82da9, FileAddr=400, FileSize=e82e00
+ section 1 - VA=e84000, VASize=24d24, FileAddr=e83200, FileSize=ec00
+ section 2 - VA=ea9000, VASize=5a8, FileAddr=e91e00, FileSize=600
+ section 3 - VA=eaa000, VASize=c183c, FileAddr=e92400, FileSize=c1a00
+
+The diagnostic output indicates that the operation was successful. If
+c:\pub\out.tmp already exists, it will be overwritten.
+\\
+
+COMMAND: gchandles.
+!GCHandles [-type handletype] [-stat] [-perdomain]
+
+!GCHandles provides statistics about GCHandles in the process.
+
+Paremeters:
+ stat - Only display the statistics and not the list of handles and
+ what they point to.
+ perdomain - Break down the statistics by the app domain in which
+ the handles reside.
+ type - A type of handle to filter it by. The handle types are:
+ Pinned
+ RefCounted
+ WeakShort
+ WeakLong
+ Strong
+ Variable
+ AsyncPinned
+ SizedRef
+
+Sometimes the source of a memory leak is a GCHandle leak. For example, code
+might keep a 50 Megabyte array alive because a strong GCHandle points to it,
+and the handle was discarded without freeing it.
+
+The most common handles are "Strong Handles," which keep the object they point
+to alive until the handle is explicitly freed. "Pinned Handles" are used to
+prevent the garbage collector from moving an object during collection. These
+should be used sparingly, and for short periods of time. If you don't follow
+that precept, the gc heap can become very fragmented.
+
+Here is sample output from a very simple program. Note that the "RefCount"
+field only applies to RefCount Handles, and this field will contain the
+reference count:
+
+ 0:000> !GCHandles
+ Handle Type Object Size RefCount Type
+ 001611c0 Strong 01d00b58 84 System.IndexOutOfRangeException
+ 001611c4 Strong 01d00b58 84 System.IndexOutOfRangeException
+ 001611c8 Strong 01d1b48c 40 System.Diagnostics.LogSwitch
+ 001611d0 Strong 01cfd2c0 36 System.Security.PermissionSet
+ 001611d4 Strong 01cf7484 56 System.Object[]
+ 001611d8 Strong 01cf1238 32 System.SharedStatics
+ 001611dc Strong 01cf11c8 84 System.Threading.ThreadAbortException
+ 001611e0 Strong 01cf1174 84 System.Threading.ThreadAbortException
+ 001611e4 Strong 01cf1120 84 System.ExecutionEngineException
+ 001611e8 Strong 01cf10cc 84 System.StackOverflowException
+ 001611ec Strong 01cf1078 84 System.OutOfMemoryException
+ 001611f0 Strong 01cf1024 84 System.Exception
+ 001611f8 Strong 01cf2068 48 System.Threading.Thread
+ 001611fc Strong 01cf1328 112 System.AppDomain
+ 001613ec Pinned 02cf3268 8176 System.Object[]
+ 001613f0 Pinned 02cf2258 4096 System.Object[]
+ 001613f4 Pinned 02cf2038 528 System.Object[]
+ 001613f8 Pinned 01cf121c 12 System.Object
+ 001613fc Pinned 02cf1010 4116 System.Object[]
+
+ Statistics:
+ MT Count TotalSize Class Name
+ 563266dc 1 12 System.Object
+ 56329708 1 32 System.SharedStatics
+ 5632bc38 1 36 System.Security.PermissionSet
+ 5635f934 1 40 System.Diagnostics.LogSwitch
+ 5632759c 1 48 System.Threading.Thread
+ 5632735c 1 84 System.ExecutionEngineException
+ 56327304 1 84 System.StackOverflowException
+ 563272ac 1 84 System.OutOfMemoryException
+ 563270c4 1 84 System.Exception
+ 56328914 1 112 System.AppDomain
+ 56335f78 2 168 System.IndexOutOfRangeException
+ 563273b4 2 168 System.Threading.ThreadAbortException
+ 563208d0 5 16972 System.Object[]
+ Total 19 objects
+
+ Handles:
+ Strong Handles: 14
+ Pinned Handles: 5
+\\
+
+COMMAND: gchandleleaks.
+!GCHandleLeaks
+
+This command is an aid in tracking down GCHandle leaks. It searches all of
+memory for any references to the Strong and Pinned GCHandles in the process,
+and reports what it found. If a handle is found, you'll see the address of the
+reference. This might be a stack address or a field within an object, for
+example. If a handle is not found in memory, you'll get notification of that
+too.
+
+The command has diagnostic output which doesn't need to be repeated here. One
+thing to keep in mind is that anytime you search all of memory for a value, you
+can get false positives because even though the value was found, it might be
+garbage in that no code knows about the address. You can also get false
+negatives because a user is free to pass that GCHandle to unmanaged code that
+might store the handle in a strange way (shifting bits, for example).
+
+For example, a GCHandle valuetype is stored on the stack with the low bit set
+if it points to a Pinned handle. So !GCHandleLeaks ignores the low bit in it's
+searches.
+
+That said, if a serious leak is going on, you'll get a ever-growing set of
+handle addresses that couldn't be found.
+\\
+
+COMMAND: vmmap.
+!VMMap
+
+!VMMap traverses the virtual address space and lists the type of protection
+applied to each region. Sample output:
+
+ 0:000> !VMMap
+ Start Stop Length AllocProtect Protect State Type
+ 00000000-0000ffff 00010000 NA Free
+ 00010000-00011fff 00002000 RdWr RdWr Commit Private
+ 00012000-0001ffff 0000e000 NA Free
+ 00020000-00020fff 00001000 RdWr RdWr Commit Private
+ 00021000-0002ffff 0000f000 NA Free
+ 00030000-00030fff 00001000 RdWr Reserve Private
+ ...
+\\
+
+COMMAND: vmstat.
+!VMStat
+
+Provides a summary view of the virtual address space, ordered by each type of
+protection applied to that memory (free, reserved, committed, private, mapped,
+image). The TOTAL column is (AVERAGE * BLK COUNT). Sample output below:
+
+ 0:000> !VMStat
+ ~~~~ ~~~~~~~ ~~~~~~~ ~~~~~~~ ~~~~~~~~~ ~~~~~
+ TYPE MINIMUM MAXIMUM AVERAGE BLK COUNT TOTAL
+ Free:
+ Small 4,096 65,536 48,393 27 1,306,611
+ Medium 139,264 528,384 337,920 4 1,351,680
+ Large 6,303,744 974,778,368 169,089,706 12 2,029,076,472
+ Summary 4,096 974,778,368 47,249,646 43 2,031,734,778
+
+ Reserve:
+ Small 4,096 65,536 43,957 41 1,802,237
+ Medium 249,856 1,019,904 521,557 6 3,129,342
+ Large 2,461,696 16,703,488 11,956,224 3 35,868,672
+ Summary 4,096 16,703,488 816,005 50 40,800,250
+
+\\
+
+COMMAND: procinfo.
+!ProcInfo [-env] [-time] [-mem]
+
+!ProcInfo lists the environment variables for the process, kernel CPU time, as
+well as memory usage statistics.
+\\
+
+COMMAND: histinit.
+!HistInit
+
+Before running any of the Hist - family commands you need to initialize the SOS
+structures from the stress log saved in the debuggee. This is achieved by the
+HistInit command.
+
+Sample output:
+
+ 0:001> !HistInit
+ Attempting to read Stress log
+ STRESS LOG:
+ facilitiesToLog = 0xffffffff
+ levelToLog = 6
+ MaxLogSizePerThread = 0x10000 (65536)
+ MaxTotalLogSize = 0x1000000 (16777216)
+ CurrentTotalLogChunk = 9
+ ThreadsWithLogs = 3
+ Clock frequency = 3.392 GHz
+ Start time 15:26:31
+ Last message time 15:26:56
+ Total elapsed time 25.077 sec
+ .....................................
+ ---------------------------- 2407 total entries -----------------------------
+
+
+ SUCCESS: GCHist structures initialized
+
+\\
+
+COMMAND: histobjfind.
+!HistObjFind <obj_address>
+
+To examine log entries related to an object whose present address is known one
+would use this command. The output of this command contains all entries that
+reference the object:
+
+ 0:003> !HistObjFind 028970d4
+ GCCount Object Message
+ ---------------------------------------------------------
+ 2296 028970d4 Promotion for root 01e411b8 (MT = 5b6c5cd8)
+ 2296 028970d4 Relocation NEWVALUE for root 00223fc4
+ 2296 028970d4 Relocation NEWVALUE for root 01e411b8
+ ...
+ 2295 028970d4 Promotion for root 01e411b8 (MT = 5b6c5cd8)
+ 2295 028970d4 Relocation NEWVALUE for root 00223fc4
+ 2295 028970d4 Relocation NEWVALUE for root 01e411b8
+ ...
+
+\\
+
+COMMAND: histroot.
+!HistRoot <root>
+
+The root value obtained from !HistObjFind can be used to track the movement of
+an object through the GCs.
+
+HistRoot provides information related to both promotions and relocations of the
+root specified as the argument.
+
+ 0:003> !HistRoot 01e411b8
+ GCCount Value MT Promoted? Notes
+ ---------------------------------------------------------
+ 2296 028970d4 5b6c5cd8 yes
+ 2295 028970d4 5b6c5cd8 yes
+ 2294 028970d4 5b6c5cd8 yes
+ 2293 028970d4 5b6c5cd8 yes
+ 2292 028970d4 5b6c5cd8 yes
+ 2291 028970d4 5b6c5cd8 yes
+ 2290 028970d4 5b6c5cd8 yes
+ 2289 028970d4 5b6c5cd8 yes
+ 2288 028970d4 5b6c5cd8 yes
+ 2287 028970d4 5b6c5cd8 yes
+ 2286 028970d4 5b6c5cd8 yes
+ 2285 028970d4 5b6c5cd8 yes
+ 322 028970e8 5b6c5cd8 yes Duplicate promote/relocs
+ ...
+
+\\
+
+COMMAND: histobj.
+!HistObj <obj_address>
+
+This command examines all stress log relocation records and displays the chain
+of GC relocations that may have led to the address passed in as an argument.
+Conceptually the output is:
+
+ GenN obj_address root1, root2, root3,
+ GenN-1 prev_obj_addr root1, root2,
+ GenN-2 prev_prev_oa root1, root4,
+ ...
+
+Sample output:
+ 0:003> !HistObj 028970d4
+ GCCount Object Roots
+ ---------------------------------------------------------
+ 2296 028970d4 00223fc4, 01e411b8,
+ 2295 028970d4 00223fc4, 01e411b8,
+ 2294 028970d4 00223fc4, 01e411b8,
+ 2293 028970d4 00223fc4, 01e411b8,
+ 2292 028970d4 00223fc4, 01e411b8,
+ 2291 028970d4 00223fc4, 01e411b8,
+ 2290 028970d4 00223fc4, 01e411b8,
+ 2289 028970d4 00223fc4, 01e411b8,
+ 2288 028970d4 00223fc4, 01e411b8,
+ 2287 028970d4 00223fc4, 01e411b8,
+ 2286 028970d4 00223fc4, 01e411b8,
+ 2285 028970d4 00223fc4, 01e411b8,
+ 322 028970d4 01e411b8,
+ 0 028970d4
+
+\\
+
+COMMAND: histclear.
+!HistClear
+
+This command releases any resources used by the Hist-family of commands.
+Generally there's no need to call this explicitly, as each HistInit will first
+cleanup the previous resources.
+
+ 0:003> !HistClear
+ Completed successfully.
+
+\\
+
+COMMAND: dumprcw.
+!DumpRCW <RCW address>
+
+This command lists information about a Runtime Callable Wrapper. You can use
+!DumpObj to obtain the RCW address corresponding to a managed object.
+
+The output contains all COM interface pointers that the RCW holds on to, which
+is useful for investigating lifetime issues of interop-heavy applications.
+\\
+
+COMMAND: dumpccw.
+!DumpCCW <CCW address or COM IP>
+
+This command lists information about a COM Callable Wrapper. You can use
+!DumpObj to obtain the CCW address corresponding to a managed object or pass
+a COM interface pointer to which the object has been marshaled.
+
+The output contains the COM reference count of the CCW, which is useful for
+investigating lifetime issues of interop-heavy applications.
+\\
+
+COMMAND: suppressjitoptimization.
+!SuppressJitOptimization [on|off]
+
+!SuppressJitOptimization helps when you want the CLR to generate more debuggable
+jitted code. This will turn off inlining, enregistering of local variables,
+optimizing across sequence point boundaries, and precise variable lifetimes.
+The resulting code should step more cleanly and make local variables more
+accesible to inspection. The cost is that code quality is lower so your
+application may execute a little slower.
+
+Once you execute !SuppressJitOptimization on, all managed modules that load
+from that point onwards will not be optimized. The setting is only checked once
+per module, at the time that module loads. Changing the setting later will only
+affect modules that are loaded later. The recommendation is to set this once at
+app startup and let the same setting apply for all managed modules.
+\\
+
+COMMAND: stoponcatch.
+!StopOnCatch
+
+This commands sets a one-time breakpoint on the first managed catch clause
+entered by managed code. This will cause the debugger to stop on the first line
+of the catch handler. This is useful step from from the point of an exception
+throw to the catch handler.
+\\
+
+
+COMMAND: savestate.
+!SaveState <file_path>
+
+!SaveState serializes SOS managed breakpoints and watch values into a script file
+that can be executed on a future windbg session to restore them. Use the windbg
+command <$ <file_path> to execute the saved script.
+\\
+
+COMMAND: watch.
+!Watch
+!Watch -add <expression>
+!Watch -remove <index>
+!Watch -save <watch_list_name>
+!Watch -rename <old_watch_list_name> <new_watch_list_name>
+!Watch [-filter <watch_list_name>] [-expand <index> [type_cast]<expression>]
+
+!Watch displays the value of managed expressions, and manages the list of
+expressions. A quick example...
+
+DEBUGGEE CODE:
+ static int Main(string[] args)
+ {
+ int p14 = -123;
+ MyStruct ms = new MyStruct();
+ ms.a = p14;
+ ms.b = true;
+ MyStruct.c = "Foo";
+ ...
+
+COMMAND WINDOW:
+0:000> !Watch -add ms
+0:000> !Watch -add p14
+0:000> !Watch
+ 1) MyStruct ms @ 0x0093D1CC
+ 2) int p14 = -123
+
+ms in the command window will be a DML link, if you click on it then you see:
+
+COMMAND WINDOW:
+0:000> !watch -expand 1 (MyStruct)ms
+ 1) MyStruct ms @ 0x0093D1CC
+ |- int a = -123
+ |- bool b = true
+ |- string c = null
+ 2) int p14 = -123
+
+
+
+Watch is capable of parsing a variety of different expressions. Expressions
+can start with the name of a local variable or parameter in any stack frame.
+To access fields of classes and structures simply add a '.' and then the field
+name. To access array elements use standard [<index>] notation. In the above
+code example these would be valid expressions:
+args[19]
+p14
+ms.a
+
+Additionally 'this' is available in the leafmost frame to access fields.
+You can not use unqualified field names. For example if there is a class:
+class C
+{
+ int m_foo;
+ public void Hello() { Console.WriteLine(m_foo); }
+}
+
+this.m_foo is a legal expression when executing inside C.Hello(),
+m_foo would not work.
+
+Expressions also allow fully qualified type names and dereferencing static
+fields from those types using the same '.' syntax. For example:
+System.Int32
+System.Type.Missing
+
+Generic types can be used, though don't forget to use the CLI type
+name which adds `<number_of_generic_params> to the type name you would see
+in VB or C#. For example:
+System.Collections.Generic.Dictionary`2<System.String,System.Type>
+
+Dereferencing fields or array indices can recurse as you might expect. A
+made up example:
+foo.bar.baz[19][12].m_field[4]
+
+Property values and method invocations can not be used in expressions as
+the !Watch command will never execute any managed code to evaluate the
+expressions.
+
+To remove entries in the list use !Watch -remove <index> where index is
+the number printed next to the expression in the viewing list. In our
+initial example !Watch -remove 2 would remove the 'p14' expression from
+the list.
+
+Saving/Renaming/Filtering
+Sometimes it can be helpful to see only those values which have changed
+since some point in the past. As an example assume that MyStruct.c changes
+values during the go:
+
+0:000> !Watch -save myOldValues
+0:000> g
+...
+0:000> !Watch -filter myOldValues
+ 3) string MyStruct.c = "Foo"
+
+When saving a list of expressions, the string that would be displayed in a
+!Watch command is also saved. It is this string that is compared to determine
+if the value has changed. Some changes might not alter the console string
+representation. In the first example changing the value of ms.a would not
+cause the 'ms' expression to be different.
+
+Renaming can be useful in scripts. It allows the name of a saved watch list to
+change after saving it. For example:
+0:000> !Watch -rename myCurrentValues myOldValues
+0:000> !Watch -save myCurrentValues
+0:000> !Watch -filter myOldValues
+ 3) string MyStruct.c = "Foo"
+
+Placing that pattern inside a script shows the changed watch values
+since the last time the script was run.
+\\
+
diff --git a/src/ToolBox/SOS/Strike/data.h b/src/ToolBox/SOS/Strike/data.h
new file mode 100644
index 0000000000..9989038653
--- /dev/null
+++ b/src/ToolBox/SOS/Strike/data.h
@@ -0,0 +1,51 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// ==++==
+//
+
+//
+// ==--==
+#ifndef __data_h__
+#define __data_h__
+
+#include "cor.h"
+#include "corhdr.h"
+#include "cor.h"
+#include "dacprivate.h"
+
+BOOL FileExist (const char *filename);
+BOOL FileExist (const WCHAR *filename);
+
+// We use global variables
+// because move returns void if it fails
+//typedef DWORD DWORD_PTR;
+//typedef ULONG ULONG_PTR;
+
+// Max length in WCHAR for a buffer to store metadata name
+const int mdNameLen = 2048;
+extern WCHAR g_mdName[mdNameLen];
+
+const int nMDIMPORT = 128;
+struct MDIMPORT
+{
+ enum MDType {InMemory, InFile, Dynamic};
+ WCHAR *name;
+ size_t base; // base of the PE module
+ size_t mdBase; // base of the metadata
+ char *metaData;
+ ULONG metaDataSize;
+ MDType type;
+ IMetaDataImport *pImport;
+
+ MDIMPORT *left;
+ MDIMPORT *right;
+};
+
+class Module;
+
+extern "C" BOOL ControlC;
+extern IMetaDataDispenserEx *pDisp;
+
+#endif // __data_h__
diff --git a/src/ToolBox/SOS/Strike/datatarget.cpp b/src/ToolBox/SOS/Strike/datatarget.cpp
new file mode 100644
index 0000000000..8f24b7d841
--- /dev/null
+++ b/src/ToolBox/SOS/Strike/datatarget.cpp
@@ -0,0 +1,215 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#include "sos.h"
+#include "datatarget.h"
+#include "corhdr.h"
+#include "cor.h"
+#include "dacprivate.h"
+#include "sospriv.h"
+#include "corerror.h"
+
+#define IMAGE_FILE_MACHINE_AMD64 0x8664 // AMD64 (K8)
+
+DataTarget::DataTarget(void) :
+ m_ref(0)
+{
+}
+
+STDMETHODIMP
+DataTarget::QueryInterface(
+ THIS_
+ ___in REFIID InterfaceId,
+ ___out PVOID* Interface
+ )
+{
+ if (InterfaceId == IID_IUnknown ||
+ InterfaceId == IID_ICLRDataTarget)
+ {
+ *Interface = (ICLRDataTarget*)this;
+ AddRef();
+ return S_OK;
+ }
+ else if (InterfaceId == IID_ICorDebugDataTarget4)
+ {
+ *Interface = (ICorDebugDataTarget4*)this;
+ AddRef();
+ return S_OK;
+ }
+ else
+ {
+ *Interface = NULL;
+ return E_NOINTERFACE;
+ }
+}
+
+STDMETHODIMP_(ULONG)
+DataTarget::AddRef(
+ THIS
+ )
+{
+ LONG ref = InterlockedIncrement(&m_ref);
+ return ref;
+}
+
+STDMETHODIMP_(ULONG)
+DataTarget::Release(
+ THIS
+ )
+{
+ LONG ref = InterlockedDecrement(&m_ref);
+ if (ref == 0)
+ {
+ delete this;
+ }
+ return ref;
+}
+
+HRESULT STDMETHODCALLTYPE
+DataTarget::GetMachineType(
+ /* [out] */ ULONG32 *machine)
+{
+ if (g_ExtControl == NULL)
+ {
+ return E_UNEXPECTED;
+ }
+ return g_ExtControl->GetExecutingProcessorType(machine);
+}
+
+HRESULT STDMETHODCALLTYPE
+DataTarget::GetPointerSize(
+ /* [out] */ ULONG32 *size)
+{
+#if defined(SOS_TARGET_AMD64) || defined(SOS_TARGET_ARM64)
+ *size = 8;
+#elif defined(SOS_TARGET_ARM)
+ *size = 4;
+#elif
+ #error Unsupported architecture
+#endif
+
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE
+DataTarget::GetImageBase(
+ /* [string][in] */ LPCWSTR name,
+ /* [out] */ CLRDATA_ADDRESS *base)
+{
+ if (g_ExtSymbols == NULL)
+ {
+ return E_UNEXPECTED;
+ }
+ CHAR lpstr[MAX_LONGPATH];
+ int name_length = WideCharToMultiByte(CP_ACP, 0, name, -1, lpstr, MAX_LONGPATH, NULL, NULL);
+ if (name_length == 0)
+ {
+ return E_FAIL;
+ }
+ return g_ExtSymbols->GetModuleByModuleName(lpstr, 0, NULL, base);
+}
+
+HRESULT STDMETHODCALLTYPE
+DataTarget::ReadVirtual(
+ /* [in] */ CLRDATA_ADDRESS address,
+ /* [length_is][size_is][out] */ PBYTE buffer,
+ /* [in] */ ULONG32 request,
+ /* [optional][out] */ ULONG32 *done)
+{
+ if (g_ExtData == NULL)
+ {
+ return E_UNEXPECTED;
+ }
+ return g_ExtData->ReadVirtual(address, (PVOID)buffer, request, done);
+}
+
+HRESULT STDMETHODCALLTYPE
+DataTarget::WriteVirtual(
+ /* [in] */ CLRDATA_ADDRESS address,
+ /* [size_is][in] */ PBYTE buffer,
+ /* [in] */ ULONG32 request,
+ /* [optional][out] */ ULONG32 *done)
+{
+ if (g_ExtData == NULL)
+ {
+ return E_UNEXPECTED;
+ }
+ return g_ExtData->WriteVirtual(address, (PVOID)buffer, request, done);
+}
+
+HRESULT STDMETHODCALLTYPE
+DataTarget::GetTLSValue(
+ /* [in] */ ULONG32 threadID,
+ /* [in] */ ULONG32 index,
+ /* [out] */ CLRDATA_ADDRESS* value)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT STDMETHODCALLTYPE
+DataTarget::SetTLSValue(
+ /* [in] */ ULONG32 threadID,
+ /* [in] */ ULONG32 index,
+ /* [in] */ CLRDATA_ADDRESS value)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT STDMETHODCALLTYPE
+DataTarget::GetCurrentThreadID(
+ /* [out] */ ULONG32* threadID)
+{
+ if (g_ExtSystem == NULL)
+ {
+ return E_UNEXPECTED;
+ }
+ return g_ExtSystem->GetCurrentThreadSystemId(threadID);
+}
+
+HRESULT STDMETHODCALLTYPE
+DataTarget::GetThreadContext(
+ /* [in] */ ULONG32 threadID,
+ /* [in] */ ULONG32 contextFlags,
+ /* [in] */ ULONG32 contextSize,
+ /* [out, size_is(contextSize)] */ PBYTE context)
+{
+ if (g_ExtSystem == NULL)
+ {
+ return E_UNEXPECTED;
+ }
+ return g_ExtSystem->GetThreadContextById(threadID, contextFlags, contextSize, context);
+}
+
+HRESULT STDMETHODCALLTYPE
+DataTarget::SetThreadContext(
+ /* [in] */ ULONG32 threadID,
+ /* [in] */ ULONG32 contextSize,
+ /* [out, size_is(contextSize)] */ PBYTE context)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT STDMETHODCALLTYPE
+DataTarget::Request(
+ /* [in] */ ULONG32 reqCode,
+ /* [in] */ ULONG32 inBufferSize,
+ /* [size_is][in] */ BYTE *inBuffer,
+ /* [in] */ ULONG32 outBufferSize,
+ /* [size_is][out] */ BYTE *outBuffer)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT STDMETHODCALLTYPE
+DataTarget::VirtualUnwind(
+ /* [in] */ DWORD threadId,
+ /* [in] */ ULONG32 contextSize,
+ /* [in, out, size_is(contextSize)] */ PBYTE context)
+{
+ if (g_ExtServices == NULL)
+ {
+ return E_UNEXPECTED;
+ }
+ return g_ExtServices->VirtualUnwind(threadId, contextSize, context);
+}
diff --git a/src/ToolBox/SOS/Strike/datatarget.h b/src/ToolBox/SOS/Strike/datatarget.h
new file mode 100644
index 0000000000..0293bc668b
--- /dev/null
+++ b/src/ToolBox/SOS/Strike/datatarget.h
@@ -0,0 +1,90 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+class DataTarget : public ICLRDataTarget, ICorDebugDataTarget4
+{
+private:
+ LONG m_ref; // Reference count.
+
+public:
+ DataTarget(void);
+ virtual ~DataTarget() {}
+
+ // IUnknown.
+ STDMETHOD(QueryInterface)(
+ THIS_
+ ___in REFIID InterfaceId,
+ ___out PVOID* Interface
+ );
+ STDMETHOD_(ULONG, AddRef)(
+ THIS
+ );
+ STDMETHOD_(ULONG, Release)(
+ THIS
+ );
+
+ //
+ // ICLRDataTarget.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE GetMachineType(
+ /* [out] */ ULONG32 *machine);
+
+ virtual HRESULT STDMETHODCALLTYPE GetPointerSize(
+ /* [out] */ ULONG32 *size);
+
+ virtual HRESULT STDMETHODCALLTYPE GetImageBase(
+ /* [string][in] */ LPCWSTR name,
+ /* [out] */ CLRDATA_ADDRESS *base);
+
+ virtual HRESULT STDMETHODCALLTYPE ReadVirtual(
+ /* [in] */ CLRDATA_ADDRESS address,
+ /* [length_is][size_is][out] */ PBYTE buffer,
+ /* [in] */ ULONG32 request,
+ /* [optional][out] */ ULONG32 *done);
+
+ virtual HRESULT STDMETHODCALLTYPE WriteVirtual(
+ /* [in] */ CLRDATA_ADDRESS address,
+ /* [size_is][in] */ PBYTE buffer,
+ /* [in] */ ULONG32 request,
+ /* [optional][out] */ ULONG32 *done);
+
+ virtual HRESULT STDMETHODCALLTYPE GetTLSValue(
+ /* [in] */ ULONG32 threadID,
+ /* [in] */ ULONG32 index,
+ /* [out] */ CLRDATA_ADDRESS* value);
+
+ virtual HRESULT STDMETHODCALLTYPE SetTLSValue(
+ /* [in] */ ULONG32 threadID,
+ /* [in] */ ULONG32 index,
+ /* [in] */ CLRDATA_ADDRESS value);
+
+ virtual HRESULT STDMETHODCALLTYPE GetCurrentThreadID(
+ /* [out] */ ULONG32* threadID);
+
+ virtual HRESULT STDMETHODCALLTYPE GetThreadContext(
+ /* [in] */ ULONG32 threadID,
+ /* [in] */ ULONG32 contextFlags,
+ /* [in] */ ULONG32 contextSize,
+ /* [out, size_is(contextSize)] */ PBYTE context);
+
+ virtual HRESULT STDMETHODCALLTYPE SetThreadContext(
+ /* [in] */ ULONG32 threadID,
+ /* [in] */ ULONG32 contextSize,
+ /* [in, size_is(contextSize)] */ PBYTE context);
+
+ virtual HRESULT STDMETHODCALLTYPE Request(
+ /* [in] */ ULONG32 reqCode,
+ /* [in] */ ULONG32 inBufferSize,
+ /* [size_is][in] */ BYTE *inBuffer,
+ /* [in] */ ULONG32 outBufferSize,
+ /* [size_is][out] */ BYTE *outBuffer);
+
+ // ICorDebugDataTarget4
+
+ virtual HRESULT STDMETHODCALLTYPE VirtualUnwind(
+ /* [in] */ DWORD threadId,
+ /* [in] */ ULONG32 contextSize,
+ /* [in, out, size_is(contextSize)] */ PBYTE context);
+}; \ No newline at end of file
diff --git a/src/ToolBox/SOS/Strike/dirs.proj b/src/ToolBox/SOS/Strike/dirs.proj
new file mode 100644
index 0000000000..1e391307aa
--- /dev/null
+++ b/src/ToolBox/SOS/Strike/dirs.proj
@@ -0,0 +1,20 @@
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\clr.props" />
+
+ <PropertyGroup>
+ <BuildInPhase1>true</BuildInPhase1>
+ <BuildInPhaseDefault>false</BuildInPhaseDefault>
+ <BuildCoreBinaries>true</BuildCoreBinaries>
+ <BuildSysBinaries>true</BuildSysBinaries>
+ </PropertyGroup>
+
+ <ItemGroup Condition="'$(BuildExePhase)' == '1'">
+ <ProjectFile Condition="'$(BuildForCoreSystem)'!='true'" Include="sos.nativeproj" />
+ <ProjectFile Condition="'$(BuildArchitecture)' == 'arm'" Include="sosx86\sosx86.nativeproj" />
+ <ProjectFile Condition="'$(BuildArchitecture)' == 'i386' and '$(BuildForCoreSystem)' == 'true'" Include="sosx86\sosx86.nativeproj" />
+ <ProjectFile Condition="'$(BuildArchitecture)' == 'amd64' and '$(BuildForCoreSystem)' == 'true'" Include="sosx64\sosx64.nativeproj" />
+ <ProjectFile Condition="'$(BuildArchitecture)' == 'arm64' and '$(BuildForCoreSystem)' == 'true'" Include="sosx64\sosx64.nativeproj" />
+ </ItemGroup>
+
+ <Import Project="$(_NTDRIVE)$(_NTROOT)\tools\Microsoft.DevDiv.Traversal.targets" />
+</Project>
diff --git a/src/ToolBox/SOS/Strike/disasm.cpp b/src/ToolBox/SOS/Strike/disasm.cpp
new file mode 100644
index 0000000000..e141f8038f
--- /dev/null
+++ b/src/ToolBox/SOS/Strike/disasm.cpp
@@ -0,0 +1,1142 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// ==++==
+//
+
+//
+// ==--==
+
+#include "strike.h"
+#include "gcinfo.h"
+#include "util.h"
+#include <dbghelp.h>
+#include <limits.h>
+
+#include "sos_md.h"
+
+#ifdef SOS_TARGET_X86
+namespace X86GCDump
+{
+#include "gcdump.h"
+#undef assert
+#define assert(a)
+#define CONTRACTL
+#define DAC_ARG(x)
+#define CONTRACTL_END
+#define LIMITED_METHOD_CONTRACT
+#define NOTHROW
+#define GC_NOTRIGGER
+#define SUPPORTS_DAC
+#define LIMITED_METHOD_DAC_CONTRACT
+#include "gcdecoder.cpp"
+#undef CONTRACTL
+#undef CONTRACTL_END
+#undef LIMITED_METHOD_CONTRACT
+#undef NOTHROW
+#undef GC_NOTRIGGER
+#undef _ASSERTE
+#define _ASSERTE(a) do {} while (0)
+
+#include "gcdump.cpp"
+#include "i386/gcdumpx86.cpp"
+}
+#endif // SOS_TARGET_X86
+
+#ifdef SOS_TARGET_AMD64
+#include "gcdump.h"
+#define DAC_ARG(x)
+#define SUPPORTS_DAC
+#define LIMITED_METHOD_DAC_CONTRACT
+#undef LIMITED_METHOD_CONTRACT
+#undef PREGDISPLAY
+ #ifdef LOG
+ #undef LOG
+ #endif
+ #define LOG(x) ((void)0)
+ #ifdef LOG_PIPTR
+ #undef LOG_PIPTR
+ #endif
+ #define LOG_PIPTR(pObjRef, gcFlags, hCallBack) ((void)0)
+#include "gcdumpnonx86.cpp"
+#endif // SOS_TARGET_AMD64
+
+#include "disasm.h"
+
+#ifndef ERANGE
+#define ERANGE 34
+#endif
+
+PVOID
+GenOpenMapping(
+ PCSTR FilePath,
+ PULONG Size
+ )
+{
+#ifndef FEATURE_PAL
+ HANDLE hFile;
+ HANDLE hMappedFile;
+ PVOID MappedFile;
+
+ hFile = CreateFileA(
+ FilePath,
+ GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL,
+ OPEN_EXISTING,
+ 0,
+ NULL
+ );
+#if 0
+ if ( hFile == NULL || hFile == INVALID_HANDLE_VALUE ) {
+
+ if (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) {
+
+ // We're on an OS that doesn't support Unicode
+ // file operations. Convert to ANSI and see if
+ // that helps.
+
+ CHAR FilePathA [ MAX_LONGPATH + 10 ];
+
+ if (WideCharToMultiByte (CP_ACP,
+ 0,
+ FilePath,
+ -1,
+ FilePathA,
+ sizeof (FilePathA),
+ 0,
+ 0
+ ) > 0) {
+
+ hFile = CreateFileA(FilePathA,
+ GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL,
+ OPEN_EXISTING,
+ 0,
+ NULL
+ );
+ }
+ }
+
+ if ( hFile == NULL || hFile == INVALID_HANDLE_VALUE ) {
+ return NULL;
+ }
+ }
+#endif
+
+ *Size = GetFileSize(hFile, NULL);
+ if (*Size == ULONG_MAX) {
+ CloseHandle( hFile );
+ return NULL;
+ }
+
+ hMappedFile = CreateFileMapping (
+ hFile,
+ NULL,
+ PAGE_READONLY,
+ 0,
+ 0,
+ NULL
+ );
+
+ if ( !hMappedFile ) {
+ CloseHandle ( hFile );
+ return NULL;
+ }
+
+ MappedFile = MapViewOfFile (
+ hMappedFile,
+ FILE_MAP_READ,
+ 0,
+ 0,
+ 0
+ );
+
+ CloseHandle (hMappedFile);
+ CloseHandle (hFile);
+
+ return MappedFile;
+#else // FEATURE_PAL
+ return NULL;
+#endif // FEATURE_PAL
+}
+
+char* PrintOneLine (__in_z char *begin, __in_z char *limit)
+{
+ if (begin == NULL || begin >= limit) {
+ return NULL;
+ }
+ char line[128];
+ size_t length;
+ char *end;
+ while (1) {
+ if (IsInterrupt())
+ return NULL;
+ length = strlen (begin);
+ end = strstr (begin, "\r\xa");
+ if (end == NULL) {
+ ExtOut ("%s", begin);
+ end = begin+length+1;
+ if (end >= limit) {
+ return NULL;
+ }
+ }
+ else {
+ end += 2;
+ length = end-begin;
+ while (length) {
+ if (IsInterrupt())
+ return NULL;
+ size_t n = length;
+ if (n > 127) {
+ n = 127;
+ }
+ strncpy_s (line,_countof(line), begin, n);
+ line[n] = '\0';
+ ExtOut ("%s", line);
+ begin += n;
+ length -= n;
+ }
+ return end;
+ }
+ }
+}
+
+void UnassemblyUnmanaged(DWORD_PTR IP, BOOL bSuppressLines)
+{
+ char filename[MAX_PATH_FNAME+1];
+ char line[256];
+ int lcount = 10;
+
+ ULONG linenum = 0;
+ ULONG64 Displacement = 0;
+ BOOL fLineAvailable = FALSE;
+ ULONG64 vIP = 0;
+
+ if (!bSuppressLines)
+ {
+ ReloadSymbolWithLineInfo();
+ fLineAvailable = SUCCEEDED (g_ExtSymbols->GetLineByOffset(TO_CDADDR(IP),
+ &linenum,
+ filename,
+ MAX_PATH_FNAME+1,
+ NULL,
+ &Displacement));
+ }
+ ULONG FileLines = 0;
+ ArrayHolder<ULONG64> Buffer = NULL;
+
+ if (fLineAvailable)
+ {
+ g_ExtSymbols->GetSourceFileLineOffsets(filename, NULL, 0, &FileLines);
+ if (FileLines == 0xFFFFFFFF || FileLines == 0)
+ fLineAvailable = FALSE;
+ }
+
+ if (fLineAvailable)
+ {
+ Buffer = new ULONG64[FileLines];
+ if (Buffer == NULL)
+ fLineAvailable = FALSE;
+ }
+
+ if (!fLineAvailable)
+ {
+ vIP = TO_CDADDR(IP);
+ // There is no line info. Just disasm the code.
+ while (lcount-- > 0)
+ {
+ if (IsInterrupt())
+ return;
+ g_ExtControl->Disassemble (vIP, 0, line, 256, NULL, &vIP);
+ ExtOut (line);
+ }
+ return;
+ }
+
+ g_ExtSymbols->GetSourceFileLineOffsets(filename, Buffer, FileLines, NULL);
+
+ int beginLine = 0;
+ int endLine = 0;
+ int lastLine;
+ linenum --;
+ for (lastLine = linenum; lastLine >= 0; lastLine --) {
+ if (IsInterrupt())
+ return;
+ if (Buffer[lastLine] != DEBUG_INVALID_OFFSET) {
+ g_ExtSymbols->GetNameByOffset(Buffer[lastLine], NULL, 0, NULL, &Displacement);
+ if (Displacement == 0) {
+ beginLine = lastLine;
+ break;
+ }
+ }
+ }
+ if (lastLine < 0) {
+ int n = lcount / 2;
+ lastLine = linenum-1;
+ beginLine = lastLine;
+ while (lastLine >= 0) {
+ if (IsInterrupt())
+ return;
+ if (Buffer[lastLine] != DEBUG_INVALID_OFFSET) {
+ beginLine = lastLine;
+ n --;
+ if (n == 0) {
+ break;
+ }
+ }
+ lastLine --;
+ }
+ }
+ while (beginLine > 0 && Buffer[beginLine-1] == DEBUG_INVALID_OFFSET) {
+ if (IsInterrupt())
+ return;
+ beginLine --;
+ }
+ int endOfFunc = 0;
+ for (lastLine = linenum+1; (ULONG)lastLine < FileLines; lastLine ++) {
+ if (IsInterrupt())
+ return;
+ if (Buffer[lastLine] != DEBUG_INVALID_OFFSET) {
+ g_ExtSymbols->GetNameByOffset(Buffer[lastLine], NULL, 0, NULL, &Displacement);
+ if (Displacement == 0) {
+ endLine = lastLine;
+ break;
+ }
+ endOfFunc = lastLine;
+ }
+ }
+ if ((ULONG)lastLine == FileLines) {
+ int n = lcount / 2;
+ lastLine = linenum+1;
+ endLine = lastLine;
+ while ((ULONG)lastLine < FileLines) {
+ if (IsInterrupt())
+ return;
+ if (Buffer[lastLine] != DEBUG_INVALID_OFFSET) {
+ endLine = lastLine;
+ n --;
+ if (n == 0) {
+ break;
+ }
+ }
+ lastLine ++;
+ }
+ }
+
+ PVOID MappedBase = NULL;
+ ULONG MappedSize = 0;
+
+ class ToUnmap
+ {
+ PVOID *m_Base;
+ public:
+ ToUnmap (PVOID *base)
+ :m_Base(base)
+ {}
+ ~ToUnmap ()
+ {
+ if (*m_Base) {
+ UnmapViewOfFile (*m_Base);
+ *m_Base = NULL;
+ }
+ }
+ };
+ ToUnmap toUnmap(&MappedBase);
+
+#define MAX_SOURCE_PATH 1024
+ char Found[MAX_SOURCE_PATH];
+ char *pFile;
+ if (g_ExtSymbols->FindSourceFile(0,
+ filename,
+ DEBUG_FIND_SOURCE_BEST_MATCH | DEBUG_FIND_SOURCE_FULL_PATH,
+ NULL,
+ Found,
+ sizeof(Found),
+ NULL) != S_OK)
+ {
+ pFile = filename;
+ }
+ else
+ {
+ MappedBase = GenOpenMapping(Found, &MappedSize);
+ pFile = Found;
+ }
+
+ lastLine = beginLine;
+ char *pFileCh = (char*)MappedBase;
+ if (MappedBase) {
+ ExtOut ("%s\n", pFile);
+ int n = beginLine;
+ while (n > 0) {
+ while (!(pFileCh[0] == '\r' && pFileCh[1] == 0xa)) {
+ if (IsInterrupt())
+ return;
+ pFileCh ++;
+ }
+ pFileCh += 2;
+ n --;
+ }
+ }
+
+ char filename1[MAX_PATH_FNAME+1];
+ for (lastLine = beginLine; lastLine < endLine; lastLine ++) {
+ if (IsInterrupt())
+ return;
+ if (MappedBase) {
+ ExtOut("%4d ", lastLine+1);
+ pFileCh = PrintOneLine(pFileCh, (char*)MappedBase+MappedSize);
+ }
+ if (Buffer[lastLine] != DEBUG_INVALID_OFFSET) {
+ if (MappedBase == 0) {
+ ExtOut (">>> %s:%d\n", pFile, lastLine+1);
+ }
+ vIP = Buffer[lastLine];
+ ULONG64 vNextLineIP;
+ int i;
+ for (i = lastLine + 1; (ULONG)i < FileLines && Buffer[i] == DEBUG_INVALID_OFFSET; i ++) {
+ if (IsInterrupt())
+ return;
+ }
+ if ((ULONG)i == FileLines) {
+ vNextLineIP = 0;
+ }
+ else
+ vNextLineIP = Buffer[i];
+ while (1) {
+ if (IsInterrupt())
+ return;
+ g_ExtControl->Disassemble(vIP, 0, line, 256, NULL, &vIP);
+ ExtOut (line);
+ if (vIP > vNextLineIP || vNextLineIP - vIP > 40) {
+ if (FAILED (g_ExtSymbols->GetLineByOffset(vIP, &linenum,
+ filename1,
+ MAX_PATH_FNAME+1,
+ NULL,
+ &Displacement))) {
+ if (lastLine != endOfFunc) {
+ break;
+ }
+ if (strstr (line, "ret") || strstr (line, "jmp")) {
+ break;
+ }
+ }
+
+ if (linenum != (ULONG)lastLine+1 || strcmp (filename, filename1)) {
+ break;
+ }
+ }
+ else if (vIP == vNextLineIP) {
+ break;
+ }
+ }
+ }
+ }
+}
+
+void DisasmAndClean (DWORD_PTR &IP, __out_ecount_opt(length) char *line, ULONG length)
+{
+ ULONG64 vIP = TO_CDADDR(IP);
+ g_ExtControl->Disassemble (vIP, 0, line, length, NULL, &vIP);
+ IP = (DWORD_PTR)vIP;
+ // remove the ending '\n'
+ char *ptr = strrchr (line, '\n');
+ if (ptr != NULL)
+ ptr[0] = '\0';
+}
+
+// If byref, move to pass the byref prefix
+BOOL IsByRef (__deref_inout_z char *& ptr)
+{
+ BOOL bByRef = FALSE;
+ const char* qindirCh = "qword ptr [";
+ const char* dindirCh = "dword ptr [";
+ const char* qindirDsCh = "qword ptr ds:[";
+ const char* dindirDsCh = "dword ptr ds:[";
+ if (ptr[0] == '[')
+ {
+ bByRef = TRUE;
+ ptr ++;
+ }
+ else if (!IsDbgTargetArm() && !strncmp (ptr, IsDbgTargetWin64() ? qindirCh : dindirCh, 11))
+ {
+ bByRef = TRUE;
+ ptr += 11;
+ }
+ // The new disassembly engine for windbg formats indirect calls
+ // slightly differently:
+ else if (!IsDbgTargetArm() && !strncmp (ptr, IsDbgTargetWin64() ? qindirDsCh : dindirDsCh, 14))
+ {
+ bByRef = TRUE;
+ ptr += 14;
+ }
+ return bByRef;
+}
+
+BOOL IsTermSep (char ch)
+{
+ return (ch == '\0' || isspace (ch) || ch == ',' || ch == '\n');
+}
+
+// Find next term. A term is seperated by space or ,
+void NextTerm (__deref_inout_z char *& ptr)
+{
+ // If we have a byref, skip to ']'
+ if (IsByRef (ptr))
+ {
+ while (ptr[0] != ']' && ptr[0] != '\0')
+ {
+ if (IsInterrupt())
+ return;
+ ptr ++;
+ }
+ if (ptr[0] == ']')
+ ptr ++;
+ }
+
+ while (!IsTermSep (ptr[0]))
+ {
+ if (IsInterrupt())
+ return;
+ ptr ++;
+ }
+
+ while (IsTermSep(ptr[0]) && (*ptr != '\0'))
+ {
+ if (IsInterrupt())
+ return;
+ ptr ++;
+ }
+}
+
+
+// Parses something like 6e24d310, 0x6e24d310, or 6e24d310h.
+// On 64-bit, also parses things like 000006fb`f9b70f50 and
+// 000006fbf9b70f50 (as well as their 0x-prefix, -h suffix variations).
+INT_PTR ParseHexNumber (__in_z char *ptr, ___out char **endptr)
+{
+ char *endptr1;
+ INT_PTR value1 = strtoul(ptr, &endptr1, 16);
+
+#ifdef _TARGET_WIN64_
+ if ('`' == endptr1[0] && isxdigit(endptr1[1]))
+ {
+ char *endptr2;
+ INT_PTR value2 = strtoul(endptr1+1, &endptr2, 16);
+
+ value1 = (value1 << 32) | value2;
+ endptr1 = endptr2;
+ }
+ // if the hex number was specified as 000006fbf9b70f50, an overflow occurred
+ else if (ULONG_MAX == value1 && errno == ERANGE)
+ {
+ if (!strncmp(ptr, "0x", 2))
+ ptr += 2;
+
+ char savedigit = ptr[8];
+ ptr[8] = '\0';
+
+ value1 = strtoul(ptr, &endptr1, 16);
+
+ ptr[8] = savedigit;
+
+ char *endptr2;
+ INT_PTR value2 = strtoul(ptr+8, &endptr2, 16);
+
+ size_t ndigits2 = endptr2 - (ptr+8);
+
+ value1 = (value1 << (ndigits2*4)) | value2;
+ endptr1 = endptr2;
+ }
+#endif // _TARGET_WIN64_
+
+ // account for the possible 'h' suffix
+ if ((*endptr1 == 'h') || (*endptr1 == 'H'))
+ {
+ ++endptr1;
+ }
+
+ *endptr = endptr1;
+ return value1;
+}
+
+
+// only handle pure value, or memory address
+INT_PTR GetValueFromExpr(__in_z char *ptr, INT_PTR &value)
+{
+ BOOL bNegative = FALSE;
+ value = 0;
+ char *myPtr = ptr;
+ BOOL bByRef = IsByRef (myPtr);
+
+ // ARM disassembly contains '#' prefixes for hex constants
+ if (*myPtr == '#')
+ ++myPtr;
+
+ if (myPtr[0] == '-')
+ {
+ myPtr ++;
+ bNegative = TRUE;
+ }
+ if (!strncmp (myPtr, "0x", 2) || isxdigit (myPtr[0]))
+ {
+ char *endptr;
+ value = ParseHexNumber(myPtr, &endptr);
+ if ((!bByRef && IsTermSep(endptr[0])) || (bByRef && endptr[0] == ']'))
+ {
+ if (bNegative)
+ value = -value;
+ ptr = endptr;
+ if (bByRef)
+ {
+ ptr += 1;
+ SafeReadMemory (TO_TADDR(value), &value, 4, NULL);
+ }
+ return ptr - myPtr;
+ }
+ }
+
+ // handle mscorlib+0xed310 (6e24d310)
+ if (!bByRef)
+ {
+ ptr = myPtr;
+ // handle 'offset ' before the expression:
+ if (strncmp(ptr, "offset ", 7) == 0)
+ {
+ ptr += 7;
+ }
+ while (ptr[0] != ' ' && ptr[0] != '+' && ptr[0] != '\0')
+ {
+ if (IsInterrupt())
+ return 0;
+ ptr ++;
+ }
+ if (ptr[0] == '+')
+ {
+ NextTerm (ptr);
+ if (ptr[0] == '(')
+ {
+ ptr ++;
+ char *endptr;
+ value = ParseHexNumber(ptr, &endptr);
+ if (endptr[0] == ')')
+ {
+ ptr ++;
+ return ptr - myPtr;
+ }
+ }
+ }
+ }
+ if (bByRef)
+ {
+ // handle dword [mscorlib+0x2bd788 (02ead788)]
+ ptr = myPtr;
+ // handle 'offset ' before the expression:
+ if (strncmp(ptr, "offset ", 7) == 0)
+ {
+ ptr += 7;
+ }
+ while (ptr[0] != '(' && ptr[0] != '\0')
+ {
+ if (IsInterrupt())
+ return 0;
+ ptr ++;
+ }
+ if (ptr[0] == '(')
+ {
+ ptr ++;
+ char *endptr;
+ value = ParseHexNumber(ptr, &endptr);
+ if (endptr[0] == ')' && endptr[1] == ']')
+ {
+ ptr = endptr + 2;
+ SafeReadMemory (TO_TADDR(value), &value, 4, NULL);
+ return ptr - myPtr;
+ }
+ }
+ }
+
+#ifdef _TARGET_WIN64_
+ // handle CLRStub@7fffc8601cc (000007fffc8601cc)
+ if (!bByRef && !strncmp(myPtr, "CLRStub[", 8))
+ {
+ ptr = myPtr;
+ while (ptr[0] != '(' && ptr[0] != '\0')
+ {
+ if (IsInterrupt())
+ return 0;
+ ptr ++;
+ }
+ if (ptr[0] == '(')
+ {
+ ptr ++;
+ char *endptr;
+ value = ParseHexNumber(ptr, &endptr);
+ if (endptr[0] == ')')
+ {
+ ptr ++;
+ return ptr - myPtr;
+ }
+ }
+ }
+#endif // _TARGET_WIN64_
+
+ return 0;
+}
+
+
+const char * HelperFuncName (size_t IP)
+{
+ static char s_szHelperName[100];
+ if (S_OK == g_sos->GetJitHelperFunctionName(IP, sizeof(s_szHelperName), &s_szHelperName[0], NULL))
+ return &s_szHelperName[0];
+ else
+ return NULL;
+}
+
+
+// Returns:
+// NULL if the EHInfo passed in does not refer to a Typed clause
+// "..." if pEHInfo->isCatchAllHandler is TRUE
+// "TypeName" if pEHInfo is a DACEHInfo*.
+// Note:
+// The return is a pointer to a global buffer, therefore this value must
+// be consumed as soon as possible after a call to this function.
+LPCWSTR EHTypedClauseTypeName(___in const DACEHInfo* pEHInfo)
+{
+ _ASSERTE(pEHInfo != NULL);
+ if ((pEHInfo->clauseType == EHTyped) && pEHInfo->isCatchAllHandler)
+ {
+ return W("...");
+ }
+
+ // is there a method table or a token to look at?
+ if (pEHInfo->clauseType == EHTyped)
+ {
+ TADDR mt;
+ if (pEHInfo->moduleAddr == 0)
+ {
+ mt = TO_TADDR(pEHInfo->mtCatch);
+ NameForMT_s(mt, g_mdName, mdNameLen);
+ } else {
+ PrettyPrintClassFromToken(TO_TADDR(pEHInfo->moduleAddr), pEHInfo->tokCatch, g_mdName, mdNameLen, FormatCSharp);
+ }
+ return g_mdName;
+ }
+
+ return NULL;
+}
+
+BOOL IsClonedFinally(DACEHInfo *pEHInfo)
+{
+ // This maybe should be determined in the VM and passed in the DACEHInfo struct.
+#if defined(_TARGET_AMD64_) || defined(_TARGET_ARM64_)
+ return ((pEHInfo->tryStartOffset == pEHInfo->tryEndOffset) &&
+ (pEHInfo->tryStartOffset == pEHInfo->handlerStartOffset) &&
+ (pEHInfo->clauseType == EHFinally) &&
+ pEHInfo->isDuplicateClause);
+#else
+ return FALSE;
+#endif
+}
+
+
+void SOSEHInfo::FormatForDisassembly(CLRDATA_ADDRESS offSet)
+{
+ LPCWSTR typeName = NULL;
+ // the order of printing and iterating will matter on the boundaries
+
+ // Print END tags in forward order (most nested to least nested). However, cloned
+ // finally clauses are always at the end, but they should be considered most nested,
+ // so have a separate loop to output them first.
+ for (UINT i=0; i < EHCount; i++)
+ {
+ DACEHInfo *pCur = &m_pInfos[i];
+
+ if (IsClonedFinally(pCur) &&
+ (offSet == pCur->handlerEndOffset))
+ {
+ ExtOut ("EHHandler %d: CLONED FINALLY END\n", i);
+ }
+ }
+
+ for (UINT i=0; i < EHCount; i++)
+ {
+ DACEHInfo *pCur = &m_pInfos[i];
+
+ if (pCur->isDuplicateClause)
+ {
+ // Don't print anything for duplicate clauses
+ continue;
+ }
+
+ if (offSet == pCur->tryEndOffset)
+ {
+ ExtOut ("EHHandler %d: %s CLAUSE END\n", i, EHTypeName(pCur->clauseType));
+ }
+
+ if (offSet == pCur->handlerEndOffset)
+ {
+ ExtOut ("EHHandler %d: %s HANDLER END\n", i, EHTypeName(pCur->clauseType));
+ }
+ }
+
+ // Print BEGIN tags in reverse order (least nested to most nested).
+ for (UINT i=EHCount-1; i != (UINT)-1; --i)
+ {
+ DACEHInfo *pCur = &m_pInfos[i];
+
+ // Must do this before the isDuplicatedClause check, since these are marked as duplicated clauses.
+ if (IsClonedFinally(pCur) &&
+ (offSet == pCur->handlerStartOffset))
+ {
+ ExtOut ("EHHandler %d: CLONED FINALLY BEGIN\n", i);
+ }
+
+ if (pCur->isDuplicateClause)
+ {
+ // Don't print anything for duplicate clauses
+ continue;
+ }
+
+ if (offSet == pCur->tryStartOffset)
+ {
+ ExtOut ("EHHandler %d: %s CLAUSE BEGIN", i, EHTypeName(pCur->clauseType));
+ typeName = EHTypedClauseTypeName(pCur);
+ if (typeName != NULL)
+ {
+ ExtOut(" catch(%S) ", typeName);
+ }
+ ExtOut ("\n");
+ }
+
+ if (offSet == pCur->handlerStartOffset)
+ {
+ ExtOut ("EHHandler %d: %s HANDLER BEGIN", i, EHTypeName(pCur->clauseType));
+ typeName = EHTypedClauseTypeName(pCur);
+ if (typeName != NULL)
+ {
+ ExtOut(" catch(%S) ", typeName);
+ }
+ ExtOut ("\n");
+ }
+
+ if ((pCur->clauseType == EHFilter) &&
+ (offSet == pCur->filterOffset))
+ {
+ ExtOut ("EHHandler %d: %s FILTER BEGIN\n",i, EHTypeName(pCur->clauseType));
+ }
+ }
+}
+
+
+//
+// Implementation shared by X86, ARM, and X64
+// Any cross platform code should resolve through g_targetMachine or should
+// use the IS_DBG_TARGET_XYZ macro.
+//
+
+void PrintNativeStack(DWORD_PTR ip, BOOL bSuppressLines)
+{
+ char filename[MAX_PATH_FNAME + 1];
+ char symbol[1024];
+ ULONG64 displacement;
+
+ HRESULT hr = g_ExtSymbols->GetNameByOffset(TO_CDADDR(ip), symbol, _countof(symbol), NULL, &displacement);
+ if (SUCCEEDED(hr) && symbol[0] != '\0')
+ {
+ ExtOut("%s", symbol);
+
+ if (displacement)
+ {
+ ExtOut(" + %#x", displacement);
+ }
+
+ if (!bSuppressLines)
+ {
+ ULONG line;
+ hr = g_ExtSymbols->GetLineByOffset(TO_CDADDR(ip), &line, filename, _countof(filename), NULL, NULL);
+ if (SUCCEEDED(hr))
+ {
+ ExtOut(" [%s:%d]", filename, line);
+ }
+ }
+ }
+ else
+ {
+ DMLOut(DMLIP(ip));
+ }
+}
+
+// Return TRUE if we have printed something.
+BOOL PrintCallInfo(DWORD_PTR vEBP, DWORD_PTR IP, DumpStackFlag& DSFlag, BOOL bSymbolOnly)
+{
+ char Symbol[1024];
+ char filename[MAX_PATH_FNAME+1];
+ ULONG64 Displacement;
+ BOOL bOutput = FALSE;
+
+ // degrade gracefully for debuggees that don't have a runtime loaded, or a DAC available
+ DWORD_PTR methodDesc = 0;
+ if (!g_bDacBroken)
+ {
+ methodDesc = FunctionType (IP);
+ }
+
+ if (methodDesc > 1)
+ {
+ bOutput = TRUE;
+ if (!bSymbolOnly)
+ DMLOut("%p %s ", SOS_PTR(vEBP), DMLIP(IP));
+ DMLOut("(MethodDesc %s ", DMLMethodDesc(methodDesc));
+
+ // TODO: Microsoft, more checks to make sure method is not eeimpl, etc. Add this field to MethodDesc
+
+ DacpCodeHeaderData codeHeaderData;
+ if (codeHeaderData.Request(g_sos, TO_CDADDR(IP)) == S_OK)
+ {
+ DWORD_PTR IPBegin = (DWORD_PTR) codeHeaderData.MethodStart;
+ methodDesc = (DWORD_PTR) codeHeaderData.MethodDescPtr;
+ Displacement = IP - IPBegin;
+ if (IP >= IPBegin && Displacement <= codeHeaderData.MethodSize)
+ ExtOut ("+ %#x ", Displacement);
+ }
+ if (NameForMD_s(methodDesc, g_mdName, mdNameLen))
+ {
+ ExtOut("%S)", g_mdName);
+ }
+ else
+ {
+ ExtOut("%s)", DMLIP(IP));
+ }
+ }
+ else
+ {
+ if (!DSFlag.fEEonly)
+ {
+ bOutput = TRUE;
+ const char *name;
+ if (!bSymbolOnly)
+ DMLOut("%p %s ", SOS_PTR(vEBP), DMLIP(IP));
+
+ // if AMD64 ever becomes a cross platform target this must be resolved through
+ // virtual dispatch rather than conditional compilation
+#ifdef _TARGET_AMD64_
+ // degrade gracefully for debuggees that don't have a runtime loaded, or a DAC available
+ eTargetType ett = ettUnk;
+ if (!g_bDacBroken)
+ {
+ DWORD_PTR finalMDorIP = 0;
+ ett = GetFinalTarget(IP, &finalMDorIP);
+ if (ett == ettNative || ett==ettJitHelp)
+ {
+ methodDesc = 0;
+ IP = finalMDorIP;
+ }
+ else
+ {
+ methodDesc = finalMDorIP;
+ }
+ }
+#endif // _TARGET_AMD64_
+ if (methodDesc == 0)
+ {
+ PrintNativeStack(IP, DSFlag.fSuppressSrcInfo);
+ }
+ else if (g_bDacBroken)
+ {
+ // degrade gracefully for debuggees that don't have a runtime loaded, or a DAC available
+ DMLOut(DMLIP(IP));
+ }
+ else if (IsMethodDesc (IP))
+ {
+ NameForMD_s(IP, g_mdName, mdNameLen);
+ ExtOut(" (stub for %S)", g_mdName);
+ }
+ else if (IsMethodDesc(IP+5)) {
+ NameForMD_s((DWORD_PTR)(IP+5), g_mdName, mdNameLen);
+ DMLOut("%s (MethodDesc %s %S)", DMLIP(IP), DMLMethodDesc(IP+5), g_mdName);
+ }
+ else if ((name = HelperFuncName(IP)) != NULL) {
+ ExtOut(" (JitHelp: %s)", name);
+ }
+#ifdef _TARGET_AMD64_
+ else if (ett == ettMD || ett == ettStub)
+ {
+ NameForMD_s(methodDesc, g_mdName,mdNameLen);
+ DMLOut("%s (stub for %S)", DMLIP(IP), g_mdName);
+ // fallthrough to return
+ }
+#endif // _TARGET_AMD64_
+ else
+ {
+ DMLOut(DMLIP(IP));
+ }
+ }
+ }
+ return bOutput;
+}
+
+void DumpStackWorker (DumpStackFlag &DSFlag)
+{
+ DWORD_PTR eip;
+ ULONG64 Offset;
+ g_ExtRegisters->GetInstructionOffset(&Offset);
+ eip = (DWORD_PTR)Offset;
+
+ ExtOut("Current frame: ");
+ PrintCallInfo (0, eip, DSFlag, TRUE);
+ ExtOut ("\n");
+
+ // make certain dword/qword aligned
+ DWORD_PTR ptr = DSFlag.top & (~ALIGNCONST);
+
+ ExtOut (g_targetMachine->GetDumpStackHeading());
+ while (ptr < DSFlag.end)
+ {
+ if (IsInterrupt())
+ return;
+ DWORD_PTR retAddr;
+ DWORD_PTR whereCalled;
+ move_xp(retAddr, ptr);
+ g_targetMachine->IsReturnAddress(retAddr, &whereCalled);
+ if (whereCalled)
+ {
+ BOOL bOutput = PrintCallInfo(ptr-sizeof(TADDR), retAddr, DSFlag, FALSE);
+ if (!DSFlag.fEEonly)
+ {
+ if (whereCalled != 0xFFFFFFFF)
+ {
+ ExtOut (", calling ");
+ PrintCallInfo (0, whereCalled, DSFlag, TRUE);
+ }
+ }
+ if (bOutput)
+ ExtOut ("\n");
+
+ DWORD_PTR cxrAddr;
+ CROSS_PLATFORM_CONTEXT cxr;
+ DWORD_PTR exrAddr;
+ EXCEPTION_RECORD exr;
+
+ if (g_targetMachine->GetExceptionContext(ptr,retAddr,&cxrAddr,&cxr,&exrAddr,&exr))
+ {
+ TADDR sp = g_targetMachine->GetSP(cxr);
+ TADDR ip = g_targetMachine->GetIP(cxr);
+ bOutput = PrintCallInfo(sp, ip, DSFlag, FALSE);
+ if (bOutput)
+ {
+ ExtOut(" ====> Exception ");
+ if (exrAddr)
+ ExtOut("Code %x ", exr.ExceptionCode);
+ ExtOut ("cxr@%p", SOS_PTR(cxrAddr));
+ if (exrAddr)
+ ExtOut(" exr@%p", SOS_PTR(exrAddr));
+ ExtOut("\n");
+ }
+ }
+ }
+ ptr += sizeof (DWORD_PTR);
+ }
+}
+
+#ifdef SOS_TARGET_X86
+///
+/// X86Machine implementation
+///
+LPCSTR X86Machine::s_DumpStackHeading = "ChildEBP RetAddr Caller, Callee\n";
+LPCSTR X86Machine::s_DSOHeading = "ESP/REG Object Name\n";
+LPCSTR X86Machine::s_GCRegs[7] = {"eax", "ebx", "ecx", "edx", "esi", "edi", "ebp"};
+LPCSTR X86Machine::s_SPName = "ESP";
+
+void PrintNothing (const char *fmt, ...)
+{
+ // Do nothing.
+}
+
+///
+/// Dump X86 GCInfo header and table
+///
+void X86Machine::DumpGCInfo(GCInfoToken gcInfoToken, unsigned methodSize, printfFtn gcPrintf, bool encBytes, bool bPrintHeader) const
+{
+ X86GCDump::InfoHdr header;
+ X86GCDump::GCDump gcDump(gcInfoToken.Version, encBytes, 5, true);
+ BYTE* pTable = dac_cast<PTR_BYTE>(gcInfoToken.Info);
+ if (bPrintHeader)
+ {
+ gcDump.gcPrintf = gcPrintf;
+ gcPrintf("Method info block:\n");
+ }
+ else
+ {
+ gcDump.gcPrintf = PrintNothing;
+ }
+ pTable += gcDump.DumpInfoHdr(pTable, &header, &methodSize, 0);
+ if (bPrintHeader)
+ {
+ gcPrintf("\n");
+ gcPrintf("Pointer table:\n");
+ }
+ gcDump.gcPrintf = gcPrintf;
+ gcDump.DumpGCTable(pTable, header, methodSize, 0);
+}
+#endif // SOS_TARGET_X86
+
+#ifdef SOS_TARGET_ARM
+///
+/// ARMMachine implementation
+///
+LPCSTR ARMMachine::s_DumpStackHeading = "ChildFP RetAddr Caller, Callee\n";
+LPCSTR ARMMachine::s_DSOHeading = "SP/REG Object Name\n";
+LPCSTR ARMMachine::s_GCRegs[14] = {"r0", "r1", "r2", "r3", "r4", "r5", "r6",
+ "r7", "r8", "r9", "r10", "r11", "r12", "lr"};
+LPCSTR ARMMachine::s_SPName = "sp";
+
+#endif // SOS_TARGET_ARM
+
+#ifdef SOS_TARGET_AMD64
+///
+/// AMD64Machine implementation
+///
+LPCSTR AMD64Machine::s_DumpStackHeading = "Child-SP RetAddr Caller, Callee\n";
+LPCSTR AMD64Machine::s_DSOHeading = "RSP/REG Object Name\n";
+LPCSTR AMD64Machine::s_GCRegs[15] = {"rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp",
+ "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15"};
+LPCSTR AMD64Machine::s_SPName = "RSP";
+
+///
+/// Dump AMD64 GCInfo table
+///
+void AMD64Machine::DumpGCInfo(GCInfoToken gcInfoToken, unsigned methodSize, printfFtn gcPrintf, bool encBytes, bool bPrintHeader) const
+{
+ if (bPrintHeader)
+ {
+ ExtOut("Pointer table:\n");
+ }
+
+ GCDump gcDump(gcInfoToken.Version, encBytes, 5, true);
+ gcDump.gcPrintf = gcPrintf;
+
+ gcDump.DumpGCTable(dac_cast<PTR_BYTE>(gcInfoToken.Info), methodSize, 0);
+}
+
+#endif // SOS_TARGET_AMD64
+
+#ifdef SOS_TARGET_ARM64
+///
+/// ARM64Machine implementation
+///
+LPCSTR ARM64Machine::s_DumpStackHeading = "ChildFP RetAddr Caller, Callee\n";
+LPCSTR ARM64Machine::s_DSOHeading = "SP/REG Object Name\n";
+// excluding x18, fp & lr as these will not contain object references
+LPCSTR ARM64Machine::s_GCRegs[28] = {"x0", "x1", "x2", "x3", "x4", "x5", "x6",
+ "x7", "x8", "x9", "x10", "x11", "x12", "x13",
+ "x14", "x15", "x16", "x17", "x19", "x20","x21",
+ "x22", "x23", "x24", "x25", "x26", "x27", "x28"};
+LPCSTR ARM64Machine::s_SPName = "sp";
+
+#endif // SOS_TARGET_ARM64
+
+
diff --git a/src/ToolBox/SOS/Strike/disasm.h b/src/ToolBox/SOS/Strike/disasm.h
new file mode 100644
index 0000000000..59fc168a6e
--- /dev/null
+++ b/src/ToolBox/SOS/Strike/disasm.h
@@ -0,0 +1,453 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// ==++==
+//
+
+//
+// ==--==
+#ifndef __disasm_h__
+#define __disasm_h__
+
+#include "sos_stacktrace.h"
+
+struct InfoHdr;
+class GCDump;
+
+
+struct DumpStackFlag
+{
+ BOOL fEEonly;
+ BOOL fSuppressSrcInfo;
+ DWORD_PTR top;
+ DWORD_PTR end;
+};
+
+struct GCEncodingInfo
+{
+ LPVOID pvMainFiber;
+ LPVOID pvGCTableFiber;
+
+ BYTE *table;
+ unsigned int methodSize;
+
+ char buf[1000];
+ int cch;
+
+ SIZE_T ofs;
+
+ // When decoding a cold region, set this to the size of the hot region to keep offset
+ // calculations working.
+ SIZE_T hotSizeToAdd;
+ bool fDoneDecoding;
+};
+
+// Returns:
+// NULL if the EHInfo passed in does not refer to a Typed clause
+// "..." if pEHInfo->isCatchAllHandler is TRUE
+// "TypeName" if pEHInfo is a DACEHInfo* that references type "TypeName".
+// Note:
+// The return is a pointer to a global buffer, therefore this value must
+// be consumed as soon as possible after a call to this function.
+LPCWSTR EHTypedClauseTypeName(const DACEHInfo* pEHInfo);
+
+struct SOSEHInfo
+{
+#ifndef FEATURE_CORECLR
+ __field_ecount(EHCount)
+#endif
+ DACEHInfo *m_pInfos;
+ UINT EHCount;
+ CLRDATA_ADDRESS methodStart;
+
+ SOSEHInfo() { ZeroMemory(this, sizeof(SOSEHInfo)); }
+ ~SOSEHInfo() { if (m_pInfos) { delete [] m_pInfos; } }
+
+ void FormatForDisassembly(CLRDATA_ADDRESS offSet);
+};
+
+BOOL IsClonedFinally(DACEHInfo *pEHInfo);
+
+void DumpStackWorker (DumpStackFlag &DSFlag);
+
+void UnassemblyUnmanaged (DWORD_PTR IP, BOOL bSuppressLines);
+
+HRESULT CheckEEDll ();
+
+BOOL GetCalleeSite (DWORD_PTR IP, DWORD_PTR &IPCallee);
+
+void DisasmAndClean (DWORD_PTR &IP, __out_ecount_opt(length) char *line, ULONG length);
+
+INT_PTR GetValueFromExpr(___in __in_z char *ptr, INT_PTR &value);
+
+void NextTerm (__deref_inout_z char *& ptr);
+
+BOOL IsByRef (__deref_inout_z char *& ptr);
+
+BOOL IsTermSep (char ch);
+
+const char * HelperFuncName (size_t IP);
+
+enum eTargetType { ettUnk = 0, ettNative = 1, ettJitHelp = 2, ettStub = 3, ettMD = 4 };
+
+// GetFinalTarget is based on HandleCall, but avoids printing anything to the output.
+// This is currently only called on x64
+eTargetType GetFinalTarget(DWORD_PTR callee, DWORD_PTR* finalMDorIP);
+
+#ifdef _MSC_VER
+// SOS is essentially single-threaded. ignore "construction of local static object is not thread-safe"
+#pragma warning(push)
+#pragma warning(disable:4640)
+#endif // _MSC_VER
+
+//-----------------------------------------------------------------------------------------
+//
+// Implementations for the supported target platforms
+//
+//-----------------------------------------------------------------------------------------
+
+#ifndef THUMB_CODE
+#define THUMB_CODE 1
+#endif
+#define STACKWALK_CONTROLPC_ADJUST_OFFSET 2
+
+#ifdef SOS_TARGET_X86
+
+/// X86 Machine specific code
+class X86Machine : public IMachine
+{
+public:
+ typedef X86_CONTEXT TGT_CTXT;
+
+ static IMachine* GetInstance()
+ { static X86Machine s_X86MachineInstance; return &s_X86MachineInstance; }
+
+ ULONG GetPlatform() const { return IMAGE_FILE_MACHINE_I386; }
+ ULONG GetContextSize() const { return sizeof(X86_CONTEXT); }
+ virtual void Unassembly(
+ TADDR IPBegin,
+ TADDR IPEnd,
+ TADDR IPAskedFor,
+ TADDR GCStressCodeCopy,
+ GCEncodingInfo * pGCEncodingInfo,
+ SOSEHInfo *pEHInfo,
+ BOOL bSuppressLines,
+ BOOL bDisplayOffsets) const;
+ virtual void IsReturnAddress(
+ TADDR retAddr,
+ TADDR* whereCalled) const;
+ virtual BOOL GetExceptionContext (
+ TADDR stack,
+ TADDR PC,
+ TADDR *cxrAddr,
+ CROSS_PLATFORM_CONTEXT * cxr,
+ TADDR * exrAddr,
+ PEXCEPTION_RECORD exr) const;
+
+ // retrieve stack pointer, frame pointer, and instruction pointer from the target context
+ virtual TADDR GetSP(const CROSS_PLATFORM_CONTEXT & ctx) const { return ctx.X86Context.Esp; }
+ virtual TADDR GetBP(const CROSS_PLATFORM_CONTEXT & ctx) const { return ctx.X86Context.Ebp; }
+ virtual TADDR GetIP(const CROSS_PLATFORM_CONTEXT & ctx) const { return ctx.X86Context.Eip; }
+
+ virtual void FillSimpleContext(StackTrace_SimpleContext * dest, LPVOID srcCtx) const;
+ virtual void FillTargetContext(LPVOID destCtx, LPVOID srcCtx, int idx = 0) const;
+
+ virtual LPCSTR GetDumpStackHeading() const { return s_DumpStackHeading; }
+ virtual LPCSTR GetDumpStackObjectsHeading() const { return s_DSOHeading; }
+ virtual LPCSTR GetSPName() const { return s_SPName; }
+ virtual void GetGCRegisters(LPCSTR** regNames, unsigned int* cntRegs) const
+ { _ASSERTE(cntRegs != NULL); *regNames = s_GCRegs; *cntRegs = _countof(s_GCRegs); }
+
+ virtual void DumpGCInfo(GCInfoToken gcInfoToken, unsigned methodSize, printfFtn gcPrintf, bool encBytes, bool bPrintHeader) const;
+
+private:
+ X86Machine() {}
+ ~X86Machine() {}
+ X86Machine(const X86Machine& machine); // undefined
+ X86Machine & operator=(const X86Machine&); // undefined
+
+private:
+ static LPCSTR s_DumpStackHeading;
+ static LPCSTR s_DSOHeading;
+ static LPCSTR s_GCRegs[7];
+ static LPCSTR s_SPName;
+}; // class X86Machine
+
+#endif // SOS_TARGET_X86
+
+
+#ifdef SOS_TARGET_ARM
+
+/// ARM Machine specific code
+class ARMMachine : public IMachine
+{
+public:
+ typedef ARM_CONTEXT TGT_CTXT;
+
+ static IMachine* GetInstance()
+ { return &s_ARMMachineInstance; }
+
+ ULONG GetPlatform() const { return IMAGE_FILE_MACHINE_ARMNT; }
+ ULONG GetContextSize() const { return sizeof(ARM_CONTEXT); }
+ virtual void Unassembly(
+ TADDR IPBegin,
+ TADDR IPEnd,
+ TADDR IPAskedFor,
+ TADDR GCStressCodeCopy,
+ GCEncodingInfo *pGCEncodingInfo,
+ SOSEHInfo *pEHInfo,
+ BOOL bSuppressLines,
+ BOOL bDisplayOffsets) const;
+ virtual void IsReturnAddress(
+ TADDR retAddr,
+ TADDR* whereCalled) const;
+ virtual BOOL GetExceptionContext (
+ TADDR stack,
+ TADDR PC,
+ TADDR *cxrAddr,
+ CROSS_PLATFORM_CONTEXT * cxr,
+ TADDR *exrAddr,
+ PEXCEPTION_RECORD exr) const;
+
+ // retrieve stack pointer, frame pointer, and instruction pointer from the target context
+ virtual TADDR GetSP(const CROSS_PLATFORM_CONTEXT & ctx) const { return ctx.ArmContext.Sp; }
+ // @ARMTODO: frame pointer
+ virtual TADDR GetBP(const CROSS_PLATFORM_CONTEXT & ctx) const { return 0; }
+ virtual TADDR GetIP(const CROSS_PLATFORM_CONTEXT & ctx) const { return ctx.ArmContext.Pc; }
+
+ virtual void FillSimpleContext(StackTrace_SimpleContext * dest, LPVOID srcCtx) const;
+ virtual void FillTargetContext(LPVOID destCtx, LPVOID srcCtx, int idx = 0) const;
+
+ virtual LPCSTR GetDumpStackHeading() const { return s_DumpStackHeading; }
+ virtual LPCSTR GetDumpStackObjectsHeading() const { return s_DSOHeading; }
+ virtual LPCSTR GetSPName() const { return s_SPName; }
+ virtual void GetGCRegisters(LPCSTR** regNames, unsigned int* cntRegs) const
+ { _ASSERTE(cntRegs != NULL); *regNames = s_GCRegs; *cntRegs = _countof(s_GCRegs); }
+
+ virtual void DumpGCInfo(GCInfoToken gcInfoToken, unsigned methodSize, printfFtn gcPrintf, bool encBytes, bool bPrintHeader) const;
+
+private:
+ ARMMachine() {}
+ ~ARMMachine() {}
+ ARMMachine(const ARMMachine& machine); // undefined
+ ARMMachine & operator=(const ARMMachine&); // undefined
+
+private:
+ static LPCSTR s_DumpStackHeading;
+ static LPCSTR s_DSOHeading;
+ static LPCSTR s_GCRegs[14];
+ static LPCSTR s_SPName;
+ static ARMMachine s_ARMMachineInstance;
+}; // class ARMMachine
+
+#endif // SOS_TARGET_ARM
+
+#ifdef SOS_TARGET_AMD64
+
+/// AMD64 Machine specific code
+class AMD64Machine : public IMachine
+{
+public:
+ typedef AMD64_CONTEXT TGT_CTXT;
+
+ static IMachine* GetInstance()
+ { static AMD64Machine s_AMD64MachineInstance; return &s_AMD64MachineInstance; }
+
+ ULONG GetPlatform() const { return IMAGE_FILE_MACHINE_AMD64; }
+ ULONG GetContextSize() const { return sizeof(AMD64_CONTEXT); }
+
+ virtual void Unassembly(
+ TADDR IPBegin,
+ TADDR IPEnd,
+ TADDR IPAskedFor,
+ TADDR GCStressCodeCopy,
+ GCEncodingInfo *pGCEncodingInfo,
+ SOSEHInfo *pEHInfo,
+ BOOL bSuppressLines,
+ BOOL bDisplayOffsets) const;
+
+ virtual void IsReturnAddress(
+ TADDR retAddr,
+ TADDR* whereCalled) const;
+
+ virtual BOOL GetExceptionContext (
+ TADDR stack,
+ TADDR PC,
+ TADDR *cxrAddr,
+ CROSS_PLATFORM_CONTEXT * cxr,
+ TADDR *exrAddr,
+ PEXCEPTION_RECORD exr) const;
+
+ // retrieve stack pointer, frame pointer, and instruction pointer from the target context
+ virtual TADDR GetSP(const CROSS_PLATFORM_CONTEXT & ctx) const { return ctx.Amd64Context.Rsp; }
+ virtual TADDR GetBP(const CROSS_PLATFORM_CONTEXT & ctx) const { return ctx.Amd64Context.Rbp; }
+ virtual TADDR GetIP(const CROSS_PLATFORM_CONTEXT & ctx) const { return ctx.Amd64Context.Rip; }
+
+ virtual void FillSimpleContext(StackTrace_SimpleContext * dest, LPVOID srcCtx) const;
+ virtual void FillTargetContext(LPVOID destCtx, LPVOID srcCtx, int idx = 0) const;
+
+ virtual LPCSTR GetDumpStackHeading() const { return s_DumpStackHeading; }
+ virtual LPCSTR GetDumpStackObjectsHeading() const { return s_DSOHeading; }
+ virtual LPCSTR GetSPName() const { return s_SPName; }
+ virtual void GetGCRegisters(LPCSTR** regNames, unsigned int* cntRegs) const
+ { _ASSERTE(cntRegs != NULL); *regNames = s_GCRegs; *cntRegs = _countof(s_GCRegs); }
+
+ virtual void DumpGCInfo(GCInfoToken gcInfoToken, unsigned methodSize, printfFtn gcPrintf, bool encBytes, bool bPrintHeader) const;
+
+private:
+ AMD64Machine() {}
+ ~AMD64Machine() {}
+ AMD64Machine(const AMD64Machine& machine); // undefined
+ AMD64Machine & operator=(const AMD64Machine&); // undefined
+
+private:
+ static LPCSTR s_DumpStackHeading;
+ static LPCSTR s_DSOHeading;
+ static LPCSTR s_GCRegs[15];
+ static LPCSTR s_SPName;
+}; // class AMD64Machine
+
+#endif // SOS_TARGET_AMD64
+
+#ifdef SOS_TARGET_ARM64
+
+/// ARM64 Machine specific code
+class ARM64Machine : public IMachine
+{
+public:
+ typedef ARM64_CONTEXT TGT_CTXT;
+
+ static IMachine* GetInstance()
+ { static ARM64Machine s_ARM64MachineInstance; return &s_ARM64MachineInstance; }
+
+ ULONG GetPlatform() const { return IMAGE_FILE_MACHINE_ARM64; }
+ ULONG GetContextSize() const { return sizeof(ARM64_CONTEXT); }
+ virtual void Unassembly(
+ TADDR IPBegin,
+ TADDR IPEnd,
+ TADDR IPAskedFor,
+ TADDR GCStressCodeCopy,
+ GCEncodingInfo *pGCEncodingInfo,
+ SOSEHInfo *pEHInfo,
+ BOOL bSuppressLines,
+ BOOL bDisplayOffsets) const;
+ virtual void IsReturnAddress(
+ TADDR retAddr,
+ TADDR* whereCalled) const;
+ virtual BOOL GetExceptionContext (
+ TADDR stack,
+ TADDR PC,
+ TADDR *cxrAddr,
+ CROSS_PLATFORM_CONTEXT * cxr,
+ TADDR *exrAddr,
+ PEXCEPTION_RECORD exr) const;
+
+ // retrieve stack pointer, frame pointer, and instruction pointer from the target context
+ virtual TADDR GetSP(const CROSS_PLATFORM_CONTEXT & ctx) const { return ctx.Arm64Context.Sp; }
+ virtual TADDR GetBP(const CROSS_PLATFORM_CONTEXT & ctx) const { return ctx.Arm64Context.Fp; }
+ virtual TADDR GetIP(const CROSS_PLATFORM_CONTEXT & ctx) const { return ctx.Arm64Context.Pc; }
+
+ virtual void FillSimpleContext(StackTrace_SimpleContext * dest, LPVOID srcCtx) const;
+ virtual void FillTargetContext(LPVOID destCtx, LPVOID srcCtx, int idx = 0) const;
+
+ virtual LPCSTR GetDumpStackHeading() const { return s_DumpStackHeading; }
+ virtual LPCSTR GetDumpStackObjectsHeading() const { return s_DSOHeading; }
+ virtual LPCSTR GetSPName() const { return s_SPName; }
+ virtual void GetGCRegisters(LPCSTR** regNames, unsigned int* cntRegs) const
+ { _ASSERTE(cntRegs != NULL); *regNames = s_GCRegs; *cntRegs = _countof(s_GCRegs);}
+
+ virtual void DumpGCInfo(GCInfoToken gcInfoToken, unsigned methodSize, printfFtn gcPrintf, bool encBytes, bool bPrintHeader) const;
+
+private:
+ ARM64Machine() {}
+ ~ARM64Machine() {}
+ ARM64Machine(const ARM64Machine& machine); // undefined
+ ARM64Machine & operator=(const ARM64Machine&); // undefined
+
+ static LPCSTR s_DumpStackHeading;
+ static LPCSTR s_DSOHeading;
+ static LPCSTR s_GCRegs[28];
+ static LPCSTR s_SPName;
+
+}; // class ARM64Machine
+
+#endif // SOS_TARGET_ARM64
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif // _MSC_VER
+
+
+//
+// Inline methods
+//
+
+
+#ifdef SOS_TARGET_X86
+inline void X86Machine::FillSimpleContext(StackTrace_SimpleContext * dest, LPVOID srcCtx) const
+{
+ TGT_CTXT& src = *(TGT_CTXT*) srcCtx;
+ dest->StackOffset = src.Esp;
+ dest->FrameOffset = src.Ebp;
+ dest->InstructionOffset = src.Eip;
+}
+
+inline void X86Machine::FillTargetContext(LPVOID destCtx, LPVOID srcCtx, int idx /*= 0*/) const
+{
+ TGT_CTXT* dest = (TGT_CTXT*)destCtx + idx;
+ *dest = *(TGT_CTXT*)srcCtx;
+}
+#endif // SOS_TARGET_X86
+
+
+#ifdef SOS_TARGET_ARM
+inline void ARMMachine::FillSimpleContext(StackTrace_SimpleContext * dest, LPVOID srcCtx) const
+{
+ TGT_CTXT& src = *(TGT_CTXT*) srcCtx;
+ dest->StackOffset = src.Sp;
+ // @ARMTODO: frame pointer - keep in sync with ARMMachine::GetBP
+ dest->FrameOffset = 0;
+ dest->InstructionOffset = src.Pc;
+}
+
+inline void ARMMachine::FillTargetContext(LPVOID destCtx, LPVOID srcCtx, int idx /*= 0*/) const
+{
+ TGT_CTXT* dest = (TGT_CTXT*)destCtx + idx;
+ *dest = *(TGT_CTXT*)srcCtx;
+}
+#endif // SOS_TARGET_ARM
+
+
+#ifdef SOS_TARGET_AMD64
+inline void AMD64Machine::FillSimpleContext(StackTrace_SimpleContext * dest, LPVOID srcCtx) const
+{
+ TGT_CTXT& src = *(TGT_CTXT*) srcCtx;
+ dest->StackOffset = src.Rsp;
+ dest->FrameOffset = src.Rbp;
+ dest->InstructionOffset = src.Rip;
+}
+
+inline void AMD64Machine::FillTargetContext(LPVOID destCtx, LPVOID srcCtx, int idx /*= 0*/) const
+{
+ TGT_CTXT* dest = (TGT_CTXT*)destCtx + idx;
+ *dest = *(TGT_CTXT*)srcCtx;
+}
+#endif // SOS_TARGET_AMD64
+
+#ifdef SOS_TARGET_ARM64
+inline void ARM64Machine::FillSimpleContext(StackTrace_SimpleContext * dest, LPVOID srcCtx) const
+{
+ TGT_CTXT& src = *(TGT_CTXT*) srcCtx;
+ dest->StackOffset = src.Sp;
+ dest->FrameOffset = src.Fp;
+ dest->InstructionOffset = src.Pc;
+}
+
+inline void ARM64Machine::FillTargetContext(LPVOID destCtx, LPVOID srcCtx, int idx /*= 0*/) const
+{
+ TGT_CTXT* dest = (TGT_CTXT*)destCtx + idx;
+ *dest = *(TGT_CTXT*)srcCtx;
+}
+#endif // SOS_TARGET_ARM64
+
+#endif // __disasm_h__
diff --git a/src/ToolBox/SOS/Strike/disasmARM.cpp b/src/ToolBox/SOS/Strike/disasmARM.cpp
new file mode 100644
index 0000000000..82173558fd
--- /dev/null
+++ b/src/ToolBox/SOS/Strike/disasmARM.cpp
@@ -0,0 +1,626 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// ==++==
+//
+
+//
+// ==--==
+
+#ifndef _TARGET_ARM_
+#define _TARGET_ARM_
+#endif
+
+
+#include "strike.h"
+#include "util.h"
+#include <dbghelp.h>
+
+
+#include "disasm.h"
+
+#include "../../../inc/corhdr.h"
+#include "../../../inc/cor.h"
+#include "../../../inc/dacprivate.h"
+
+#ifndef FEATURE_PAL
+namespace ARMGCDump
+{
+#undef _TARGET_X86_
+#undef LIMITED_METHOD_CONTRACT
+#define LIMITED_METHOD_DAC_CONTRACT
+#define SUPPORTS_DAC
+#define LF_GCROOTS
+#define LL_INFO1000
+#define LOG(x)
+#define LOG_PIPTR(pObjRef, gcFlags, hCallBack)
+#define DAC_ARG(x)
+#include "gcdumpnonx86.cpp"
+}
+#endif // !FEATURE_PAL
+
+#if defined(_TARGET_WIN64_)
+#error This file does not support SOS targeting ARM from a 64-bit debugger
+#endif
+
+#if !defined(SOS_TARGET_ARM)
+#error This file should be used to support SOS targeting ARM debuggees
+#endif
+
+#ifdef SOS_TARGET_ARM
+ARMMachine ARMMachine::s_ARMMachineInstance;
+
+// Decodes the target label of the immediate form of bl and blx instructions. The PC given is that of the
+// start of the instruction.
+static TADDR DecodeCallTarget(TADDR PC, WORD rgInstr[2])
+{
+ // Displacement is spread across several bitfields in the two words of the instruction. Using the same
+ // bitfield names as the ARM Architecture Reference Manual.
+ DWORD S = (rgInstr[0] & 0x0400) >> 10;
+ DWORD imm10 = rgInstr[0] & 0x03ff;
+ DWORD J1 = (rgInstr[1] & 0x2000) >> 13;
+ DWORD J2 = (rgInstr[1] & 0x0800) >> 11;
+ DWORD imm11 = rgInstr[1] & 0x07ff;
+
+ // For reasons that escape me the I1 and I2 fields are computed by XOR'ing J1 and J2 with S.
+ DWORD I1 = (~J1 ^ S) & 0x1;
+ DWORD I2 = (~J2 ^ S) & 0x1;
+
+ // The final displacement is put together as: SignExtend(S:I1:I2:imm10:imm11:0)
+ DWORD highByte = S ? 0xff000000 : 0x00000000;
+ DWORD disp = highByte | (I1 << 23) | (I2 << 22) | (imm10 << 12) | (imm11 << 1);
+
+ // The displacement is relative to the PC but the PC for a given instruction reads as the PC for the
+ // beginning of the instruction plus 4.
+ return PC + 4 + disp;
+}
+
+// Validate that a potential call target points to readable memory. If so, and the code appears to be one of
+// our standard jump thunks we'll deference through that and return the real target. Returns 0 if any checks
+// fail.
+static TADDR GetRealCallTarget(TADDR PC)
+{
+ WORD instr[2];
+
+ // Read the minimum (a WORD) first in case we're calling to a single WORD method at the end of a page
+ // (e.g. BLX <reg>).
+ if (g_ExtData->ReadVirtual(TO_CDADDR(PC), &instr[0], sizeof(WORD), NULL) != S_OK)
+ return 0;
+
+ // All the jump thunks we handle start with the literal form of LDR (i.e. LDR <reg>, [PC +/- <imm>]). It's
+ // always the two word form since we're either loading R12 or PC. We never use the decrement version of
+ // the instruction (U == 0).
+ // If it's not an instruction of that form we can return immediately.
+ if (instr[0] != 0xf8df)
+ return PC;
+
+ // The first instruction is defintely a LDR of the form we expect so it's OK to read the second half of
+ // the encoding.
+ if (g_ExtData->ReadVirtual(TO_CDADDR(PC + 2), &instr[1], sizeof(WORD), NULL) != S_OK)
+ return 0;
+
+ // Determine which register we're loading. There are three cases:
+ // 1) PC: we're jumping, perform final calculation of the jump target
+ // 2) R12: we're possibly setting up a special argument to the jump target. Ignore this instruction and
+ // check for a LDR PC in the next instruction
+ // 3) Any other register: we don't recognize this instruction sequence, just return the PC we have
+ WORD reg = (instr[1] & 0xf000) >> 12;
+ if (reg == 12)
+ {
+ // Possibly a LDR R12, [...]; LDR PC, [...] thunk. Overwrite the current instruction with the next and
+ // then fall through into the common LDR PC, [...] handling below. If we fail to read the next word
+ // we're not really looking at valid code. But we need to be more careful reading the second word of
+ // the potential instruction since there are valid sequences that would terminate with a single word
+ // at the end of page.
+ if (g_ExtData->ReadVirtual(TO_CDADDR(PC + 4), &instr[0], sizeof(WORD), NULL) != S_OK)
+ return 0;
+
+ // Following instruction is not a LDR <literal>. Return this PC as the real target.
+ if (instr[0] != 0xf8df)
+ return PC;
+
+ // Read second half of the LDR instruction.
+ if (g_ExtData->ReadVirtual(TO_CDADDR(PC + 6), &instr[1], sizeof(WORD), NULL) != S_OK)
+ return 0;
+
+ // Determine the target register. If it's not the PC then return this PC as the real target.
+ reg = (instr[1] & 0xf000) >> 12;
+ if (reg != 12)
+ return PC;
+
+ // Fall through to process this LDR PC, [...] instruction. Update the input PC because it figures into
+ // the calculation below.
+ PC += 4;
+ }
+ else if (reg == 15)
+ {
+ // First instruction was a LDR PC, [...] Just fall through to common handling below.
+ }
+ else
+ {
+ // Any other target register is unrecognized. Just return what we have as the final target.
+ return PC;
+ }
+
+ // Decode the LDR PC, [PC + <imm>] to find the jump target.
+ // The displacement is in the low order 12 bits of the second instruction word.
+ DWORD disp = instr[1] & 0x0fff;
+
+ // The PC used for the effective address calculation is the PC from the start of the instruction rounded
+ // down to 4-byte alignment then incremented by 4.
+ TADDR targetAddress = (PC & ~3) + 4 + disp;
+
+ // Read the target address from this routine.
+ TADDR target;
+ if (g_ExtData->ReadVirtual(TO_CDADDR(targetAddress), &target, sizeof(target), NULL) != S_OK)
+ return 0;
+
+ // Clear the low-bit in the target used to indicate a Thumb mode destination. If this is not set we can't
+ // be looking at one of our jump thunks (in fact ARM mode code is illegal under CoreARM so this would
+ // indicate an issue).
+ _ASSERTE((target & 1) == 1);
+ target &= ~1;
+
+ // Recursively call ourselves on this target in case we have any double jump thunks.
+ return GetRealCallTarget(target);
+}
+
+// Determine (heuristically, basically a best effort guess) whether an address on the stack represents a
+// return address. This is achieved by looking at the memory prior to the potential return address and
+// disassembling it to see whether it looks like a potential call. If possible the target of the callsite is
+// also returned.
+//
+// Result is returned in whereCalled:
+// 0 : retAddr doesn't look like a return address
+// 0xffffffff : retAddr looks like a return address but we couldn't tell where the call site was targeted
+// <other> : retAddr looks like a return address, *whereCalled set to target address
+void ARMMachine::IsReturnAddress(TADDR retAddr, TADDR* whereCalled) const
+{
+ *whereCalled = 0;
+
+ // If retAddr doesn't have the low-order bit set (indicating a return to Thumb code) then it can't be a
+ // legal return address.
+ if ((retAddr & 1) == 0)
+ return;
+ retAddr &= ~1;
+
+ // Potential calling instructions may have been one or two WORDs in length.
+ WORD rgPrevious[2];
+ move_xp(rgPrevious, retAddr - sizeof(rgPrevious));
+
+ // Check two-word variants first.
+ if (((rgPrevious[0] & 0xf800) == 0xf000) &&
+ ((rgPrevious[1] & 0xd000) == 0xd000))
+ {
+ // BL <label>
+
+ // Decode and validate PC-relative call target. Dereference through any jump thunks and return the
+ // call target.
+ TADDR target = GetRealCallTarget(DecodeCallTarget(retAddr - 4, rgPrevious));
+ if (target)
+ {
+ *whereCalled = target;
+ return;
+ }
+ }
+ else if (((rgPrevious[0] & 0xf800) == 0xf000) &&
+ ((rgPrevious[1] & 0xd001) == 0xc000))
+ {
+ // BLX <label>
+
+ // Decode and validate PC-relative call target. Dereference through any jump thunks and return the
+ // call target.
+ TADDR target = GetRealCallTarget(DecodeCallTarget(retAddr - 4, rgPrevious));
+ if (target)
+ {
+ *whereCalled = target;
+ return;
+ }
+ }
+ else if (((rgPrevious[0] & 0xfff0) == 0xf8d0) &&
+ ((rgPrevious[1] & 0xf000) == 0xf000))
+ {
+ // LDR PC, [<reg> + #<imm>]
+ *whereCalled = 0xffffffff;
+ return;
+ }
+ else if (((rgPrevious[0] & 0xff7f) == 0xf85f) &&
+ ((rgPrevious[1] & 0xf000) == 0xf000))
+ {
+ // LDR PC, [PC + #<imm>]
+ *whereCalled = 0xffffffff;
+ return;
+ }
+ else if (((rgPrevious[0] & 0xfff0) == 0xf850) &&
+ ((rgPrevious[1] & 0xffc0) == 0xf000))
+ {
+ // LDR PC, [<reg> + <reg>, LSL #<imm>]
+ *whereCalled = 0xffffffff;
+ return;
+ }
+
+ // Fall through any failures to decode as a two-word instruction to the one word cases below...
+
+ // BLX <register>
+ if ((rgPrevious[1] & 0xff87) == 0x4780)
+ {
+ *whereCalled = 0xffffffff;
+ return;
+ }
+}
+
+
+// Return 0 for non-managed call. Otherwise return MD address.
+static TADDR MDForCall (TADDR callee)
+{
+ // call managed code?
+ JITTypes jitType;
+ TADDR methodDesc;
+ TADDR PC = callee;
+ TADDR gcinfoAddr;
+
+ PC = GetRealCallTarget(callee);
+ if (!PC)
+ return 0;
+
+ IP2MethodDesc (PC, methodDesc, jitType, gcinfoAddr);
+ return methodDesc;
+}
+
+// Determine if a value is MT/MD/Obj
+static void HandleValue(TADDR value)
+{
+#ifndef FEATURE_PAL
+ // remove the thumb bit (if set)
+ value = value & ~1;
+#else
+ // set the thumb bit (if not set)
+ value = value | 1;
+#endif //!FEATURE_PAL
+
+ // A MethodTable?
+ if (IsMethodTable(value))
+ {
+ NameForMT_s (value, g_mdName,mdNameLen);
+ ExtOut (" (MT: %S)", g_mdName);
+ return;
+ }
+
+ // A Managed Object?
+ TADDR dwMTAddr;
+ move_xp (dwMTAddr, value);
+ if (IsStringObject(value))
+ {
+ ExtOut (" (\"");
+ StringObjectContent (value, TRUE);
+ ExtOut ("\")");
+ return;
+ }
+ else if (IsMethodTable(dwMTAddr))
+ {
+ NameForMT_s (dwMTAddr, g_mdName,mdNameLen);
+ ExtOut (" (Object: %S)", g_mdName);
+ return;
+ }
+
+ // A MethodDesc?
+ if (IsMethodDesc(value))
+ {
+ NameForMD_s (value, g_mdName,mdNameLen);
+ ExtOut (" (MD: %S)", g_mdName);
+ return;
+ }
+
+ // A JitHelper?
+ const char* name = HelperFuncName(value);
+ if (name) {
+ ExtOut (" (JitHelp: %s)", name);
+ return;
+ }
+
+ // A call to managed code?
+ TADDR methodDesc = MDForCall(value);
+ if (methodDesc)
+ {
+ NameForMD_s (methodDesc, g_mdName,mdNameLen);
+ ExtOut (" (code for MD: %S)", g_mdName);
+ return;
+ }
+
+ // Random symbol.
+ char Symbol[1024];
+ if (SUCCEEDED(g_ExtSymbols->GetNameByOffset(TO_CDADDR(value), Symbol, 1024,
+ NULL, NULL)))
+ {
+ if (Symbol[0] != '\0')
+ {
+ ExtOut (" (%s)", Symbol);
+ return;
+ }
+ }
+}
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* Unassembly a managed code. Translating managed object, *
+* call. *
+* *
+\**********************************************************************/
+void ARMMachine::Unassembly (
+ TADDR PCBegin,
+ TADDR PCEnd,
+ TADDR PCAskedFor,
+ TADDR GCStressCodeCopy,
+ GCEncodingInfo *pGCEncodingInfo,
+ SOSEHInfo *pEHInfo,
+ BOOL bSuppressLines,
+ BOOL bDisplayOffsets) const
+{
+ ULONG_PTR PC = PCBegin;
+ char line[1024];
+ char *ptr;
+ char *valueptr;
+ bool fLastWasMovW = false;
+ INT_PTR lowbits = 0;
+ ULONG curLine = -1;
+ WCHAR filename[MAX_LONGPATH];
+ ULONG linenum;
+
+ while (PC < PCEnd)
+ {
+ if (IsInterrupt())
+ return;
+
+ // Print out line numbers if needed
+ if (!bSuppressLines
+ && SUCCEEDED(GetLineByOffset(TO_CDADDR(PC), &linenum, filename, MAX_LONGPATH)))
+ {
+ if (linenum != curLine)
+ {
+ curLine = linenum;
+ ExtOut("\n%S @ %d:\n", filename, linenum);
+ }
+ }
+
+#ifndef FEATURE_PAL
+ //
+ // Print out any GC information corresponding to the current instruction offset.
+ //
+ if (pGCEncodingInfo)
+ {
+ SIZE_T curOffset = (PC - PCBegin) + pGCEncodingInfo->hotSizeToAdd;
+ while ( !pGCEncodingInfo->fDoneDecoding
+ && pGCEncodingInfo->ofs <= curOffset)
+ {
+ ExtOut(pGCEncodingInfo->buf);
+ ExtOut("\n");
+ SwitchToFiber(pGCEncodingInfo->pvGCTableFiber);
+ }
+ }
+#endif //!FEATURE_PAL
+ //
+ // Print out any EH info corresponding to the current offset
+ //
+ if (pEHInfo)
+ {
+ pEHInfo->FormatForDisassembly(PC - PCBegin);
+ }
+
+ if ((PC & ~1) == (PCAskedFor & ~1))
+ {
+ ExtOut (">>> ");
+ }
+
+ //
+ // Print offsets, in addition to actual address.
+ //
+ if (bDisplayOffsets)
+ {
+ ExtOut("%04x ", PC - PCBegin);
+ }
+
+ ULONG_PTR prevPC = PC;
+ DisasmAndClean (PC, line, _countof(line));
+
+ // look at the disassembled bytes
+ ptr = line;
+ NextTerm (ptr);
+
+ //
+ // If there is gcstress info for this method, and this is a 'hlt'
+ // instruction, then gcstress probably put the 'hlt' there. Look
+ // up the original instruction and print it instead.
+ //
+
+
+ if ( GCStressCodeCopy
+ && ( !strncmp (ptr, "de00 ", 5)
+ || !strncmp (ptr, "de01 ", 5)
+ || !strncmp (ptr, "de02 ", 5)
+ || !strncmp (ptr, "f7f0a001", 8)
+ || !strncmp (ptr, "f7f0a002", 8)
+ || !strncmp (ptr, "f7f0a003", 8)
+ ))
+ {
+ ULONG_PTR InstrAddr = prevPC;
+
+ //
+ // Compute address into saved copy of the code, and
+ // disassemble the original instruction
+ //
+
+ ULONG_PTR OrigInstrAddr = GCStressCodeCopy + (InstrAddr - PCBegin);
+ ULONG_PTR OrigPC = OrigInstrAddr;
+
+ DisasmAndClean(OrigPC, line, _countof(line));
+
+ //
+ // Increment the real PC based on the size of the unmodifed
+ // instruction
+ //
+
+ PC = InstrAddr + (OrigPC - OrigInstrAddr);
+
+ //
+ // Print out real code address in place of the copy address
+ //
+
+ ExtOut("%08x ", (ULONG)InstrAddr);
+
+ ptr = line;
+ NextTerm (ptr);
+
+ //
+ // Print out everything after the code address, and skip the
+ // instruction bytes
+ //
+
+ ExtOut(ptr);
+
+ //
+ // Add an indicator that this address has not executed yet
+ //
+
+ ExtOut(" (gcstress)");
+ }
+ else
+ {
+ ExtOut (line);
+ }
+
+ // Now advance to the opcode
+ NextTerm (ptr);
+
+ if (!strncmp (ptr, "movw ", 5) || !strncmp (ptr, "mov ", 4))
+ {
+ // Possibly the loading the low-order 16-bits of a 32-bit constant. Cache the value in case the
+ // next instruction is a movt with the high-order bits.
+ if ((valueptr = strchr(ptr, '#')) != NULL)
+ {
+ GetValueFromExpr(valueptr, lowbits);
+ fLastWasMovW = true;
+ }
+ }
+ else
+ {
+ if (!strncmp (ptr, "movt ", 5) && fLastWasMovW)
+ {
+ // A movt following a movw (if we were being really careful we'd check that the destination
+ // register was the same in both cases). Assemble the two 16-bit immediate values from both
+ // instructions and see if the resultant constant is interesting.
+ if ((valueptr = strchr(ptr, '#')) != NULL)
+ {
+ INT_PTR highbits;
+ GetValueFromExpr(valueptr, highbits);
+ HandleValue((highbits << 16) | lowbits);
+ }
+ }
+ else if ((valueptr = strchr(ptr, '=')) != NULL)
+ {
+ // Some instruction fetched a PC-relative constant which the disassembler nicely decoded for
+ // us using the ARM convention =<constant>. Retrieve this value and see if it's interesting.
+ INT_PTR value;
+ GetValueFromExpr(valueptr, value);
+ HandleValue(value);
+ }
+
+ fLastWasMovW = false;
+ }
+
+ ExtOut ("\n");
+ }
+}
+
+#if 0 // @ARMTODO: Figure out how to extract this information under CoreARM
+static void ExpFuncStateInit (TADDR *PCRetAddr)
+{
+ ULONG64 offset;
+ if (FAILED(g_ExtSymbols->GetOffsetByName("ntdll!KiUserExceptionDispatcher", &offset))) {
+ return;
+ }
+ char line[256];
+ int i = 0;
+ while (i < 3) {
+ g_ExtControl->Disassemble (offset, 0, line, 256, NULL, &offset);
+ if (strstr (line, "call")) {
+ PCRetAddr[i++] = (TADDR)offset;
+ }
+ }
+}
+#endif // 0
+
+
+// @ARMTODO: Figure out how to extract this information under CoreARM
+BOOL ARMMachine::GetExceptionContext (TADDR stack, TADDR PC, TADDR *cxrAddr, CROSS_PLATFORM_CONTEXT * cxr,
+ TADDR * exrAddr, PEXCEPTION_RECORD exr) const
+{
+ return FALSE;
+#if 0 // @ARMTODO: Figure out how to extract this information under CoreARM
+ static TADDR PCRetAddr[3] = {0,0,0};
+
+ if (PCRetAddr[0] == 0) {
+ ExpFuncStateInit (PCRetAddr);
+ }
+ *cxrAddr = 0;
+ *exrAddr = 0;
+ if (PC == PCRetAddr[0]) {
+ *exrAddr = stack + sizeof(TADDR);
+ *cxrAddr = stack + 2*sizeof(TADDR);
+ }
+ else if (PC == PCRetAddr[1]) {
+ *cxrAddr = stack + sizeof(TADDR);
+ }
+ else if (PC == PCRetAddr[2]) {
+ *exrAddr = stack + sizeof(TADDR);
+ *cxrAddr = stack + 2*sizeof(TADDR);
+ }
+ else
+ return FALSE;
+
+ if (FAILED (g_ExtData->ReadVirtual(TO_CDADDR(*cxrAddr), &stack, sizeof(stack), NULL)))
+ return FALSE;
+ *cxrAddr = stack;
+
+ if (FAILED (g_ExtData->ReadVirtual(TO_CDADDR(stack), cxr, sizeof(DT_CONTEXT), NULL))) {
+ return FALSE;
+ }
+
+ if (*exrAddr) {
+ if (FAILED (g_ExtData->ReadVirtual(TO_CDADDR(*exrAddr), &stack, sizeof(stack), NULL)))
+ {
+ *exrAddr = 0;
+ return TRUE;
+ }
+ *exrAddr = stack;
+ size_t erSize = offsetof (EXCEPTION_RECORD, ExceptionInformation);
+ if (FAILED (g_ExtData->ReadVirtual(TO_CDADDR(stack), exr, erSize, NULL))) {
+ *exrAddr = 0;
+ return TRUE;
+ }
+ }
+ return TRUE;
+#endif // 0
+}
+
+
+///
+/// Dump ARM GCInfo table
+///
+void ARMMachine::DumpGCInfo(GCInfoToken gcInfoToken, unsigned methodSize, printfFtn gcPrintf, bool encBytes, bool bPrintHeader) const
+{
+#ifndef FEATURE_PAL
+ if (bPrintHeader)
+ {
+ ExtOut("Pointer table:\n");
+ }
+
+ ARMGCDump::GCDump gcDump(gcInfoToken.Version, encBytes, 5, true);
+ gcDump.gcPrintf = gcPrintf;
+
+ gcDump.DumpGCTable(dac_cast<PTR_BYTE>(gcInfoToken.Info), methodSize, 0);
+#endif // !FEATURE_PAL
+}
+
+#endif // SOS_TARGET_ARM
diff --git a/src/ToolBox/SOS/Strike/disasmARM64.cpp b/src/ToolBox/SOS/Strike/disasmARM64.cpp
new file mode 100644
index 0000000000..6a19fc9377
--- /dev/null
+++ b/src/ToolBox/SOS/Strike/disasmARM64.cpp
@@ -0,0 +1,392 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// ==++==
+//
+
+//
+// ==--==
+
+#ifndef _TARGET_ARM64_
+#define _TARGET_ARM64_
+#endif
+
+#ifdef _TARGET_AMD64_
+#undef _TARGET_AMD64_
+#endif
+
+#include "strike.h"
+#include "util.h"
+#include <dbghelp.h>
+
+
+#include "disasm.h"
+
+#include "../../../inc/corhdr.h"
+#include "../../../inc/cor.h"
+#include "../../../inc/dacprivate.h"
+
+namespace ARM64GCDump
+{
+#undef _TARGET_X86_
+#undef LIMITED_METHOD_CONTRACT
+#define LIMITED_METHOD_DAC_CONTRACT
+#define SUPPORTS_DAC
+#define LF_GCROOTS
+#define LL_INFO1000
+#define LOG(x)
+#define LOG_PIPTR(pObjRef, gcFlags, hCallBack)
+#define DAC_ARG(x)
+#include "gcdumpnonx86.cpp"
+}
+
+#ifdef FEATURE_PAL
+void SwitchToFiber(void*)
+{
+ // TODO: Fix for linux
+ assert(false);
+}
+#endif
+
+#if !defined(_TARGET_WIN64_)
+#error This file only supports SOS targeting ARM64 from a 64-bit debugger
+#endif
+
+#if !defined(SOS_TARGET_ARM64)
+#error This file should be used to support SOS targeting ARM64 debuggees
+#endif
+
+
+void ARM64Machine::IsReturnAddress(TADDR retAddr, TADDR* whereCalled) const
+{
+ *whereCalled = 0;
+
+ DWORD previousInstr;
+ move_xp(previousInstr, retAddr - sizeof(previousInstr));
+
+ // ARM64TODO: needs to be implemented for jump stubs for ngen case
+
+ if ((previousInstr & 0xfffffc1f) == 0xd63f0000)
+ {
+ // BLR <reg>
+ *whereCalled = 0xffffffff;
+ }
+ else if ((previousInstr & 0xfc000000) == 0x94000000)
+ {
+ // BL <label>
+ DWORD imm26 = previousInstr & 0x03ffffff;
+ // offset = SignExtend(imm26:'00', 64);
+ INT64 offset = ((INT64)imm26 << 38) >> 36;
+ *whereCalled = retAddr - 4 + offset;
+ }
+}
+
+// Determine if a value is MT/MD/Obj
+static void HandleValue(TADDR value)
+{
+ // A MethodTable?
+ if (IsMethodTable(value))
+ {
+ NameForMT_s (value, g_mdName,mdNameLen);
+ ExtOut (" (MT: %S)", g_mdName);
+ return;
+ }
+
+ // A Managed Object?
+ TADDR dwMTAddr;
+ move_xp (dwMTAddr, value);
+ if (IsStringObject(value))
+ {
+ ExtOut (" (\"");
+ StringObjectContent (value, TRUE);
+ ExtOut ("\")");
+ return;
+ }
+ else if (IsMethodTable(dwMTAddr))
+ {
+ NameForMT_s (dwMTAddr, g_mdName,mdNameLen);
+ ExtOut (" (Object: %S)", g_mdName);
+ return;
+ }
+
+ // A MethodDesc?
+ if (IsMethodDesc(value))
+ {
+ NameForMD_s (value, g_mdName,mdNameLen);
+ ExtOut (" (MD: %S)", g_mdName);
+ return;
+ }
+
+ // A JitHelper?
+ const char* name = HelperFuncName(value);
+ if (name) {
+ ExtOut (" (JitHelp: %s)", name);
+ return;
+ }
+
+ // A call to managed code?
+ // ARM64TODO: not (yet) implemented. perhaps we don't need it at all.
+
+ // Random symbol.
+ char Symbol[1024];
+ if (SUCCEEDED(g_ExtSymbols->GetNameByOffset(TO_CDADDR(value), Symbol, 1024,
+ NULL, NULL)))
+ {
+ if (Symbol[0] != '\0')
+ {
+ ExtOut (" (%s)", Symbol);
+ return;
+ }
+ }
+
+}
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* Unassembly a managed code. Translating managed object, *
+* call. *
+* *
+\**********************************************************************/
+void ARM64Machine::Unassembly (
+ TADDR PCBegin,
+ TADDR PCEnd,
+ TADDR PCAskedFor,
+ TADDR GCStressCodeCopy,
+ GCEncodingInfo *pGCEncodingInfo,
+ SOSEHInfo *pEHInfo,
+ BOOL bSuppressLines,
+ BOOL bDisplayOffsets) const
+{
+ TADDR PC = PCBegin;
+ char line[1024];
+ ULONG lineNum;
+ ULONG curLine = -1;
+ WCHAR fileName[MAX_LONGPATH];
+ char *ptr;
+ INT_PTR accumulatedConstant = 0;
+ BOOL loBitsSet = FALSE;
+ BOOL hiBitsSet = FALSE;
+ char *szConstant = NULL;
+
+
+ while(PC < PCEnd)
+ {
+ ULONG_PTR currentPC = PC;
+ DisasmAndClean (PC, line, _countof(line));
+
+ // This is the closing of the previous run.
+ // Check the next instruction. if it's not a the last movk, handle the accumulated value
+ // else simply print a new line.
+ if (loBitsSet && hiBitsSet)
+ {
+ ptr = line;
+ // Advance to the instruction encoding
+ NextTerm(ptr);
+ // Advance to the opcode
+ NextTerm(ptr);
+ // if it's not movk, handle the accumulated value
+ // otherwise simply print the new line. The constant in this expression will be
+ // accumulated below.
+ if (strncmp(ptr, "movk ", 5))
+ {
+ HandleValue(accumulatedConstant);
+ accumulatedConstant = 0;
+ }
+ ExtOut ("\n");
+ }
+ else if (currentPC != PCBegin)
+ {
+ ExtOut ("\n");
+ }
+
+ // This is the new instruction
+
+ if (IsInterrupt())
+ return;
+ //
+ // Print out line numbers if needed
+ //
+ if (!bSuppressLines &&
+ SUCCEEDED(GetLineByOffset(TO_CDADDR(currentPC), &lineNum, fileName, MAX_LONGPATH)))
+ {
+ if (lineNum != curLine)
+ {
+ curLine = lineNum;
+ ExtOut("\n%S @ %d:\n", fileName, lineNum);
+ }
+ }
+
+ //
+ // Print out any GC information corresponding to the current instruction offset.
+ //
+ if (pGCEncodingInfo)
+ {
+ SIZE_T curOffset = (currentPC - PCBegin) + pGCEncodingInfo->hotSizeToAdd;
+ while ( !pGCEncodingInfo->fDoneDecoding
+ && pGCEncodingInfo->ofs <= curOffset)
+ {
+ ExtOut(pGCEncodingInfo->buf);
+ ExtOut("\n");
+ SwitchToFiber(pGCEncodingInfo->pvGCTableFiber);
+ }
+ }
+
+ //
+ // Print out any EH info corresponding to the current offset
+ //
+ if (pEHInfo)
+ {
+ pEHInfo->FormatForDisassembly(currentPC - PCBegin);
+ }
+
+ if (currentPC == PCAskedFor)
+ {
+ ExtOut (">>> ");
+ }
+
+ //
+ // Print offsets, in addition to actual address.
+ //
+ if (bDisplayOffsets)
+ {
+ ExtOut("%04x ", currentPC - PCBegin);
+ }
+
+ // look at the disassembled bytes
+ ptr = line;
+ NextTerm (ptr);
+
+ //
+ // If there is gcstress info for this method, and this is a 'hlt'
+ // instruction, then gcstress probably put the 'hlt' there. Look
+ // up the original instruction and print it instead.
+ //
+
+
+ if ( GCStressCodeCopy
+ && ( !strncmp (ptr, "badc0de0", 8)
+ || !strncmp (ptr, "badc0de1", 8)
+ || !strncmp (ptr, "badc0de2", 8)
+ ))
+ {
+ ULONG_PTR InstrAddr = currentPC;
+
+ //
+ // Compute address into saved copy of the code, and
+ // disassemble the original instruction
+ //
+
+ ULONG_PTR OrigInstrAddr = GCStressCodeCopy + (InstrAddr - PCBegin);
+ ULONG_PTR OrigPC = OrigInstrAddr;
+
+ DisasmAndClean(OrigPC, line, _countof(line));
+
+ //
+ // Increment the real PC based on the size of the unmodifed
+ // instruction
+ //
+
+ PC = InstrAddr + (OrigPC - OrigInstrAddr);
+
+ //
+ // Print out real code address in place of the copy address
+ //
+
+ ExtOut("%08x`%08x ", (ULONG)(InstrAddr >> 32), (ULONG)InstrAddr);
+
+ ptr = line;
+ NextTerm (ptr);
+
+ //
+ // Print out everything after the code address, and skip the
+ // instruction bytes
+ //
+
+ ExtOut(ptr);
+
+ //
+ // Add an indicator that this address has not executed yet
+ //
+
+ ExtOut(" (gcstress)");
+ }
+ else
+ {
+ ExtOut (line);
+ }
+
+ // Now advance to the opcode
+ NextTerm (ptr);
+
+ if (!strncmp(ptr, "mov ", 4))
+ {
+ if ((szConstant = strchr(ptr, '#')) != NULL)
+ {
+ GetValueFromExpr(szConstant, accumulatedConstant);
+ loBitsSet = TRUE;
+ }
+ }
+ else if (!strncmp(ptr, "movk ", 5))
+ {
+ char *szShiftAmount = NULL;
+ INT_PTR shiftAmount = 0;
+ INT_PTR constant = 0;
+ if (((szShiftAmount = strrchr(ptr, '#')) != NULL) &&
+ ((szConstant = strchr(ptr, '#')) != NULL) &&
+ (szShiftAmount != szConstant) &&
+ (accumulatedConstant > 0)) // Misses when movk is succeeding mov reg, #0x0, which I don't think makes any sense
+ {
+ GetValueFromExpr(szShiftAmount, shiftAmount);
+ GetValueFromExpr(szConstant, constant);
+ accumulatedConstant += (constant<<shiftAmount);
+ hiBitsSet = TRUE;
+ }
+ }
+ else
+ {
+ accumulatedConstant = 0;
+ loBitsSet = hiBitsSet = FALSE;
+ if ((szConstant = strchr(ptr, '=')) != NULL)
+ {
+ // Some instruction fetched a PC-relative constant which the disassembler nicely decoded for
+ // us using the ARM convention =<constant>. Retrieve this value and see if it's interesting.
+ INT_PTR value;
+ GetValueFromExpr(szConstant, value);
+ HandleValue(value);
+ }
+
+
+ // ARM64TODO: we could possibly handle adr(p)/ldr pair too.
+ }
+
+ }
+ ExtOut ("\n");
+}
+
+
+// @ARMTODO: Figure out how to extract this information under CoreARM
+BOOL ARM64Machine::GetExceptionContext (TADDR stack, TADDR PC, TADDR *cxrAddr, CROSS_PLATFORM_CONTEXT * cxr,
+ TADDR * exrAddr, PEXCEPTION_RECORD exr) const
+{
+ _ASSERTE("ARM64:NYI");
+ return FALSE;
+}
+
+///
+/// Dump ARM GCInfo table
+///
+void ARM64Machine::DumpGCInfo(GCInfoToken gcInfoToken, unsigned methodSize, printfFtn gcPrintf, bool encBytes, bool bPrintHeader) const
+{
+ if (bPrintHeader)
+ {
+ ExtOut("Pointer table:\n");
+ }
+
+ ARM64GCDump::GCDump gcDump(gcInfoToken.Version, encBytes, 5, true);
+ gcDump.gcPrintf = gcPrintf;
+
+ gcDump.DumpGCTable(dac_cast<PTR_BYTE>(gcInfoToken.Info), methodSize, 0);
+}
+
diff --git a/src/ToolBox/SOS/Strike/disasmX86.cpp b/src/ToolBox/SOS/Strike/disasmX86.cpp
new file mode 100644
index 0000000000..36a08d20a3
--- /dev/null
+++ b/src/ToolBox/SOS/Strike/disasmX86.cpp
@@ -0,0 +1,1707 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// ==++==
+//
+
+//
+// ==--==
+#include "strike.h"
+#include "util.h"
+#include "disasm.h"
+#include <dbghelp.h>
+
+#include "../../../inc/corhdr.h"
+#include "../../../inc/cor.h"
+#include "../../../inc/dacprivate.h"
+
+
+#if defined(SOS_TARGET_X86) && defined(SOS_TARGET_AMD64)
+#error This file does not support SOS targeting both X86 and AMD64 debuggees
+#endif
+
+#if !defined(SOS_TARGET_X86) && !defined(SOS_TARGET_AMD64)
+#error This file should be used to support SOS targeting either X86 or AMD64 debuggees
+#endif
+
+
+// These must be in the same order as they are used in the instruction
+// encodings/same as the CONTEXT field order.
+enum RegIndex
+{
+ EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI,
+
+#ifdef _TARGET_AMD64_
+ R8, R9, R10, R11, R12, R13, R14, R15,
+#endif // _TARGET_AMD64_
+
+ EIP, NONE
+};
+
+const int NumReg = NONE;
+struct Register
+{
+ TADDR value;
+ BOOL bValid;
+ TADDR stack;
+ BOOL bOnStack;
+};
+
+// Find the index for a register name
+inline RegIndex FindReg (___in __in_z char *ptr, __out_opt int *plen = NULL, __out_opt int *psize = NULL)
+{
+ struct RegName
+ {
+ RegIndex index;
+ PCSTR pszName;
+ int cchName;
+ int size;
+ };
+
+ static RegName rgRegNames[] = {
+
+#define REG(index, reg, size) { index, #reg, sizeof(#reg)-1, size }
+#define REG8(index, reg) REG(index, reg, 1)
+#define REG16(index, reg) REG(index, reg, 2)
+#define REG32(index, reg) REG(index, reg, 4)
+#define REG64(index, reg) REG(index, reg, 8)
+
+ REG8(EAX, al),
+ REG8(EAX, ah),
+ REG8(EBX, bl),
+ REG8(EBX, bh),
+ REG8(ECX, cl),
+ REG8(ECX, ch),
+ REG8(EDX, dl),
+ REG8(EDX, dh),
+
+ REG16(EAX, ax),
+ REG16(EBX, bx),
+ REG16(ECX, cx),
+ REG16(EDX, dx),
+ REG16(ESI, si),
+ REG16(EDI, di),
+ REG16(EBP, bp),
+ REG16(ESP, sp),
+
+ REG32(EAX, eax),
+ REG32(EBX, ebx),
+ REG32(ECX, ecx),
+ REG32(EDX, edx),
+ REG32(ESI, esi),
+ REG32(EDI, edi),
+ REG32(EBP, ebp),
+ REG32(ESP, esp),
+
+#ifdef _TARGET_AMD64_
+
+ REG8(R8, r8b),
+ REG8(R9, r9b),
+ REG8(R10, r10b),
+ REG8(R11, r11b),
+ REG8(R12, r12b),
+ REG8(R13, r13b),
+ REG8(R14, r14b),
+ REG8(R15, r15b),
+
+ REG16(R8, r8w),
+ REG16(R9, r9w),
+ REG16(R10, r10w),
+ REG16(R11, r11w),
+ REG16(R12, r12w),
+ REG16(R13, r13w),
+ REG16(R14, r14w),
+ REG16(R15, r15w),
+
+ REG32(R8, r8d),
+ REG32(R9, r9d),
+ REG32(R10, r10d),
+ REG32(R11, r11d),
+ REG32(R12, r12d),
+ REG32(R13, r13d),
+ REG32(R14, r14d),
+ REG32(R15, r15d),
+
+ REG64(EAX, rax),
+ REG64(EBX, rbx),
+ REG64(ECX, rcx),
+ REG64(EDX, rdx),
+ REG64(ESI, rsi),
+ REG64(EDI, rdi),
+ REG64(EBP, rbp),
+ REG64(ESP, rsp),
+ REG64(R8, r8),
+ REG64(R9, r9),
+ REG64(R10, r10),
+ REG64(R11, r11),
+ REG64(R12, r12),
+ REG64(R13, r13),
+ REG64(R14, r14),
+ REG64(R15, r15),
+
+#endif // _TARGET_AMD64_
+
+#undef REG
+#undef REG8
+#undef REG16
+#undef REG32
+#undef REG64
+
+ };
+
+ for (size_t i = 0; i < sizeof(rgRegNames)/sizeof(rgRegNames[0]); i++)
+ {
+ if (!strncmp(ptr, rgRegNames[i].pszName, rgRegNames[i].cchName))
+ {
+ if (psize)
+ *psize = rgRegNames[i].size;
+
+ if (plen)
+ *plen = rgRegNames[i].cchName;
+
+ return rgRegNames[i].index;
+ }
+ }
+
+ return NONE;
+}
+
+// Find the value of an expression.
+inline BOOL FindSrc (__in_z char *ptr, ___in Register *reg, INT_PTR &value, BOOL &bDigit)
+{
+ if (GetValueFromExpr (ptr, value))
+ {
+ bDigit = TRUE;
+ return TRUE;
+ }
+
+ BOOL bValid = FALSE;
+ BOOL bByRef = IsByRef (ptr);
+ bDigit = FALSE;
+
+ int regnamelen;
+ RegIndex index = FindReg (ptr, &regnamelen);
+ if (index != NONE)
+ {
+ if (reg[index].bValid)
+ {
+ value = reg[index].value;
+ ptr += regnamelen;
+ // TODO: consider ecx+edi*4+0x4
+ if ((IsTermSep (ptr[0]) && !bByRef)
+ || (ptr[0] == ']' && bByRef))
+ {
+ bValid = TRUE;
+ if (bByRef)
+ SafeReadMemory (TO_TADDR(value), &value, sizeof(void*), NULL);
+ }
+ }
+ }
+ return bValid;
+}
+
+enum ADDRESSMODE {REG, DATA, INDIRECT, NODATA, BAD};
+
+struct RegState
+{
+ RegIndex reg;
+ BOOL bFullReg;
+ char scale;
+ int namelen;
+};
+
+struct InstData
+{
+ ADDRESSMODE mode;
+ RegState reg[2];
+ INT_PTR value;
+};
+
+void FindMainReg (___in __in_z char *ptr, RegState &reg)
+{
+ int size = 0;
+
+ reg.reg = FindReg(ptr, &reg.namelen, &size);
+
+ reg.bFullReg = (reg.reg!=NONE && sizeof(void*)==size) ? TRUE : FALSE;
+}
+
+static void DecodeAddressIndirect (___in __in_z char *term, InstData& arg)
+{
+ arg.mode = BAD;
+ arg.value = 0;
+ arg.reg[0].scale = 0;
+ arg.reg[1].scale = 0;
+
+ if (!IsByRef (term))
+ {
+ return;
+ }
+
+ // first part must be a reg
+ arg.reg[0].scale = 1;
+ if (term[0] == '+')
+ term ++;
+ else if (term[0] == '-')
+ {
+ term ++;
+ arg.reg[0].scale = -1;
+ }
+ if (isdigit(term[0]))
+ {
+ arg.reg[0].scale *= term[0]-'0';
+ term ++;
+ }
+
+ FindMainReg (term, arg.reg[0]);
+ if (arg.reg[0].reg == NONE)
+ return;
+ term += arg.reg[0].namelen;
+
+ if (term[0] == ']')
+ {
+ // It is [reg]
+ arg.mode = INDIRECT;
+ arg.value = 0;
+ return;
+ }
+
+ char sign = (char)((term[0] == '+')?1:-1);
+ term ++;
+ FindMainReg (term, arg.reg[1]);
+ if (arg.reg[1].reg != NONE)
+ {
+ // It is either [reg+reg*c] or [reg+reg*c+c]
+
+ term += arg.reg[1].namelen;
+
+ if (term[0] == '*')
+ {
+ term ++;
+ arg.reg[1].scale = sign*(term[0]-'0');
+ term ++;
+ }
+ else
+ arg.reg[1].scale = sign;
+
+ if (term[0] == ']')
+ {
+ // It is [reg+reg*c]
+ arg.mode = INDIRECT;
+ arg.value = 0;
+ return;
+ }
+ sign = (char)((term[0] == '+')?1:-1);
+ term ++;
+ }
+
+ char *endptr;
+ arg.value = strtoul(term, &endptr, 16);
+ if (endptr[0] == ']')
+ {
+ // It is [reg+reg*c+c]
+ arg.value *= sign;
+ arg.mode = INDIRECT;
+ }
+}
+
+void DecodeAddressTerm (___in __in_z char *term, InstData& arg)
+{
+ arg.mode = BAD;
+ arg.reg[0].scale = 0;
+ arg.reg[1].scale = 0;
+ arg.value = 0;
+ INT_PTR value;
+
+ if (GetValueFromExpr (term, value))
+ {
+ arg.value = value;
+ arg.mode = DATA;
+ }
+ else
+ {
+ FindMainReg (term, arg.reg[0]);
+ if (arg.reg[0].reg != NONE)
+ {
+ arg.mode = REG;
+ }
+ else
+ {
+ DecodeAddressIndirect (term, arg);
+ }
+ }
+}
+
+// Return 0 for non-managed call. Otherwise return MD address.
+TADDR MDForCall (TADDR callee)
+{
+ // call managed code?
+ JITTypes jitType;
+ TADDR methodDesc;
+ TADDR IP = callee;
+ TADDR gcinfoAddr;
+
+ if (!GetCalleeSite (callee, IP))
+ return 0;
+
+ IP2MethodDesc (IP, methodDesc, jitType, gcinfoAddr);
+ if (methodDesc)
+ {
+ return methodDesc;
+ }
+
+ // jmp stub
+ char line[256];
+ DisasmAndClean (IP, line, 256);
+ char *ptr = line;
+ NextTerm (ptr);
+ NextTerm (ptr);
+ if (!strncmp (ptr, "jmp ", 4))
+ {
+ // jump thunk
+ NextTerm (ptr);
+ INT_PTR value;
+ methodDesc = 0;
+ if (GetValueFromExpr (ptr, value))
+ {
+ IP2MethodDesc (value, methodDesc, jitType, gcinfoAddr);
+ }
+ return methodDesc;
+ }
+ return 0;
+}
+
+// Handle a call instruction.
+void HandleCall(TADDR callee, Register *reg)
+{
+ // call managed code?
+ TADDR methodDesc = MDForCall (callee);
+ if (methodDesc)
+ {
+ DacpMethodDescData MethodDescData;
+ if (MethodDescData.Request(g_sos, TO_CDADDR(methodDesc)) == S_OK)
+ {
+ NameForMD_s(methodDesc, g_mdName,mdNameLen);
+ ExtOut(" (%S, mdToken: %p)", g_mdName, SOS_PTR(MethodDescData.MDToken));
+ return;
+ }
+ }
+
+#ifdef _TARGET_AMD64_
+ // A jump thunk?
+
+ CONTEXT ctx = {0};
+
+ ctx.ContextFlags = (CONTEXT_AMD64 | CONTEXT_CONTROL | CONTEXT_INTEGER);
+
+ for (unsigned ireg = 0; ireg < 16; ireg++)
+ {
+ if (reg[ireg].bValid)
+ {
+ *(&ctx.Rax + ireg) = reg[ireg].value;
+ }
+ }
+
+ ctx.Rip = callee;
+
+ CLRDATA_ADDRESS ip = 0, md = 0;
+ if (S_OK == g_sos->GetJumpThunkTarget(&ctx, &ip, &md))
+ {
+ if (md)
+ {
+ DacpMethodDescData MethodDescData;
+ if (MethodDescData.Request(g_sos, md) == S_OK)
+ {
+ NameForMD_s(md, g_mdName,mdNameLen);
+ ExtOut(" (%S, mdToken: %p)", g_mdName, SOS_PTR(MethodDescData.MDToken));
+ return;
+ }
+ }
+
+ if (ip != callee)
+ {
+ return HandleCall(ip, reg);
+ }
+ }
+#endif // _TARGET_AMD64_
+
+ // A JitHelper?
+ const char* name = HelperFuncName(callee);
+ if (name) {
+ ExtOut (" (JitHelp: %s)", name);
+ return;
+ }
+
+ // call unmanaged code?
+ char Symbol[1024];
+ if (SUCCEEDED(g_ExtSymbols->GetNameByOffset(TO_CDADDR(callee), Symbol, 1024,
+ NULL, NULL)))
+ {
+ if (Symbol[0] != '\0')
+ {
+ ExtOut (" (%s)", Symbol);
+ return;
+ }
+ }
+}
+
+// Determine if a value is MT/MD/Obj
+void HandleValue(TADDR value)
+{
+ // A MethodTable?
+ if (IsMethodTable(value))
+ {
+ NameForMT_s (value, g_mdName,mdNameLen);
+ ExtOut (" (MT: %S)", g_mdName);
+ return;
+ }
+
+ // A Managed Object?
+ TADDR dwMTAddr;
+ move_xp (dwMTAddr, value);
+ if (IsStringObject(value))
+ {
+ ExtOut (" (\"");
+ StringObjectContent (value, TRUE);
+ ExtOut ("\")");
+ return;
+ }
+ else if (IsMethodTable(dwMTAddr))
+ {
+ NameForMT_s (dwMTAddr, g_mdName,mdNameLen);
+ ExtOut (" (Object: %S)", g_mdName);
+ return;
+ }
+
+ // A MethodDesc?
+ if (IsMethodDesc(value))
+ {
+ NameForMD_s (value, g_mdName,mdNameLen);
+ ExtOut (" (MD: %S)", g_mdName);
+ return;
+ }
+
+ // A JitHelper?
+ const char* name = HelperFuncName(value);
+ if (name) {
+ ExtOut (" (JitHelp: %s)", name);
+ return;
+ }
+}
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* Unassembly a managed code. Translating managed object, *
+* call. *
+* *
+\**********************************************************************/
+void
+#ifdef _TARGET_X86_
+ X86Machine::Unassembly
+#elif defined(_TARGET_AMD64_)
+ AMD64Machine::Unassembly
+#endif
+ (TADDR IPBegin,
+ TADDR IPEnd,
+ TADDR IPAskedFor,
+ TADDR GCStressCodeCopy,
+ GCEncodingInfo *pGCEncodingInfo,
+ SOSEHInfo *pEHInfo,
+ BOOL bSuppressLines,
+ BOOL bDisplayOffsets) const
+{
+ ULONG_PTR IP = IPBegin;
+ char line[1024];
+ Register reg [NumReg];
+ ZeroMemory (reg, sizeof(reg));
+ RegIndex dest;
+ INT_PTR value;
+ BOOL bDigit;
+ char *ptr;
+
+ ULONG curLine = -1;
+ WCHAR filename[MAX_LONGPATH];
+ ULONG linenum;
+
+ while (IP < IPEnd)
+ {
+ if (IsInterrupt())
+ return;
+
+ // Print out line numbers if needed
+ if (!bSuppressLines
+ && SUCCEEDED(GetLineByOffset(TO_CDADDR(IP), &linenum, filename, MAX_LONGPATH)))
+ {
+ if (linenum != curLine)
+ {
+ curLine = linenum;
+ ExtOut("\n%S @ %d:\n", filename, linenum);
+ }
+ }
+
+ //
+ // Print out any GC information corresponding to the current instruction offset.
+ //
+
+#ifndef FEATURE_PAL
+ if (pGCEncodingInfo)
+ {
+ SIZE_T curOffset = (IP - IPBegin) + pGCEncodingInfo->hotSizeToAdd;
+ while ( !pGCEncodingInfo->fDoneDecoding
+ && pGCEncodingInfo->ofs <= curOffset)
+ {
+ ExtOut(pGCEncodingInfo->buf);
+ ExtOut("\n");
+ SwitchToFiber(pGCEncodingInfo->pvGCTableFiber);
+ }
+ }
+#endif // FEATURE_PAL
+
+ ULONG_PTR InstrAddr = IP;
+
+ //
+ // Print out any EH info corresponding to the current offset
+ //
+ if (pEHInfo)
+ {
+ pEHInfo->FormatForDisassembly(IP - IPBegin);
+ }
+
+ if (IP == IPAskedFor)
+ {
+ ExtOut (">>> ");
+ }
+
+ //
+ // Print offsets, in addition to actual address.
+ //
+ if (bDisplayOffsets)
+ {
+ ExtOut("%04x ", IP - IPBegin);
+ }
+
+ DisasmAndClean (IP, line, _countof(line));
+
+ // look at key word
+ ptr = line;
+ NextTerm (ptr);
+ NextTerm (ptr);
+
+ //
+ // If there is gcstress info for this method, and this is a 'hlt'
+ // instruction, then gcstress probably put the 'hlt' there. Look
+ // up the original instruction and print it instead.
+ //
+
+ SSIZE_T cbIPOffset = 0;
+
+ if ( GCStressCodeCopy
+ && ( !strncmp (ptr, "hlt", 3)
+ || !strncmp (ptr, "cli", 3)
+ || !strncmp (ptr, "sti", 3)))
+ {
+ //
+ // Compute address into saved copy of the code, and
+ // disassemble the original instruction
+ //
+
+ ULONG_PTR OrigInstrAddr = GCStressCodeCopy + (InstrAddr - IPBegin);
+ ULONG_PTR OrigIP = OrigInstrAddr;
+
+ DisasmAndClean(OrigIP, line, _countof(line));
+
+ //
+ // Increment the real IP based on the size of the unmodifed
+ // instruction
+ //
+
+ IP = InstrAddr + (OrigIP - OrigInstrAddr);
+
+ cbIPOffset = IP - OrigIP;
+
+ //
+ // Print out real code address in place of the copy address
+ //
+
+#ifdef _WIN64
+ ExtOut("%08x`%08x ", (ULONG)(InstrAddr >> 32), (ULONG)InstrAddr);
+#else
+ ExtOut("%08x ", (ULONG)InstrAddr);
+#endif
+
+ ptr = line;
+ NextTerm (ptr);
+
+ //
+ // Print out everything after the code address, and skip the
+ // instruction bytes
+ //
+
+ ExtOut(ptr);
+
+ NextTerm (ptr);
+
+ //
+ // Add an indicator that this address has not executed yet
+ //
+
+ ExtOut(" (gcstress)");
+ }
+ else
+ {
+ ExtOut (line);
+ }
+
+ if (!strncmp (ptr, "mov ", 4))
+ {
+ NextTerm (ptr);
+
+ dest = FindReg(ptr);
+ if (dest != NONE)
+ {
+ NextTerm (ptr);
+
+ if (FindSrc (ptr, reg, value, bDigit))
+ {
+ reg[dest].bValid = TRUE;
+ reg[dest].value = value;
+ // Is it a managed obj
+ if (bDigit)
+ HandleValue (reg[dest].value);
+ }
+ else
+ {
+ reg[dest].bValid = FALSE;
+ }
+ }
+ }
+ else if (!strncmp (ptr, "call ", 5))
+ {
+ NextTerm (ptr);
+ if (FindSrc (ptr, reg, value, bDigit))
+ {
+ if (bDigit)
+ value += cbIPOffset;
+
+ HandleCall (value, reg);
+ }
+
+ // trash EAX, ECX, EDX
+ reg[EAX].bValid = FALSE;
+ reg[ECX].bValid = FALSE;
+ reg[EDX].bValid = FALSE;
+
+#ifdef _TARGET_AMD64_
+ reg[R8].bValid = FALSE;
+ reg[R9].bValid = FALSE;
+ reg[R10].bValid = FALSE;
+ reg[R11].bValid = FALSE;
+#endif // _TARGET_AMD64_
+ }
+ else if (!strncmp (ptr, "lea ", 4))
+ {
+ NextTerm (ptr);
+ dest = FindReg(ptr);
+ if (dest != NONE)
+ {
+ NextTerm (ptr);
+ if (FindSrc (ptr, reg, value, bDigit))
+ {
+ reg[dest].bValid = TRUE;
+ reg[dest].value = value;
+ }
+ else
+ {
+ reg[dest].bValid = FALSE;
+ }
+ }
+ }
+ else if (!strncmp (ptr, "push ", 5))
+ {
+ // do not do anything
+ NextTerm (ptr);
+ if (FindSrc (ptr, reg, value, bDigit))
+ {
+ if (bDigit)
+ {
+ HandleValue (value);
+ }
+ }
+ }
+ else
+ {
+ // assume this instruction will trash dest reg
+ NextTerm (ptr);
+ dest = FindReg(ptr);
+ if (dest != NONE)
+ reg[dest].bValid = FALSE;
+ }
+ ExtOut ("\n");
+ }
+
+ //
+ // Print out any "end" EH info (where the end address is the byte immediately following the last instruction)
+ //
+ if (pEHInfo)
+ {
+ pEHInfo->FormatForDisassembly(IP - IPBegin);
+ }
+}
+
+// Find the real callee site. Handle JMP instruction.
+// Return TRUE if we get the address, FALSE if not.
+BOOL GetCalleeSite (TADDR IP, TADDR &IPCallee)
+{
+ while (TRUE) {
+ unsigned char inst[2];
+ if (g_ExtData->ReadVirtual(TO_CDADDR(IP), inst, sizeof(inst), NULL) != S_OK)
+ {
+ return FALSE;
+ }
+ if (inst[0] == 0xEB) {
+ IP += 2+(char)inst[1];
+ }
+ else if (inst[0] == 0xE9) {
+ int displace;
+ if (g_ExtData->ReadVirtual(TO_CDADDR(IP+1), &displace, sizeof(displace), NULL) != S_OK)
+ {
+ return FALSE;
+ }
+ else
+ {
+ IP += 5+displace;
+ }
+ }
+ else if (inst[0] == 0xFF && (inst[1] & 070) == 040) {
+ if (inst[1] == 0x25) {
+ DWORD displace;
+ if (g_ExtData->ReadVirtual(TO_CDADDR(IP+2), &displace, sizeof(displace), NULL) != S_OK)
+ {
+ return FALSE;
+ }
+ if (g_ExtData->ReadVirtual(TO_CDADDR(displace), &displace, sizeof(displace), NULL) != S_OK)
+ {
+ return FALSE;
+ }
+ else
+ {
+ IP = displace;
+ }
+ }
+ else
+ // Target for jmp is determined from register values.
+ return FALSE;
+ }
+ else
+ {
+ IPCallee = IP;
+ return TRUE;
+ }
+ }
+}
+
+// GetFinalTarget is based on HandleCall, but avoids printing anything to the output.
+// This is currently only called on x64
+eTargetType GetFinalTarget(TADDR callee, TADDR* finalMDorIP)
+{
+ // call managed code?
+ TADDR methodDesc = MDForCall (callee);
+ if (methodDesc)
+ {
+ DacpMethodDescData MethodDescData;
+ if (MethodDescData.Request(g_sos, TO_CDADDR(methodDesc)) == S_OK)
+ {
+ *finalMDorIP = methodDesc;
+ return ettMD;
+ }
+ }
+
+#ifdef _TARGET_AMD64_
+ // A jump thunk?
+
+ CONTEXT ctx = {0};
+ ctx.ContextFlags = (CONTEXT_AMD64 | CONTEXT_CONTROL | CONTEXT_INTEGER);
+ ctx.Rip = callee;
+
+ CLRDATA_ADDRESS ip = 0, md = 0;
+ if (S_OK == g_sos->GetJumpThunkTarget(&ctx, &ip, &md))
+ {
+ if (md)
+ {
+ DacpMethodDescData MethodDescData;
+ if (MethodDescData.Request(g_sos, md) == S_OK)
+ {
+ *finalMDorIP = md;
+ return ettStub;
+ }
+ }
+
+ if (ip != callee)
+ {
+ return GetFinalTarget(ip, finalMDorIP);
+ }
+ }
+#endif // _TARGET_AMD64_
+
+ // A JitHelper?
+ const char* name = HelperFuncName(callee);
+ if (name) {
+ *finalMDorIP = callee;
+ return ettJitHelp;
+ }
+
+ // call unmanaged code?
+ *finalMDorIP = callee;
+ return ettNative;
+}
+
+#ifndef FEATURE_PAL
+
+void ExpFuncStateInit (TADDR *IPRetAddr)
+{
+ ULONG64 offset;
+ if (FAILED(g_ExtSymbols->GetOffsetByName("ntdll!KiUserExceptionDispatcher", &offset))) {
+ return;
+ }
+
+ // test if we have a minidump for which the image is not cached anymore. this avoids
+ // the having the while loop below spin forever (or a very long time)...
+ // (Watson backend hit this a few times, and they had to institute a timeout policy
+ // to work around this)
+ SIZE_T instrs;
+ if (FAILED(g_ExtData->ReadVirtual(offset, &instrs, sizeof(instrs), NULL)) || instrs == 0) {
+ return;
+ }
+
+ char line[256];
+ int i = 0;
+ int cnt = 0;
+#ifdef SOS_TARGET_X86
+ // On x86 and x64 the last 3 "call" instructions in ntdll!KiUserExceptionDispatcher
+ // are making calls to OS APIs that take as argument the context record (and some
+ // of them the exception record as well)
+ const int cCallInstrs = 3;
+#elif defined(SOS_TARGET_AMD64)
+ // On x64 the first "call" instruction should be considered, as well
+ const int cCallInstrs = 4;
+#endif
+
+ while (i < cCallInstrs) {
+ g_ExtControl->Disassemble (offset, 0, line, 256, NULL, &offset);
+ if (strstr (line, "call")) {
+ IPRetAddr[i++] = (TADDR)offset;
+ }
+ // if we didn't find at least one "call" in the first 500 instructions give up...
+ if (++cnt >= 500 && IPRetAddr[0] == 0)
+ break;
+ }
+}
+
+#endif // FEATURE_PAL
+
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function is called to fill in a cross platform context *
+* struct by looking on the stack for return addresses into *
+* KiUserExceptionDispatcher *
+* *
+\**********************************************************************/
+BOOL
+#ifdef SOS_TARGET_X86
+ X86Machine::GetExceptionContext
+#elif defined(SOS_TARGET_AMD64)
+ AMD64Machine::GetExceptionContext
+#endif
+ (TADDR stack,
+ TADDR IP,
+ TADDR * cxrAddr,
+ CROSS_PLATFORM_CONTEXT * pcxr,
+ TADDR * exrAddr,
+ PEXCEPTION_RECORD exr) const
+{
+#ifndef FEATURE_PAL
+#ifdef SOS_TARGET_X86
+ X86_CONTEXT * cxr = &pcxr->X86Context;
+ size_t contextSize = offsetof(CONTEXT, ExtendedRegisters);
+#elif defined(SOS_TARGET_AMD64)
+ AMD64_CONTEXT * cxr = &pcxr->Amd64Context;
+ size_t contextSize = offsetof(CONTEXT, FltSave);
+#endif
+
+ static TADDR IPRetAddr[4] = {0, 0, 0, 0};
+
+ if (IPRetAddr[0] == 0) {
+ ExpFuncStateInit (IPRetAddr);
+ }
+ *cxrAddr = 0;
+ *exrAddr = 0;
+
+#ifdef SOS_TARGET_X86
+
+ if (IP == IPRetAddr[0]) {
+ *exrAddr = stack + sizeof(TADDR);
+ *cxrAddr = stack + 2*sizeof(TADDR);
+ }
+ else if (IP == IPRetAddr[1]) {
+ *cxrAddr = stack + sizeof(TADDR);
+ }
+ else if (IP == IPRetAddr[2]) {
+ *exrAddr = stack + sizeof(TADDR);
+ *cxrAddr = stack + 2*sizeof(TADDR);
+ }
+ else
+ return FALSE;
+
+ if (FAILED (g_ExtData->ReadVirtual(TO_CDADDR(*cxrAddr), &stack, sizeof(stack), NULL)))
+ return FALSE;
+ *cxrAddr = stack;
+
+ //if ((pContext->ContextFlags & CONTEXT_EXTENDED_REGISTERS) == CONTEXT_EXTENDED_REGISTERS)
+ // contextSize += sizeof(pContext->ExtendedRegisters);
+ if (FAILED (g_ExtData->ReadVirtual(TO_CDADDR(stack), cxr, (ULONG)contextSize, NULL))) {
+ return FALSE;
+ }
+
+ if (*exrAddr) {
+ if (FAILED (g_ExtData->ReadVirtual(TO_CDADDR(*exrAddr), &stack, sizeof(stack), NULL)))
+ {
+ *exrAddr = 0;
+ return TRUE;
+ }
+ *exrAddr = stack;
+ size_t erSize = offsetof (EXCEPTION_RECORD, ExceptionInformation);
+ if (FAILED (g_ExtData->ReadVirtual(TO_CDADDR(stack), exr, (ULONG)erSize, NULL))) {
+ *exrAddr = 0;
+ return TRUE;
+ }
+ }
+
+#elif defined(SOS_TARGET_AMD64)
+
+ if (IP == IPRetAddr[0] || IP == IPRetAddr[1] || IP == IPRetAddr[3]) {
+ *exrAddr = stack + sizeof(TADDR) + 0x4F0;
+ *cxrAddr = stack + sizeof(TADDR);
+ } else if (IP == IPRetAddr[2]) {
+ *cxrAddr = stack + sizeof(TADDR);
+ }
+ else {
+ return FALSE;
+ }
+
+ if (FAILED (g_ExtData->ReadVirtual(TO_CDADDR(*cxrAddr), cxr, (ULONG)contextSize, NULL))) {
+ return FALSE;
+ }
+
+ if (*exrAddr) {
+ size_t erSize = offsetof (EXCEPTION_RECORD, ExceptionInformation);
+ if (FAILED (g_ExtData->ReadVirtual(TO_CDADDR(*exrAddr), exr, (ULONG)erSize, NULL))) {
+ *exrAddr = 0;
+ return TRUE;
+ }
+ }
+
+#endif
+ return TRUE;
+#else
+ return FALSE;
+#endif // FEATURE_PAL
+}
+
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function is called to determine if a DWORD on the stack is *
+* a return address. *
+* It does this by checking several bytes before the DWORD to see if *
+* there is a call instruction. *
+* *
+\**********************************************************************/
+
+void
+#ifdef _TARGET_X86_
+ X86Machine::IsReturnAddress
+#elif defined(_TARGET_AMD64_)
+ AMD64Machine::IsReturnAddress
+#endif
+ (TADDR retAddr, TADDR* whereCalled) const
+{
+ *whereCalled = 0;
+
+ unsigned char spotend[6];
+ move_xp (spotend, retAddr-6);
+ unsigned char *spot = spotend+6;
+ TADDR addr;
+
+ // Note this is possible to be spoofed, but pretty unlikely
+ // call XXXXXXXX
+ if (spot[-5] == 0xE8) {
+ DWORD offs = 0;
+ move_xp (offs, retAddr-4);
+ *whereCalled = retAddr + (ULONG64)(LONG)(offs);
+ //*whereCalled = *((int*) (retAddr-4)) + retAddr;
+ // on WOW64 the range valid for code is almost the whole 4GB adddress space
+ if (g_ExtData->ReadVirtual(TO_CDADDR(*whereCalled), &addr, sizeof(addr), NULL) == S_OK)
+ {
+ TADDR callee;
+ if (GetCalleeSite(*whereCalled, callee)) {
+ *whereCalled = callee;
+ }
+ return;
+ }
+ else
+ *whereCalled = 0;
+ }
+
+ // call [XXXXXXXX]
+ if (spot[-6] == 0xFF && (spot[-5] == 025)) {
+ DWORD offs = 0;
+ move_xp (offs, retAddr-4);
+#ifdef _TARGET_AMD64_
+ // on x64 this 32-bit is an RIP offset
+ addr = retAddr + (ULONG64)(LONG)(offs);
+#elif defined (_TARGET_X86_)
+ addr = offs;
+#endif
+ if (g_ExtData->ReadVirtual(TO_CDADDR(addr), whereCalled, sizeof(*whereCalled), NULL) == S_OK) {
+ move_xp (*whereCalled, addr);
+ //*whereCalled = **((unsigned**) (retAddr-4));
+ // on WOW64 the range valid for code is almost the whole 4GB adddress space
+ if (g_ExtData->ReadVirtual(TO_CDADDR(*whereCalled), &addr, sizeof(addr), NULL) == S_OK)
+ {
+ TADDR callee;
+ if (GetCalleeSite(*whereCalled,callee)) {
+ *whereCalled = callee;
+ }
+ return;
+ }
+ else
+ *whereCalled = 0;
+ }
+ else
+ *whereCalled = 0;
+ }
+
+ // call [REG+XX]
+ if (spot[-3] == 0xFF && (spot[-2] & ~7) == 0120 && (spot[-2] & 7) != 4)
+ {
+ *whereCalled = 0xFFFFFFFF;
+ return;
+ }
+ if (spot[-4] == 0xFF && spot[-3] == 0124)
+ {
+ *whereCalled = 0xFFFFFFFF;
+ return;
+ }
+
+ // call [REG+XXXX]
+ if (spot[-6] == 0xFF && (spot[-5] & ~7) == 0220 && (spot[-5] & 7) != 4)
+ {
+ *whereCalled = 0xFFFFFFFF;
+ return;
+ }
+ if (spot[-7] == 0xFF && spot[-6] == 0224)
+ {
+ *whereCalled = 0xFFFFFFFF;
+ return;
+ }
+
+ // call [REG]
+ if (spot[-2] == 0xFF && (spot[-1] & ~7) == 0020 && (spot[-1] & 7) != 4 && (spot[-1] & 7) != 5)
+ {
+ *whereCalled = 0xFFFFFFFF;
+ return;
+ }
+
+ // call REG
+ if (spot[-2] == 0xFF && (spot[-1] & ~7) == 0320 && (spot[-1] & 7) != 4)
+ {
+ *whereCalled = 0xFFFFFFFF;
+ return;
+ }
+
+ // There are other cases, but I don't believe they are used.
+ return;
+}
+
+
+#ifdef _X86_
+
+///
+/// This is dead code, not called from anywhere, not linked in the final product.
+///
+static BOOL DecodeLine (___in __in_z char *line, ___in __in_z const char *const inst, InstData& arg1, InstData& arg2)
+{
+ char *ptr = line;
+ if (inst[0] == '*' || !strncmp (ptr, inst, strlen (inst)))
+ {
+ arg1.mode = BAD;
+ arg2.mode = BAD;
+ NextTerm (ptr);
+ if (*ptr == '\0')
+ {
+ arg1.mode = NODATA;
+ return TRUE;
+ }
+
+ DecodeAddressTerm (ptr, arg1);
+ NextTerm (ptr);
+ if (*ptr == '\0')
+ {
+ return TRUE;
+ }
+ DecodeAddressTerm (ptr, arg2);
+ return TRUE;
+ }
+ else
+ return FALSE;
+}
+
+void PrintReg (Register *reg)
+{
+ ExtOut ("[EBX=%08x ESI=%08x EDI=%08x EBP=%08x ESP=%08x]\n",
+ reg[EBX].value, reg[ESI].value, reg[EDI].value, reg[EBP].value,
+ reg[ESP].value);
+}
+
+
+struct CallInfo
+{
+ DWORD_PTR stackPos;
+ DWORD_PTR retAddr;
+ DWORD_PTR whereCalled;
+};
+
+// Search for a Return address on stack.
+BOOL GetNextRetAddr (DWORD_PTR stackBegin, DWORD_PTR stackEnd,
+ CallInfo &callInfo)
+{
+ for (callInfo.stackPos = stackBegin;
+ callInfo.stackPos <= stackEnd;
+ callInfo.stackPos += 4)
+ {
+ if (!SafeReadMemory (callInfo.stackPos, &callInfo.retAddr, 4, NULL))
+ continue;
+
+ g_targetMachine->IsReturnAddress(callInfo.retAddr, &callInfo.whereCalled);
+ if (callInfo.whereCalled)
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+struct FrameInfo
+{
+ DWORD_PTR IPStart;
+ DWORD_PTR Prolog;
+ DWORD_PTR FrameBase; // The value of ESP at the entry.
+ DWORD_PTR StackEnd;
+ DWORD_PTR argCount;
+ BOOL bEBPFrame;
+};
+
+// if a EBP frame, return TRUE if EBP has been setup
+void GetFrameBaseHelper (DWORD_PTR IPBegin, DWORD_PTR IPEnd,
+ INT_PTR &StackChange)
+{
+ char line[256];
+ char *ptr;
+ InstData arg1;
+ InstData arg2;
+ DWORD_PTR IP = IPBegin;
+ StackChange = 0;
+ while (IP < IPEnd)
+ {
+ DisasmAndClean (IP, line, 256);
+ ptr = line;
+ NextTerm (ptr);
+ NextTerm (ptr);
+ if (DecodeLine (ptr, "push ", arg1, arg2))
+ {
+ StackChange += 4;
+ }
+ else if (DecodeLine (ptr, "pop ", arg1, arg2))
+ {
+ StackChange -= 4;
+ }
+ else if (DecodeLine (ptr, "sub ", arg1, arg2))
+ {
+ if (arg1.mode == REG && arg1.reg[0].reg == ESP)
+ {
+ if (arg2.mode == DATA)
+ StackChange -= arg2.value;
+ }
+ }
+ else if (DecodeLine (ptr, "add ", arg1, arg2))
+ {
+ if (arg1.mode == REG && arg1.reg[0].reg == ESP)
+ {
+ if (arg2.mode == DATA)
+ StackChange += arg2.value;
+ }
+ }
+ else if (!strncmp (ptr, "ret", 3)) {
+ return;
+ }
+ }
+}
+
+enum IPSTATE {IPPROLOG1 /*Before EBP set*/, IPPROLOG2 /*After EBP set*/, IPCODE, IPEPILOG, IPEND};
+
+IPSTATE GetIpState (DWORD_PTR IP, FrameInfo* pFrame)
+{
+ char line[256];
+ char *ptr;
+
+ if (IP >= pFrame->IPStart && IP < pFrame->IPStart + pFrame->Prolog)
+ {
+ if (pFrame->bEBPFrame) {
+ DWORD_PTR pIP = pFrame->IPStart;
+ while (pIP < IP) {
+ DisasmAndClean (IP,line, 256);
+ ptr = line;
+ NextTerm (ptr);
+ NextTerm (ptr);
+ if (!strncmp (ptr, "mov ", 4)) {
+ NextTerm (ptr);
+ if (!strncmp (ptr, "ebp", 3)) {
+ NextTerm (ptr);
+ if (!strncmp (ptr, "esp", 3)) {
+ return IPPROLOG2;
+ }
+ }
+ }
+ else if (!strncmp (ptr, "call ", 5)) {
+ NextTerm (ptr);
+ if (strstr (ptr, "__EH_prolog")) {
+ return IPPROLOG2;
+ }
+ }
+ }
+ pIP = IP;
+ while (pIP < pFrame->IPStart + pFrame->Prolog) {
+ DisasmAndClean (IP,line, 256);
+ ptr = line;
+ NextTerm (ptr);
+ NextTerm (ptr);
+ if (!strncmp (ptr, "mov ", 4)) {
+ NextTerm (ptr);
+ if (!strncmp (ptr, "ebp", 3)) {
+ NextTerm (ptr);
+ if (!strncmp (ptr, "esp", 3)) {
+ return IPPROLOG1;
+ }
+ }
+ }
+ else if (!strncmp (ptr, "call ", 5)) {
+ NextTerm (ptr);
+ if (strstr (ptr, "__EH_prolog")) {
+ return IPPROLOG1;
+ }
+ }
+ }
+
+ ExtOut ("Fail to find where EBP is saved\n");
+ return IPPROLOG2;
+ }
+ else
+ {
+ return IPPROLOG1;
+ }
+ }
+
+ int nline = 0;
+ while (1) {
+ DisasmAndClean (IP,line, 256);
+ nline ++;
+ ptr = line;
+ NextTerm (ptr);
+ NextTerm (ptr);
+ if (!strncmp (ptr, "ret", 3)) {
+ return (nline==1)?IPEND:IPEPILOG;
+ }
+ else if (!strncmp (ptr, "leave", 5)) {
+ return IPEPILOG;
+ }
+ else if (!strncmp (ptr, "call", 4)) {
+ return IPCODE;
+ }
+ else if (ptr[0] == 'j') {
+ return IPCODE;
+ }
+ }
+}
+
+// FrameBase is the ESP value at the entry of a function.
+BOOL GetFrameBase (Register callee[], FrameInfo* pFrame)
+{
+ //char line[256];
+ //char *ptr;
+ INT_PTR dwpushed = 0;
+ //DWORD_PTR IP;
+
+ IPSTATE IpState = GetIpState (callee[EIP].value, pFrame);
+
+ if (pFrame->bEBPFrame)
+ {
+ if (IpState == IPEND || IpState == IPPROLOG1) {
+ pFrame->FrameBase = callee[ESP].value;
+ }
+ else
+ {
+ pFrame->FrameBase = callee[EBP].value+4;
+ }
+ return TRUE;
+ }
+ else
+ {
+ if (IpState == IPEND) {
+ pFrame->FrameBase = callee[ESP].value;
+ return TRUE;
+ }
+
+ DWORD_PTR IPBegin, IPEnd;
+ if (IpState == IPEPILOG) {
+ IPBegin = callee[EIP].value;
+ IPEnd = ~0ul;
+ }
+ else if (IpState == IPPROLOG1) {
+ IPBegin = pFrame->IPStart;
+ IPEnd = callee[EIP].value;
+ }
+ else
+ {
+ IPBegin = pFrame->IPStart;
+ IPEnd = IPBegin + pFrame->Prolog;
+ }
+ GetFrameBaseHelper (IPBegin, IPEnd, dwpushed);
+
+ if (IpState == IPEPILOG) {
+ ExtOut ("stack %d\n", dwpushed);
+ pFrame->FrameBase = callee[ESP].value - dwpushed;
+ return TRUE;
+ }
+
+ CallInfo callInfo;
+ if (GetNextRetAddr (callee[ESP].value + dwpushed,
+ pFrame->StackEnd, callInfo))
+ {
+ pFrame->FrameBase = callInfo.stackPos;
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+}
+
+// caller[ESP]: the ESP value when we return to caller.
+void RestoreCallerRegister (Register callee[], Register caller[],
+ FrameInfo *pFrame)
+{
+ if (pFrame->bEBPFrame)
+ {
+ if (callee[ESP].value < pFrame->FrameBase)
+ {
+ SafeReadMemory (pFrame->FrameBase-4, &caller[EBP].value, 4, NULL);
+ }
+ else
+ caller[EBP].value = callee[EBP].value;
+ }
+ else
+ caller[EBP].value = callee[EBP].value;
+
+ caller[EBP].bValid = TRUE;
+ caller[ESP].value = pFrame->FrameBase + 4 + pFrame->argCount;
+ callee[EBP].value = pFrame->FrameBase - sizeof(void*);
+ SafeReadMemory (pFrame->FrameBase, &caller[EIP].value, 4, NULL);
+}
+
+BOOL GetFrameInfoHelper (Register callee[], Register caller[],
+ FrameInfo *pFrame)
+{
+ if (GetFrameBase (callee, pFrame))
+ {
+ RestoreCallerRegister (callee, caller, pFrame);
+ return TRUE;
+ }
+ else
+ return FALSE;
+}
+
+// Return TRUE if Frame Info is OK, otherwise FALSE.
+BOOL GetUnmanagedFrameInfo (Register callee[], Register caller[],
+ DumpStackFlag &DSFlag, PFPO_DATA data)
+{
+ FrameInfo Frame;
+ ULONG64 base;
+ g_ExtSymbols->GetModuleByOffset (callee[EIP].value, 0, NULL, &base);
+ Frame.IPStart = data->ulOffStart + (ULONG_PTR)base;
+ Frame.Prolog = data->cbProlog;
+ // Why do we have to do this to make it work?
+ if (Frame.Prolog == 1) {
+ Frame.Prolog = 0;
+ }
+ Frame.bEBPFrame = (data->cbFrame == FRAME_NONFPO);
+ Frame.StackEnd = DSFlag.end;
+ Frame.argCount = data->cdwParams*4;
+
+ return GetFrameInfoHelper (callee, caller, &Frame);
+}
+
+// offsetEBP: offset of stack position where EBP is saved.
+// If EBP is not saved, *offsetEBP = -1 (~0ul);
+BOOL IPReachable (DWORD_PTR IPBegin, DWORD_PTR IP, DWORD *offsetEBP)
+{
+ *offsetEBP = ~0ul;
+ return FALSE;
+}
+
+BOOL HandleEEStub (Register callee[], Register caller[],
+ DumpStackFlag &DSFlag)
+{
+ // EEStub can only be called by IP directory. Let's look for possible caller.
+ CallInfo callInfo;
+ DWORD_PTR stackPos = callee[ESP].value;
+ while (stackPos < DSFlag.end) {
+ if (GetNextRetAddr (stackPos,
+ DSFlag.end, callInfo))
+ {
+ if (callInfo.whereCalled != ~0ul) {
+ DWORD offsetEBP;
+ if (IPReachable (callInfo.whereCalled, callee[EIP].value, &offsetEBP)) {
+ caller[EIP].value = callInfo.retAddr;
+ // TODO: We may have saved EBP.
+ if (offsetEBP == ~0ul) {
+ caller[EBP].value = callee[EBP].value;
+ }
+ else
+ {
+ TADDR offs = TO_TADDR(callInfo.stackPos)-sizeof(PVOID)-offsetEBP;
+ SafeReadMemory (offs, &caller[EBP].value, sizeof(PVOID), NULL);
+ }
+ caller[ESP].value = callInfo.stackPos+sizeof(PVOID);
+ return TRUE;
+ }
+ }
+ stackPos = callInfo.stackPos+sizeof(PVOID);
+ }
+ else
+ return FALSE;
+ }
+
+ return FALSE;
+}
+
+
+BOOL HandleByEpilog (Register callee[], Register caller[],
+ DumpStackFlag &DSFlag)
+{
+ return FALSE;
+}
+
+#ifndef FEATURE_PAL
+void RestoreFrameUnmanaged (Register *reg, DWORD_PTR CurIP)
+{
+ char line[256];
+ char *ptr;
+ DWORD_PTR IP = CurIP;
+ INT_PTR value;
+ BOOL bDigit;
+ BOOL bGoodESP = true;
+ RegIndex dest;
+
+ ULONG64 base;
+ g_ExtSymbols->GetModuleByOffset (TO_CDADDR(CurIP), 0, NULL, &base);
+ ULONG64 handle;
+ g_ExtSystem->GetCurrentProcessHandle(&handle);
+ PFPO_DATA data =
+ (PFPO_DATA)SymFunctionTableAccess((HANDLE)handle, CurIP);
+ DWORD_PTR IPBegin = data->ulOffStart + (ULONG_PTR)base;
+
+ if (CurIP - IPBegin <= data->cbProlog)
+ {
+ // We are inside a prolog.
+ // See where we save the callee saved register.
+ // Also how many DWORD's we pushd
+ IP = IPBegin;
+ reg[ESP].stack = 0;
+ reg[ESP].bOnStack = FALSE;
+ reg[EBP].stack = 0;
+ reg[EBP].bOnStack = FALSE;
+ reg[ESI].stack = 0;
+ reg[ESI].bOnStack = FALSE;
+ reg[EDI].stack = 0;
+ reg[EDI].bOnStack = FALSE;
+ reg[EBX].stack = 0;
+ reg[EBX].bOnStack = FALSE;
+
+ while (IP < CurIP)
+ {
+ DisasmAndClean (IP, line, 256);
+ ptr = line;
+ NextTerm (ptr);
+ NextTerm (ptr);
+ if (!strncmp (ptr, "push ", 5))
+ {
+ reg[ESP].stack += 4;
+ NextTerm (ptr);
+ dest = FindReg(ptr);
+ if (dest == EBP || dest == EBX || dest == ESI || dest == EDI)
+ {
+ reg[dest].bOnStack = TRUE;
+ reg[dest].stack = reg[ESP].stack;
+ }
+ }
+ else if (!strncmp (ptr, "sub ", 4))
+ {
+ NextTerm (ptr);
+ dest = FindReg(ptr);
+ if (dest == ESP)
+ {
+ NextTerm (ptr);
+ char *endptr;
+ reg[ESP].stack += strtoul(ptr, &endptr, 16);;
+ }
+ }
+ }
+
+ DWORD_PTR baseESP = reg[ESP].value + reg[ESP].stack;
+ if (reg[EBP].bOnStack)
+ {
+ move_xp (reg[EBP].value, baseESP-reg[EBP].stack);
+ }
+ if (reg[EBX].bOnStack)
+ {
+ move_xp (reg[EBX].value, baseESP-reg[EBX].stack);
+ }
+ if (reg[ESI].bOnStack)
+ {
+ move_xp (reg[ESI].value, baseESP-reg[ESI].stack);
+ }
+ if (reg[EDI].bOnStack)
+ {
+ move_xp (reg[EDI].value, baseESP-reg[EDI].stack);
+ }
+ move_xp (reg[EIP].value, baseESP);
+ reg[ESP].value = baseESP + 4;
+ return;
+ }
+
+ if (data->cbFrame == FRAME_NONFPO)
+ {
+ // EBP Frame
+ }
+
+ // Look for epilog
+ while (1)
+ {
+ DisasmAndClean (IP, line, 256);
+ ptr = line;
+ NextTerm (ptr);
+ NextTerm (ptr);
+ if (!strncmp (ptr, "mov ", 4))
+ {
+ NextTerm (ptr);
+ dest = FindReg(ptr);
+ if (dest == ESP)
+ {
+ NextTerm (ptr);
+ if (FindReg(ptr) == EBP)
+ {
+ // We have a EBP frame
+ bGoodESP = true;
+ reg[ESP].value = reg[EBP].value;
+ }
+ }
+ }
+ else if (!strncmp (ptr, "ret", 3))
+ {
+ NextTerm (ptr);
+ // check the value on stack is a return address.
+ DWORD_PTR retAddr;
+ DWORD_PTR whereCalled;
+ move_xp (retAddr, reg[ESP].value);
+ int ESPAdjustCount = 0;
+ while (1)
+ {
+ g_targetMachine->IsReturnAddress(retAddr, &whereCalled);
+ if (whereCalled)
+ break;
+ ESPAdjustCount ++;
+ reg[ESP].value += 4;
+ move_xp (retAddr, reg[ESP].value);
+ }
+ reg[EIP].value = retAddr;
+ if (ESPAdjustCount)
+ {
+ ESPAdjustCount *= 4;
+ }
+ if (reg[EBX].bOnStack)
+ {
+ reg[EBX].stack += ESPAdjustCount;
+ move_xp (reg[EBX].value, reg[EBX].stack);
+ }
+ if (reg[ESI].bOnStack)
+ {
+ reg[ESI].stack += ESPAdjustCount;
+ move_xp (reg[ESI].value, reg[EBX].stack);
+ }
+ if (reg[EDI].bOnStack)
+ {
+ reg[EDI].stack += ESPAdjustCount;
+ move_xp (reg[EDI].value, reg[EBX].stack);
+ }
+
+ reg[ESP].value += 4;
+ if (ptr[0] != '\0')
+ {
+ FindSrc (ptr, reg, value, bDigit);
+ reg[ESP].value += value;
+ }
+ break;
+ }
+ else if (!strncmp (ptr, "pop ", 4))
+ {
+ NextTerm (ptr);
+ dest = FindReg(ptr);
+ if (dest == EBP || dest == EBX || dest == ESI || dest == EDI)
+ {
+ reg[dest].stack = reg[ESP].value;
+ reg[dest].bOnStack = TRUE;
+ }
+ reg[ESP].value += 4;
+ }
+ else if (!strncmp (ptr, "add ", 4))
+ {
+ NextTerm (ptr);
+ dest = FindReg(ptr);
+ if (dest == ESP)
+ {
+ NextTerm (ptr);
+ FindSrc (ptr, reg, value, bDigit);
+ reg[ESP].value += value;
+ }
+ }
+ else if (!strncmp (ptr, "call ", 5))
+ {
+ // assume we do not have a good value on ESP.
+ // We could go into the call and find out number of pushed args.
+ bGoodESP = FALSE;
+ }
+ }
+
+ // Look for prolog
+}
+#endif // !FEATURE_PAL
+
+#elif defined(_AMD64_)
+
+
+#endif // !_X86_
diff --git a/src/ToolBox/SOS/Strike/dllsext.cpp b/src/ToolBox/SOS/Strike/dllsext.cpp
new file mode 100644
index 0000000000..757a04c91f
--- /dev/null
+++ b/src/ToolBox/SOS/Strike/dllsext.cpp
@@ -0,0 +1,278 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// ==++==
+//
+
+//
+// ==--==
+#include "strike.h"
+#include "data.h"
+#include "util.h"
+#include "platformspecific.h"
+
+typedef struct _PRIVATE_LDR_DATA_TABLE_ENTRY {
+ LIST_ENTRY InLoadOrderLinks;
+ LIST_ENTRY InMemoryOrderLinks;
+ LIST_ENTRY InInitializationOrderLinks;
+ PVOID DllBase;
+ PVOID EntryPoint;
+ ULONG SizeOfImage;
+ UNICODE_STRING FullDllName;
+ UNICODE_STRING BaseDllName;
+ ULONG Flags;
+ USHORT LoadCount;
+ USHORT TlsIndex;
+ union _LDR_DATA_TABLE_ENTRY_UNION1 { //DevDiv LKG RC Changes: Added union name to avoid warning C4408
+ LIST_ENTRY HashLinks;
+ struct _LDR_DATA_TABLE_ENTRY_STRUCT1 { //DevDiv LKG RC Changes: Added struct name to avoid warning C4201
+ PVOID SectionPointer;
+ ULONG CheckSum;
+ };
+ };
+ union _LDR_DATA_TABLE_ENTRY_UNION2 { //DevDiv LKG RC Changes: Added union name to avoid warning C4408
+ struct _LDR_DATA_TABLE_ENTRY_STRUCT2 { //DevDiv LKG RC Changes: Added struct name to avoid warning C4201
+ ULONG TimeDateStamp;
+ };
+ struct _LDR_DATA_TABLE_ENTRY_STRUCT3 { //DevDiv LKG RC Changes: Added struct name to avoid warning C4201
+ PVOID LoadedImports;
+ };
+ };
+ struct _ACTIVATION_CONTEXT * EntryPointActivationContext;
+
+ PVOID PatchInformation;
+
+} PRIVATE_LDR_DATA_TABLE_ENTRY, *PRIVATE_PLDR_DATA_TABLE_ENTRY;
+
+
+#ifndef FEATURE_PAL
+static void DllsNameFromPeb(
+ ULONG_PTR addrContaining,
+ __out_ecount (MAX_LONGPATH) WCHAR *dllName
+ )
+{
+ ULONG64 ProcessPeb;
+ g_ExtSystem->GetCurrentProcessPeb (&ProcessPeb);
+
+ ULONG64 pLdrEntry;
+ ULONG64 PebLdrAddress;
+ ULONG64 Next;
+ ULONG64 OrderModuleListStart;
+
+ //
+ // Capture PebLdrData
+ //
+
+ static ULONG Offset_Ldr = -1;
+ if (Offset_Ldr == -1)
+ {
+ ULONG TypeId;
+ ULONG64 NtDllBase;
+ if (SUCCEEDED(g_ExtSymbols->GetModuleByModuleName ("ntdll",0,NULL,
+ &NtDllBase))
+ && SUCCEEDED(g_ExtSymbols->GetTypeId (NtDllBase, "PEB", &TypeId)))
+ {
+ if (FAILED (g_ExtSymbols->GetFieldOffset(NtDllBase, TypeId,
+ "Ldr", &Offset_Ldr)))
+ Offset_Ldr = -1;
+ }
+ }
+ // We can not get it from PDB. Use the fixed one.
+ if (Offset_Ldr == -1)
+ Offset_Ldr = offsetof (DT_PEB, Ldr);
+
+ DT_PEB peb = {0};
+ if (FAILED(g_ExtData->ReadVirtual(ProcessPeb+Offset_Ldr, &peb.Ldr,
+ sizeof(peb.Ldr), NULL)))
+ {
+ ExtOut ( " Unable to read PEB_LDR_DATA address at %p\n", SOS_PTR(ProcessPeb+Offset_Ldr));
+ return;
+ }
+
+ PebLdrAddress = (ULONG64)peb.Ldr;
+
+ //
+ // Walk through the loaded module table and display all ldr data
+ //
+
+ static ULONG Offset_ModuleList = -1;
+ if (Offset_ModuleList == -1)
+ {
+ ULONG TypeId;
+ ULONG64 NtDllBase;
+ if (SUCCEEDED(g_ExtSymbols->GetModuleByModuleName ("ntdll",0,NULL,
+ &NtDllBase))
+ && SUCCEEDED(g_ExtSymbols->GetTypeId (NtDllBase, "PEB_LDR_DATA",
+ &TypeId)))
+ {
+ if (FAILED (g_ExtSymbols->GetFieldOffset(NtDllBase, TypeId,
+ "InMemoryOrderModuleList",
+ &Offset_ModuleList)))
+ Offset_ModuleList = -1;
+ }
+ }
+ // We can not get it from PDB. Use the fixed one.
+ if (Offset_ModuleList == -1)
+ Offset_ModuleList = offsetof (DT_PEB_LDR_DATA, InMemoryOrderModuleList);
+
+ OrderModuleListStart = PebLdrAddress + Offset_ModuleList;
+ DT_PEB_LDR_DATA Ldr = {0};
+ if (FAILED(g_ExtData->ReadVirtual(OrderModuleListStart,
+ &Ldr.InMemoryOrderModuleList,
+ sizeof(Ldr.InMemoryOrderModuleList),
+ NULL)))
+ {
+ ExtOut ( " Unable to read InMemoryOrderModuleList address at %p\n", SOS_PTR(OrderModuleListStart));
+ return;
+ }
+ Next = (ULONG64)Ldr.InMemoryOrderModuleList.Flink;
+
+ static ULONG Offset_OrderLinks = -1;
+ static ULONG Offset_FullDllName = -1;
+ static ULONG Offset_DllBase = -1;
+ static ULONG Offset_SizeOfImage = -1;
+ if (Offset_OrderLinks == -1)
+ {
+ ULONG TypeId;
+ ULONG64 NtDllBase;
+ if (SUCCEEDED(g_ExtSymbols->GetModuleByModuleName ("ntdll",0,NULL,
+ &NtDllBase))
+ && SUCCEEDED(g_ExtSymbols->GetTypeId (NtDllBase, "LDR_DATA_TABLE_ENTRY",
+ &TypeId)))
+ {
+ if (FAILED (g_ExtSymbols->GetFieldOffset(NtDllBase, TypeId,
+ "InMemoryOrderLinks",
+ &Offset_OrderLinks)))
+ Offset_OrderLinks = -1;
+ if (FAILED (g_ExtSymbols->GetFieldOffset(NtDllBase, TypeId,
+ "FullDllName",
+ &Offset_FullDllName)))
+ Offset_FullDllName = -1;
+ if (FAILED (g_ExtSymbols->GetFieldOffset(NtDllBase, TypeId,
+ "DllBase",
+ &Offset_DllBase)))
+ Offset_DllBase = -1;
+ if (FAILED (g_ExtSymbols->GetFieldOffset(NtDllBase, TypeId,
+ "SizeOfImage",
+ &Offset_SizeOfImage)))
+ Offset_SizeOfImage = -1;
+ }
+ }
+
+ // We can not get it from PDB. Use the fixed one.
+ if (Offset_OrderLinks == -1 || Offset_OrderLinks == 0)
+ {
+ Offset_OrderLinks = offsetof (PRIVATE_LDR_DATA_TABLE_ENTRY,
+ InMemoryOrderLinks);
+ Offset_FullDllName = offsetof (PRIVATE_LDR_DATA_TABLE_ENTRY,
+ FullDllName);
+ Offset_DllBase = offsetof (PRIVATE_LDR_DATA_TABLE_ENTRY,
+ DllBase);
+ Offset_SizeOfImage = offsetof (PRIVATE_LDR_DATA_TABLE_ENTRY,
+ SizeOfImage);
+ }
+
+ _UNICODE_STRING FullDllName;
+ __try {
+ while (Next != OrderModuleListStart) {
+ if (IsInterrupt())
+ return;
+
+ pLdrEntry = Next - Offset_OrderLinks;
+
+ //
+ // Capture LdrEntry
+ //
+ if (FAILED(g_ExtData->ReadVirtual(pLdrEntry + Offset_FullDllName,
+ &FullDllName,
+ sizeof(FullDllName),
+ NULL)))
+ {
+ ExtOut ( " Unable to read FullDllName address at %p\n",
+ pLdrEntry + Offset_FullDllName);
+ return;
+ }
+ ZeroMemory( dllName, MAX_LONGPATH * sizeof (WCHAR) );
+ if (FAILED(g_ExtData->ReadVirtual((ULONG64)FullDllName.Buffer,
+ dllName,
+ MAX_LONGPATH < FullDllName.Length ? MAX_LONGPATH : FullDllName.Length,
+ NULL)))
+ {
+#if 0
+ ExtOut ( " Unable to read FullDllName.Buffer address at %p\n",
+ SOS_PTR(FullDllName.Buffer));
+#endif
+ ZeroMemory( dllName, MAX_LONGPATH * sizeof (WCHAR) );
+ }
+
+ //
+ // Dump the ldr entry data
+ // (dump all the entries if no containing address specified)
+ //
+ PRIVATE_LDR_DATA_TABLE_ENTRY LdrEntry = {0};
+ if (SUCCEEDED(g_ExtData->ReadVirtual(pLdrEntry + Offset_DllBase,
+ &LdrEntry.DllBase,
+ sizeof(LdrEntry.DllBase),
+ NULL))
+ &&
+ SUCCEEDED(g_ExtData->ReadVirtual(pLdrEntry + Offset_SizeOfImage,
+ &LdrEntry.SizeOfImage,
+ sizeof(LdrEntry.SizeOfImage),
+ NULL))
+ )
+ {
+ if (((ULONG_PTR)LdrEntry.DllBase <= addrContaining) &&
+ (addrContaining <= (ULONG_PTR)LdrEntry.DllBase + (ULONG_PTR)LdrEntry.SizeOfImage))
+ break;
+ }
+
+ ZeroMemory( dllName, MAX_LONGPATH * sizeof (WCHAR) );
+ if (FAILED(g_ExtData->ReadVirtual(pLdrEntry + Offset_OrderLinks,
+ &LdrEntry.InMemoryOrderLinks,
+ sizeof(LdrEntry.InMemoryOrderLinks),
+ NULL)))
+ break;
+
+ Next = (ULONG64)LdrEntry.InMemoryOrderLinks.Flink;
+ }
+ } __except (EXCEPTION_EXECUTE_HANDLER)
+ {
+ ExtOut ("exception during reading PEB\n");
+ return;
+ }
+}
+#endif
+
+HRESULT
+DllsName(
+ ULONG_PTR addrContaining,
+ __out_ecount (MAX_LONGPATH) WCHAR *dllName
+ )
+{
+ dllName[0] = L'\0';
+
+ ULONG Index;
+ ULONG64 base;
+ HRESULT hr = g_ExtSymbols->GetModuleByOffset(addrContaining, 0, &Index, &base);
+ if (FAILED(hr))
+ return hr;
+
+ CHAR name[MAX_LONGPATH+1];
+ ULONG length;
+
+ hr = g_ExtSymbols->GetModuleNames(Index,base,name,MAX_LONGPATH,&length,NULL,0,NULL,NULL,0,NULL);
+
+ if (SUCCEEDED(hr))
+ {
+ MultiByteToWideChar (CP_ACP,0,name,-1,dllName,MAX_LONGPATH);
+ }
+
+#ifndef FEATURE_PAL
+ if (_wcsrchr (dllName, '\\') == NULL) {
+ DllsNameFromPeb (addrContaining,dllName);
+ }
+#endif
+
+ return hr;
+}
diff --git a/src/ToolBox/SOS/Strike/eeheap.cpp b/src/ToolBox/SOS/Strike/eeheap.cpp
new file mode 100644
index 0000000000..ac41e2deb6
--- /dev/null
+++ b/src/ToolBox/SOS/Strike/eeheap.cpp
@@ -0,0 +1,1913 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// ==++==
+//
+
+//
+// ==--==
+#include <assert.h>
+#include "sos.h"
+#include "safemath.h"
+
+
+// This is the increment for the segment lookup data
+const int nSegLookupStgIncrement = 100;
+
+#define CCH_STRING_PREFIX_SUMMARY 64
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function is called to update GC heap statistics. *
+* *
+\**********************************************************************/
+void HeapStat::Add(DWORD_PTR aData, DWORD aSize)
+{
+ if (head == 0)
+ {
+ head = new Node();
+ if (head == NULL)
+ {
+ ReportOOM();
+ ControlC = TRUE;
+ return;
+ }
+
+ if (bHasStrings)
+ {
+ size_t capacity_pNew = _wcslen((WCHAR*)aData) + 1;
+ WCHAR *pNew = new WCHAR[capacity_pNew];
+ if (pNew == NULL)
+ {
+ ReportOOM();
+ ControlC = TRUE;
+ return;
+ }
+ wcscpy_s(pNew, capacity_pNew, (WCHAR*)aData);
+ aData = (DWORD_PTR)pNew;
+ }
+
+ head->data = aData;
+ }
+ Node *walk = head;
+ int cmp = 0;
+
+ for (;;)
+ {
+ if (IsInterrupt())
+ return;
+
+ cmp = CompareData(aData, walk->data);
+
+ if (cmp == 0)
+ break;
+
+ if (cmp < 0)
+ {
+ if (walk->left == NULL)
+ break;
+ walk = walk->left;
+ }
+ else
+ {
+ if (walk->right == NULL)
+ break;
+ walk = walk->right;
+ }
+ }
+
+ if (cmp == 0)
+ {
+ walk->count ++;
+ walk->totalSize += aSize;
+ }
+ else
+ {
+ Node *node = new Node();
+ if (node == NULL)
+ {
+ ReportOOM();
+ ControlC = TRUE;
+ return;
+ }
+
+ if (bHasStrings)
+ {
+ size_t capacity_pNew = _wcslen((WCHAR*)aData) + 1;
+ WCHAR *pNew = new WCHAR[capacity_pNew];
+ if (pNew == NULL)
+ {
+ ReportOOM();
+ ControlC = TRUE;
+ return;
+ }
+ wcscpy_s(pNew, capacity_pNew, (WCHAR*)aData);
+ aData = (DWORD_PTR)pNew;
+ }
+
+ node->data = aData;
+ node->totalSize = aSize;
+ node->count ++;
+
+ if (cmp < 0)
+ {
+ walk->left = node;
+ }
+ else
+ {
+ walk->right = node;
+ }
+ }
+}
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function compares two nodes in the tree. *
+* *
+\**********************************************************************/
+int HeapStat::CompareData(DWORD_PTR d1, DWORD_PTR d2)
+{
+ if (bHasStrings)
+ return _wcscmp((WCHAR*)d1, (WCHAR*)d2);
+
+ if (d1 > d2)
+ return 1;
+
+ if (d1 < d2)
+ return -1;
+
+ return 0;
+}
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function is called to sort all entries in the heap stat. *
+* *
+\**********************************************************************/
+void HeapStat::Sort ()
+{
+ Node *root = head;
+ head = NULL;
+ ReverseLeftMost (root);
+
+ Node *sortRoot = NULL;
+ while (head)
+ {
+ Node *tmp = head;
+ head = head->left;
+ if (tmp->right)
+ ReverseLeftMost (tmp->right);
+ // add tmp
+ tmp->right = NULL;
+ tmp->left = NULL;
+ SortAdd (sortRoot, tmp);
+ }
+ head = sortRoot;
+
+ Linearize();
+
+ //reverse the order
+ root = head;
+ head = NULL;
+ sortRoot = NULL;
+ while (root)
+ {
+ Node *tmp = root->right;
+ root->left = NULL;
+ root->right = NULL;
+ LinearAdd (sortRoot, root);
+ root = tmp;
+ }
+ head = sortRoot;
+}
+
+void HeapStat::Linearize()
+{
+ // Change binary tree to a linear tree
+ Node *root = head;
+ head = NULL;
+ ReverseLeftMost (root);
+ Node *sortRoot = NULL;
+ while (head)
+ {
+ Node *tmp = head;
+ head = head->left;
+ if (tmp->right)
+ ReverseLeftMost (tmp->right);
+ // add tmp
+ tmp->right = NULL;
+ tmp->left = NULL;
+ LinearAdd (sortRoot, tmp);
+ }
+ head = sortRoot;
+ fLinear = TRUE;
+}
+
+void HeapStat::ReverseLeftMost (Node *root)
+{
+ while (root)
+ {
+ Node *tmp = root->left;
+ root->left = head;
+ head = root;
+ root = tmp;
+ }
+}
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function is called to help to sort heap stat. *
+* *
+\**********************************************************************/
+void HeapStat::SortAdd (Node *&root, Node *entry)
+{
+ if (root == NULL)
+ {
+ root = entry;
+ }
+ else
+ {
+ Node *parent = root;
+ Node *ptr = root;
+ while (ptr)
+ {
+ parent = ptr;
+ if (ptr->totalSize < entry->totalSize)
+ ptr = ptr->right;
+ else
+ ptr = ptr->left;
+ }
+ if (parent->totalSize < entry->totalSize)
+ parent->right = entry;
+ else
+ parent->left = entry;
+ }
+}
+
+void HeapStat::LinearAdd(Node *&root, Node *entry)
+{
+ if (root == NULL)
+ {
+ root = entry;
+ }
+ else
+ {
+ entry->right = root;
+ root = entry;
+ }
+}
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function is called to print GC heap statistics. *
+* *
+\**********************************************************************/
+void HeapStat::Print(const char* label /* = NULL */)
+{
+ if (label == NULL)
+ {
+ label = "Statistics:\n";
+ }
+ ExtOut(label);
+ if (bHasStrings)
+ ExtOut("%8s %12s %s\n", "Count", "TotalSize", "String Value");
+ else
+ ExtOut("%" POINTERSIZE "s %8s %12s %s\n","MT", "Count", "TotalSize", "Class Name");
+
+ Node *root = head;
+ int ncount = 0;
+ while (root)
+ {
+ if (IsInterrupt())
+ return;
+
+ ncount += root->count;
+
+ if (bHasStrings)
+ {
+ ExtOut("%8d %12I64u \"%S\"\n", root->count, (unsigned __int64)root->totalSize, root->data);
+ }
+ else
+ {
+ DMLOut("%s %8d %12I64u ", DMLDumpHeapMT(root->data), root->count, (unsigned __int64)root->totalSize);
+ if (IsMTForFreeObj(root->data))
+ {
+ ExtOut("%9s\n", "Free");
+ }
+ else
+ {
+ wcscpy_s(g_mdName, mdNameLen, W("UNKNOWN"));
+ NameForMT_s((DWORD_PTR) root->data, g_mdName, mdNameLen);
+ ExtOut("%S\n", g_mdName);
+ }
+ }
+ root = root->right;
+
+ }
+ ExtOut ("Total %d objects\n", ncount);
+}
+
+void HeapStat::Delete()
+{
+ if (head == NULL)
+ return;
+
+ // Ensure the data structure is already linearized.
+ if (!fLinear)
+ Linearize();
+
+ while (head)
+ {
+ // The list is linearized on such that the left node is always null.
+ Node *tmp = head;
+ head = head->right;
+
+ if (bHasStrings)
+ delete[] ((WCHAR*)tmp->data);
+ delete tmp;
+ }
+
+ // return to default state
+ bHasStrings = FALSE;
+ fLinear = FALSE;
+}
+
+// -----------------------------------------------------------------------
+//
+// MethodTableCache implementation
+//
+// Used during heap traversals for quick object size computation
+//
+MethodTableInfo* MethodTableCache::Lookup (DWORD_PTR aData)
+{
+ Node** addHere = &head;
+ if (head != 0) {
+ Node *walk = head;
+ int cmp = 0;
+
+ for (;;)
+ {
+ cmp = CompareData(aData, walk->data);
+
+ if (cmp == 0)
+ return &walk->info;
+
+ if (cmp < 0)
+ {
+ if (walk->left == NULL)
+ {
+ addHere = &walk->left;
+ break;
+ }
+ walk = walk->left;
+ }
+ else
+ {
+ if (walk->right == NULL)
+ {
+ addHere = &walk->right;
+ break;
+ }
+ walk = walk->right;
+ }
+ }
+ }
+ Node* newNode = new Node(aData);
+ if (newNode == NULL)
+ {
+ ReportOOM();
+ return NULL;
+ }
+ *addHere = newNode;
+ return &newNode->info;
+}
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function compares two nodes in the tree. *
+* *
+\**********************************************************************/
+int MethodTableCache::CompareData(DWORD_PTR d1, DWORD_PTR d2)
+{
+ if (d1 > d2)
+ return 1;
+
+ if (d1 < d2)
+ return -1;
+
+ return 0;
+}
+
+void MethodTableCache::ReverseLeftMost (Node *root)
+{
+ if (root)
+ {
+ if (root->left) ReverseLeftMost(root->left);
+ if (root->right) ReverseLeftMost(root->right);
+ delete root;
+ }
+}
+
+void MethodTableCache::Clear()
+{
+ Node *root = head;
+ head = NULL;
+ ReverseLeftMost (root);
+}
+
+MethodTableCache g_special_mtCache;
+
+size_t Align (size_t nbytes)
+{
+ return (nbytes + ALIGNCONST) & ~ALIGNCONST;
+}
+
+size_t AlignLarge(size_t nbytes)
+{
+ return (nbytes + ALIGNCONSTLARGE) & ~ALIGNCONSTLARGE;
+}
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* Print the gc heap info. *
+* *
+\**********************************************************************/
+void GCPrintGenerationInfo(const DacpGcHeapDetails &heap)
+{
+ UINT n;
+ for (n = 0; n <= GetMaxGeneration(); n ++)
+ {
+ if (IsInterrupt())
+ return;
+ ExtOut("generation %d starts at 0x%p\n",
+ n, SOS_PTR(heap.generation_table[n].allocation_start));
+ }
+
+ // We also need to look at the gen0 alloc context.
+ ExtOut("ephemeral segment allocation context: ");
+ if (heap.generation_table[0].allocContextPtr)
+ {
+ ExtOut("(0x%p, 0x%p)\n",
+ SOS_PTR(heap.generation_table[0].allocContextPtr),
+ SOS_PTR(heap.generation_table[0].allocContextLimit + Align(min_obj_size)));
+ }
+ else
+ {
+ ExtOut("none\n");
+ }
+}
+
+
+void GCPrintSegmentInfo(const DacpGcHeapDetails &heap, DWORD_PTR &total_size)
+{
+ DWORD_PTR dwAddrSeg;
+ DacpHeapSegmentData segment;
+
+ dwAddrSeg = (DWORD_PTR)heap.generation_table[GetMaxGeneration()].start_segment;
+ total_size = 0;
+ // the loop below will terminate, because we retrieved at most nMaxHeapSegmentCount segments
+ while (dwAddrSeg != (DWORD_PTR)heap.generation_table[0].start_segment)
+ {
+ if (IsInterrupt())
+ return;
+ if (segment.Request(g_sos, dwAddrSeg, heap) != S_OK)
+ {
+ ExtOut("Error requesting heap segment %p\n", SOS_PTR(dwAddrSeg));
+ return;
+ }
+ ExtOut("%p %p %p 0x%" POINTERSIZE_TYPE "x(%" POINTERSIZE_TYPE "d)\n", SOS_PTR(dwAddrSeg),
+ SOS_PTR(segment.mem), SOS_PTR(segment.allocated),
+ (ULONG_PTR)(segment.allocated - segment.mem),
+ (ULONG_PTR)(segment.allocated - segment.mem));
+ total_size += (DWORD_PTR) (segment.allocated - segment.mem);
+ dwAddrSeg = (DWORD_PTR)segment.next;
+ }
+
+ if (segment.Request(g_sos, dwAddrSeg, heap) != S_OK)
+ {
+ ExtOut("Error requesting heap segment %p\n", SOS_PTR(dwAddrSeg));
+ return;
+ }
+
+ DWORD_PTR end = (DWORD_PTR)heap.alloc_allocated;
+ ExtOut("%p %p %p 0x%" POINTERSIZE_TYPE "x(%" POINTERSIZE_TYPE "d)\n", SOS_PTR(dwAddrSeg),
+ SOS_PTR(segment.mem), SOS_PTR(end),
+ (ULONG_PTR)(end - (DWORD_PTR)segment.mem),
+ (ULONG_PTR)(end - (DWORD_PTR)segment.mem));
+
+ total_size += end - (DWORD_PTR)segment.mem;
+
+}
+
+
+void GCPrintLargeHeapSegmentInfo(const DacpGcHeapDetails &heap, DWORD_PTR &total_size)
+{
+ DWORD_PTR dwAddrSeg;
+ DacpHeapSegmentData segment;
+ dwAddrSeg = (DWORD_PTR)heap.generation_table[GetMaxGeneration()+1].start_segment;
+
+ // total_size = 0;
+ // the loop below will terminate, because we retrieved at most nMaxHeapSegmentCount segments
+ while (dwAddrSeg != NULL)
+ {
+ if (IsInterrupt())
+ return;
+ if (segment.Request(g_sos, dwAddrSeg, heap) != S_OK)
+ {
+ ExtOut("Error requesting heap segment %p\n", SOS_PTR(dwAddrSeg));
+ return;
+ }
+ ExtOut("%p %p %p 0x%" POINTERSIZE_TYPE "x(%" POINTERSIZE_TYPE "d)\n", SOS_PTR(dwAddrSeg),
+ SOS_PTR(segment.mem), SOS_PTR(segment.allocated),
+ (ULONG_PTR)(segment.allocated - segment.mem),
+ segment.allocated - segment.mem);
+ total_size += (DWORD_PTR) (segment.allocated - segment.mem);
+ dwAddrSeg = (DWORD_PTR)segment.next;
+ }
+}
+
+void GCHeapInfo(const DacpGcHeapDetails &heap, DWORD_PTR &total_size)
+{
+ GCPrintGenerationInfo(heap);
+ ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %" POINTERSIZE "s %" POINTERSIZE "s\n", "segment", "begin", "allocated", "size");
+ GCPrintSegmentInfo(heap, total_size);
+ ExtOut("Large object heap starts at 0x%p\n",
+ SOS_PTR(heap.generation_table[GetMaxGeneration()+1].allocation_start));
+ ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %" POINTERSIZE "s %" POINTERSIZE "s\n", "segment", "begin", "allocated", "size");
+ GCPrintLargeHeapSegmentInfo(heap,total_size);
+}
+
+BOOL GCObjInGeneration(TADDR taddrObj, const DacpGcHeapDetails &heap,
+ const TADDR_SEGINFO& /*seg*/, int& gen, TADDR_RANGE& allocCtx)
+{
+ gen = -1;
+ for (UINT n = 0; n <= GetMaxGeneration(); n ++)
+ {
+ if (taddrObj >= TO_TADDR(heap.generation_table[n].allocation_start))
+ {
+ gen = n;
+ break;
+ }
+ }
+
+ // We also need to look at the gen0 alloc context.
+ if (heap.generation_table[0].allocContextPtr
+ && taddrObj >= TO_TADDR(heap.generation_table[0].allocContextPtr)
+ && taddrObj < TO_TADDR(heap.generation_table[0].allocContextLimit) + Align(min_obj_size))
+ {
+ gen = 0;
+ allocCtx.start = (TADDR)heap.generation_table[0].allocContextPtr;
+ allocCtx.end = (TADDR)heap.generation_table[0].allocContextLimit;
+ }
+ else
+ {
+ allocCtx.start = allocCtx.end = 0;
+ }
+ return (gen != -1);
+}
+
+
+BOOL GCObjInSegment(TADDR taddrObj, const DacpGcHeapDetails &heap,
+ TADDR_SEGINFO& rngSeg, int& gen, TADDR_RANGE& allocCtx)
+{
+ TADDR taddrSeg;
+ DacpHeapSegmentData dacpSeg;
+
+ taddrSeg = (TADDR)heap.generation_table[GetMaxGeneration()].start_segment;
+ // the loop below will terminate, because we retrieved at most nMaxHeapSegmentCount segments
+ while (taddrSeg != (TADDR)heap.generation_table[0].start_segment)
+ {
+ if (IsInterrupt())
+ return FALSE;
+ if (dacpSeg.Request(g_sos, taddrSeg, heap) != S_OK)
+ {
+ ExtOut("Error requesting heap segment %p\n", SOS_PTR(taddrSeg));
+ return FALSE;
+ }
+ if (taddrObj >= TO_TADDR(dacpSeg.mem) && taddrObj < TO_TADDR(dacpSeg.allocated))
+ {
+ rngSeg.segAddr = (TADDR)dacpSeg.segmentAddr;
+ rngSeg.start = (TADDR)dacpSeg.mem;
+ rngSeg.end = (TADDR)dacpSeg.allocated;
+ gen = 2;
+ allocCtx.start = allocCtx.end = 0;
+ return TRUE;
+ }
+ taddrSeg = (TADDR)dacpSeg.next;
+ }
+
+ // the ephemeral segment
+ if (dacpSeg.Request(g_sos, taddrSeg, heap) != S_OK)
+ {
+ ExtOut("Error requesting heap segment %p\n", SOS_PTR(taddrSeg));
+ return FALSE;
+ }
+
+ if (taddrObj >= TO_TADDR(dacpSeg.mem) && taddrObj < TO_TADDR(heap.alloc_allocated))
+ {
+ if (GCObjInGeneration(taddrObj, heap, rngSeg, gen, allocCtx))
+ {
+ rngSeg.segAddr = (TADDR)dacpSeg.segmentAddr;
+ rngSeg.start = (TADDR)dacpSeg.mem;
+ rngSeg.end = (TADDR)heap.alloc_allocated;
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+BOOL GCObjInLargeSegment(TADDR taddrObj, const DacpGcHeapDetails &heap, TADDR_SEGINFO& rngSeg)
+{
+ TADDR taddrSeg;
+ DacpHeapSegmentData dacpSeg;
+ taddrSeg = (TADDR)heap.generation_table[GetMaxGeneration()+1].start_segment;
+
+ // the loop below will terminate, because we retrieved at most nMaxHeapSegmentCount segments
+ while (taddrSeg != NULL)
+ {
+ if (IsInterrupt())
+ return FALSE;
+ if (dacpSeg.Request(g_sos, taddrSeg, heap) != S_OK)
+ {
+ ExtOut("Error requesting heap segment %p\n", SOS_PTR(taddrSeg));
+ return FALSE;
+ }
+ if (taddrObj >= TO_TADDR(dacpSeg.mem) && taddrObj && taddrObj < TO_TADDR(dacpSeg.allocated))
+ {
+ rngSeg.segAddr = (TADDR)dacpSeg.segmentAddr;
+ rngSeg.start = (TADDR)dacpSeg.mem;
+ rngSeg.end = (TADDR)dacpSeg.allocated;
+ return TRUE;
+ }
+ taddrSeg = (TADDR)dacpSeg.next;
+ }
+ return FALSE;
+}
+
+BOOL GCObjInHeap(TADDR taddrObj, const DacpGcHeapDetails &heap,
+ TADDR_SEGINFO& rngSeg, int& gen, TADDR_RANGE& allocCtx, BOOL &bLarge)
+{
+ if (GCObjInSegment(taddrObj, heap, rngSeg, gen, allocCtx))
+ {
+ bLarge = FALSE;
+ return TRUE;
+ }
+ if (GCObjInLargeSegment(taddrObj, heap, rngSeg))
+ {
+ bLarge = TRUE;
+ gen = GetMaxGeneration()+1;
+ allocCtx.start = allocCtx.end = 0;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+#ifndef FEATURE_PAL
+// this function updates genUsage to reflect statistics from the range defined by [start, end)
+void GCGenUsageStats(TADDR start, TADDR end, const std::unordered_set<TADDR> &liveObjs,
+ const DacpGcHeapDetails &heap, BOOL bLarge, const AllocInfo *pAllocInfo, GenUsageStat *genUsage)
+{
+ // if this is an empty segment or generation return
+ if (start >= end)
+ {
+ return;
+ }
+
+ // otherwise it should start with a valid object
+ _ASSERTE(sos::IsObject(start));
+
+ // update the "allocd" field
+ genUsage->allocd += end - start;
+
+ size_t objSize = 0;
+ for (TADDR taddrObj = start; taddrObj < end; taddrObj += objSize)
+ {
+ TADDR taddrMT;
+
+ move_xp(taddrMT, taddrObj);
+ taddrMT &= ~3;
+
+ // skip allocation contexts
+ if (!bLarge)
+ {
+ // Is this the beginning of an allocation context?
+ int i;
+ for (i = 0; i < pAllocInfo->num; i ++)
+ {
+ if (taddrObj == (TADDR)pAllocInfo->array[i].alloc_ptr)
+ {
+ ExtDbgOut("Skipping allocation context: [%#p-%#p)\n",
+ SOS_PTR(pAllocInfo->array[i].alloc_ptr), SOS_PTR(pAllocInfo->array[i].alloc_limit));
+ taddrObj =
+ (TADDR)pAllocInfo->array[i].alloc_limit + Align(min_obj_size);
+ break;
+ }
+ }
+ if (i < pAllocInfo->num)
+ {
+ // we already adjusted taddrObj, so reset objSize
+ objSize = 0;
+ continue;
+ }
+
+ // We also need to look at the gen0 alloc context.
+ if (taddrObj == (DWORD_PTR) heap.generation_table[0].allocContextPtr)
+ {
+ taddrObj = (DWORD_PTR) heap.generation_table[0].allocContextLimit + Align(min_obj_size);
+ // we already adjusted taddrObj, so reset objSize
+ objSize = 0;
+ continue;
+ }
+
+ // Are we at the end of gen 0?
+ if (taddrObj == end - Align(min_obj_size))
+ {
+ objSize = 0;
+ break;
+ }
+ }
+
+ BOOL bContainsPointers;
+ BOOL bMTOk = GetSizeEfficient(taddrObj, taddrMT, bLarge, objSize, bContainsPointers);
+ if (!bMTOk)
+ {
+ ExtErr("bad object: %#p - bad MT %#p\n", SOS_PTR(taddrObj), SOS_PTR(taddrMT));
+ // set objSize to size_t to look for the next valid MT
+ objSize = sizeof(TADDR);
+ continue;
+ }
+
+ // at this point we should have a valid objSize, and there whould be no
+ // integer overflow when moving on to next object in heap
+ _ASSERTE(objSize > 0 && taddrObj < taddrObj + objSize);
+ if (objSize == 0 || taddrObj > taddrObj + objSize)
+ {
+ break;
+ }
+
+ if (IsMTForFreeObj(taddrMT))
+ {
+ genUsage->freed += objSize;
+ }
+ else if (!(liveObjs.empty()) && liveObjs.find(taddrObj) == liveObjs.end())
+ {
+ genUsage->unrooted += objSize;
+ }
+ }
+}
+#endif // !FEATURE_PAL
+
+BOOL GCHeapUsageStats(const DacpGcHeapDetails& heap, BOOL bIncUnreachable, HeapUsageStat *hpUsage)
+{
+ memset(hpUsage, 0, sizeof(*hpUsage));
+
+ AllocInfo allocInfo;
+ allocInfo.Init();
+
+ // 1. Start with small object segments
+ TADDR taddrSeg;
+ DacpHeapSegmentData dacpSeg;
+
+ taddrSeg = (TADDR)heap.generation_table[GetMaxGeneration()].start_segment;
+
+#ifndef FEATURE_PAL
+ // this will create the bitmap of rooted objects only if bIncUnreachable is true
+ GCRootImpl gcroot;
+ std::unordered_set<TADDR> emptyLiveObjs;
+ const std::unordered_set<TADDR> &liveObjs = (bIncUnreachable ? gcroot.GetLiveObjects() : emptyLiveObjs);
+
+ // 1a. enumerate all non-ephemeral segments
+ while (taddrSeg != (TADDR)heap.generation_table[0].start_segment)
+ {
+ if (IsInterrupt())
+ return FALSE;
+
+ if (dacpSeg.Request(g_sos, taddrSeg, heap) != S_OK)
+ {
+ ExtErr("Error requesting heap segment %p\n", SOS_PTR(taddrSeg));
+ return FALSE;
+ }
+ GCGenUsageStats((TADDR)dacpSeg.mem, (TADDR)dacpSeg.allocated, liveObjs, heap, FALSE, &allocInfo, &hpUsage->genUsage[2]);
+ taddrSeg = (TADDR)dacpSeg.next;
+ }
+#endif
+
+ // 1b. now handle the ephemeral segment
+ if (dacpSeg.Request(g_sos, taddrSeg, heap) != S_OK)
+ {
+ ExtErr("Error requesting heap segment %p\n", SOS_PTR(taddrSeg));
+ return FALSE;
+ }
+
+ TADDR endGen = TO_TADDR(heap.alloc_allocated);
+ for (UINT n = 0; n <= GetMaxGeneration(); n ++)
+ {
+ TADDR startGen;
+ // gen 2 starts at the beginning of the segment
+ if (n == GetMaxGeneration())
+ {
+ startGen = TO_TADDR(dacpSeg.mem);
+ }
+ else
+ {
+ startGen = TO_TADDR(heap.generation_table[n].allocation_start);
+ }
+
+#ifndef FEATURE_PAL
+ GCGenUsageStats(startGen, endGen, liveObjs, heap, FALSE, &allocInfo, &hpUsage->genUsage[n]);
+#endif
+ endGen = startGen;
+ }
+
+ // 2. Now process LOH
+ taddrSeg = (TADDR) heap.generation_table[GetMaxGeneration()+1].start_segment;
+ while (taddrSeg != NULL)
+ {
+ if (IsInterrupt())
+ return FALSE;
+
+ if (dacpSeg.Request(g_sos, taddrSeg, heap) != S_OK)
+ {
+ ExtErr("Error requesting heap segment %p\n", SOS_PTR(taddrSeg));
+ return FALSE;
+ }
+
+#ifndef FEATURE_PAL
+ GCGenUsageStats((TADDR) dacpSeg.mem, (TADDR) dacpSeg.allocated, liveObjs, heap, TRUE, NULL, &hpUsage->genUsage[3]);
+#endif
+ taddrSeg = (TADDR)dacpSeg.next;
+ }
+
+ return TRUE;
+}
+
+DWORD GetNumComponents(TADDR obj)
+{
+ // The number of components is always the second pointer in the object.
+ DWORD Value = NULL;
+ HRESULT hr = MOVE(Value, obj + sizeof(size_t));
+
+ // If we fail to read out the number of components, let's assume 0 so we don't try to
+ // read further data from the object.
+ if (FAILED(hr))
+ return 0;
+
+ // The component size on a String does not contain the trailing NULL character,
+ // so we must add that ourselves.
+ if(IsStringObject(obj))
+ return Value+1;
+
+ return Value;
+}
+
+BOOL GetSizeEfficient(DWORD_PTR dwAddrCurrObj,
+ DWORD_PTR dwAddrMethTable, BOOL bLarge, size_t& s, BOOL& bContainsPointers)
+{
+ // Remove lower bits in case we are in mark phase
+ dwAddrMethTable = dwAddrMethTable & ~3;
+ MethodTableInfo* info = g_special_mtCache.Lookup(dwAddrMethTable);
+ if (!info->IsInitialized()) // An uninitialized entry
+ {
+ // this is the first time we see this method table, so we need to get the information
+ // from the target
+ DacpMethodTableData dmtd;
+ // see code:ClrDataAccess::RequestMethodTableData for details
+ if (dmtd.Request(g_sos,dwAddrMethTable) != S_OK)
+ return FALSE;
+
+ info->BaseSize = dmtd.BaseSize;
+ info->ComponentSize = dmtd.ComponentSize;
+ info->bContainsPointers = dmtd.bContainsPointers;
+ }
+
+ bContainsPointers = info->bContainsPointers;
+ s = info->BaseSize;
+
+ if (info->ComponentSize)
+ {
+ // this is an array, so the size has to include the size of the components. We read the number
+ // of components from the target and multiply by the component size to get the size.
+ s += info->ComponentSize*GetNumComponents(dwAddrCurrObj);
+ }
+
+ // On x64 we do an optimization to save 4 bytes in almost every string we create
+ // IMPORTANT: This cannot be done in ObjectSize, which is a wrapper to this function,
+ // because we must Align only after these changes are made
+#ifdef _TARGET_WIN64_
+ // Pad to min object size if necessary
+ if (s < min_obj_size)
+ s = min_obj_size;
+#endif // _TARGET_WIN64_
+
+ s = (bLarge ? AlignLarge(s) : Align (s));
+ return TRUE;
+}
+
+// This function expects stat to be valid, and ready to get statistics.
+void GatherOneHeapFinalization(DacpGcHeapDetails& heapDetails, HeapStat *stat, BOOL bAllReady, BOOL bShort)
+{
+ DWORD_PTR dwAddr=0;
+ UINT m;
+
+ if (!bShort)
+ {
+ for (m = 0; m <= GetMaxGeneration(); m ++)
+ {
+ if (IsInterrupt())
+ return;
+
+ ExtOut("generation %d has %d finalizable objects ", m,
+ (SegQueueLimit(heapDetails,gen_segment(m)) - SegQueue(heapDetails,gen_segment(m))) / sizeof(size_t));
+
+ ExtOut ("(%p->%p)\n",
+ SOS_PTR(SegQueue(heapDetails,gen_segment(m))),
+ SOS_PTR(SegQueueLimit(heapDetails,gen_segment(m))));
+ }
+ }
+#ifndef FEATURE_PAL
+ if (bAllReady)
+ {
+ if (!bShort)
+ {
+ ExtOut ("Finalizable but not rooted: ");
+ }
+
+ TADDR rngStart = (TADDR)SegQueue(heapDetails, gen_segment(GetMaxGeneration()));
+ TADDR rngEnd = (TADDR)SegQueueLimit(heapDetails, gen_segment(0));
+
+ PrintNotReachableInRange(rngStart, rngEnd, TRUE, bAllReady ? stat : NULL, bShort);
+ }
+#endif
+
+ if (!bShort)
+ {
+ ExtOut ("Ready for finalization %d objects ",
+ (SegQueueLimit(heapDetails,FinalizerListSeg)-SegQueue(heapDetails,CriticalFinalizerListSeg)) / sizeof(size_t));
+ ExtOut ("(%p->%p)\n",
+ SOS_PTR(SegQueue(heapDetails,CriticalFinalizerListSeg)),
+ SOS_PTR(SegQueueLimit(heapDetails,FinalizerListSeg)));
+ }
+
+ // if bAllReady we only count objects that are ready for finalization,
+ // otherwise we count all finalizable objects.
+ TADDR taddrLowerLimit = (bAllReady ? (TADDR)SegQueue(heapDetails, CriticalFinalizerListSeg) :
+ (DWORD_PTR)SegQueue(heapDetails, gen_segment(GetMaxGeneration())));
+ for (dwAddr = taddrLowerLimit;
+ dwAddr < (DWORD_PTR)SegQueueLimit(heapDetails, FinalizerListSeg);
+ dwAddr += sizeof (dwAddr))
+ {
+ if (IsInterrupt())
+ {
+ return;
+ }
+
+ DWORD_PTR objAddr = NULL,
+ MTAddr = NULL;
+
+ if (SUCCEEDED(MOVE(objAddr, dwAddr)) && SUCCEEDED(GetMTOfObject(objAddr, &MTAddr)) && MTAddr)
+ {
+ if (bShort)
+ {
+ DMLOut("%s\n", DMLObject(objAddr));
+ }
+ else
+ {
+ size_t s = ObjectSize(objAddr);
+ stat->Add(MTAddr, (DWORD)s);
+ }
+ }
+ }
+}
+
+BOOL GCHeapTraverse(const DacpGcHeapDetails &heap, AllocInfo* pallocInfo, VISITGCHEAPFUNC pFunc, LPVOID token, BOOL verify)
+{
+ DWORD_PTR begin_youngest;
+ DWORD_PTR end_youngest;
+ begin_youngest = (DWORD_PTR)heap.generation_table[0].allocation_start;
+ DWORD_PTR dwAddr = (DWORD_PTR)heap.ephemeral_heap_segment;
+ DacpHeapSegmentData segment;
+
+ end_youngest = (DWORD_PTR)heap.alloc_allocated;
+
+ DWORD_PTR dwAddrSeg = (DWORD_PTR)heap.generation_table[GetMaxGeneration()].start_segment;
+ dwAddr = dwAddrSeg;
+
+ if (segment.Request(g_sos, dwAddr, heap) != S_OK)
+ {
+ ExtOut("Error requesting heap segment %p\n", SOS_PTR(dwAddr));
+ return FALSE;
+ }
+
+ // DWORD_PTR dwAddrCurrObj = (DWORD_PTR)heap.generation_table[GetMaxGeneration()].allocation_start;
+ DWORD_PTR dwAddrCurrObj = (DWORD_PTR)segment.mem;
+
+ size_t s, sPrev=0;
+ BOOL bPrevFree=FALSE;
+ DWORD_PTR dwAddrMethTable;
+ DWORD_PTR dwAddrPrevObj=0;
+
+ while(1)
+ {
+ if (IsInterrupt())
+ {
+ ExtOut("<heap walk interrupted>\n");
+ return FALSE;
+ }
+ DWORD_PTR end_of_segment = (DWORD_PTR)segment.allocated;
+ if (dwAddrSeg == (DWORD_PTR)heap.ephemeral_heap_segment)
+ {
+ end_of_segment = end_youngest;
+ if (dwAddrCurrObj - SIZEOF_OBJHEADER == end_youngest - Align(min_obj_size))
+ break;
+ }
+ if (dwAddrCurrObj >= (DWORD_PTR)end_of_segment)
+ {
+ if (dwAddrCurrObj > (DWORD_PTR)end_of_segment)
+ {
+ ExtOut ("curr_object: %p > heap_segment_allocated (seg: %p)\n",
+ SOS_PTR(dwAddrCurrObj), SOS_PTR(dwAddrSeg));
+ if (dwAddrPrevObj) {
+ ExtOut ("Last good object: %p\n", SOS_PTR(dwAddrPrevObj));
+ }
+ return FALSE;
+ }
+ dwAddrSeg = (DWORD_PTR)segment.next;
+ if (dwAddrSeg)
+ {
+ dwAddr = dwAddrSeg;
+ if (segment.Request(g_sos, dwAddr, heap) != S_OK)
+ {
+ ExtOut("Error requesting heap segment %p\n", SOS_PTR(dwAddr));
+ return FALSE;
+ }
+ dwAddrCurrObj = (DWORD_PTR)segment.mem;
+ continue;
+ }
+ else
+ break; // Done Verifying Heap
+ }
+
+ if (dwAddrSeg == (DWORD_PTR)heap.ephemeral_heap_segment
+ && dwAddrCurrObj >= end_youngest)
+ {
+ if (dwAddrCurrObj > end_youngest)
+ {
+ // prev_object length is too long
+ ExtOut("curr_object: %p > end_youngest: %p\n",
+ SOS_PTR(dwAddrCurrObj), SOS_PTR(end_youngest));
+ if (dwAddrPrevObj) {
+ DMLOut("Last good object: %s\n", DMLObject(dwAddrPrevObj));
+ }
+ return FALSE;
+ }
+ return FALSE;
+ }
+
+ if (FAILED(GetMTOfObject(dwAddrCurrObj, &dwAddrMethTable)))
+ {
+ return FALSE;
+ }
+
+ dwAddrMethTable = dwAddrMethTable & ~3;
+ if (dwAddrMethTable == 0)
+ {
+ // Is this the beginning of an allocation context?
+ int i;
+ for (i = 0; i < pallocInfo->num; i ++)
+ {
+ if (dwAddrCurrObj == (DWORD_PTR)pallocInfo->array[i].alloc_ptr)
+ {
+ dwAddrCurrObj =
+ (DWORD_PTR)pallocInfo->array[i].alloc_limit + Align(min_obj_size);
+ break;
+ }
+ }
+ if (i < pallocInfo->num)
+ continue;
+
+ // We also need to look at the gen0 alloc context.
+ if (dwAddrCurrObj == (DWORD_PTR) heap.generation_table[0].allocContextPtr)
+ {
+ dwAddrCurrObj = (DWORD_PTR) heap.generation_table[0].allocContextLimit + Align(min_obj_size);
+ continue;
+ }
+ }
+
+ BOOL bContainsPointers;
+ BOOL bMTOk = GetSizeEfficient(dwAddrCurrObj, dwAddrMethTable, FALSE, s, bContainsPointers);
+ if (verify && bMTOk)
+ bMTOk = VerifyObject (heap, dwAddrCurrObj, dwAddrMethTable, s, TRUE);
+ if (!bMTOk)
+ {
+ DMLOut("curr_object: %s\n", DMLListNearObj(dwAddrCurrObj));
+ if (dwAddrPrevObj)
+ DMLOut("Last good object: %s\n", DMLObject(dwAddrPrevObj));
+
+ ExtOut ("----------------\n");
+ return FALSE;
+ }
+
+ pFunc (dwAddrCurrObj, s, dwAddrMethTable, token);
+
+ // We believe we did this alignment in ObjectSize above.
+ assert((s & ALIGNCONST) == 0);
+ dwAddrPrevObj = dwAddrCurrObj;
+ sPrev = s;
+ bPrevFree = IsMTForFreeObj(dwAddrMethTable);
+
+ dwAddrCurrObj += s;
+ }
+
+ // Now for the large object generation:
+ dwAddrSeg = (DWORD_PTR)heap.generation_table[GetMaxGeneration()+1].start_segment;
+ dwAddr = dwAddrSeg;
+
+ if (segment.Request(g_sos, dwAddr, heap) != S_OK)
+ {
+ ExtOut("Error requesting heap segment %p\n", SOS_PTR(dwAddr));
+ return FALSE;
+ }
+
+ // dwAddrCurrObj = (DWORD_PTR)heap.generation_table[GetMaxGeneration()+1].allocation_start;
+ dwAddrCurrObj = (DWORD_PTR)segment.mem;
+
+ dwAddrPrevObj=0;
+
+ while(1)
+ {
+ if (IsInterrupt())
+ {
+ ExtOut("<heap traverse interrupted>\n");
+ return FALSE;
+ }
+
+ DWORD_PTR end_of_segment = (DWORD_PTR)segment.allocated;
+
+ if (dwAddrCurrObj >= (DWORD_PTR)end_of_segment)
+ {
+ if (dwAddrCurrObj > (DWORD_PTR)end_of_segment)
+ {
+ ExtOut("curr_object: %p > heap_segment_allocated (seg: %p)\n",
+ SOS_PTR(dwAddrCurrObj), SOS_PTR(dwAddrSeg));
+ if (dwAddrPrevObj) {
+ ExtOut("Last good object: %p\n", SOS_PTR(dwAddrPrevObj));
+ }
+ return FALSE;
+ }
+ dwAddrSeg = (DWORD_PTR)segment.next;
+ if (dwAddrSeg)
+ {
+ dwAddr = dwAddrSeg;
+ if (segment.Request(g_sos, dwAddr, heap) != S_OK)
+ {
+ ExtOut("Error requesting heap segment %p\n", SOS_PTR(dwAddr));
+ return FALSE;
+ }
+ dwAddrCurrObj = (DWORD_PTR)segment.mem;
+ continue;
+ }
+ else
+ break; // Done Verifying Heap
+ }
+
+ if (FAILED(GetMTOfObject(dwAddrCurrObj, &dwAddrMethTable)))
+ {
+ return FALSE;
+ }
+
+ dwAddrMethTable = dwAddrMethTable & ~3;
+ BOOL bContainsPointers;
+ BOOL bMTOk = GetSizeEfficient(dwAddrCurrObj, dwAddrMethTable, TRUE, s, bContainsPointers);
+ if (verify && bMTOk)
+ bMTOk = VerifyObject (heap, dwAddrCurrObj, dwAddrMethTable, s, TRUE);
+ if (!bMTOk)
+ {
+ DMLOut("curr_object: %s\n", DMLListNearObj(dwAddrCurrObj));
+
+ if (dwAddrPrevObj)
+ DMLOut("Last good object: %s\n", dwAddrPrevObj);
+
+ ExtOut ("----------------\n");
+ return FALSE;
+ }
+
+ pFunc (dwAddrCurrObj, s, dwAddrMethTable, token);
+
+ // We believe we did this alignment in ObjectSize above.
+ assert((s & ALIGNCONSTLARGE) == 0);
+ dwAddrPrevObj = dwAddrCurrObj;
+ dwAddrCurrObj += s;
+ }
+
+ return TRUE;
+}
+
+BOOL GCHeapsTraverse(VISITGCHEAPFUNC pFunc, LPVOID token, BOOL verify)
+{
+ // Obtain allocation context for each managed thread.
+ AllocInfo allocInfo;
+ allocInfo.Init();
+
+ if (!IsServerBuild())
+ {
+ DacpGcHeapDetails heapDetails;
+ if (heapDetails.Request(g_sos) != S_OK)
+ {
+ ExtOut("Error requesting gc heap details\n");
+ return FALSE;
+ }
+
+ return GCHeapTraverse (heapDetails, &allocInfo, pFunc, token, verify);
+ }
+ else
+ {
+ DacpGcHeapData gcheap;
+ if (gcheap.Request(g_sos) != S_OK)
+ {
+ ExtOut("Error requesting GC Heap data\n");
+ return FALSE;
+ }
+
+ DWORD dwAllocSize;
+ DWORD dwNHeaps = gcheap.HeapCount;
+ if (!ClrSafeInt<DWORD>::multiply(sizeof(CLRDATA_ADDRESS), dwNHeaps, dwAllocSize))
+ {
+ ExtOut("Failed to get GCHeaps: integer overflow error\n");
+ return FALSE;
+ }
+ CLRDATA_ADDRESS *heapAddrs = (CLRDATA_ADDRESS*)alloca(dwAllocSize);
+ if (g_sos->GetGCHeapList(dwNHeaps, heapAddrs, NULL) != S_OK)
+ {
+ ExtOut("Failed to get GCHeaps\n");
+ return FALSE;
+ }
+
+ DWORD n;
+ for (n = 0; n < dwNHeaps; n ++)
+ {
+ DacpGcHeapDetails heapDetails;
+ if (heapDetails.Request(g_sos, heapAddrs[n]) != S_OK)
+ {
+ ExtOut("Error requesting details\n");
+ return FALSE;
+ }
+
+ if (!GCHeapTraverse (heapDetails, &allocInfo, pFunc, token, verify))
+ {
+ ExtOut("Traversing a gc heap failed\n");
+ return FALSE;
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+GCHeapSnapshot::GCHeapSnapshot()
+{
+ m_isBuilt = FALSE;
+ m_heapDetails = NULL;
+}
+
+///////////////////////////////////////////////////////////
+SegmentLookup::SegmentLookup()
+{
+ m_iSegmentsSize = m_iSegmentCount = 0;
+
+ m_segments = new DacpHeapSegmentData[nSegLookupStgIncrement];
+ if (m_segments == NULL)
+ {
+ ReportOOM();
+ }
+ else
+ {
+ m_iSegmentsSize = nSegLookupStgIncrement;
+ }
+}
+
+BOOL SegmentLookup::AddSegment(DacpHeapSegmentData *pData)
+{
+ // appends the address of a new (initialized) instance of DacpHeapSegmentData to the list of segments
+ // (m_segments) adding space for a segment when necessary.
+ // @todo Microsoft: The field name m_iSegmentSize is a little misleading. It's not the size in bytes,
+ // but the number of elements allocated for the array. It probably should have been named something like
+ // m_iMaxSegments instead.
+ if (m_iSegmentCount >= m_iSegmentsSize)
+ {
+ // expand buffer--allocate enough space to hold the elements we already have plus nSegLookupStgIncrement
+ // more elements
+ DacpHeapSegmentData *pNewBuffer = new DacpHeapSegmentData[m_iSegmentsSize+nSegLookupStgIncrement];
+ if (pNewBuffer==NULL)
+ return FALSE;
+
+ // copy the old elements into the new array
+ memcpy(pNewBuffer, m_segments, sizeof(DacpHeapSegmentData)*m_iSegmentsSize);
+
+ // record the new number of elements available
+ m_iSegmentsSize+=nSegLookupStgIncrement;
+
+ // delete the old array
+ delete [] m_segments;
+
+ // set m_segments to point to the new array
+ m_segments = pNewBuffer;
+ }
+
+ // add pData to the array
+ m_segments[m_iSegmentCount++] = *pData;
+
+ return TRUE;
+}
+
+SegmentLookup::~SegmentLookup()
+{
+ if (m_segments)
+ {
+ delete [] m_segments;
+ m_segments = NULL;
+ }
+}
+
+void SegmentLookup::Clear()
+{
+ m_iSegmentCount = 0;
+}
+
+CLRDATA_ADDRESS SegmentLookup::GetHeap(CLRDATA_ADDRESS object, BOOL& bFound)
+{
+ CLRDATA_ADDRESS ret = NULL;
+ bFound = FALSE;
+
+ // Visit our segments
+ for (int i=0; i<m_iSegmentCount; i++)
+ {
+ if (TO_TADDR(m_segments[i].mem) <= TO_TADDR(object) &&
+ TO_TADDR(m_segments[i].highAllocMark) > TO_TADDR(object))
+ {
+ ret = m_segments[i].gc_heap;
+ bFound = TRUE;
+ break;
+ }
+ }
+
+ return ret;
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+BOOL GCHeapSnapshot::Build()
+{
+ Clear();
+
+ m_isBuilt = FALSE;
+
+ ///- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ /// 1. Get some basic information such as the heap type (SVR or WKS), how many heaps there are, mode and max generation
+ /// (See code:ClrDataAccess::RequestGCHeapData)
+ ///- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_gcheap.Request(g_sos) != S_OK)
+ {
+ ExtOut("Error requesting GC Heap data\n");
+ return FALSE;
+ }
+
+ ArrayHolder<CLRDATA_ADDRESS> heapAddrs = NULL;
+
+ ///- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ /// 2. Get a list of the addresses of the heaps when we have multiple heaps in server mode
+ ///- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_gcheap.bServerMode)
+ {
+ UINT AllocSize;
+ // allocate an array to hold the starting addresses of each heap when we're in server mode
+ if (!ClrSafeInt<UINT>::multiply(sizeof(CLRDATA_ADDRESS), m_gcheap.HeapCount, AllocSize) ||
+ (heapAddrs = new CLRDATA_ADDRESS [m_gcheap.HeapCount]) == NULL)
+ {
+ ReportOOM();
+ return FALSE;
+ }
+
+ // and initialize it with their addresses (see code:ClrDataAccess::RequestGCHeapList
+ // for details)
+ if (g_sos->GetGCHeapList(m_gcheap.HeapCount, heapAddrs, NULL) != S_OK)
+ {
+ ExtOut("Failed to get GCHeaps\n");
+ return FALSE;
+ }
+ }
+
+ ///- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ /// 3. Get some necessary information about each heap, such as the card table location, the generation
+ /// table, the heap bounds, etc., and retrieve the heap segments
+ ///- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+ // allocate an array to hold the information
+ m_heapDetails = new DacpGcHeapDetails[m_gcheap.HeapCount];
+
+ if (m_heapDetails == NULL)
+ {
+ ReportOOM();
+ return FALSE;
+ }
+
+ // get the heap information for each heap
+ // See code:ClrDataAccess::RequestGCHeapDetails for details
+ for (UINT n = 0; n < m_gcheap.HeapCount; n ++)
+ {
+ if (m_gcheap.bServerMode)
+ {
+ if (m_heapDetails[n].Request(g_sos, heapAddrs[n]) != S_OK)
+ {
+ ExtOut("Error requesting details\n");
+ return FALSE;
+ }
+ }
+ else
+ {
+ if (m_heapDetails[n].Request(g_sos) != S_OK)
+ {
+ ExtOut("Error requesting details\n");
+ return FALSE;
+ }
+ }
+
+ // now get information about the heap segments for this heap
+ if (!AddSegments(m_heapDetails[n]))
+ {
+ ExtOut("Failed to retrieve segments for gc heap\n");
+ return FALSE;
+ }
+ }
+
+ m_isBuilt = TRUE;
+ return TRUE;
+}
+
+BOOL GCHeapSnapshot::AddSegments(DacpGcHeapDetails& details)
+{
+ int n = 0;
+ DacpHeapSegmentData segment;
+
+ // This array of two addresses gives us access to all the segments. The generation segments are linked
+ // to each other, starting with the maxGeneration segment. The second address gives us the large object heap.
+ CLRDATA_ADDRESS AddrSegs[] =
+ {
+ details.generation_table[GetMaxGeneration()].start_segment,
+ details.generation_table[GetMaxGeneration()+1].start_segment // large object heap
+ };
+
+ // this loop will get information for all the heap segments in this heap. The outer loop iterates once
+ // for the "normal" generation segments and once for the large object heap. The inner loop follows the chain
+ // of segments rooted at AddrSegs[i]
+ for (unsigned int i = 0; i < sizeof(AddrSegs)/sizeof(AddrSegs[0]); ++i)
+ {
+ CLRDATA_ADDRESS AddrSeg = AddrSegs[i];
+
+ while (AddrSeg != NULL)
+ {
+ if (IsInterrupt())
+ {
+ return FALSE;
+ }
+ // Initialize segment by copying fields from the target's heap segment at AddrSeg.
+ // See code:ClrDataAccess::RequestGCHeapSegment for details.
+ if (segment.Request(g_sos, AddrSeg, details) != S_OK)
+ {
+ ExtOut("Error requesting heap segment %p\n", SOS_PTR(AddrSeg));
+ return FALSE;
+ }
+ if (n++ > nMaxHeapSegmentCount) // that would be insane
+ {
+ ExtOut("More than %d heap segments, there must be an error\n", nMaxHeapSegmentCount);
+ return FALSE;
+ }
+
+ // add the new segment to the array of segments. This will expand the array if necessary
+ if (!m_segments.AddSegment(&segment))
+ {
+ ExtOut("strike: Failed to store segment\n");
+ return FALSE;
+ }
+ // get the next segment in the chain
+ AddrSeg = segment.next;
+ }
+ }
+
+ return TRUE;
+}
+
+void GCHeapSnapshot::Clear()
+{
+ if (m_heapDetails != NULL)
+ {
+ delete [] m_heapDetails;
+ m_heapDetails = NULL;
+ }
+
+ m_segments.Clear();
+
+ m_isBuilt = FALSE;
+}
+
+GCHeapSnapshot g_snapshot;
+
+DacpGcHeapDetails *GCHeapSnapshot::GetHeap(CLRDATA_ADDRESS objectPointer)
+{
+ // We need bFound because heap will be NULL if we are Workstation Mode.
+ // We still need a way to know if the address was found in our segment
+ // list.
+ BOOL bFound = FALSE;
+ CLRDATA_ADDRESS heap = m_segments.GetHeap(objectPointer, bFound);
+ if (heap)
+ {
+ for (UINT i=0; i<m_gcheap.HeapCount; i++)
+ {
+ if (m_heapDetails[i].heapAddr == heap)
+ return m_heapDetails + i;
+ }
+ }
+ else if (!m_gcheap.bServerMode)
+ {
+ if (bFound)
+ {
+ return m_heapDetails;
+ }
+ }
+
+ // Not found
+ return NULL;
+}
+
+// TODO: Do we need to handle the LOH here?
+int GCHeapSnapshot::GetGeneration(CLRDATA_ADDRESS objectPointer)
+{
+ DacpGcHeapDetails *pDetails = GetHeap(objectPointer);
+ if (pDetails == NULL)
+ {
+ ExtOut("Object %p has no generation\n", SOS_PTR(objectPointer));
+ return 0;
+ }
+
+ TADDR taObj = TO_TADDR(objectPointer);
+ // The DAC doesn't fill the generation table with true CLRDATA_ADDRESS values
+ // but rather with ULONG64 values (i.e. non-sign-extended 64-bit values)
+ // We use the TO_TADDR below to ensure we won't break if this will ever
+ // be fixed in the DAC.
+ if (taObj >= TO_TADDR(pDetails->generation_table[0].allocation_start) &&
+ taObj <= TO_TADDR(pDetails->alloc_allocated))
+ return 0;
+
+ if (taObj >= TO_TADDR(pDetails->generation_table[1].allocation_start) &&
+ taObj <= TO_TADDR(pDetails->generation_table[0].allocation_start))
+ return 1;
+
+ return 2;
+}
+
+
+DWORD_PTR g_trav_totalSize = 0;
+DWORD_PTR g_trav_wastedSize = 0;
+
+void LoaderHeapTraverse(CLRDATA_ADDRESS blockData,size_t blockSize,BOOL blockIsCurrentBlock)
+{
+ DWORD_PTR dwAddr1;
+ DWORD_PTR curSize = 0;
+ char ch;
+ for (dwAddr1 = (DWORD_PTR)blockData;
+ dwAddr1 < (DWORD_PTR)blockData + blockSize;
+ dwAddr1 += OSPageSize())
+ {
+ if (IsInterrupt())
+ break;
+ if (SafeReadMemory(dwAddr1, &ch, sizeof(ch), NULL))
+ {
+ curSize += OSPageSize();
+ }
+ else
+ break;
+ }
+
+ if (!blockIsCurrentBlock)
+ {
+ g_trav_wastedSize += blockSize - curSize;
+ }
+
+ g_trav_totalSize += curSize;
+ ExtOut("%p(%x:%x) ", SOS_PTR(blockData), blockSize, curSize);
+}
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function prints out the size for various heaps. *
+* total - the total size of the heap *
+* wasted - the amount of size wasted by the heap. *
+* *
+\**********************************************************************/
+void PrintHeapSize(DWORD_PTR total, DWORD_PTR wasted)
+{
+ ExtOut("Size: 0x%" POINTERSIZE_TYPE "x (%" POINTERSIZE_TYPE "lu) bytes", total, total);
+ if (wasted)
+ ExtOut(" total, 0x%" POINTERSIZE_TYPE "x (%" POINTERSIZE_TYPE "lu) bytes wasted", wasted, wasted);
+ ExtOut(".\n");
+}
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function prints out the size information for the JIT heap. *
+* *
+* Returns: The size of this heap. *
+* *
+\**********************************************************************/
+DWORD_PTR JitHeapInfo()
+{
+ // walk ExecutionManager__m_pJitList
+ unsigned int count = 0;
+ if (FAILED(g_sos->GetJitManagerList(0, NULL, &count)))
+ {
+ ExtOut("Unable to get JIT info\n");
+ return 0;
+ }
+
+ ArrayHolder<DacpJitManagerInfo> pArray = new DacpJitManagerInfo[count];
+ if (pArray==NULL)
+ {
+ ReportOOM();
+ return 0;
+ }
+
+ if (g_sos->GetJitManagerList(count, pArray, NULL) != S_OK)
+ {
+ ExtOut("Unable to get array of JIT Managers\n");
+ return 0;
+ }
+
+ DWORD_PTR totalSize = 0;
+ DWORD_PTR wasted = 0;
+
+ for (unsigned int n=0; n < count; n++)
+ {
+ if (IsInterrupt())
+ break;
+
+ if (IsMiIL(pArray[n].codeType)) // JIT
+ {
+ unsigned int heapCount = 0;
+ if (FAILED(g_sos->GetCodeHeapList(pArray[n].managerAddr, 0, NULL, &heapCount)))
+ {
+ ExtOut("Error getting EEJitManager code heaps\n");
+ break;
+ }
+
+ if (heapCount > 0)
+ {
+ ArrayHolder<DacpJitCodeHeapInfo> codeHeapInfo = new DacpJitCodeHeapInfo[heapCount];
+ if (codeHeapInfo == NULL)
+ {
+ ReportOOM();
+ break;
+ }
+
+ if (g_sos->GetCodeHeapList(pArray[n].managerAddr, heapCount, codeHeapInfo, NULL) != S_OK)
+ {
+ ExtOut("Unable to get code heap info\n");
+ break;
+ }
+
+ for (unsigned int iHeaps = 0; iHeaps < heapCount; iHeaps++)
+ {
+ if (IsInterrupt())
+ break;
+
+ if (codeHeapInfo[iHeaps].codeHeapType == CODEHEAP_LOADER)
+ {
+ ExtOut("LoaderCodeHeap: ");
+ totalSize += LoaderHeapInfo(codeHeapInfo[iHeaps].LoaderHeap, &wasted);
+ }
+ else if (codeHeapInfo[iHeaps].codeHeapType == CODEHEAP_HOST)
+ {
+ ExtOut("HostCodeHeap: ");
+ ExtOut("%p ", SOS_PTR(codeHeapInfo[iHeaps].HostData.baseAddr));
+ DWORD dwSize = (DWORD)(codeHeapInfo[iHeaps].HostData.currentAddr - codeHeapInfo[iHeaps].HostData.baseAddr);
+ PrintHeapSize(dwSize, 0);
+ totalSize += dwSize;
+ }
+ }
+ }
+ }
+ else if (!IsMiNative(pArray[n].codeType)) // ignore native heaps for now
+ {
+ ExtOut("Unknown Jit encountered, ignored\n");
+ }
+ }
+
+ ExtOut("Total size: ");
+ PrintHeapSize(totalSize, wasted);
+
+ return totalSize;
+}
+
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function prints out the loader heap info for a single AD. *
+* pLoaderHeapAddr - pointer to the loader heap *
+* wasted - a pointer to store the number of bytes wasted in this *
+* VSDHeap (this pointer can be NULL) *
+* *
+* Returns: The size of this heap. *
+* *
+\**********************************************************************/
+DWORD_PTR LoaderHeapInfo(CLRDATA_ADDRESS pLoaderHeapAddr, DWORD_PTR *wasted)
+{
+ g_trav_totalSize = 0;
+ g_trav_wastedSize = 0;
+
+ if (pLoaderHeapAddr)
+ g_sos->TraverseLoaderHeap(pLoaderHeapAddr, LoaderHeapTraverse);
+
+ PrintHeapSize(g_trav_totalSize, g_trav_wastedSize);
+
+ if (wasted)
+ *wasted += g_trav_wastedSize;
+ return g_trav_totalSize;
+}
+
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function prints out the heap info for a single VSDHeap. *
+* name - the name to print *
+* type - the type of heap *
+* appDomain - the app domain in which this resides *
+* wasted - a pointer to store the number of bytes wasted in this *
+* VSDHeap (this pointer can be NULL) *
+* *
+* Returns: The size of this heap. *
+* *
+\**********************************************************************/
+static DWORD_PTR PrintOneVSDHeap(const char *name, VCSHeapType type, CLRDATA_ADDRESS appDomain, DWORD_PTR *wasted)
+{
+ g_trav_totalSize = 0; g_trav_wastedSize = 0;
+
+ ExtOut(name);
+ g_sos->TraverseVirtCallStubHeap(appDomain, type, LoaderHeapTraverse);
+
+ PrintHeapSize(g_trav_totalSize, g_trav_wastedSize);
+ if (wasted)
+ *wasted += g_trav_wastedSize;
+ return g_trav_totalSize;
+}
+
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function prints out the heap info for VSDHeaps. *
+* appDomain - The AppDomain to print info for. *
+* wasted - a pointer to store the number of bytes wasted in this *
+* AppDomain (this pointer can be NULL) *
+* *
+* Returns: The size of this heap. *
+* *
+\**********************************************************************/
+DWORD_PTR VSDHeapInfo(CLRDATA_ADDRESS appDomain, DWORD_PTR *wasted)
+{
+ DWORD_PTR totalSize = 0;
+
+ if (appDomain)
+ {
+ totalSize += PrintOneVSDHeap(" IndcellHeap: ", IndcellHeap, appDomain, wasted);
+ totalSize += PrintOneVSDHeap(" LookupHeap: ", LookupHeap, appDomain, wasted);
+ totalSize += PrintOneVSDHeap(" ResolveHeap: ", ResolveHeap, appDomain, wasted);
+ totalSize += PrintOneVSDHeap(" DispatchHeap: ", DispatchHeap, appDomain, wasted);
+ totalSize += PrintOneVSDHeap(" CacheEntryHeap: ", CacheEntryHeap, appDomain, wasted);
+ }
+
+ return totalSize;
+}
+
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function prints out the heap info for a domain *
+* name - the name of the domain (to be printed) *
+* adPtr - a pointer to the AppDomain to print info about *
+* outSize - a pointer to an int to store the size at (this may be *
+* NULL) *
+* outWasted - a pointer to an int to store the number of bytes this *
+* domain is wasting (this may be NULL) *
+* *
+* returns: SUCCESS if we successfully printed out the domain heap *
+* info, FAILED otherwise; if FAILED, outSize and *
+* outWasted are untouched. *
+* *
+\**********************************************************************/
+HRESULT PrintDomainHeapInfo(const char *name, CLRDATA_ADDRESS adPtr, DWORD_PTR *outSize, DWORD_PTR *outWasted)
+{
+ DacpAppDomainData appDomain;
+ HRESULT hr = appDomain.Request(g_sos, adPtr);
+ if (FAILED(hr))
+ {
+ ExtOut("Unable to get information for %s.\n", name);
+ return hr;
+ }
+
+ ExtOut("--------------------------------------\n");
+
+ const int column = 19;
+ ExtOut("%s:", name);
+ WhitespaceOut(column - (int)strlen(name) - 1);
+ DMLOut("%s\n", DMLDomain(adPtr));
+
+ DWORD_PTR domainHeapSize = 0;
+ DWORD_PTR wasted = 0;
+
+ ExtOut("LowFrequencyHeap: ");
+ domainHeapSize += LoaderHeapInfo(appDomain.pLowFrequencyHeap, &wasted);
+
+ ExtOut("HighFrequencyHeap: ");
+ domainHeapSize += LoaderHeapInfo(appDomain.pHighFrequencyHeap, &wasted);
+
+ ExtOut("StubHeap: ");
+ domainHeapSize += LoaderHeapInfo(appDomain.pStubHeap, &wasted);
+
+ ExtOut("Virtual Call Stub Heap:\n");
+ domainHeapSize += VSDHeapInfo(appDomain.AppDomainPtr, &wasted);
+
+ ExtOut("Total size: ");
+ PrintHeapSize(domainHeapSize, wasted);
+
+ if (outSize)
+ *outSize += domainHeapSize;
+ if (outWasted)
+ *outWasted += wasted;
+
+ return hr;
+}
+
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function prints out the heap info for a list of modules. *
+* moduleList - an array of modules *
+* count - the number of modules in moduleList *
+* type - the type of heap *
+* outWasted - a pointer to store the number of bytes wasted in this *
+* heap (this pointer can be NULL) *
+* *
+* Returns: The size of this heap. *
+* *
+\**********************************************************************/
+DWORD_PTR PrintModuleHeapInfo(__out_ecount(count) DWORD_PTR *moduleList, int count, ModuleHeapType type, DWORD_PTR *outWasted)
+{
+ DWORD_PTR toReturn = 0;
+ DWORD_PTR wasted = 0;
+
+ if (IsMiniDumpFile())
+ {
+ ExtOut("<no information>\n");
+ }
+ else
+ {
+ DWORD_PTR thunkHeapSize = 0;
+
+ for (int i = 0; i < count; i++)
+ {
+ CLRDATA_ADDRESS addr = moduleList[i];
+ DacpModuleData dmd;
+ if (dmd.Request(g_sos, addr) != S_OK)
+ {
+ ExtOut("Unable to read module %p\n", SOS_PTR(addr));
+ }
+ else
+ {
+ DMLOut("Module %s: ", DMLModule(addr));
+ CLRDATA_ADDRESS heap = type == ModuleHeapType_ThunkHeap ? dmd.pThunkHeap : dmd.pLookupTableHeap;
+ thunkHeapSize += LoaderHeapInfo(heap, &wasted);
+ }
+ }
+
+ ExtOut("Total size: " WIN86_8SPACES);
+ PrintHeapSize(thunkHeapSize, wasted);
+
+ toReturn = thunkHeapSize;
+ }
+
+ if (outWasted)
+ *outWasted += wasted;
+
+ return toReturn;
+}
diff --git a/src/ToolBox/SOS/Strike/exts.cpp b/src/ToolBox/SOS/Strike/exts.cpp
new file mode 100644
index 0000000000..0b1f976cc2
--- /dev/null
+++ b/src/ToolBox/SOS/Strike/exts.cpp
@@ -0,0 +1,435 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// ==++==
+//
+
+//
+// ==--==
+#include "exts.h"
+#include "disasm.h"
+#ifndef FEATURE_PAL
+#include "EventCallbacks.h"
+
+#define VER_PRODUCTVERSION_W (0x0100)
+
+//
+// globals
+//
+EXT_API_VERSION ApiVersion = { (VER_PRODUCTVERSION_W >> 8), (VER_PRODUCTVERSION_W & 0xff), EXT_API_VERSION_NUMBER64, 0 };
+WINDBG_EXTENSION_APIS ExtensionApis;
+
+ULONG PageSize;
+
+OnUnloadTask *OnUnloadTask::s_pUnloadTaskList = NULL;
+
+//
+// Valid for the lifetime of the debug session.
+//
+
+ULONG TargetMachine;
+BOOL Connected;
+ULONG g_TargetClass;
+DWORD_PTR g_filterHint = 0;
+
+PDEBUG_CLIENT g_ExtClient;
+PDEBUG_DATA_SPACES2 g_ExtData2;
+PDEBUG_SYMBOLS2 g_ExtSymbols2;
+PDEBUG_ADVANCED3 g_ExtAdvanced3;
+PDEBUG_CLIENT g_pCallbacksClient;
+
+#else
+
+DebugClient* g_DebugClient;
+ILLDBServices* g_ExtServices;
+
+#endif // FEATURE_PAL
+
+IMachine* g_targetMachine = NULL;
+BOOL g_bDacBroken = FALSE;
+
+PDEBUG_CONTROL2 g_ExtControl;
+PDEBUG_DATA_SPACES g_ExtData;
+PDEBUG_REGISTERS g_ExtRegisters;
+PDEBUG_SYMBOLS g_ExtSymbols;
+PDEBUG_SYSTEM_OBJECTS g_ExtSystem;
+
+#define SOS_ExtQueryFailGo(var, riid) \
+ var = NULL; \
+ if ((Status = client->QueryInterface(__uuidof(riid), \
+ (void **)&var)) != S_OK) \
+ { \
+ goto Fail; \
+ }
+
+// Queries for all debugger interfaces.
+#ifndef FEATURE_PAL
+extern "C" HRESULT
+ExtQuery(PDEBUG_CLIENT client)
+{
+ g_ExtClient = client;
+#else
+extern "C" HRESULT
+ExtQuery(ILLDBServices* services)
+{
+ g_ExtServices = services;
+ DebugClient* client = new DebugClient(services);
+ g_DebugClient = client;
+#endif
+ HRESULT Status;
+ SOS_ExtQueryFailGo(g_ExtControl, IDebugControl2);
+ SOS_ExtQueryFailGo(g_ExtData, IDebugDataSpaces);
+ SOS_ExtQueryFailGo(g_ExtRegisters, IDebugRegisters);
+ SOS_ExtQueryFailGo(g_ExtSymbols, IDebugSymbols);
+ SOS_ExtQueryFailGo(g_ExtSystem, IDebugSystemObjects);
+#ifndef FEATURE_PAL
+ SOS_ExtQueryFailGo(g_ExtData2, IDebugDataSpaces2);
+ SOS_ExtQueryFailGo(g_ExtSymbols2, IDebugSymbols2);
+ SOS_ExtQueryFailGo(g_ExtAdvanced3, IDebugAdvanced3);
+#endif // FEATURE_PAL
+ return S_OK;
+
+ Fail:
+ if (Status == E_OUTOFMEMORY)
+ ReportOOM();
+
+ ExtRelease();
+ return Status;
+}
+
+extern "C" HRESULT
+ArchQuery(void)
+{
+ ULONG targetArchitecture;
+ IMachine* targetMachine = NULL;
+
+ g_ExtControl->GetExecutingProcessorType(&targetArchitecture);
+
+#ifdef SOS_TARGET_AMD64
+ if(targetArchitecture == IMAGE_FILE_MACHINE_AMD64)
+ {
+ targetMachine = AMD64Machine::GetInstance();
+ }
+#endif // SOS_TARGET_AMD64
+#ifdef SOS_TARGET_X86
+ if (targetArchitecture == IMAGE_FILE_MACHINE_I386)
+ {
+ targetMachine = X86Machine::GetInstance();
+ }
+#endif // SOS_TARGET_X86
+#ifdef SOS_TARGET_ARM
+ if (targetArchitecture == IMAGE_FILE_MACHINE_ARMNT)
+ {
+ targetMachine = ARMMachine::GetInstance();
+ }
+#endif // SOS_TARGET_ARM
+#ifdef SOS_TARGET_ARM64
+ if (targetArchitecture == IMAGE_FILE_MACHINE_ARM64)
+ {
+ targetMachine = ARM64Machine::GetInstance();
+ }
+#endif // SOS_TARGET_ARM64
+
+ if (targetMachine == NULL)
+ {
+ g_targetMachine = NULL;
+ ExtErr("SOS does not support the current target architecture.\n");
+ return E_FAIL;
+ }
+
+ g_targetMachine = targetMachine;
+ return S_OK;
+}
+
+// Cleans up all debugger interfaces.
+void
+ExtRelease(void)
+{
+ EXT_RELEASE(g_ExtControl);
+ EXT_RELEASE(g_ExtData);
+ EXT_RELEASE(g_ExtRegisters);
+ EXT_RELEASE(g_ExtSymbols);
+ EXT_RELEASE(g_ExtSystem);
+#ifndef FEATURE_PAL
+ EXT_RELEASE(g_ExtData2);
+ EXT_RELEASE(g_ExtSymbols2);
+ EXT_RELEASE(g_ExtAdvanced3);
+ g_ExtClient = NULL;
+#else
+ EXT_RELEASE(g_DebugClient);
+ g_ExtServices = NULL;
+#endif // FEATURE_PAL
+}
+
+#ifndef FEATURE_PAL
+
+BOOL IsMiniDumpFileNODAC();
+extern HMODULE g_hInstance;
+
+// This function throws an exception that can be caught by the debugger,
+// instead of allowing the default CRT behavior of invoking Watson to failfast.
+void __cdecl _SOS_invalid_parameter(
+ const WCHAR * expression,
+ const WCHAR * function,
+ const WCHAR * file,
+ unsigned int line,
+ uintptr_t pReserved
+)
+{
+ ExtErr("\nSOS failure!\n");
+ throw "SOS failure";
+}
+
+// Unregisters our windbg event callbacks and releases the client, event callback objects
+void CleanupEventCallbacks()
+{
+ if(g_pCallbacksClient != NULL)
+ {
+ g_pCallbacksClient->Release();
+ g_pCallbacksClient = NULL;
+ }
+}
+
+bool g_Initialized = false;
+
+extern "C"
+HRESULT
+CALLBACK
+DebugExtensionInitialize(PULONG Version, PULONG Flags)
+{
+ IDebugClient *DebugClient;
+ PDEBUG_CONTROL DebugControl;
+ HRESULT Hr;
+
+ *Version = DEBUG_EXTENSION_VERSION(1, 0);
+ *Flags = 0;
+
+ if (g_Initialized)
+ {
+ return S_OK;
+ }
+ g_Initialized = true;
+
+ if ((Hr = DebugCreate(__uuidof(IDebugClient),
+ (void **)&DebugClient)) != S_OK)
+ {
+ return Hr;
+ }
+ if ((Hr = DebugClient->QueryInterface(__uuidof(IDebugControl),
+ (void **)&DebugControl)) != S_OK)
+ {
+ return Hr;
+ }
+
+ ExtensionApis.nSize = sizeof (ExtensionApis);
+ if ((Hr = DebugControl->GetWindbgExtensionApis64(&ExtensionApis)) != S_OK)
+ {
+ return Hr;
+ }
+
+ // Fixes the "Unable to read dynamic function table entries" error messages by disabling the WinDbg security
+ // feature that prevents the loading of unknown out of proc tack walkers.
+ DebugControl->Execute(DEBUG_OUTCTL_IGNORE, ".settings set EngineInitialization.VerifyFunctionTableCallbacks=false",
+ DEBUG_EXECUTE_NOT_LOGGED | DEBUG_EXECUTE_NO_REPEAT);
+
+ ExtQuery(DebugClient);
+ if (IsMiniDumpFileNODAC())
+ {
+ ExtOut (
+ "----------------------------------------------------------------------------\n"
+ "The user dump currently examined is a minidump. Consequently, only a subset\n"
+ "of sos.dll functionality will be available. If needed, attaching to the live\n"
+ "process or debugging a full dump will allow access to sos.dll's full feature\n"
+ "set.\n"
+ "To create a full user dump use the command: .dump /ma <filename>\n"
+ "----------------------------------------------------------------------------\n");
+ }
+ ExtRelease();
+
+ OnUnloadTask::Register(CleanupEventCallbacks);
+ g_pCallbacksClient = DebugClient;
+ EventCallbacks* pCallbacksObj = new EventCallbacks(DebugClient);
+ IDebugEventCallbacks* pCallbacks = NULL;
+ pCallbacksObj->QueryInterface(__uuidof(IDebugEventCallbacks), (void**)&pCallbacks);
+ pCallbacksObj->Release();
+
+ if(FAILED(Hr = g_pCallbacksClient->SetEventCallbacks(pCallbacks)))
+ {
+ ExtOut ("SOS: Failed to register callback events\n");
+ pCallbacks->Release();
+ return Hr;
+ }
+ pCallbacks->Release();
+
+#ifndef _ARM_
+ // Make sure we do not tear down the debugger when a security function fails
+ // Since we link statically against CRT this will only affect the SOS module.
+ _set_invalid_parameter_handler(_SOS_invalid_parameter);
+#endif
+
+ DebugControl->Release();
+ return S_OK;
+}
+
+extern "C"
+void
+CALLBACK
+DebugExtensionNotify(ULONG Notify, ULONG64 /*Argument*/)
+{
+ //
+ // The first time we actually connect to a target, get the page size
+ //
+
+ if ((Notify == DEBUG_NOTIFY_SESSION_ACCESSIBLE) && (!Connected))
+ {
+ IDebugClient *DebugClient;
+ PDEBUG_DATA_SPACES DebugDataSpaces;
+ PDEBUG_CONTROL DebugControl;
+ HRESULT Hr;
+ ULONG64 Page;
+
+ if ((Hr = DebugCreate(__uuidof(IDebugClient),
+ (void **)&DebugClient)) == S_OK)
+ {
+ //
+ // Get the page size and PAE enable flag
+ //
+
+ if ((Hr = DebugClient->QueryInterface(__uuidof(IDebugDataSpaces),
+ (void **)&DebugDataSpaces)) == S_OK)
+ {
+ if ((Hr = DebugDataSpaces->ReadDebuggerData(
+ DEBUG_DATA_MmPageSize, &Page,
+ sizeof(Page), NULL)) == S_OK)
+ {
+ PageSize = (ULONG)(ULONG_PTR)Page;
+ }
+
+ DebugDataSpaces->Release();
+ }
+ //
+ // Get the architecture type.
+ //
+
+ if ((Hr = DebugClient->QueryInterface(__uuidof(IDebugControl),
+ (void **)&DebugControl)) == S_OK)
+ {
+ if ((Hr = DebugControl->GetActualProcessorType(
+ &TargetMachine)) == S_OK)
+ {
+ Connected = TRUE;
+ }
+ ULONG Qualifier;
+ if ((Hr = DebugControl->GetDebuggeeType(&g_TargetClass, &Qualifier)) == S_OK)
+ {
+ }
+
+ DebugControl->Release();
+ }
+
+ DebugClient->Release();
+ }
+ }
+
+
+ if (Notify == DEBUG_NOTIFY_SESSION_INACTIVE)
+ {
+ Connected = FALSE;
+ PageSize = 0;
+ TargetMachine = 0;
+ }
+
+ return;
+}
+
+extern "C"
+void
+CALLBACK
+DebugExtensionUninitialize(void)
+{
+ // execute all registered cleanup tasks
+ OnUnloadTask::Run();
+ return;
+}
+
+
+BOOL DllInit(
+ HANDLE /*hModule*/,
+ DWORD dwReason,
+ DWORD /*dwReserved*/
+ )
+{
+ switch (dwReason) {
+ case DLL_THREAD_ATTACH:
+ break;
+
+ case DLL_THREAD_DETACH:
+ break;
+
+ case DLL_PROCESS_DETACH:
+ break;
+
+ case DLL_PROCESS_ATTACH:
+ break;
+ }
+
+ return TRUE;
+}
+
+BOOL WINAPI DllMain(HANDLE hInstance, DWORD dwReason, LPVOID lpReserved)
+{
+ if (dwReason == DLL_PROCESS_ATTACH)
+ {
+ g_hInstance = (HMODULE) hInstance;
+ }
+ return true;
+}
+
+#else // FEATURE_PAL
+
+HRESULT
+DebugClient::QueryInterface(
+ REFIID InterfaceId,
+ PVOID* Interface
+ )
+{
+ if (InterfaceId == __uuidof(IUnknown) ||
+ InterfaceId == __uuidof(IDebugControl2) ||
+ InterfaceId == __uuidof(IDebugControl4) ||
+ InterfaceId == __uuidof(IDebugDataSpaces) ||
+ InterfaceId == __uuidof(IDebugSymbols) ||
+ InterfaceId == __uuidof(IDebugSystemObjects) ||
+ InterfaceId == __uuidof(IDebugRegisters))
+ {
+ *Interface = this;
+ AddRef();
+ return S_OK;
+ }
+ else
+ {
+ *Interface = NULL;
+ return E_NOINTERFACE;
+ }
+}
+
+ULONG
+DebugClient::AddRef()
+{
+ LONG ref = InterlockedIncrement(&m_ref);
+ return ref;
+}
+
+ULONG
+DebugClient::Release()
+{
+ LONG ref = InterlockedDecrement(&m_ref);
+ if (ref == 0)
+ {
+ m_lldbservices->Release();
+ delete this;
+ }
+ return ref;
+}
+
+#endif // FEATURE_PAL
diff --git a/src/ToolBox/SOS/Strike/exts.h b/src/ToolBox/SOS/Strike/exts.h
new file mode 100644
index 0000000000..36b5230c37
--- /dev/null
+++ b/src/ToolBox/SOS/Strike/exts.h
@@ -0,0 +1,513 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// ==++==
+//
+
+//
+// ==--==
+#ifndef __exts_h__
+#define __exts_h__
+
+#define KDEXT_64BIT
+
+#include <windows.h>
+#include <winternl.h>
+
+#if defined(_MSC_VER)
+#pragma warning(disable:4245) // signed/unsigned mismatch
+#pragma warning(disable:4100) // unreferenced formal parameter
+#pragma warning(disable:4201) // nonstandard extension used : nameless struct/union
+#pragma warning(disable:4127) // conditional expression is constant
+#pragma warning(disable:4430) // missing type specifier: C++ doesn't support default-int
+#endif
+#include "strike.h"
+#include <wdbgexts.h>
+#include <dbgeng.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+// wdbgexts.h defines StackTrace which interferes with other parts of the
+// system that use the StackTrace identifier
+#ifdef StackTrace
+#undef StackTrace
+#endif
+
+#include "platformspecific.h"
+
+// We need to define the target address type. This has to be used in the
+// functions that read directly from the debuggee address space, vs. using
+// the DAC to read the DAC-ized data structures.
+#include "daccess.h"
+
+#include "gcinfo.h"
+
+// Convert between CLRDATA_ADDRESS and TADDR.
+#define TO_TADDR(cdaddr) ((TADDR)(cdaddr))
+#define TO_CDADDR(taddr) ((CLRDATA_ADDRESS)(LONG_PTR)(taddr))
+
+// We also need a "correction" macro: there are a number of places in the DAC
+// where instead of using the CLRDATA_ADDRESS sign-extension convention
+// we 0-extend (most notably DacpGcHeapDetails)
+#define NEED_DAC_CLRDATA_ADDRESS_CORRECTION 1
+#if NEED_DAC_CLRDATA_ADDRESS_CORRECTION == 1
+ // the macro below "corrects" a CDADDR to always represent the
+ // sign-extended equivalent ULONG64 value of the original TADDR
+ #define UL64_TO_CDA(ul64) (TO_CDADDR(TO_TADDR(ul64)))
+#else
+ #define UL64_TO_CDA(ul64) (ul64)
+#endif // NEED_DAC_CLRDATA_ADDRESS_CORRECTION 1
+
+// The macro below removes the sign extension, returning the
+// equivalent ULONG64 value to the original TADDR. Useful when
+// printing CDA values.
+#define CDA_TO_UL64(cda) ((ULONG64)(TO_TADDR(cda)))
+
+typedef struct _TADDR_RANGE
+{
+ TADDR start;
+ TADDR end;
+} TADDR_RANGE;
+
+typedef struct _TADDR_SEGINFO
+{
+ TADDR segAddr;
+ TADDR start;
+ TADDR end;
+} TADDR_SEGINFO;
+
+#include "util.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Cleanup tasks to be executed when the extension is unloaded
+class OnUnloadTask
+{
+public:
+ FORCEINLINE static void Register(void (*fn)())
+ {
+ // append a new unload task to the head of the list
+ OnUnloadTask *pNew = new OnUnloadTask(fn);
+ pNew->pNext = s_pUnloadTaskList;
+ s_pUnloadTaskList = pNew;
+ }
+
+ static void Run()
+ {
+ // walk the list of UnloadTasks and execute each in turn
+ OnUnloadTask* pCur = s_pUnloadTaskList;
+ while (pCur != NULL)
+ {
+ OnUnloadTask* pNext = pCur->pNext;
+ pCur->OnUnloadFn();
+ delete pCur;
+ pCur = pNext;
+ }
+ s_pUnloadTaskList = NULL;
+ }
+
+private:
+ OnUnloadTask(void(*fn)())
+ : OnUnloadFn(fn)
+ , pNext(NULL)
+ { }
+
+private:
+ void (*OnUnloadFn)();
+ OnUnloadTask* pNext;
+
+ static OnUnloadTask *s_pUnloadTaskList;
+};
+
+#ifndef MINIDUMP
+
+#define EXIT_API ExtRelease
+
+// Safe release and NULL.
+#define EXT_RELEASE(Unk) \
+ ((Unk) != NULL ? ((Unk)->Release(), (Unk) = NULL) : NULL)
+
+extern PDEBUG_CONTROL2 g_ExtControl;
+extern PDEBUG_DATA_SPACES g_ExtData;
+extern PDEBUG_SYMBOLS g_ExtSymbols;
+extern PDEBUG_SYSTEM_OBJECTS g_ExtSystem;
+extern PDEBUG_REGISTERS g_ExtRegisters;
+
+#ifndef FEATURE_PAL
+
+// Global variables initialized by query.
+extern PDEBUG_CLIENT g_ExtClient;
+extern PDEBUG_DATA_SPACES2 g_ExtData2;
+extern PDEBUG_SYMBOLS2 g_ExtSymbols2;
+extern PDEBUG_ADVANCED3 g_ExtAdvanced3;
+
+#else // FEATURE_PAL
+
+extern ILLDBServices* g_ExtServices;
+
+#endif // FEATURE_PAL
+
+HRESULT
+ExtQuery(PDEBUG_CLIENT client);
+
+HRESULT
+ArchQuery(void);
+
+void
+ExtRelease(void);
+
+#ifdef _DEBUG
+extern DWORD_PTR g_filterHint;
+#endif
+
+extern BOOL ControlC;
+
+inline BOOL IsInterrupt()
+{
+ if (!ControlC && g_ExtControl->GetInterrupt() == S_OK)
+ {
+ ExtOut("Command cancelled at the user's request.\n");
+ ControlC = TRUE;
+ }
+
+ return ControlC;
+}
+
+//
+// undef the wdbgexts
+//
+#undef DECLARE_API
+
+#define DECLARE_API(extension) \
+CPPMOD HRESULT CALLBACK extension(PDEBUG_CLIENT client, PCSTR args)
+
+class __ExtensionCleanUp
+{
+public:
+ __ExtensionCleanUp(){}
+ ~__ExtensionCleanUp(){ExtRelease();}
+};
+
+inline void EENotLoadedMessage(HRESULT Status)
+{
+ ExtOut("Failed to find runtime DLL (%s), 0x%08x\n", MAKEDLLNAME_A("coreclr"), Status);
+ ExtOut("Extension commands need it in order to have something to do.\n");
+}
+
+inline void DACMessage(HRESULT Status)
+{
+ ExtOut("Failed to load data access DLL, 0x%08x\n", Status);
+#ifndef FEATURE_PAL
+ ExtOut("Verify that 1) you have a recent build of the debugger (6.2.14 or newer)\n");
+ ExtOut(" 2) the file mscordacwks.dll that matches your version of coreclr.dll is \n");
+ ExtOut(" in the version directory or on the symbol path\n");
+ ExtOut(" 3) or, if you are debugging a dump file, verify that the file \n");
+ ExtOut(" mscordacwks_<arch>_<arch>_<version>.dll is on your symbol path.\n");
+ ExtOut(" 4) you are debugging on supported cross platform architecture as \n");
+ ExtOut(" the dump file. For example, an ARM dump file must be debugged\n");
+ ExtOut(" on an X86 or an ARM machine; an AMD64 dump file must be\n");
+ ExtOut(" debugged on an AMD64 machine.\n");
+ ExtOut("\n");
+ ExtOut("You can also run the debugger command .cordll to control the debugger's\n");
+ ExtOut("load of mscordacwks.dll. .cordll -ve -u -l will do a verbose reload.\n");
+ ExtOut("If that succeeds, the SOS command should work on retry.\n");
+ ExtOut("\n");
+ ExtOut("If you are debugging a minidump, you need to make sure that your executable\n");
+ ExtOut("path is pointing to coreclr.dll as well.\n");
+#else // FEATURE_PAL
+ if (Status == CORDBG_E_MISSING_DEBUGGER_EXPORTS)
+ {
+ ExtOut("You can run the debugger command 'setclrpath' to control the load of %s.\n", MAKEDLLNAME_A("mscordaccore"));
+ ExtOut("If that succeeds, the SOS command should work on retry.\n");
+ }
+ else
+ {
+ ExtOut("Can not load or initialize %s. The target runtime may not be initialized.\n", MAKEDLLNAME_A("mscordaccore"));
+ }
+#endif // FEATURE_PAL
+}
+
+HRESULT CheckEEDll();
+
+#define INIT_API_NOEE() \
+ HRESULT Status; \
+ __ExtensionCleanUp __extensionCleanUp; \
+ if ((Status = ExtQuery(client)) != S_OK) return Status; \
+ if ((Status = ArchQuery()) != S_OK) return Status; \
+ ControlC = FALSE; \
+ g_bDacBroken = TRUE; \
+ g_clrData = NULL; \
+ g_sos = NULL;
+
+#define INIT_API_EE() \
+ if ((Status = CheckEEDll()) != S_OK) \
+ { \
+ EENotLoadedMessage(Status); \
+ return Status; \
+ }
+
+#define INIT_API_NODAC() \
+ INIT_API_NOEE() \
+ INIT_API_EE()
+
+#define INIT_API_DAC() \
+ if ((Status = LoadClrDebugDll()) != S_OK) \
+ { \
+ DACMessage(Status); \
+ return Status; \
+ } \
+ g_bDacBroken = FALSE; \
+ /* If LoadClrDebugDll() succeeded make sure we release g_clrData. */ \
+ /* We may reconsider caching g_clrData in the future */ \
+ ToRelease<IXCLRDataProcess> spIDP(g_clrData); \
+ ToRelease<ISOSDacInterface> spISD(g_sos); \
+ ResetGlobals();
+
+#define INIT_API() \
+ INIT_API_NODAC() \
+ INIT_API_DAC()
+
+// Attempt to initialize DAC and SOS globals, but do not "return" on failure.
+// Instead, mark the failure to initialize the DAC by setting g_bDacBroken to TRUE.
+// This should be used from extension commands that should work OK even when no
+// runtime is loaded in the debuggee, e.g. DumpLog, DumpStack. These extensions
+// and functions they call should test g_bDacBroken before calling any DAC enabled
+// feature.
+#define INIT_API_NO_RET_ON_FAILURE() \
+ INIT_API_NOEE() \
+ if ((Status = CheckEEDll()) != S_OK) \
+ { \
+ ExtOut("Failed to find runtime DLL (%s), 0x%08x\n", MAKEDLLNAME_A("coreclr"), Status); \
+ ExtOut("Some functionality may be impaired\n"); \
+ } \
+ else if ((Status = LoadClrDebugDll()) != S_OK) \
+ { \
+ ExtOut("Failed to load data access DLL (%s), 0x%08x\n", MAKEDLLNAME_A("mscordaccore"), Status); \
+ ExtOut("Some functionality may be impaired\n"); \
+ } \
+ else \
+ { \
+ g_bDacBroken = FALSE; \
+ ResetGlobals(); \
+ } \
+ /* If LoadClrDebugDll() succeeded make sure we release g_clrData. */ \
+ /* We may reconsider caching g_clrData in the future */ \
+ ToRelease<ISOSDacInterface> spISD(g_sos); \
+ ToRelease<IXCLRDataProcess> spIDP(g_clrData);
+
+extern BOOL g_bDacBroken;
+
+#define PAGE_ALIGN64(Va) ((ULONG64)((Va) & ~((ULONG64) ((LONG64) (LONG) PageSize - 1))))
+
+extern ULONG PageSize;
+
+
+//-----------------------------------------------------------------------------------------
+//
+// Target platform abstraction
+//
+//-----------------------------------------------------------------------------------------
+
+// some needed forward declarations
+struct StackTrace_SimpleContext;
+struct GCEncodingInfo;
+struct SOSEHInfo;
+class GCDump;
+
+///
+/// IMachine interface
+///
+/// Note:
+/// The methods accepting target address args take them as size_t==DWORD_PTR==TADDR,
+/// which means this can only provide cross platform support for same-word size
+/// architectures (only ARM on x86 currently). Since this is not exposed outside SOS
+/// and since the some-word-size limitation exists across EE/DAC/SOS this is not an
+/// actual limitation.
+///
+
+class IMachine
+{
+public:
+ // Returns the IMAGE_FILE_MACHINE_*** constant corresponding to the target machine
+ virtual ULONG GetPlatform() const = 0;
+ // Returns the size of the CONTEXT for the target machine
+ virtual ULONG GetContextSize() const = 0;
+
+ // Disassembles a managed method specified by the IPBegin-IPEnd range
+ virtual void Unassembly(
+ TADDR IPBegin,
+ TADDR IPEnd,
+ TADDR IPAskedFor,
+ TADDR GCStressCodeCopy,
+ GCEncodingInfo *pGCEncodingInfo,
+ SOSEHInfo *pEHInfo,
+ BOOL bSuppressLines,
+ BOOL bDisplayOffsets) const = 0;
+
+ // Validates whether retAddr represents a return address by unassembling backwards.
+ // If the instruction before retAddr represents a target-specific call instruction
+ // it attempts to identify the target of the call. If successful it sets *whereCalled
+ // to the call target, otherwise it sets it to 0xffffffff.
+ virtual void IsReturnAddress(
+ TADDR retAddr,
+ TADDR* whereCalled) const = 0;
+
+ // If, while unwinding the stack, "PC" represents a known return address in
+ // KiUserExceptionDispatcher, "stack" is used to retrieve an exception context record
+ // in "cxr", and an exception record in "exr"
+ virtual BOOL GetExceptionContext (
+ TADDR stack,
+ TADDR PC,
+ TADDR *cxrAddr,
+ CROSS_PLATFORM_CONTEXT * cxr,
+ TADDR *exrAddr,
+ PEXCEPTION_RECORD exr) const = 0;
+
+ // Retrieves stack pointer, frame pointer, and instruction pointer from the target context
+ virtual TADDR GetSP(const CROSS_PLATFORM_CONTEXT & ctx) const = 0;
+ virtual TADDR GetBP(const CROSS_PLATFORM_CONTEXT & ctx) const = 0;
+ virtual TADDR GetIP(const CROSS_PLATFORM_CONTEXT & ctx) const = 0;
+
+ // Fills dest's data fields from a target specific context
+ virtual void FillSimpleContext(StackTrace_SimpleContext * dest, LPVOID srcCtx) const = 0;
+ // Fills a target specific context, destCtx, from the idx-th location in a target specific
+ // array of contexts that start at srcCtx
+ virtual void FillTargetContext(LPVOID destCtx, LPVOID srcCtx, int idx = 0) const = 0;
+
+ // Retrieve some target specific output strings
+ virtual LPCSTR GetDumpStackHeading() const = 0;
+ virtual LPCSTR GetDumpStackObjectsHeading() const = 0;
+ virtual LPCSTR GetSPName() const = 0;
+ // Retrieves the non-volatile registers reported to the GC
+ virtual void GetGCRegisters(LPCSTR** regNames, unsigned int* cntRegs) const = 0;
+
+ typedef void (*printfFtn)(const char* fmt, ...);
+ // Dumps the GCInfo
+ virtual void DumpGCInfo(GCInfoToken gcInfoToken, unsigned methodSize, printfFtn gcPrintf, bool encBytes, bool bPrintHeader) const = 0;
+
+protected:
+ IMachine() {}
+ virtual ~IMachine() {}
+
+private:
+ IMachine(const IMachine& machine); // undefined
+ IMachine & operator=(const IMachine&); // undefined
+}; // class IMachine
+
+
+extern IMachine* g_targetMachine;
+
+
+inline BOOL IsDbgTargetX86() { return g_targetMachine->GetPlatform() == IMAGE_FILE_MACHINE_I386; }
+inline BOOL IsDbgTargetAmd64() { return g_targetMachine->GetPlatform() == IMAGE_FILE_MACHINE_AMD64; }
+inline BOOL IsDbgTargetArm() { return g_targetMachine->GetPlatform() == IMAGE_FILE_MACHINE_ARMNT; }
+inline BOOL IsDbgTargetWin64() { return IsDbgTargetAmd64(); }
+
+/* Returns the instruction pointer for the given CONTEXT. We need this and its family of
+ * functions because certain headers are inconsistantly included on the various platforms,
+ * meaning that we cannot use GetIP and GetSP as defined by CLR.
+ */
+inline CLRDATA_ADDRESS GetIP(const CROSS_PLATFORM_CONTEXT& context)
+{
+ return TO_CDADDR(g_targetMachine->GetIP(context));
+}
+
+/* Returns the stack pointer for the given CONTEXT.
+ */
+inline CLRDATA_ADDRESS GetSP(const CROSS_PLATFORM_CONTEXT& context)
+{
+ return TO_CDADDR(g_targetMachine->GetSP(context));
+}
+
+/* Returns the base/frame pointer for the given CONTEXT.
+ */
+inline CLRDATA_ADDRESS GetBP(const CROSS_PLATFORM_CONTEXT& context)
+{
+ return TO_CDADDR(g_targetMachine->GetBP(context));
+}
+
+
+//-----------------------------------------------------------------------------------------
+//
+// api declaration macros & api access macros
+//
+//-----------------------------------------------------------------------------------------
+
+#ifndef FEATURE_PAL
+
+extern WINDBG_EXTENSION_APIS ExtensionApis;
+#define GetExpression (ExtensionApis.lpGetExpressionRoutine)
+
+extern ULONG TargetMachine;
+extern ULONG g_TargetClass;
+extern ULONG g_VDbgEng;
+
+#else // FEATURE_PAL
+
+#define GetExpression(exp) g_ExtServices->GetExpression(exp)
+
+#endif // FEATURE_PAL
+
+#define CACHE_SIZE DT_OS_PAGE_SIZE
+
+struct ReadVirtualCache
+{
+ BYTE m_cache[CACHE_SIZE];
+ TADDR m_startCache;
+ BOOL m_cacheValid;
+ ULONG m_cacheSize;
+
+ ReadVirtualCache() { Clear(); }
+ HRESULT Read(TADDR Offset, PVOID Buffer, ULONG BufferSize, PULONG lpcbBytesRead);
+ void Clear() { m_cacheValid = FALSE; m_cacheSize = CACHE_SIZE; }
+};
+
+extern ReadVirtualCache *rvCache;
+
+#define MOVE(dst, src) rvCache->Read(TO_TADDR(src), &(dst), sizeof(dst), NULL)
+#define MOVEBLOCK(dst, src, size) rvCache->Read(TO_TADDR(src), &(dst), size, NULL)
+
+#define moveN(dst, src) \
+{ \
+ HRESULT ret = MOVE(dst, src); \
+ if (FAILED(ret)) return ret; \
+}
+
+#define moveBlockN(dst, src, size) \
+{ \
+ HRESULT ret = MOVEBLOCK(dst, src, size); \
+ if (FAILED(ret)) return ret; \
+}
+
+// move cross-process: reads memory from the debuggee into
+// debugger address space and returns in case of error
+#define move_xp(dst, src) \
+{ \
+ HRESULT ret = MOVE(dst, src); \
+ if (FAILED(ret)) return; \
+}
+
+#define moveBlock(dst, src, size) \
+{ \
+ HRESULT ret = MOVEBLOCK(dst, src, size); \
+ if (FAILED(ret)) return; \
+}
+
+#ifdef __cplusplus
+#define CPPMOD extern "C"
+#else
+#define CPPMOD
+#endif
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // __exts_h__
+
diff --git a/src/ToolBox/SOS/Strike/gchist.cpp b/src/ToolBox/SOS/Strike/gchist.cpp
new file mode 100644
index 0000000000..ee9d5b4033
--- /dev/null
+++ b/src/ToolBox/SOS/Strike/gchist.cpp
@@ -0,0 +1,636 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// ==++==
+//
+
+//
+// ==--==
+/****************************************************************************
+* STRIKE.C *
+* Routines for the NTSD extension - STRIKE *
+* *
+* History: *
+* 09/07/99 Microsoft Created *
+* *
+* *
+\***************************************************************************/
+#include <windows.h>
+#include <winternl.h>
+#include <winver.h>
+#include <wchar.h>
+
+#define NOEXTAPI
+#define KDEXT_64BIT
+#include <wdbgexts.h>
+#undef DECLARE_API
+#undef StackTrace
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <stddef.h>
+
+#include "strike.h"
+// We need to define the target address type. This will be used in the
+// functions that read directly from the debuggee address space, vs. using
+// the DAC tgo read the DAC-ized data structures.
+#include "daccess.h"
+//#include "dbgeng.h"
+
+
+#ifndef STRESS_LOG
+#define STRESS_LOG
+#endif // STRESS_LOG
+#define STRESS_LOG_READONLY
+#include "stresslog.h"
+#include <dbghelp.h>
+
+#include "corhdr.h"
+#include "dacprivate.h"
+
+#define CORHANDLE_MASK 0x1
+#define DEFINE_EXT_GLOBALS
+
+#include "util.h"
+
+#ifndef _ASSERTE
+#ifdef _DEBUG
+#define _ASSERTE(expr) \
+ do { if (!(expr) ) { ExtOut(#expr); DebugBreak(); } } while (0)
+#else // _DEBUG
+#define _ASSERTE(expr)
+#endif // _DEBUG else
+#endif // !_ASSERTE
+
+#ifdef _MSC_VER
+#pragma warning(disable:4244) // conversion from 'unsigned int' to 'unsigned short', possible loss of data
+#pragma warning(disable:4189) // local variable is initialized but not referenced
+#endif // _MSC_VER
+
+struct PlugRecord
+{
+ PlugRecord *next;
+
+ size_t PlugStart;
+ size_t PlugEnd;
+ size_t Delta;
+
+ PlugRecord() { ZeroMemory(this,sizeof(PlugRecord)); }
+};
+
+struct PromoteRecord
+{
+ PromoteRecord *next;
+
+ size_t Root;
+ size_t Value;
+ size_t methodTable;
+
+ PromoteRecord() { ZeroMemory(this,sizeof(PromoteRecord)); }
+};
+
+struct RelocRecord
+{
+ RelocRecord *next;
+
+ size_t Root;
+ size_t PrevValue;
+ size_t NewValue;
+ size_t methodTable;
+
+ RelocRecord() { ZeroMemory(this,sizeof(RelocRecord)); }
+};
+
+struct GCRecord
+{
+ ULONG64 GCCount;
+
+ // BOOL IsComplete() { return bFinished && bHaveStart; }
+
+ PlugRecord *PlugList;
+ RelocRecord *RelocList;
+ PromoteRecord *PromoteList;
+
+ void AddPlug(PlugRecord& p) {
+ PlugRecord *pTmp = PlugList;
+ PlugList = new PlugRecord(p);
+ PlugList->next = pTmp;
+ }
+
+ void AddReloc(RelocRecord& r) {
+ RelocRecord *pTmp = RelocList;
+ RelocList = new RelocRecord(r);
+ RelocList->next = pTmp;
+ }
+
+ void AddPromote(PromoteRecord& r) {
+ PromoteRecord *pTmp = PromoteList;
+ PromoteList = new PromoteRecord(r);
+ PromoteList->next = pTmp;
+ }
+
+ UINT PlugCount() {
+ UINT ret = 0;
+ PlugRecord *Iter = PlugList;
+ while (Iter) {
+ Iter = Iter->next;
+ ret++;
+ }
+ return ret;
+ }
+
+ UINT RelocCount() {
+ UINT ret = 0;
+ RelocRecord *Iter = RelocList;
+ while (Iter) {
+ Iter = Iter->next;
+ ret++;
+ }
+ return ret;
+ }
+
+ UINT PromoteCount() {
+ UINT ret = 0;
+ PromoteRecord *Iter = PromoteList;
+ while (Iter) {
+ Iter = Iter->next;
+ ret++;
+ }
+ return ret;
+ }
+
+ void Clear() {
+
+ PlugRecord *pTrav = PlugList;
+ while (pTrav) {
+ PlugRecord *pTmp = pTrav->next;
+ delete pTrav;
+ pTrav = pTmp;
+ }
+
+ RelocRecord *pTravR = RelocList;
+ while (pTravR) {
+ RelocRecord *pTmp = pTravR->next;
+ delete pTravR;
+ pTravR = pTmp;
+ }
+
+ PromoteRecord *pTravP = PromoteList;
+ while (pTravP) {
+ PromoteRecord *pTmp = pTravP->next;
+ delete pTravP;
+ pTravP = pTmp;
+ }
+
+ ZeroMemory(this,sizeof(GCRecord));
+ }
+
+};
+
+#define MAX_GCRECORDS 500
+UINT g_recordCount = 0;
+GCRecord g_records[MAX_GCRECORDS];
+
+void GcHistClear()
+{
+ for (UINT i=0; i < g_recordCount; i++)
+ {
+ g_records[i].Clear();
+ }
+ g_recordCount = 0;
+}
+
+void GcHistAddLog(LPCSTR msg, StressMsg* stressMsg)
+{
+ if (g_recordCount >= MAX_GCRECORDS)
+ {
+ return;
+ }
+
+ if (strcmp(msg, ThreadStressLog::gcPlugMoveMsg()) == 0)
+ {
+ PlugRecord pr;
+ // this is a plug message
+ _ASSERTE(stressMsg->numberOfArgs == 3);
+ pr.PlugStart = (size_t) stressMsg->args[0];
+ pr.PlugEnd = (size_t) stressMsg->args[1];
+ pr.Delta = (size_t) stressMsg->args[2];
+
+ g_records[g_recordCount].AddPlug(pr);
+ }
+ else if (strcmp(msg, ThreadStressLog::gcRootMsg()) == 0)
+ {
+ // this is a root message
+ _ASSERTE(stressMsg->numberOfArgs == 4);
+ RelocRecord rr;
+ rr.Root = (size_t) stressMsg->args[0];
+ rr.PrevValue = (size_t) stressMsg->args[1];
+ rr.NewValue = (size_t) stressMsg->args[2];
+ rr.methodTable = (size_t) stressMsg->args[3];
+ g_records[g_recordCount].AddReloc(rr);
+ }
+ else if (strcmp(msg, ThreadStressLog::gcRootPromoteMsg()) == 0)
+ {
+ // this is a promote message
+ _ASSERTE(stressMsg->numberOfArgs == 3);
+ PromoteRecord pr;
+ pr.Root = (size_t) stressMsg->args[0];
+ pr.Value = (size_t) stressMsg->args[1];
+ pr.methodTable = (size_t) stressMsg->args[2];
+ g_records[g_recordCount].AddPromote(pr);
+ }
+ else if (strcmp(msg, ThreadStressLog::gcStartMsg()) == 0)
+ {
+ // Gc start!
+ _ASSERTE(stressMsg->numberOfArgs == 3);
+ ULONG64 gc_count = (ULONG64) stressMsg->args[0];
+ g_records[g_recordCount].GCCount = gc_count;
+ g_recordCount++;
+ }
+ else if (strcmp(msg, ThreadStressLog::gcEndMsg()) == 0)
+ {
+ // Gc end!
+ // ULONG64 gc_count = (ULONG64) stressMsg->data;
+ // ExtOut ("ENDGC %d\n", gc_count);
+ }
+}
+
+DECLARE_API(HistStats)
+{
+ INIT_API();
+
+ ExtOut ("%8s %8s %8s\n",
+ "GCCount", "Promotes", "Relocs");
+ ExtOut ("-----------------------------------\n");
+
+ // Just traverse the data structure, printing basic stats
+ for (UINT i=0; i < g_recordCount; i++)
+ {
+ UINT PromoteCount = g_records[i].PromoteCount();
+ UINT RelocCount = g_records[i].RelocCount();
+ UINT GCCount = (UINT) g_records[i].GCCount;
+
+ ExtOut ("%8d %8d %8d\n",
+ GCCount,
+ PromoteCount,
+ RelocCount);
+ }
+
+ BOOL bErrorFound = FALSE;
+
+ // Check for duplicate Reloc or Promote messages within one gc.
+ // Method is very inefficient, improve it later.
+ for (UINT i=0; i < g_recordCount; i++)
+ {
+ { // Promotes
+ PromoteRecord *Iter = g_records[i].PromoteList;
+ UINT GCCount = (UINT) g_records[i].GCCount;
+ while (Iter)
+ {
+ PromoteRecord *innerIter = Iter->next;
+ while (innerIter)
+ {
+ if (Iter->Root == innerIter->Root)
+ {
+ ExtOut ("Root %p promoted multiple times in gc %d\n",
+ SOS_PTR(Iter->Root),
+ GCCount);
+ bErrorFound = TRUE;
+ }
+ innerIter = innerIter->next;
+ }
+
+ Iter = Iter->next;
+ }
+ }
+
+ { // Relocates
+ RelocRecord *Iter = g_records[i].RelocList;
+ UINT GCCount = (UINT) g_records[i].GCCount;
+ while (Iter)
+ {
+ RelocRecord *innerIter = Iter->next;
+ while (innerIter)
+ {
+ if (Iter->Root == innerIter->Root)
+ {
+ ExtOut ("Root %p relocated multiple times in gc %d\n",
+ SOS_PTR(Iter->Root),
+ GCCount);
+ bErrorFound = TRUE;
+ }
+ innerIter = innerIter->next;
+ }
+
+ Iter = Iter->next;
+ }
+ }
+ }
+
+ if (!bErrorFound)
+ {
+ ExtOut ("No duplicate promote or relocate messages found in the log.\n");
+ }
+
+ return Status;
+}
+
+DECLARE_API(HistRoot)
+{
+ INIT_API();
+ size_t nArg;
+
+ StringHolder rootstr;
+ CMDValue arg[] =
+ {
+ // vptr, type
+ {&rootstr.data, COSTRING},
+ };
+
+ if (!GetCMDOption(args, NULL, 0, arg, _countof(arg), &nArg))
+ return Status;
+
+ if (nArg != 1)
+ {
+ ExtOut ("!Root <valid object pointer>\n");
+ return Status;
+ }
+
+ size_t Root = (size_t) GetExpression(rootstr.data);
+
+ ExtOut ("%8s %" POINTERSIZE "s %" POINTERSIZE "s %9s %20s\n",
+ "GCCount", "Value", "MT", "Promoted?", "Notes");
+ ExtOut ("---------------------------------------------------------\n");
+
+ bool bBoringPeople = false;
+
+ // Just traverse the data structure, printing basic stats
+ for (UINT i=0; i < g_recordCount; i++)
+ {
+ UINT GCCount = (UINT) g_records[i].GCCount;
+
+ // Find promotion records...there should only be one.
+ PromoteRecord *pPtr = g_records[i].PromoteList;
+ PromoteRecord *pPromoteRec = NULL;
+ bool bPromotedMoreThanOnce = false;
+ while(pPtr)
+ {
+ if (pPtr->Root == Root)
+ {
+ if (pPromoteRec)
+ {
+ bPromotedMoreThanOnce = true;
+ }
+ else
+ {
+ pPromoteRec = pPtr;
+ }
+ }
+ pPtr = pPtr->next;
+ }
+
+ RelocRecord *pReloc = g_records[i].RelocList;
+ RelocRecord *pRelocRec = NULL;
+ bool bRelocatedMoreThanOnce = false;
+ while(pReloc)
+ {
+ if (pReloc->Root == Root)
+ {
+ if (pRelocRec)
+ {
+ bRelocatedMoreThanOnce = true;
+ }
+ else
+ {
+ pRelocRec = pReloc;
+ }
+ }
+ pReloc = pReloc->next;
+ }
+
+ // Validate the records found for this root.
+ if (pRelocRec != NULL)
+ {
+ bBoringPeople = false;
+
+ ExtOut ("%8d %p %p %9s ", GCCount,
+ SOS_PTR(pRelocRec->NewValue),
+ SOS_PTR(pRelocRec->methodTable),
+ pPromoteRec ? "yes" : "no");
+ if (pPromoteRec != NULL)
+ {
+ // There should be similarities between the promote and reloc record
+ if (pPromoteRec->Value != pRelocRec->PrevValue ||
+ pPromoteRec->methodTable != pRelocRec->methodTable)
+ {
+ ExtOut ("promote/reloc records in error ");
+ }
+
+ if (bPromotedMoreThanOnce || bRelocatedMoreThanOnce)
+ {
+ ExtOut ("Duplicate promote/relocs");
+ }
+ }
+ ExtOut ("\n");
+ }
+ else if (pPromoteRec)
+ {
+ ExtOut ("Error: There is a promote record for root %p, but no relocation record\n",
+ (ULONG64) pPromoteRec->Root);
+ }
+ else
+ {
+ if (!bBoringPeople)
+ {
+ ExtOut ("...\n");
+ bBoringPeople = true;
+ }
+ }
+ }
+ return Status;
+}
+
+DECLARE_API(HistObjFind)
+{
+ INIT_API();
+ size_t nArg;
+
+ StringHolder objstr;
+ CMDValue arg[] =
+ {
+ // vptr, type
+ {&objstr.data, COSTRING},
+ };
+
+ if (!GetCMDOption(args, NULL, 0, arg, _countof(arg), &nArg))
+ return Status;
+
+ if (nArg != 1)
+ {
+ ExtOut ("!ObjSearch <valid object pointer>\n");
+ return Status;
+ }
+
+ size_t object = (size_t) GetExpression(objstr.data);
+
+ ExtOut ("%8s %" POINTERSIZE "s %40s\n",
+ "GCCount", "Object", "Message");
+ ExtOut ("---------------------------------------------------------\n");
+
+ size_t curAddress = object;
+ bool bBoringPeople = false;
+
+ // Just traverse the data structure, printing basic stats
+ for (UINT i=0; i < g_recordCount; i++)
+ {
+ if (curAddress == 0)
+ {
+ break;
+ }
+
+ UINT GCCount = (UINT) g_records[i].GCCount;
+
+ PromoteRecord *pPtr = g_records[i].PromoteList;
+ while(pPtr)
+ {
+ if (pPtr->Value == curAddress)
+ {
+ bBoringPeople = false;
+ ExtOut ("%8d %p ", GCCount, SOS_PTR(curAddress));
+ ExtOut ("Promotion for root %p (MT = %p)\n",
+ SOS_PTR(pPtr->Root),
+ SOS_PTR(pPtr->methodTable));
+ }
+ pPtr = pPtr->next;
+ }
+
+ RelocRecord *pReloc = g_records[i].RelocList;
+ while(pReloc)
+ {
+ if (pReloc->NewValue == curAddress ||
+ pReloc->PrevValue == curAddress)
+ {
+ bBoringPeople = false;
+ ExtOut ("%8d %p ", GCCount, SOS_PTR(curAddress));
+ ExtOut ("Relocation %s for root %p\n",
+ (pReloc->NewValue == curAddress) ? "NEWVALUE" : "PREVVALUE",
+ SOS_PTR(pReloc->Root));
+ }
+ pReloc = pReloc->next;
+ }
+
+ if (!bBoringPeople)
+ {
+ ExtOut ("...\n");
+ bBoringPeople = true;
+ }
+
+ }
+ return Status;
+}
+
+DECLARE_API(HistObj)
+{
+ INIT_API();
+ size_t nArg;
+
+ StringHolder objstr;
+ CMDValue arg[] =
+ {
+ // vptr, type
+ {&objstr.data, COSTRING},
+ };
+
+ if (!GetCMDOption(args, NULL, 0, arg, _countof(arg), &nArg))
+ return Status;
+
+ if (nArg != 1)
+ {
+ ExtOut ("!object <valid object pointer>\n");
+ return Status;
+ }
+
+ size_t object = (size_t) GetExpression(objstr.data);
+
+ ExtOut ("%8s %" POINTERSIZE "s %40s\n",
+ "GCCount", "Object", "Roots");
+ ExtOut ("---------------------------------------------------------\n");
+
+ size_t curAddress = object;
+
+ // Just traverse the data structure, printing basic stats
+ for (UINT i=0; i < g_recordCount; i++)
+ {
+ if (curAddress == 0)
+ {
+ break;
+ }
+
+ UINT GCCount = (UINT) g_records[i].GCCount;
+
+ ExtOut ("%8d %p ", GCCount, SOS_PTR(curAddress));
+
+ RelocRecord *pReloc = g_records[i].RelocList;
+ size_t candidateCurAddress = curAddress;
+ bool bFirstReloc = true;
+ while(pReloc)
+ {
+ if (pReloc->NewValue == curAddress)
+ {
+ ExtOut ("%p, ", SOS_PTR(pReloc->Root));
+ if (bFirstReloc)
+ {
+ candidateCurAddress = pReloc->PrevValue;
+ bFirstReloc = false;
+ }
+ else if (candidateCurAddress != pReloc->PrevValue)
+ {
+ ExtOut ("differing reloc values for this object!\n");
+ }
+ }
+ pReloc = pReloc->next;
+ }
+
+ ExtOut ("\n");
+ curAddress = candidateCurAddress;
+ }
+ return Status;
+}
+
+DECLARE_API(HistInit)
+{
+ INIT_API();
+
+ GcHistClear();
+
+ CLRDATA_ADDRESS stressLogAddr = 0;
+ if (g_sos->GetStressLogAddress(&stressLogAddr) != S_OK)
+ {
+ ExtOut("Unable to find stress log via DAC\n");
+ return E_FAIL;
+ }
+
+ ExtOut ("Attempting to read Stress log\n");
+
+ Status = StressLog::Dump(stressLogAddr, NULL, g_ExtData);
+ if (Status == S_OK)
+ ExtOut("SUCCESS: GCHist structures initialized\n");
+ else if (Status == S_FALSE)
+ ExtOut("No Stress log in the image, GCHist commands unavailable\n");
+ else
+ ExtOut("FAILURE: Stress log unreadable\n");
+
+ return Status;
+}
+
+DECLARE_API(HistClear)
+{
+ INIT_API();
+ GcHistClear();
+ ExtOut("Completed successfully.\n");
+ return Status;
+}
+
diff --git a/src/ToolBox/SOS/Strike/gcroot.cpp b/src/ToolBox/SOS/Strike/gcroot.cpp
new file mode 100644
index 0000000000..f68b935e21
--- /dev/null
+++ b/src/ToolBox/SOS/Strike/gcroot.cpp
@@ -0,0 +1,2503 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// ==++==
+//
+
+//
+// ==--==
+
+/*
+ * This file defines the support classes that allow us to operate on the object graph of the process that SOS
+ * is analyzing.
+ *
+ * The GCRoot algorithm is based on three simple principles:
+ * 1. Only consider an object once. When we inspect an object, read its references and don't ever touch
+ * it again. This ensures that our upper bound on the amount of time we spend walking the object
+ * graph very quickly reaches resolution. The objects we've already inspected (and thus won't inspect
+ * again) is tracked by the mConsidered variable.
+ * 2. Be extremely careful about reads from the target process. We use a linear cache for reading from
+ * object data. We also cache everything about the method tables we read out of, as well as caching
+ * the GCDesc which is required to walk the object's references.
+ * 3. Use O(1) data structures for anything perf-critical. Almost all of the data structures we use to
+ * keep track of data have very fast lookups. For example, to keep track of the objects we've considered
+ * we use a unordered_set. Similarly to keep track of MethodTable data we use a unordered_map to track the
+ * mt -> mtinfo mapping.
+ */
+
+#include "sos.h"
+#include "disasm.h"
+
+#ifdef _ASSERTE
+#undef _ASSERTE
+#endif
+
+#define _ASSERTE(a) {;}
+
+#include "gcdesc.h"
+
+#include "safemath.h"
+
+
+#ifdef _ASSERTE
+#undef _ASSERTE
+#endif
+
+#ifndef _ASSERTE
+#ifdef _DEBUG
+#define _ASSERTE(expr) \
+ do { if (!(expr) ) { ExtErr("_ASSERTE fired:\n\t%s\n", #expr); if (IsDebuggerPresent()) DebugBreak(); } } while (0)
+#else
+#define _ASSERTE(x)
+#endif
+#endif // ASSERTE
+
+inline size_t ALIGN_DOWN( size_t val, size_t alignment )
+{
+ // alignment must be a power of 2 for this implementation to work (need modulo otherwise)
+ _ASSERTE( 0 == (alignment & (alignment - 1)) );
+ size_t result = val & ~(alignment - 1);
+ return result;
+}
+
+inline void* ALIGN_DOWN( void* val, size_t alignment )
+{
+ return (void*) ALIGN_DOWN( (size_t)val, alignment );
+}
+
+LinearReadCache::LinearReadCache(ULONG pageSize)
+ : mCurrPageStart(0), mPageSize(pageSize), mCurrPageSize(0), mPage(0)
+{
+ mPage = new BYTE[pageSize];
+ ClearStats();
+}
+
+LinearReadCache::~LinearReadCache()
+{
+ if (mPage)
+ delete [] mPage;
+}
+
+bool LinearReadCache::MoveToPage(TADDR addr, unsigned int size)
+{
+ if (size > mPageSize)
+ size = mPageSize;
+
+ mCurrPageStart = addr;
+ HRESULT hr = g_ExtData->ReadVirtual(mCurrPageStart, mPage, size, &mCurrPageSize);
+
+ if (hr != S_OK)
+ {
+ mCurrPageStart = 0;
+ mCurrPageSize = 0;
+ return false;
+ }
+
+#ifdef _DEBUG
+ mMisses++;
+#endif
+ return true;
+}
+
+
+static const char *NameForHandle(unsigned int type)
+{
+ switch (type)
+ {
+ case 0:
+ return "weak short";
+ case 1:
+ return "weak long";
+ case 2:
+ return "strong";
+ case 3:
+ return "pinned";
+ case 5:
+ return "ref counted";
+ case 6:
+ return "dependent";
+ case 7:
+ return "async pinned";
+ case 8:
+ return "sized ref";
+ }
+
+ return "unknown";
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// GCRoot functions to cleanup data.
+///////////////////////////////////////////////////////////////////////////////
+void GCRootImpl::ClearSizeData()
+{
+ mConsidered.clear();
+ mSizes.clear();
+}
+
+void GCRootImpl::ClearAll()
+{
+ ClearNodes();
+
+ {
+ std::unordered_map<TADDR, MTInfo*>::iterator itr;
+ for (itr = mMTs.begin(); itr != mMTs.end(); ++itr)
+ delete itr->second;
+ }
+
+ {
+ std::unordered_map<TADDR, RootNode*>::iterator itr;
+ for (itr = mTargets.begin(); itr != mTargets.end(); ++itr)
+ delete itr->second;
+ }
+
+ mMTs.clear();
+ mTargets.clear();
+ mConsidered.clear();
+ mSizes.clear();
+ mDependentHandleMap.clear();
+ mCache.ClearStats();
+
+ mAll = false;
+ mSize = false;
+}
+
+void GCRootImpl::ClearNodes()
+{
+ std::list<RootNode*>::iterator itr;
+
+ for (itr = mCleanupList.begin(); itr != mCleanupList.end(); ++itr)
+ delete *itr;
+
+ mCleanupList.clear();
+ mRootNewList.clear();
+}
+
+GCRootImpl::RootNode *GCRootImpl::NewNode(TADDR obj, MTInfo *mtInfo, bool fromDependent)
+{
+ // We need to create/destroy a TON of nodes during execution of GCRoot functions.
+ // To avoid heap fragmentation (and since it's faster), we don't actually new/delete
+ // nodes unless we have to. Instead we keep a stl list with free nodes to use.
+ RootNode *toReturn = NULL;
+
+ if (mRootNewList.size())
+ {
+ toReturn = mRootNewList.back();
+ mRootNewList.pop_back();
+ }
+ else
+ {
+ toReturn = new RootNode();
+ mCleanupList.push_back(toReturn);
+ }
+
+ toReturn->Object = obj;
+ toReturn->MTInfo = mtInfo;
+ toReturn->FromDependentHandle = fromDependent;
+ return toReturn;
+}
+
+void GCRootImpl::DeleteNode(RootNode *node)
+{
+ // Add node to the "new list".
+ node->Clear();
+ mRootNewList.push_back(node);
+}
+
+void GCRootImpl::GetDependentHandleMap(std::unordered_map<TADDR, std::list<TADDR>> &map)
+{
+ unsigned int type = HNDTYPE_DEPENDENT;
+ ToRelease<ISOSHandleEnum> handles;
+
+ HRESULT hr = g_sos->GetHandleEnumForTypes(&type, 1, &handles);
+
+ if (FAILED(hr))
+ {
+ ExtOut("Failed to walk dependent handles. GCRoot may miss paths.\n");
+ return;
+ }
+
+ SOSHandleData data[4];
+ unsigned int fetched = 0;
+
+ do
+ {
+ hr = handles->Next(_countof(data), data, &fetched);
+
+ if (FAILED(hr))
+ {
+ ExtOut("Error walking dependent handles. GCRoot may miss paths.\n");
+ return;
+ }
+
+ for (unsigned int i = 0; i < fetched; ++i)
+ {
+ if (data[i].Secondary != 0)
+ {
+ TADDR obj = 0;
+ TADDR target = TO_TADDR(data[i].Secondary);
+
+ MOVE(obj, TO_TADDR(data[i].Handle));
+
+ map[obj].push_back(target);
+ }
+ }
+ } while (fetched == _countof(data));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Public functions.
+///////////////////////////////////////////////////////////////////////////////
+int GCRootImpl::PrintRootsForObject(TADDR target, bool all, bool noStacks)
+{
+ ClearAll();
+ GetDependentHandleMap(mDependentHandleMap);
+
+ mAll = all;
+
+ // Add "target" to the mTargets list.
+ TADDR mt = ReadPointerCached(target);
+ RootNode *node = NewNode(target, GetMTInfo(mt));
+ mTargets[target] = node;
+
+ // Look for roots on the HandleTable, FQ, and all threads.
+ int count = 0;
+
+ if (!noStacks)
+ count += PrintRootsOnAllThreads();
+
+ count += PrintRootsOnHandleTable();
+ count += PrintRootsOnFQ();
+
+ if(count == 0)
+ {
+ count += PrintRootsOnFQ(true);
+ if(count)
+ {
+ ExtOut("Warning: These roots are from finalizable objects that are not yet ready for finalization.\n");
+ ExtOut("This is to handle the case where objects re-register themselves for finalization.\n");
+ ExtOut("These roots may be false positives.\n");
+ }
+ }
+
+ mCache.PrintStats(__FUNCTION__);
+ return count;
+}
+
+
+bool GCRootImpl::PrintPathToObject(TADDR root, TADDR target)
+{
+ ClearAll();
+ GetDependentHandleMap(mDependentHandleMap);
+
+ // Add "target" to the mTargets list.
+ TADDR mt = ReadPointerCached(target);
+ RootNode *node = NewNode(target, GetMTInfo(mt));
+ mTargets[target] = node;
+
+ // Check to see if the root reaches the target.
+ RootNode *path = FindPathToTarget(root);
+ if (path)
+ {
+ ExtOut("%p %S\n", SOS_PTR(path->Object), path->GetTypeName());
+ path = path->Next;
+
+ while (path)
+ {
+ ExtOut(" -> %p %S%s\n",SOS_PTR(path->Object), path->GetTypeName(), path->FromDependentHandle ? " (dependent handle)" : "");
+ path = path->Next;
+ }
+
+ mCache.PrintStats(__FUNCTION__);
+ return true;
+ }
+
+ mCache.PrintStats(__FUNCTION__);
+ return false;
+}
+
+size_t GCRootImpl::ObjSize(TADDR root)
+{
+ // Calculates the size of the closure of objects kept alive by root.
+ ClearAll();
+ GetDependentHandleMap(mDependentHandleMap);
+
+ // mSize tells GCRootImpl to build the "mSizes" table with the total size
+ // each object roots.
+ mSize = true;
+
+ // Note that we are calling the same method as we would to locate the rooting
+ // chain for an object, but we haven't added any items to mTargets. This means
+ // the algorithm will scan all objects and never terminate until it has walked
+ // all objects in the closure.
+ FindPathToTarget(root);
+
+ mCache.PrintStats(__FUNCTION__);
+ return mSizes[root];
+}
+
+void GCRootImpl::ObjSize()
+{
+ ClearAll();
+ GetDependentHandleMap(mDependentHandleMap);
+ mSize = true;
+
+ // Walks all roots in the process, and prints out the object size for each one.
+ PrintRootsOnAllThreads();
+ PrintRootsOnHandleTable();
+ PrintRootsOnFQ();
+
+ mCache.PrintStats(__FUNCTION__);
+}
+
+
+const std::unordered_set<TADDR> &GCRootImpl::GetLiveObjects(bool excludeFQ)
+{
+ ClearAll();
+ GetDependentHandleMap(mDependentHandleMap);
+
+ // Walk each root in the process without setting a target. This has the effect of
+ // causing us to walk every object in the process, adding them to mConsidered as we
+ // go.
+ PrintRootsOnAllThreads();
+ PrintRootsOnHandleTable();
+
+ if (!excludeFQ)
+ PrintRootsOnFQ();
+
+ mCache.PrintStats(__FUNCTION__);
+ return mConsidered;
+}
+
+int GCRootImpl::FindRoots(int gen, TADDR target)
+{
+ ClearAll();
+ GetDependentHandleMap(mDependentHandleMap);
+
+ if (gen == -1 || ((UINT)gen) == GetMaxGeneration())
+ {
+ // If this is a gen 2 !FindRoots, just do a regular !GCRoot
+ return PrintRootsForObject(target, false, false);
+ }
+ else
+ {
+ // Otherwise walk all roots for only the given generation.
+ int count = PrintRootsInOlderGen();
+ count += PrintRootsOnHandleTable(gen);
+ count += PrintRootsOnFQ();
+ return count;
+ }
+}
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// GCRoot Methods for printing out results.
+///////////////////////////////////////////////////////////////////////////////
+void GCRootImpl::ReportSizeInfo(const SOSHandleData &handle, TADDR obj)
+{
+ // Print size for a handle (!objsize)
+ TADDR mt = ReadPointer(obj);
+ MTInfo *mtInfo = GetMTInfo(mt);
+
+ const WCHAR *type = mtInfo ? mtInfo->GetTypeName() : W("unknown type");
+
+ size_t size = mSizes[obj];
+ ExtOut("Handle (%s): %p -> %p: %d (0x%x) bytes (%S)\n", NameForHandle(handle.Type), SOS_PTR(handle.Handle),
+ SOS_PTR(obj), size, size, type);
+}
+
+
+void GCRootImpl::ReportSizeInfo(DWORD thread, const SOSStackRefData &stackRef, TADDR obj)
+{
+ // Print size for a stack root (!objsize)
+ WString frame;
+ if (stackRef.SourceType == SOS_StackSourceIP)
+ frame = MethodNameFromIP(stackRef.Source);
+ else
+ frame = GetFrameFromAddress(TO_TADDR(stackRef.Source));
+
+ WString regOutput = BuildRegisterOutput(stackRef);
+
+ TADDR mt = ReadPointer(obj);
+ MTInfo *mtInfo = GetMTInfo(mt);
+ const WCHAR *type = mtInfo ? mtInfo->GetTypeName() : W("unknown type");
+
+ size_t size = mSizes[obj];
+ ExtOut("Thread %x (%S): %S: %d (0x%x) bytes (%S)\n", thread, frame.c_str(), regOutput.c_str(), size, size, type);
+}
+
+void GCRootImpl::ReportOneHandlePath(const SOSHandleData &handle, RootNode *path, bool printHeader)
+{
+ if (printHeader)
+ ExtOut("HandleTable:\n");
+
+ ExtOut(" %p (%s handle)\n", SOS_PTR(handle.Handle), NameForHandle(handle.Type));
+ while (path)
+ {
+ ExtOut(" -> %p %S%s\n", SOS_PTR(path->Object), path->GetTypeName(), path->FromDependentHandle ? " (dependent handle)" : "");
+ path = path->Next;
+ }
+
+ ExtOut("\n");
+}
+
+void GCRootImpl::ReportOnePath(DWORD thread, const SOSStackRefData &stackRef, RootNode *path, bool printThread, bool printFrame)
+{
+ if (printThread)
+ ExtOut("Thread %x:\n", thread);
+
+ if (printFrame)
+ {
+ if (stackRef.SourceType == SOS_StackSourceIP)
+ {
+ WString methodName = MethodNameFromIP(stackRef.Source);
+ ExtOut(" %p %p %S\n", SOS_PTR(stackRef.StackPointer), SOS_PTR(stackRef.Source), methodName.c_str());
+ }
+ else
+ {
+ WString frameName = GetFrameFromAddress(TO_TADDR(stackRef.Source));
+ ExtOut(" %p %S\n", SOS_PTR(stackRef.Source), frameName.c_str());
+ }
+ }
+
+ WString regOutput = BuildRegisterOutput(stackRef, false);
+ ExtOut(" %S\n", regOutput.c_str());
+
+ while (path)
+ {
+ ExtOut(" -> %p %S%s\n", SOS_PTR(path->Object), path->GetTypeName(), path->FromDependentHandle ? " (dependent handle)" : "");
+ path = path->Next;
+ }
+
+ ExtOut("\n");
+}
+
+void GCRootImpl::ReportOneFQEntry(TADDR root, RootNode *path, bool printHeader)
+{
+ if (printHeader)
+ ExtOut("Finalizer Queue:\n");
+
+ ExtOut(" %p\n", SOS_PTR(root));
+ while (path)
+ {
+ ExtOut(" -> %p %S%s\n", SOS_PTR(path->Object), path->GetTypeName(), path->FromDependentHandle ? " (dependent handle)" : "");
+ path = path->Next;
+ }
+
+ ExtOut("\n");
+}
+
+void GCRootImpl::ReportOlderGenEntry(TADDR root, RootNode *path, bool printHeader)
+{
+ if (printHeader)
+ ExtOut("Older Generation:\n");
+
+ ExtOut(" %p\n", SOS_PTR(root));
+ while (path)
+ {
+ ExtOut(" -> %p %S%s\n", SOS_PTR(path->Object), path->GetTypeName(), path->FromDependentHandle ? " (dependent handle)" : "");
+ path = path->Next;
+ }
+
+ ExtOut("\n");
+}
+
+//////////////////////////////////////////////////////
+int GCRootImpl::PrintRootsInOlderGen()
+{
+ // Use a different linear read cache here instead of polluting the object read cache.
+ LinearReadCache cache(512);
+
+ if (!IsServerBuild())
+ {
+ DacpGcHeapAnalyzeData analyzeData;
+ if (analyzeData.Request(g_sos) != S_OK)
+ {
+ ExtErr("Error requesting gc heap analyze data\n");
+ return 0;
+ }
+
+ if (!analyzeData.heap_analyze_success)
+ {
+ ExtOut("Failed to gather needed data, possibly due to memory contraints in the debuggee.\n");
+ ExtOut("To try again re-issue the !FindRoots -gen <N> command.\n");
+ return 0;
+ }
+
+ ExtDbgOut("internal_root_array = %#p\n", SOS_PTR(analyzeData.internal_root_array));
+ ExtDbgOut("internal_root_array_index = %#p\n", SOS_PTR(analyzeData.internal_root_array_index));
+
+ TADDR start = TO_TADDR(analyzeData.internal_root_array);
+ TADDR stop = TO_TADDR(analyzeData.internal_root_array + sizeof(TADDR) * (size_t)analyzeData.internal_root_array_index);
+
+ return PrintRootsInRange(cache, start, stop, &GCRootImpl::ReportOlderGenEntry, true);
+ }
+ else
+ {
+ int total = 0;
+ DWORD dwAllocSize;
+ DWORD dwNHeaps = GetGcHeapCount();
+ if (!ClrSafeInt<DWORD>::multiply(sizeof(CLRDATA_ADDRESS), dwNHeaps, dwAllocSize))
+ {
+ ExtErr("Failed to get GCHeaps: integer overflow\n");
+ return 0;
+ }
+
+ CLRDATA_ADDRESS *heapAddrs = (CLRDATA_ADDRESS*)alloca(dwAllocSize);
+ if (g_sos->GetGCHeapList(dwNHeaps, heapAddrs, NULL) != S_OK)
+ {
+ ExtErr("Failed to get GCHeaps\n");
+ return 0;
+ }
+
+ for (UINT n = 0; n < dwNHeaps; n ++)
+ {
+ DacpGcHeapAnalyzeData analyzeData;
+ if (analyzeData.Request(g_sos, heapAddrs[n]) != S_OK)
+ {
+ ExtErr("Error requesting gc heap analyze data for heap %p\n", SOS_PTR(heapAddrs[n]));
+ continue;
+ }
+
+ if (!analyzeData.heap_analyze_success)
+ {
+ ExtOut("Failed to gather needed data, possibly due to memory contraints in the debuggee.\n");
+ ExtOut("To try again re-issue the !FindRoots -gen <N> command.\n");
+ continue;
+ }
+
+ ExtDbgOut("internal_root_array = %#p\n", SOS_PTR(analyzeData.internal_root_array));
+ ExtDbgOut("internal_root_array_index = %#p\n", SOS_PTR(analyzeData.internal_root_array_index));
+
+ TADDR start = TO_TADDR(analyzeData.internal_root_array);
+ TADDR stop = TO_TADDR(analyzeData.internal_root_array + sizeof(TADDR) * (size_t)analyzeData.internal_root_array_index);
+
+ total += PrintRootsInRange(cache, start, stop, &GCRootImpl::ReportOlderGenEntry, total == 0);
+ }
+
+ return total;
+ }
+}
+
+
+int GCRootImpl::PrintRootsOnFQ(bool notReadyForFinalization)
+{
+ // Here are objects kept alive by objects in the finalizer queue.
+ DacpGcHeapDetails heapDetails;
+
+ // Use a different linear read cache here instead of polluting the object read cache.
+ LinearReadCache cache(512);
+
+ if (!IsServerBuild())
+ {
+ if (heapDetails.Request(g_sos) != S_OK)
+ {
+ ExtErr("Error requesting heap data.\n");
+ return 0;
+ }
+
+ // If we include objects that are not ready for finalization, we may report
+ // false positives. False positives occur if the object is not ready for finalization
+ // and does not re-register itself for finalization inside the finalizer.
+ TADDR start = 0;
+ TADDR stop = 0;
+ if(notReadyForFinalization)
+ {
+ start = TO_TADDR(SegQueue(heapDetails, gen_segment(GetMaxGeneration())));
+ stop = TO_TADDR(SegQueueLimit(heapDetails, CriticalFinalizerListSeg));
+ }
+ else
+ {
+ start = TO_TADDR(SegQueue(heapDetails, CriticalFinalizerListSeg));
+ stop = TO_TADDR(SegQueue(heapDetails, FinalizerListSeg));
+ }
+
+ return PrintRootsInRange(cache, start, stop, &GCRootImpl::ReportOneFQEntry, true);
+ }
+ else
+ {
+ DWORD dwAllocSize;
+ DWORD dwNHeaps = GetGcHeapCount();
+ if (!ClrSafeInt<DWORD>::multiply(sizeof(CLRDATA_ADDRESS), dwNHeaps, dwAllocSize))
+ {
+ ExtErr("Failed to get GCHeaps: integer overflow\n");
+ return 0;
+ }
+
+ CLRDATA_ADDRESS *heapAddrs = (CLRDATA_ADDRESS*)alloca(dwAllocSize);
+ if (g_sos->GetGCHeapList(dwNHeaps, heapAddrs, NULL) != S_OK)
+ {
+ ExtErr("Error requesting heap data.\n");
+ return 0;
+ }
+
+ int total = 0;
+ for (UINT n = 0; n < dwNHeaps; n ++)
+ {
+ if (heapDetails.Request(g_sos, heapAddrs[n]) != S_OK)
+ {
+ ExtErr("Error requesting heap data for heap %d.\n", n);
+ continue;
+ }
+
+ // If we include objects that are not ready for finalization, we may report
+ // false positives. False positives occur if the object is not ready for finalization
+ // and does not re-register itself for finalization inside the finalizer.
+ TADDR start = 0;
+ TADDR stop = 0;
+ if(notReadyForFinalization)
+ {
+ start = TO_TADDR(SegQueue(heapDetails, gen_segment(GetMaxGeneration())));
+ stop = TO_TADDR(SegQueueLimit(heapDetails, CriticalFinalizerListSeg));
+ }
+ else
+ {
+ start = TO_TADDR(SegQueue(heapDetails, CriticalFinalizerListSeg));
+ stop = TO_TADDR(SegQueueLimit(heapDetails, FinalizerListSeg));
+ }
+
+ total += PrintRootsInRange(cache, start, stop, &GCRootImpl::ReportOneFQEntry, total == 0);
+ }
+
+ return total;
+ }
+}
+
+int GCRootImpl::PrintRootsInRange(LinearReadCache &cache, TADDR start, TADDR stop, ReportCallback func, bool printHeader)
+{
+ int total = 0;
+
+ // Walk the range [start, stop) and consider each pointer in the range as a root.
+ while (start < stop)
+ {
+ if (IsInterrupt())
+ return total;
+
+ // Use the cache parameter here instead of mCache. If you use mCache it will be reset
+ // when calling into FindPathToTarget.
+ TADDR root = 0;
+ bool res = cache.Read(start, &root, true);
+
+ if (res && root)
+ {
+ RootNode *path = FindPathToTarget(root);
+ if (path)
+ {
+ func(root, path, printHeader);
+ total++;
+ printHeader = false;
+ }
+ }
+
+ start += sizeof(TADDR);
+ }
+
+ return total;
+}
+
+int GCRootImpl::PrintRootsOnAllThreads()
+{
+ ArrayHolder<DWORD_PTR> threadList = NULL;
+ int numThreads = 0;
+
+ // GetThreadList calls ReportOOM so we don't need to do that here.
+ HRESULT hr = GetThreadList(&threadList, &numThreads);
+ if (FAILED(hr) || !threadList)
+ return 0;
+
+ // Walk each thread and process the roots on it.
+ int total = 0;
+ DacpThreadData vThread;
+ for (int i = 0; i < numThreads; i++)
+ {
+ if (IsInterrupt())
+ return total;
+
+ if (FAILED(vThread.Request(g_sos, threadList[i])))
+ continue;
+
+ if (vThread.osThreadId)
+ total += PrintRootsOnThread(vThread.osThreadId);
+ }
+
+ return total;
+}
+
+int GCRootImpl::PrintRootsOnThread(DWORD osThreadId)
+{
+ // Grab all object rootson the thread.
+ unsigned int refCount = 0;
+ ArrayHolder<SOSStackRefData> refs = NULL;
+
+ int total = 0;
+ bool first = true;
+ if (FAILED(::GetGCRefs(osThreadId, &refs, &refCount, NULL, NULL)))
+ {
+ ExtOut("Failed to walk thread %x\n", osThreadId);
+ return total;
+ }
+
+ // Walk each non-null root, find if it contains a path to the target,
+ // and if so print it out.
+ CLRDATA_ADDRESS prevSource = 0, prevSP = 0;
+ for (unsigned int i = 0; i < refCount; ++i)
+ {
+ if (IsInterrupt())
+ return total;
+
+ if (refs[i].Object)
+ {
+ if (mSize)
+ ClearSizeData();
+
+ RootNode *path = FindPathToTarget(TO_TADDR(refs[i].Object));
+ if (path)
+ {
+ bool reportFrame = refs[i].Source != prevSource || refs[i].StackPointer != prevSP;
+ ReportOnePath(osThreadId, refs[i], path, first, reportFrame);
+ first = false;
+ total++;
+ }
+
+ if (mSize)
+ ReportSizeInfo(osThreadId, refs[i], TO_TADDR(refs[i].Object));
+ }
+ }
+
+ return total;
+}
+
+int GCRootImpl::PrintRootsOnHandleTable(int gen)
+{
+ // Get handle data.
+ ToRelease<ISOSHandleEnum> pEnum = NULL;
+ HRESULT hr = S_OK;
+
+ if (gen == -1 || (ULONG)gen == GetMaxGeneration())
+ hr = g_sos->GetHandleEnum(&pEnum);
+ else
+ hr = g_sos->GetHandleEnumForGC(gen, &pEnum);
+
+ if (FAILED(hr))
+ {
+ ExtOut("Failed to walk the HandleTable!\n");
+ return 0;
+ }
+
+ int total = 0;
+ unsigned int fetched = 0;
+ SOSHandleData handles[8];
+
+ bool printHeader = true;
+ do
+ {
+ // Fetch more handles
+ hr = pEnum->Next(_countof(handles), handles, &fetched);
+ if (FAILED(hr))
+ {
+ ExtOut("Failed to request more handles.");
+ return total;
+ }
+
+ // Find rooting info for each handle.
+ for (unsigned int i = 0; i < fetched; ++i)
+ {
+ if (IsInterrupt())
+ return total;
+
+ // Ignore handles which aren't actually roots.
+ if (!handles[i].StrongReference)
+ continue;
+
+ // clear the size table
+ if (mAll)
+ ClearSizeData();
+
+ // Get the object the handle points to.
+ TADDR root = ReadPointer(TO_TADDR(handles[i].Handle));
+
+ // Only inspect handle if the object is non-null, and not one we've already walked.
+ if (root)
+ {
+ // Find all paths to the object and don't clean up the return value.
+ // It's tracked by mCleanupList.
+ RootNode *path = FindPathToTarget(root);
+ if (path)
+ {
+ ReportOneHandlePath(handles[i], path, printHeader);
+ printHeader = false;
+ total++;
+ }
+
+ if (mSize)
+ ReportSizeInfo(handles[i], root);
+ }
+ }
+ }
+ while (_countof(handles) == fetched);
+
+ return total;
+}
+
+GCRootImpl::RootNode *GCRootImpl::FilterRoots(RootNode *&list)
+{
+ // Filter the list of GC refs:
+ // - Remove objects that we have already considered
+ // - Check to see if we've located the target object (or an object which points to the target).
+ RootNode *curr = list;
+ RootNode *keep = NULL;
+
+ while (curr)
+ {
+ // We don't check for Control-C in this loop to avoid inconsistent data.
+ RootNode *curr_next = curr->Next;
+
+ std::unordered_map<TADDR, RootNode *>::iterator targetItr = mTargets.find(curr->Object);
+ if (targetItr != mTargets.end())
+ {
+ // We found the object we are looking for (or an object which points to it)!
+ // Return the target, propogate whether we got the target from a dependent handle.
+ targetItr->second->FromDependentHandle = curr->FromDependentHandle;
+ return targetItr->second;
+ }
+ else if (mConsidered.find(curr->Object) != mConsidered.end())
+ {
+ curr->Remove(list);
+
+ DeleteNode(curr);
+ }
+
+ curr = curr_next;
+ }
+
+ return NULL;
+}
+
+/* This is the core of the GCRoot algorithm. It is:
+ * 1. Start with a list of "targets" (objects we are trying to find the roots for) and a root
+ * in the process.
+ * 2. Let the root be "curr".
+ * 3. Find all objects curr points to and place them in curr->GCRefs (a linked list).
+ * 4. Walk curr->GCRefs. If we find any of the "targets", return the current path. If not,
+ * filter out any objects we've already considered (the mConsidered set).
+ * 5. Look at curr->GCRefs:
+ * 5a. If curr->GCRefs is NULL then we have walked all references of this object. Pop "curr"
+ * from the current path (curr = curr->Prev). If curr is NULL, we walked all objects and
+ * didn't find a target, return NULL. If curr is non-null, goto 5.
+ * 5b. If curr->GCRefs is non-NULL, pop one entry and push it onto the path (that is:
+ * curr->Next = curr->GCRefs; curr = curr->Next; curr->GCRefs = curr->GCRefs->Next)
+ * 6. Goto 3.
+ */
+GCRootImpl::RootNode *GCRootImpl::FindPathToTarget(TADDR root)
+{
+ // Early out. We may have already looked at this object.
+ std::unordered_map<TADDR, RootNode *>::iterator targetItr = mTargets.find(root);
+ if (targetItr != mTargets.end())
+ return targetItr->second;
+ else if (mConsidered.find(root) != mConsidered.end())
+ return NULL;
+
+ // Add obj as a considered node (since we are considering it now).
+ mConsidered.insert(root);
+
+ // Create path.
+ RootNode *path = NewNode(root);
+
+ RootNode *curr = path;
+ while (curr)
+ {
+ if (IsInterrupt())
+ return NULL;
+
+ // If this is a new reference we are walking, we haven't filled the list of objects
+ // this one points to. Update that first.
+ if (!curr->FilledRefs)
+ {
+ // Get the list of GC refs.
+ curr->GCRefs = GetGCRefs(path, curr);
+
+ // Filter the refs to remove objects we've already inspected.
+ RootNode *foundTarget = FilterRoots(curr->GCRefs);
+
+ // If we've found the target, great! Return the path to the target.
+ if (foundTarget)
+ {
+ // Link the current to the target.
+ curr->Next = foundTarget;
+ foundTarget->Prev = curr;
+
+ // If the user requested all paths, set each node in the path to be a target.
+ // Normally, we don't consider a node we've already seen, which means if we don't
+ // get a *completely* unique path, it's not printed out. By adding each of the
+ // nodes in the paths we find as potential targets, it prints out *every* path
+ // to the target, including ones with duplicate nodes. This is much slower.
+ if (mAll)
+ {
+ RootNode *tmp = path;
+
+ while (tmp)
+ {
+ if (mTargets.find(tmp->Object) != mTargets.end())
+ break;
+
+ mTargets[tmp->Object] = tmp;
+ tmp = tmp->Next;
+ }
+ }
+
+ return path;
+ }
+ }
+
+ // We have filled the references, now walk them depth-first.
+ if (curr->GCRefs)
+ {
+ RootNode *next = curr->GCRefs;
+ curr->GCRefs = next->Next;
+
+ if (mConsidered.find(next->Object) != mConsidered.end())
+ {
+ // Whoops. This object was considered deeper down the tree, so we
+ // don't need to do it again. Delete this node and continue looping.
+ DeleteNode(next);
+ }
+ else
+ {
+ // Clear associations.
+ if (curr->GCRefs)
+ curr->GCRefs->Prev = NULL;
+
+ next->Next = NULL;
+
+ // Link curr and next, make next the current node.
+ curr->Next = next;
+ next->Prev = curr;
+ curr = next;
+
+ // Finally, insert the current object into the considered set.
+ mConsidered.insert(curr->Object);
+ // Now the next iteration will operate on "next".
+ }
+ }
+ else
+ {
+ // This object has no more GCRefs. We now need to "pop" it from the current path.
+ RootNode *tmp = curr;
+ curr = curr->Prev;
+ DeleteNode(tmp);
+ }
+ }
+
+ return NULL;
+}
+
+
+GCRootImpl::RootNode *GCRootImpl::GetGCRefs(RootNode *path, RootNode *node)
+{
+ // If this node doesn't have the method table ready, fill it out
+ TADDR obj = node->Object;
+ if (!node->MTInfo)
+ {
+ TADDR mt = ReadPointerCached(obj);
+ MTInfo *mtInfo = GetMTInfo(mt);
+ node->MTInfo = mtInfo;
+ }
+
+ node->FilledRefs = true;
+
+ // MTInfo can be null if we encountered an error reading out of the target
+ // process, just early out here as if it has no references.
+ if (!node->MTInfo)
+ return NULL;
+
+ // Only calculate the size if we need it.
+ size_t objSize = 0;
+ if (mSize || node->MTInfo->ContainsPointers)
+ {
+ objSize = GetSizeOfObject(obj, node->MTInfo);
+
+ // Update object size list, if requested.
+ if (mSize)
+ {
+ mSizes[obj] = 0;
+
+ while (path)
+ {
+ mSizes[path->Object] += objSize;
+ path = path->Next;
+ }
+ }
+ }
+
+ // Early out: If the object doesn't contain any pointers, return.
+ if (!node->MTInfo->ContainsPointers)
+ return NULL;
+
+ // Make sure we have the object's data in the cache.
+ mCache.EnsureRangeInCache(obj, (unsigned int)objSize);
+
+ // Storage for the gc refs.
+ RootNode *refs = NewNode();
+ RootNode *curr = refs;
+
+ // Walk the GCDesc, fill "refs" with non-null references.
+ CGCDesc *gcdesc = node->MTInfo->GCDesc;
+ bool aovc = node->MTInfo->ArrayOfVC;
+ for (sos::RefIterator itr(obj, gcdesc, aovc, &mCache); itr; ++itr)
+ {
+ if (*itr)
+ {
+ curr->Next = NewNode(*itr);
+ curr->Next->Prev = curr;
+ curr = curr->Next;
+ }
+ }
+
+ // Add edges from dependent handles.
+ std::unordered_map<TADDR, std::list<TADDR>>::iterator itr = mDependentHandleMap.find(obj);
+ if (itr != mDependentHandleMap.end())
+ {
+ for (std::list<TADDR>::iterator litr = itr->second.begin(); litr != itr->second.end(); ++litr)
+ {
+ curr->Next = NewNode(*litr, NULL, true);
+ curr->Next->Prev = curr;
+ curr = curr->Next;
+ }
+ }
+
+ // The gcrefs actually start on refs->Next.
+ curr = refs;
+ refs = refs->Next;
+ DeleteNode(curr);
+
+ return refs;
+}
+
+DWORD GCRootImpl::GetComponents(TADDR obj, TADDR mt)
+{
+ // Get the number of components in the object (for arrays and such).
+ DWORD Value = 0;
+
+ // If we fail to read out the number of components, let's assume 0 so we don't try to
+ // read further data from the object.
+ if (!mCache.Read(obj + sizeof(TADDR), &Value, false))
+ return 0;
+
+ // The component size on a String does not contain the trailing NULL character,
+ // so we must add that ourselves.
+ if(TO_TADDR(g_special_usefulGlobals.StringMethodTable) == mt)
+ return Value+1;
+
+ return Value;
+}
+
+// Get the size of the object.
+size_t GCRootImpl::GetSizeOfObject(TADDR obj, MTInfo *info)
+{
+ size_t res = info->BaseSize;
+
+ if (info->ComponentSize)
+ {
+ // this is an array, so the size has to include the size of the components. We read the number
+ // of components from the target and multiply by the component size to get the size.
+ DWORD components = GetComponents(obj, info->MethodTable);
+ res += info->ComponentSize * components;
+ }
+
+#ifdef _TARGET_WIN64_
+ // On x64 we do an optimization to save 4 bytes in almost every string we create, so
+ // pad to min object size if necessary.
+ if (res < min_obj_size)
+ res = min_obj_size;
+#endif // _TARGET_WIN64_
+
+ return (res > 0x10000) ? AlignLarge(res) : Align(res);
+}
+
+GCRootImpl::MTInfo *GCRootImpl::GetMTInfo(TADDR mt)
+{
+ // Remove lower bits in case we are in mark phase
+ mt &= ~3;
+
+ // Do we already have this MethodTable?
+ std::unordered_map<TADDR, MTInfo *>::iterator itr = mMTs.find(mt);
+
+ if (itr != mMTs.end())
+ return itr->second;
+
+ MTInfo *curr = new MTInfo;
+ curr->MethodTable = mt;
+
+ // Get Base/Component size.
+ DacpMethodTableData dmtd;
+
+ if (dmtd.Request(g_sos, mt) != S_OK)
+ {
+ delete curr;
+ return NULL;
+ }
+
+ // Fill out size info.
+ curr->BaseSize = (size_t)dmtd.BaseSize;
+ curr->ComponentSize = (size_t)dmtd.ComponentSize;
+ curr->ContainsPointers = dmtd.bContainsPointers ? true : false;
+
+ // If this method table contains pointers, fill out and cache the GCDesc.
+ if (curr->ContainsPointers)
+ {
+ int nEntries;
+
+ if (FAILED(MOVE(nEntries, mt-sizeof(TADDR))))
+ {
+ ExtOut("Failed to request number of entries.");
+ delete curr;
+ return NULL;
+ }
+
+ if (nEntries < 0)
+ {
+ curr->ArrayOfVC = true;
+ nEntries = -nEntries;
+ }
+ else
+ {
+ curr->ArrayOfVC = false;
+ }
+
+ size_t nSlots = 1 + nEntries * sizeof(CGCDescSeries)/sizeof(TADDR);
+ curr->Buffer = new TADDR[nSlots];
+
+ if (curr->Buffer == NULL)
+ {
+ ReportOOM();
+ delete curr;
+ return NULL;
+ }
+
+ if (FAILED(g_ExtData->ReadVirtual(TO_CDADDR(mt - nSlots*sizeof(TADDR)), curr->Buffer, (ULONG)(nSlots*sizeof(TADDR)), NULL)))
+ {
+ ExtOut("Failed to read GCDesc for MethodTable %p.\n", SOS_PTR(mt));
+ delete curr;
+ return NULL;
+ }
+
+ // Construct the GCDesc map and series.
+ curr->GCDesc = (CGCDesc *)(curr->Buffer+nSlots);
+ }
+
+ mMTs[mt] = curr;
+ return curr;
+}
+
+
+TADDR GCRootImpl::ReadPointer(TADDR location)
+{
+ // Reads a pointer from the cache, but doesn't update the cache if it wasn't in it.
+ TADDR obj = NULL;
+ bool res = mCache.Read(location, &obj, false);
+
+ if (!res)
+ return NULL;
+
+ return obj;
+}
+
+TADDR GCRootImpl::ReadPointerCached(TADDR location)
+{
+ // Reads a pointer from the cache, but updates the cache if it wasn't in it.
+ TADDR obj = NULL;
+ bool res = mCache.Read(location, &obj, true);
+
+ if (!res)
+ return NULL;
+
+ return obj;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+UINT FindAllPinnedAndStrong(DWORD_PTR handlearray[], UINT arraySize)
+{
+ unsigned int fetched = 0;
+ SOSHandleData data[64];
+ UINT pos = 0;
+
+ // We do not call GetHandleEnumByType here with a list of strong handles since we would be
+ // statically setting the list of strong handles, which could change in a future release.
+ // Instead we rely on the dac to provide whether a handle is strong or not.
+ ToRelease<ISOSHandleEnum> handles;
+ HRESULT hr = g_sos->GetHandleEnum(&handles);
+ if (FAILED(hr))
+ {
+ // This should basically never happen unless there's an OOM.
+ ExtOut("Failed to enumerate GC handles. HRESULT=%x.\n", hr);
+ return 0;
+ }
+
+ do
+ {
+ hr = handles->Next(_countof(data), data, &fetched);
+
+ if (FAILED(hr))
+ {
+ ExtOut("Failed to enumerate GC handles. HRESULT=%x.\n", hr);
+ break;
+ }
+
+ for (unsigned int i = 0; i < fetched; ++i)
+ {
+ if (pos >= arraySize)
+ {
+ ExtOut("Buffer overflow while enumerating handles.\n");
+ return pos;
+ }
+
+ if (data[i].StrongReference)
+ {
+ handlearray[pos++] = (DWORD_PTR)data[i].Handle;
+ }
+ }
+ } while (fetched == _countof(data));
+
+ return pos;
+}
+
+
+
+void PrintNotReachableInRange(TADDR rngStart, TADDR rngEnd, BOOL bExcludeReadyForFinalization, HeapStat* hpstat, BOOL bShort)
+{
+ GCRootImpl gcroot;
+ const std::unordered_set<TADDR> &liveObjs = gcroot.GetLiveObjects(bExcludeReadyForFinalization == TRUE);
+
+ LinearReadCache cache(512);
+ cache.EnsureRangeInCache(rngStart, (unsigned int)(rngEnd-rngStart));
+
+ for (TADDR p = rngStart; p < rngEnd; p += sizeof(TADDR))
+ {
+ if (IsInterrupt())
+ break;
+
+ TADDR header = 0;
+ TADDR obj = 0;
+ TADDR taddrMT = 0;
+
+ bool read = cache.Read(p-sizeof(SIZEOF_OBJHEADER), &header);
+ read = read && cache.Read(p, &obj);
+ if (read && ((header & BIT_SBLK_FINALIZER_RUN) == 0) && liveObjs.find(obj) == liveObjs.end())
+ {
+ if (bShort)
+ {
+ DMLOut("%s\n", DMLObject(obj));
+ }
+ else
+ {
+ DMLOut("%s ", DMLObject(obj));
+ if (SUCCEEDED(GetMTOfObject(obj, &taddrMT)) && taddrMT)
+ {
+ size_t s = ObjectSize(obj);
+ if (hpstat)
+ {
+ hpstat->Add(taddrMT, (DWORD)s);
+ }
+ }
+ }
+ }
+ }
+
+ if (!bShort)
+ ExtOut("\n");
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Some defines for cards taken from gc code
+//
+#define card_word_width ((size_t)32)
+
+//
+// The value of card_size is determined empirically according to the average size of an object
+// In the code we also rely on the assumption that one card_table entry (DWORD) covers an entire os page
+//
+#if defined (_TARGET_WIN64_)
+#define card_size ((size_t)(2*DT_OS_PAGE_SIZE/card_word_width))
+#else
+#define card_size ((size_t)(DT_OS_PAGE_SIZE/card_word_width))
+#endif //_TARGET_WIN64_
+
+// so card_size = 128 on x86, 256 on x64
+
+inline
+size_t card_word (size_t card)
+{
+ return card / card_word_width;
+}
+
+inline
+unsigned card_bit (size_t card)
+{
+ return (unsigned)(card % card_word_width);
+}
+
+inline
+size_t card_of ( BYTE* object)
+{
+ return (size_t)(object) / card_size;
+}
+
+BOOL CardIsSet(const DacpGcHeapDetails &heap, TADDR objAddr)
+{
+ // The card table has to be translated to look at the refcount, etc.
+ // g_card_table[card_word(card_of(g_lowest_address))].
+
+ TADDR card_table = TO_TADDR(heap.card_table);
+ card_table = card_table + card_word(card_of((BYTE *)heap.lowest_address))*sizeof(DWORD);
+
+ do
+ {
+ TADDR card_table_lowest_addr;
+ TADDR card_table_next;
+
+ if (MOVE(card_table_lowest_addr, ALIGN_DOWN(card_table, 0x1000) + sizeof(PVOID)) != S_OK)
+ {
+ ExtErr("Error getting card table lowest address\n");
+ return FALSE;
+ }
+
+ if (MOVE(card_table_next, card_table - sizeof(PVOID)) != S_OK)
+ {
+ ExtErr("Error getting next card table\n");
+ return FALSE;
+ }
+
+ size_t card = (objAddr - card_table_lowest_addr) / card_size;
+ DWORD value;
+ if (MOVE(value, card_table + card_word(card)*sizeof(DWORD)) != S_OK)
+ {
+ ExtErr("Error reading card bits\n");
+ return FALSE;
+ }
+
+ if (value & 1<<card_bit(card))
+ return TRUE;
+
+ card_table = card_table_next;
+ }
+ while(card_table);
+
+ return FALSE;
+}
+
+BOOL NeedCard(TADDR parent, TADDR child)
+{
+ int iChildGen = g_snapshot.GetGeneration(child);
+
+ if (iChildGen == 2)
+ return FALSE;
+
+ int iParentGen = g_snapshot.GetGeneration(parent);
+
+ return (iChildGen < iParentGen);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Some defines for mark_array taken from gc code
+//
+
+#define mark_bit_pitch 8
+#define mark_word_width 32
+#define mark_word_size (mark_word_width * mark_bit_pitch)
+#define heap_segment_flags_swept 16
+
+inline
+size_t mark_bit_bit_of(CLRDATA_ADDRESS add)
+{
+ return (size_t)((add / mark_bit_pitch) % mark_word_width);
+}
+
+inline
+size_t mark_word_of(CLRDATA_ADDRESS add)
+{
+ return (size_t)(add / mark_word_size);
+}
+
+inline BOOL mark_array_marked(const DacpGcHeapDetails &heap, CLRDATA_ADDRESS add)
+{
+
+ DWORD entry = 0;
+ HRESULT hr = MOVE(entry, heap.mark_array + sizeof(DWORD) * mark_word_of(add));
+
+ if (FAILED(hr))
+ ExtOut("Failed to read card table entry.\n");
+
+ return entry & (1 << mark_bit_bit_of(add));
+}
+
+BOOL background_object_marked(const DacpGcHeapDetails &heap, CLRDATA_ADDRESS o)
+{
+ BOOL m = TRUE;
+
+ if ((o >= heap.background_saved_lowest_address) && (o < heap.background_saved_highest_address))
+ m = mark_array_marked(heap, o);
+
+ return m;
+}
+
+BOOL fgc_should_consider_object(const DacpGcHeapDetails &heap,
+ CLRDATA_ADDRESS o,
+ const DacpHeapSegmentData &seg,
+ BOOL consider_bgc_mark_p,
+ BOOL check_current_sweep_p,
+ BOOL check_saved_sweep_p)
+{
+ // the logic for this function must be kept in sync with the analogous function in gc.cpp
+ BOOL no_bgc_mark_p = FALSE;
+
+ if (consider_bgc_mark_p)
+ {
+ if (check_current_sweep_p && (o < heap.next_sweep_obj))
+ {
+ no_bgc_mark_p = TRUE;
+ }
+
+ if (!no_bgc_mark_p)
+ {
+ if(check_saved_sweep_p && (o >= heap.saved_sweep_ephemeral_start))
+ {
+ no_bgc_mark_p = TRUE;
+ }
+
+ if (!check_saved_sweep_p)
+ {
+ CLRDATA_ADDRESS background_allocated = seg.background_allocated;
+ if (o >= background_allocated)
+ {
+ no_bgc_mark_p = TRUE;
+ }
+ }
+ }
+ }
+ else
+ {
+ no_bgc_mark_p = TRUE;
+ }
+
+ return no_bgc_mark_p ? TRUE : background_object_marked(heap, o);
+}
+
+enum c_gc_state
+{
+ c_gc_state_marking,
+ c_gc_state_planning,
+ c_gc_state_free
+};
+
+inline BOOL in_range_for_segment(const DacpHeapSegmentData &seg, CLRDATA_ADDRESS addr)
+{
+ return (addr >= seg.mem) && (addr < seg.reserved);
+}
+
+void should_check_bgc_mark(const DacpGcHeapDetails &heap,
+ const DacpHeapSegmentData &seg,
+ BOOL* consider_bgc_mark_p,
+ BOOL* check_current_sweep_p,
+ BOOL* check_saved_sweep_p)
+{
+ // the logic for this function must be kept in sync with the analogous function in gc.cpp
+ *consider_bgc_mark_p = FALSE;
+ *check_current_sweep_p = FALSE;
+ *check_saved_sweep_p = FALSE;
+
+ if (heap.current_c_gc_state == c_gc_state_planning)
+ {
+ // We are doing the next_sweep_obj comparison here because we have yet to
+ // turn on the swept flag for the segment but in_range_for_segment will return
+ // FALSE if the address is the same as reserved.
+ if ((seg.flags & heap_segment_flags_swept) || (heap.next_sweep_obj == seg.reserved))
+ {
+ // this seg was already swept.
+ }
+ else
+ {
+ *consider_bgc_mark_p = TRUE;
+
+ if (seg.segmentAddr == heap.saved_sweep_ephemeral_seg)
+ {
+ *check_saved_sweep_p = TRUE;
+ }
+
+ if (in_range_for_segment(seg, heap.next_sweep_obj))
+ {
+ *check_current_sweep_p = TRUE;
+ }
+ }
+ }
+}
+
+// TODO: FACTOR TOGETHER THE OBJECT MEMBER WALKING CODE FROM
+// TODO: VerifyObjectMember(), GetListOfRefs(), HeapTraverser::PrintRefs()
+BOOL VerifyObjectMember(const DacpGcHeapDetails &heap, DWORD_PTR objAddr)
+{
+ BOOL ret = TRUE;
+ BOOL bCheckCard = TRUE;
+ size_t size = 0;
+ {
+ DWORD_PTR dwAddrCard = objAddr;
+ while (dwAddrCard < objAddr + size)
+ {
+ if (CardIsSet(heap, dwAddrCard))
+ {
+ bCheckCard = FALSE;
+ break;
+ }
+ dwAddrCard += card_size;
+ }
+
+ if (bCheckCard)
+ {
+ dwAddrCard = objAddr + size - 2*sizeof(PVOID);
+ if (CardIsSet(heap, dwAddrCard))
+ {
+ bCheckCard = FALSE;
+ }
+ }
+ }
+
+ for (sos::RefIterator itr(TO_TADDR(objAddr)); itr; ++itr)
+ {
+ TADDR dwAddr1 = (DWORD_PTR)*itr;
+ if (dwAddr1)
+ {
+ TADDR dwChild = dwAddr1;
+ // Try something more efficient than IsObject here. Is the methodtable valid?
+ size_t s;
+ BOOL bPointers;
+ TADDR dwAddrMethTable;
+ if (FAILED(GetMTOfObject(dwAddr1, &dwAddrMethTable)) ||
+ (GetSizeEfficient(dwAddr1, dwAddrMethTable, FALSE, s, bPointers) == FALSE))
+ {
+ DMLOut("object %s: bad member %p at %p\n", DMLObject(objAddr), SOS_PTR(dwAddr1), SOS_PTR(itr.GetOffset()));
+ ret = FALSE;
+ }
+
+ if (IsMTForFreeObj(dwAddrMethTable))
+ {
+ DMLOut("object %s contains free object %p at %p\n", DMLObject(objAddr),
+ SOS_PTR(dwAddr1), SOS_PTR(objAddr+itr.GetOffset()));
+ ret = FALSE;
+ }
+
+ // verify card table
+ if (bCheckCard && NeedCard(objAddr+itr.GetOffset(), dwAddr1))
+ {
+ DMLOut("object %s:%s missing card_table entry for %p\n",
+ DMLObject(objAddr), (dwChild == dwAddr1) ? "" : " maybe",
+ SOS_PTR(objAddr+itr.GetOffset()));
+ ret = FALSE;
+ }
+ }
+ }
+
+ return ret;
+}
+
+// search for can_verify_deep in gc.cpp for examples of how these functions are used.
+BOOL VerifyObject(const DacpGcHeapDetails &heap, const DacpHeapSegmentData &seg, DWORD_PTR objAddr, DWORD_PTR MTAddr, size_t objSize,
+ BOOL bVerifyMember)
+{
+ if (IsMTForFreeObj(MTAddr))
+ {
+ return TRUE;
+ }
+
+ if (objSize < min_obj_size)
+ {
+ DMLOut("object %s: size %d too small\n", DMLObject(objAddr), objSize);
+ return FALSE;
+ }
+
+ // If we requested to verify the object's members, the GC may be in a state where that's not possible.
+ // Here we check to see if the object in question needs to have its members updated. If so, we turn off
+ // verification for the object.
+ if (bVerifyMember)
+ {
+ BOOL consider_bgc_mark = FALSE, check_current_sweep = FALSE, check_saved_sweep = FALSE;
+ should_check_bgc_mark(heap, seg, &consider_bgc_mark, &check_current_sweep, &check_saved_sweep);
+ bVerifyMember = fgc_should_consider_object(heap, objAddr, seg, consider_bgc_mark, check_current_sweep, check_saved_sweep);
+ }
+
+ return bVerifyMember ? VerifyObjectMember(heap, objAddr) : TRUE;
+}
+
+
+BOOL FindSegment(const DacpGcHeapDetails &heap, DacpHeapSegmentData &seg, CLRDATA_ADDRESS addr)
+{
+ CLRDATA_ADDRESS dwAddrSeg = heap.generation_table[GetMaxGeneration()].start_segment;
+
+ // Request the inital segment.
+ if (seg.Request(g_sos, dwAddrSeg, heap) != S_OK)
+ {
+ ExtOut("Error requesting heap segment %p.\n", SOS_PTR(dwAddrSeg));
+ return FALSE;
+ }
+
+ // Loop while the object is not in range of the segment.
+ while (addr < TO_TADDR(seg.mem) ||
+ addr >= (dwAddrSeg == heap.ephemeral_heap_segment ? heap.alloc_allocated : TO_TADDR(seg.allocated)))
+ {
+ // get the next segment
+ dwAddrSeg = seg.next;
+
+ // We reached the last segment without finding the object.
+ if (dwAddrSeg == NULL)
+ return FALSE;
+
+ if (seg.Request(g_sos, dwAddrSeg, heap) != S_OK)
+ {
+ ExtOut("Error requesting heap segment %p.\n", SOS_PTR(dwAddrSeg));
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL VerifyObject(const DacpGcHeapDetails &heap, DWORD_PTR objAddr, DWORD_PTR MTAddr, size_t objSize, BOOL bVerifyMember)
+{
+ // This is only used by the other VerifyObject function if bVerifyMember is true,
+ // so we only intialize it if we need it for verifying object members.
+ DacpHeapSegmentData seg;
+
+ if (bVerifyMember)
+ {
+ // if we fail to find the segment, we cannot verify the object's members
+ bVerifyMember = FindSegment(heap, seg, objAddr);
+ }
+
+ return VerifyObject(heap, seg, objAddr, MTAddr, objSize, bVerifyMember);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+typedef void (*TYPETREEVISIT)(size_t methodTable, size_t ID, LPVOID token);
+
+// TODO remove this. MethodTableCache already maps method tables to
+// various information. We don't need TypeTree to do this too.
+// Straightfoward to do, but low priority.
+class TypeTree
+{
+private:
+ size_t methodTable;
+ size_t ID;
+ TypeTree *pLeft;
+ TypeTree *pRight;
+
+public:
+ TypeTree(size_t MT) : methodTable(MT),ID(0),pLeft(NULL),pRight(NULL) { }
+
+ BOOL isIn(size_t MT, size_t *pID)
+ {
+ TypeTree *pCur = this;
+
+ while (pCur)
+ {
+ if (MT == pCur->methodTable)
+ {
+ if (pID)
+ *pID = pCur->ID;
+ return TRUE;
+ }
+ else if (MT < pCur->methodTable)
+ pCur = pCur->pLeft;
+ else
+ pCur = pCur->pRight;
+ }
+
+ return FALSE;
+ }
+
+ BOOL insert(size_t MT)
+ {
+ TypeTree *pCur = this;
+
+ while (pCur)
+ {
+ if (MT == pCur->methodTable)
+ return TRUE;
+ else if ((MT < pCur->methodTable))
+ {
+ if (pCur->pLeft)
+ pCur = pCur->pLeft;
+ else
+ break;
+ }
+ else if (pCur->pRight)
+ pCur = pCur->pRight;
+ else
+ break;
+ }
+
+ // If we got here, we need to append at the current node.
+ TypeTree *pNewNode = new TypeTree(MT);
+ if (pNewNode == NULL)
+ return FALSE;
+
+ if (MT < pCur->methodTable)
+ pCur->pLeft = pNewNode;
+ else
+ pCur->pRight = pNewNode;
+
+ return TRUE;
+ }
+
+ static void destroy(TypeTree *pStart)
+ {
+ TypeTree *pCur = pStart;
+
+ if (pCur)
+ {
+ destroy(pCur->pLeft);
+ destroy(pCur->pRight);
+ delete [] pCur;
+ }
+ }
+
+ static void visit_inorder(TypeTree *pStart, TYPETREEVISIT pFunc, LPVOID token)
+ {
+ TypeTree *pCur = pStart;
+
+ if (pCur)
+ {
+ visit_inorder(pCur->pLeft, pFunc, token);
+ pFunc (pCur->methodTable, pCur->ID, token);
+ visit_inorder(pCur->pRight, pFunc, token);
+ }
+ }
+
+ static void setTypeIDs(TypeTree *pStart, size_t *pCurID)
+ {
+ TypeTree *pCur = pStart;
+
+ if (pCur)
+ {
+ setTypeIDs(pCur->pLeft, pCurID);
+ pCur->ID = *pCurID;
+ (*pCurID)++;
+ setTypeIDs(pCur->pRight, pCurID);
+ }
+ }
+
+};
+
+///////////////////////////////////////////////////////////////////////////////
+//
+
+HeapTraverser::HeapTraverser(bool verify)
+{
+ m_format = 0;
+ m_file = NULL;
+ m_objVisited = 0;
+ m_pTypeTree = NULL;
+ m_curNID = 1;
+ m_verify = verify;
+}
+
+HeapTraverser::~HeapTraverser()
+{
+ if (m_pTypeTree) {
+ TypeTree::destroy(m_pTypeTree);
+ m_pTypeTree = NULL;
+ }
+}
+
+BOOL HeapTraverser::Initialize()
+{
+ if (!GCHeapsTraverse (HeapTraverser::GatherTypes, this, m_verify))
+ {
+ ExtOut("Error during heap traverse\n");
+ return FALSE;
+ }
+
+ GCRootImpl::GetDependentHandleMap(mDependentHandleMap);
+
+ size_t startID = 1;
+ TypeTree::setTypeIDs(m_pTypeTree, &startID);
+
+ return TRUE;
+}
+
+BOOL HeapTraverser::CreateReport (FILE *fp, int format)
+{
+ if (fp == NULL || (format!=FORMAT_XML && format != FORMAT_CLRPROFILER))
+ {
+ return FALSE;
+ }
+
+ m_file = fp;
+ m_format = format;
+
+ PrintSection(TYPE_START,TRUE);
+
+ PrintSection(TYPE_TYPES,TRUE);
+ TypeTree::visit_inorder(m_pTypeTree, HeapTraverser::PrintOutTree, this);
+ PrintSection(TYPE_TYPES,FALSE);
+
+ ExtOut("tracing roots...\n");
+ PrintSection(TYPE_ROOTS,TRUE);
+ PrintRootHead();
+
+ TraceHandles();
+ FindGCRootOnStacks();
+
+ PrintRootTail();
+ PrintSection(TYPE_ROOTS,FALSE);
+
+ // now print type tree
+ PrintSection(TYPE_OBJECTS,TRUE);
+ ExtOut("\nWalking heap...\n");
+ m_objVisited = 0; // for UI updates
+ GCHeapsTraverse (HeapTraverser::PrintHeap, this, FALSE); // Never verify on the second pass
+ PrintSection(TYPE_OBJECTS,FALSE);
+
+ PrintSection(TYPE_START,FALSE);
+
+ m_file = NULL;
+ return TRUE;
+}
+
+void HeapTraverser::insert(size_t mTable)
+{
+ if (m_pTypeTree == NULL)
+ {
+ m_pTypeTree = new TypeTree(mTable);
+ if (m_pTypeTree == NULL)
+ {
+ ReportOOM();
+ return;
+ }
+ }
+ else
+ {
+ m_pTypeTree->insert(mTable);
+ }
+}
+
+size_t HeapTraverser::getID(size_t mTable)
+{
+ if (m_pTypeTree == NULL)
+ {
+ return 0;
+ }
+ // IDs start at 1, so we can return 0 if not found.
+ size_t ret;
+ if (m_pTypeTree->isIn(mTable,&ret))
+ {
+ return ret;
+ }
+
+ return 0;
+}
+
+#ifndef FEATURE_PAL
+void replace(std::wstring &str, const WCHAR *toReplace, const WCHAR *replaceWith)
+{
+ const size_t replaceLen = _wcslen(toReplace);
+ const size_t replaceWithLen = _wcslen(replaceWith);
+
+ size_t i = str.find(toReplace);
+ while (i != std::wstring::npos)
+ {
+ str.replace(i, replaceLen, replaceWith);
+ i = str.find(toReplace, i + replaceWithLen);
+ }
+}
+#endif
+
+void HeapTraverser::PrintType(size_t ID,LPCWSTR name)
+{
+ if (m_format==FORMAT_XML)
+ {
+#ifndef FEATURE_PAL
+ // Sanitize name based on XML spec.
+ std::wstring wname = name;
+ replace(wname, W("&"), W("&amp;"));
+ replace(wname, W("\""), W("&quot;"));
+ replace(wname, W("'"), W("&apos;"));
+ replace(wname, W("<"), W("&lt;"));
+ replace(wname, W(">"), W("&gt;"));
+ name = wname.c_str();
+#endif
+ fprintf(m_file,
+ "<type id=\"%d\" name=\"%S\"/>\n",
+ ID, name);
+ }
+ else if (m_format==FORMAT_CLRPROFILER)
+ {
+ fprintf(m_file,
+ "t %d 0 %S\n",
+ ID,name);
+ }
+}
+
+void HeapTraverser::PrintObjectHead(size_t objAddr,size_t typeID,size_t Size)
+{
+ if (m_format==FORMAT_XML)
+ {
+ fprintf(m_file,
+ "<object address=\"0x%p\" typeid=\"%d\" size=\"%d\">\n",
+ (PBYTE)objAddr,typeID, Size);
+ }
+ else if (m_format==FORMAT_CLRPROFILER)
+ {
+ fprintf(m_file,
+ "n %d 1 %d %d\n",
+ m_curNID,typeID,Size);
+
+ fprintf(m_file,
+ "! 1 0x%p %d\n",
+ (PBYTE)objAddr,m_curNID);
+
+ m_curNID++;
+
+ fprintf(m_file,
+ "o 0x%p %d %d ",
+ (PBYTE)objAddr,typeID,Size);
+ }
+}
+
+void HeapTraverser::PrintObjectMember(size_t memberValue, bool dependentHandle)
+{
+ if (m_format==FORMAT_XML)
+ {
+ fprintf(m_file,
+ " <member address=\"0x%p\"%s/>\n",
+ (PBYTE)memberValue, dependentHandle ? " dependentHandle=\"1\"" : "");
+ }
+ else if (m_format==FORMAT_CLRPROFILER)
+ {
+ fprintf(m_file,
+ " 0x%p",
+ (PBYTE)memberValue);
+ }
+}
+
+void HeapTraverser::PrintObjectTail()
+{
+ if (m_format==FORMAT_XML)
+ {
+ fprintf(m_file,
+ "</object>\n");
+ }
+ else if (m_format==FORMAT_CLRPROFILER)
+ {
+ fprintf(m_file,
+ "\n");
+ }
+}
+
+void HeapTraverser::PrintRootHead()
+{
+ if (m_format==FORMAT_CLRPROFILER)
+ {
+ fprintf(m_file,
+ "r ");
+ }
+}
+
+void HeapTraverser::PrintRoot(LPCWSTR kind,size_t Value)
+{
+ if (m_format==FORMAT_XML)
+ {
+ fprintf(m_file,
+ "<root kind=\"%S\" address=\"0x%p\"/>\n",
+ kind,
+ (PBYTE)Value);
+ }
+ else if (m_format==FORMAT_CLRPROFILER)
+ {
+ fprintf(m_file,
+ "0x%p ",
+ (PBYTE)Value);
+ }
+}
+
+void HeapTraverser::PrintRootTail()
+{
+ if (m_format==FORMAT_CLRPROFILER)
+ {
+ fprintf(m_file,
+ "\n");
+ }
+}
+
+void HeapTraverser::PrintSection(int Type,BOOL bOpening)
+{
+ const char *const pTypes[] = {"<gcheap>","<types>","<roots>","<objects>"};
+ const char *const pTypeEnds[] = {"</gcheap>","</types>","</roots>","</objects>"};
+
+ if (m_format==FORMAT_XML)
+ {
+ if ((Type >= 0) && (Type < TYPE_HIGHEST))
+ {
+ fprintf(m_file,"%s\n",bOpening ? pTypes[Type] : pTypeEnds[Type]);
+ }
+ else
+ {
+ ExtOut ("INVALID TYPE %d\n", Type);
+ }
+ }
+ else if (m_format==FORMAT_CLRPROFILER)
+ {
+ if ((Type == TYPE_START) && !bOpening) // a final newline is needed
+ {
+ fprintf(m_file,"\n");
+ }
+ }
+}
+
+void HeapTraverser::FindGCRootOnStacks()
+{
+ ArrayHolder<DWORD_PTR> threadList = NULL;
+ int numThreads = 0;
+
+ // GetThreadList calls ReportOOM so we don't need to do that here.
+ HRESULT hr = GetThreadList(&threadList, &numThreads);
+ if (FAILED(hr) || !threadList)
+ {
+ ExtOut("Failed to enumerate threads in the process.\n");
+ return;
+ }
+
+ int total = 0;
+ DacpThreadData vThread;
+ for (int i = 0; i < numThreads; i++)
+ {
+ if (FAILED(vThread.Request(g_sos, threadList[i])))
+ continue;
+
+ if (vThread.osThreadId)
+ {
+ unsigned int refCount = 0;
+ ArrayHolder<SOSStackRefData> refs = NULL;
+
+ if (FAILED(::GetGCRefs(vThread.osThreadId, &refs, &refCount, NULL, NULL)))
+ {
+ ExtOut("Failed to walk thread %x\n", vThread.osThreadId);
+ continue;
+ }
+
+ for (unsigned int i = 0; i < refCount; ++i)
+ if (refs[i].Object)
+ PrintRoot(W("stack"), TO_TADDR(refs[i].Object));
+ }
+ }
+
+}
+
+
+/* static */ void HeapTraverser::PrintOutTree(size_t methodTable, size_t ID,
+ LPVOID token)
+{
+ HeapTraverser *pHolder = (HeapTraverser *) token;
+ NameForMT_s(methodTable, g_mdName, mdNameLen);
+ pHolder->PrintType(ID,g_mdName);
+}
+
+
+/* static */ void HeapTraverser::PrintHeap(DWORD_PTR objAddr,size_t Size,
+ DWORD_PTR methodTable, LPVOID token)
+{
+ if (!IsMTForFreeObj (methodTable))
+ {
+ HeapTraverser *pHolder = (HeapTraverser *) token;
+ pHolder->m_objVisited++;
+ size_t ID = pHolder->getID(methodTable);
+
+ pHolder->PrintObjectHead(objAddr, ID, Size);
+ pHolder->PrintRefs(objAddr, methodTable, Size);
+ pHolder->PrintObjectTail();
+
+ if (pHolder->m_objVisited % 1024 == 0) {
+ ExtOut(".");
+ if (pHolder->m_objVisited % (1024*64) == 0)
+ ExtOut("\r\n");
+ }
+ }
+}
+
+void HeapTraverser::TraceHandles()
+{
+ unsigned int fetched = 0;
+ SOSHandleData data[64];
+
+ ToRelease<ISOSHandleEnum> handles;
+ HRESULT hr = g_sos->GetHandleEnum(&handles);
+ if (FAILED(hr))
+ return;
+
+ do
+ {
+ hr = handles->Next(_countof(data), data, &fetched);
+
+ if (FAILED(hr))
+ break;
+
+ for (unsigned int i = 0; i < fetched; ++i)
+ PrintRoot(W("handle"), (size_t)data[i].Handle);
+ } while (fetched == _countof(data));
+}
+
+/* static */ void HeapTraverser::GatherTypes(DWORD_PTR objAddr,size_t Size,
+ DWORD_PTR methodTable, LPVOID token)
+{
+ if (!IsMTForFreeObj (methodTable))
+ {
+ HeapTraverser *pHolder = (HeapTraverser *) token;
+ pHolder->insert(methodTable);
+ }
+}
+
+void HeapTraverser::PrintRefs(size_t obj, size_t methodTable, size_t size)
+{
+ DWORD_PTR dwAddr = methodTable;
+
+ // TODO: pass info to callback having to lookup the MethodTableInfo again
+ MethodTableInfo* info = g_special_mtCache.Lookup((DWORD_PTR)methodTable);
+ _ASSERTE(info->IsInitialized()); // This is the second pass, so we should be intialized
+
+ if (!info->bContainsPointers)
+ return;
+
+ // Fetch the GCInfo from the other process
+ CGCDesc *map = info->GCInfo;
+ if (map == NULL)
+ {
+ INT_PTR nEntries;
+ move_xp (nEntries, dwAddr-sizeof(PVOID));
+ bool arrayOfVC = false;
+ if (nEntries<0)
+ {
+ arrayOfVC = true;
+ nEntries = -nEntries;
+ }
+
+ size_t nSlots = 1+nEntries*sizeof(CGCDescSeries)/sizeof(DWORD_PTR);
+ info->GCInfoBuffer = new DWORD_PTR[nSlots];
+ if (info->GCInfoBuffer == NULL)
+ {
+ ReportOOM();
+ return;
+ }
+
+ if (FAILED(rvCache->Read(TO_CDADDR(dwAddr - nSlots*sizeof(DWORD_PTR)),
+ info->GCInfoBuffer, (ULONG) (nSlots*sizeof(DWORD_PTR)), NULL)))
+ return;
+
+ map = info->GCInfo = (CGCDesc*)(info->GCInfoBuffer+nSlots);
+ info->ArrayOfVC = arrayOfVC;
+ }
+
+ mCache.EnsureRangeInCache((TADDR)obj, (unsigned int)size);
+ for (sos::RefIterator itr(obj, info->GCInfo, info->ArrayOfVC, &mCache); itr; ++itr)
+ {
+ if (*itr && (!m_verify || sos::IsObject(*itr)))
+ PrintObjectMember(*itr, false);
+ }
+
+ std::unordered_map<TADDR, std::list<TADDR>>::iterator itr = mDependentHandleMap.find((TADDR)obj);
+ if (itr != mDependentHandleMap.end())
+ {
+ for (std::list<TADDR>::iterator litr = itr->second.begin(); litr != itr->second.end(); ++litr)
+ {
+ PrintObjectMember(*litr, true);
+ }
+ }
+}
+
+
+void sos::ObjectIterator::BuildError(char *out, size_t count, const char *format, ...) const
+{
+ if (out == NULL || count == 0)
+ return;
+
+ va_list args;
+ va_start(args, format);
+
+ int written = vsprintf_s(out, count, format, args);
+ if (written > 0 && mLastObj)
+ sprintf_s(out+written, count-written, "\nLast good object: %p.\n", (int*)mLastObj);
+
+ va_end(args);
+}
+
+bool sos::ObjectIterator::VerifyObjectMembers(char *reason, size_t count) const
+{
+ if (!mCurrObj.HasPointers())
+ return true;
+
+ size_t size = mCurrObj.GetSize();
+ size_t objAddr = (size_t)mCurrObj.GetAddress();
+ TADDR mt = mCurrObj.GetMT();
+
+ INT_PTR nEntries;
+ MOVE(nEntries, mt-sizeof(PVOID));
+ if (nEntries < 0)
+ nEntries = -nEntries;
+
+ size_t nSlots = 1 + nEntries * sizeof(CGCDescSeries)/sizeof(DWORD_PTR);
+ ArrayHolder<DWORD_PTR> buffer = new DWORD_PTR[nSlots];
+
+ if (FAILED(g_ExtData->ReadVirtual(TO_CDADDR(mt - nSlots*sizeof(DWORD_PTR)),
+ buffer, (ULONG) (nSlots*sizeof(DWORD_PTR)), NULL)))
+ {
+ BuildError(reason, count, "Object %s has a bad GCDesc.", DMLObject(objAddr));
+ return false;
+ }
+
+ CGCDesc *map = (CGCDesc *)(buffer+nSlots);
+ CGCDescSeries* cur = map->GetHighestSeries();
+ CGCDescSeries* last = map->GetLowestSeries();
+
+ const size_t bufferSize = sizeof(size_t)*128;
+ size_t objBuffer[bufferSize/sizeof(size_t)];
+ size_t dwBeginAddr = (size_t)objAddr;
+ size_t bytesInBuffer = bufferSize;
+ if (size < bytesInBuffer)
+ bytesInBuffer = size;
+
+
+ if (FAILED(g_ExtData->ReadVirtual(TO_CDADDR(dwBeginAddr), objBuffer, (ULONG) bytesInBuffer,NULL)))
+ {
+ BuildError(reason, count, "Object %s: Failed to read members.", DMLObject(objAddr));
+ return false;
+ }
+
+ BOOL bCheckCard = TRUE;
+ {
+ DWORD_PTR dwAddrCard = (DWORD_PTR)objAddr;
+ while (dwAddrCard < objAddr + size)
+ {
+ if (CardIsSet (mHeaps[mCurrHeap], dwAddrCard))
+ {
+ bCheckCard = FALSE;
+ break;
+ }
+ dwAddrCard += card_size;
+ }
+ if (bCheckCard)
+ {
+ dwAddrCard = objAddr + size - 2*sizeof(PVOID);
+ if (CardIsSet (mHeaps[mCurrHeap], dwAddrCard))
+ {
+ bCheckCard = FALSE;
+ }
+ }
+ }
+
+ if (cur >= last)
+ {
+ do
+ {
+ BYTE** parm = (BYTE**)((objAddr) + cur->GetSeriesOffset());
+ BYTE** ppstop =
+ (BYTE**)((BYTE*)parm + cur->GetSeriesSize() + (size));
+ while (parm < ppstop)
+ {
+ CheckInterrupt();
+ size_t dwAddr1;
+
+ // Do we run out of cache?
+ if ((size_t)parm >= dwBeginAddr+bytesInBuffer)
+ {
+ // dwBeginAddr += bytesInBuffer;
+ dwBeginAddr = (size_t)parm;
+ if (dwBeginAddr >= objAddr + size)
+ {
+ return true;
+ }
+ bytesInBuffer = bufferSize;
+ if (objAddr+size-dwBeginAddr < bytesInBuffer)
+ {
+ bytesInBuffer = objAddr+size-dwBeginAddr;
+ }
+ if (FAILED(g_ExtData->ReadVirtual(TO_CDADDR(dwBeginAddr), objBuffer, (ULONG) bytesInBuffer, NULL)))
+ {
+ BuildError(reason, count, "Object %s: Failed to read members.", DMLObject(objAddr));
+ return false;
+ }
+ }
+ dwAddr1 = objBuffer[((size_t)parm-dwBeginAddr)/sizeof(size_t)];
+ if (dwAddr1) {
+ DWORD_PTR dwChild = dwAddr1;
+ // Try something more efficient than IsObject here. Is the methodtable valid?
+ size_t s;
+ BOOL bPointers;
+ DWORD_PTR dwAddrMethTable;
+ if (FAILED(GetMTOfObject(dwAddr1, &dwAddrMethTable)) ||
+ (GetSizeEfficient(dwAddr1, dwAddrMethTable, FALSE, s, bPointers) == FALSE))
+ {
+ BuildError(reason, count, "object %s: bad member %p at %p", DMLObject(objAddr),
+ SOS_PTR(dwAddr1), SOS_PTR(objAddr+(size_t)parm-objAddr));
+
+ return false;
+ }
+
+ if (IsMTForFreeObj(dwAddrMethTable))
+ {
+ sos::Throw<HeapCorruption>("object %s contains free object %p at %p", DMLObject(objAddr),
+ SOS_PTR(dwAddr1), SOS_PTR(objAddr+(size_t)parm-objAddr));
+ }
+
+ // verify card table
+ if (bCheckCard &&
+ NeedCard(objAddr+(size_t)parm-objAddr,dwChild))
+ {
+ BuildError(reason, count, "Object %s: %s missing card_table entry for %p",
+ DMLObject(objAddr), (dwChild == dwAddr1)? "" : " maybe",
+ SOS_PTR(objAddr+(size_t)parm-objAddr));
+
+ return false;
+ }
+ }
+ parm++;
+ }
+ cur--;
+ CheckInterrupt();
+
+ } while (cur >= last);
+ }
+ else
+ {
+ int cnt = (int) map->GetNumSeries();
+ BYTE** parm = (BYTE**)((objAddr) + cur->startoffset);
+ while ((BYTE*)parm < (BYTE*)((objAddr)+(size)-plug_skew))
+ {
+ for (int __i = 0; __i > cnt; __i--)
+ {
+ CheckInterrupt();
+
+ unsigned skip = cur->val_serie[__i].skip;
+ unsigned nptrs = cur->val_serie[__i].nptrs;
+ BYTE** ppstop = parm + nptrs;
+ do
+ {
+ size_t dwAddr1;
+ // Do we run out of cache?
+ if ((size_t)parm >= dwBeginAddr+bytesInBuffer)
+ {
+ // dwBeginAddr += bytesInBuffer;
+ dwBeginAddr = (size_t)parm;
+ if (dwBeginAddr >= objAddr + size)
+ return true;
+
+ bytesInBuffer = bufferSize;
+ if (objAddr+size-dwBeginAddr < bytesInBuffer)
+ bytesInBuffer = objAddr+size-dwBeginAddr;
+
+ if (FAILED(g_ExtData->ReadVirtual(TO_CDADDR(dwBeginAddr), objBuffer, (ULONG) bytesInBuffer, NULL)))
+ {
+ BuildError(reason, count, "Object %s: Failed to read members.", DMLObject(objAddr));
+ return false;
+ }
+ }
+ dwAddr1 = objBuffer[((size_t)parm-dwBeginAddr)/sizeof(size_t)];
+ {
+ if (dwAddr1)
+ {
+ DWORD_PTR dwChild = dwAddr1;
+ // Try something more efficient than IsObject here. Is the methodtable valid?
+ size_t s;
+ BOOL bPointers;
+ DWORD_PTR dwAddrMethTable;
+ if (FAILED(GetMTOfObject(dwAddr1, &dwAddrMethTable)) ||
+ (GetSizeEfficient(dwAddr1, dwAddrMethTable, FALSE, s, bPointers) == FALSE))
+ {
+ BuildError(reason, count, "Object %s: Bad member %p at %p.\n", DMLObject(objAddr),
+ SOS_PTR(dwAddr1), SOS_PTR(objAddr+(size_t)parm-objAddr));
+
+ return false;
+ }
+
+ if (IsMTForFreeObj(dwAddrMethTable))
+ {
+ BuildError(reason, count, "Object %s contains free object %p at %p.", DMLObject(objAddr),
+ SOS_PTR(dwAddr1), SOS_PTR(objAddr+(size_t)parm-objAddr));
+ return false;
+ }
+
+ // verify card table
+ if (bCheckCard &&
+ NeedCard (objAddr+(size_t)parm-objAddr,dwAddr1))
+ {
+ BuildError(reason, count, "Object %s:%s missing card_table entry for %p",
+ DMLObject(objAddr), (dwChild == dwAddr1) ? "" : " maybe",
+ SOS_PTR(objAddr+(size_t)parm-objAddr));
+
+ return false;
+ }
+ }
+ }
+ parm++;
+ CheckInterrupt();
+ } while (parm < ppstop);
+ parm = (BYTE**)((BYTE*)parm + skip);
+ }
+ }
+ }
+
+ return true;
+}
+
+bool sos::ObjectIterator::Verify(char *reason, size_t count) const
+{
+ try
+ {
+ TADDR mt = mCurrObj.GetMT();
+
+ if (MethodTable::GetFreeMT() == mt)
+ {
+ return true;
+ }
+
+ size_t size = mCurrObj.GetSize();
+ if (size < min_obj_size)
+ {
+ BuildError(reason, count, "Object %s: Size %d is too small.", DMLObject(mCurrObj.GetAddress()), size);
+ return false;
+ }
+
+ if (mCurrObj.GetAddress() + mCurrObj.GetSize() > mSegmentEnd)
+ {
+ BuildError(reason, count, "Object %s is too large. End of segment at %p.", DMLObject(mCurrObj), mSegmentEnd);
+ return false;
+ }
+
+ BOOL bVerifyMember = TRUE;
+
+ // If we requested to verify the object's members, the GC may be in a state where that's not possible.
+ // Here we check to see if the object in question needs to have its members updated. If so, we turn off
+ // verification for the object.
+ BOOL consider_bgc_mark = FALSE, check_current_sweep = FALSE, check_saved_sweep = FALSE;
+ should_check_bgc_mark(mHeaps[mCurrHeap], mSegment, &consider_bgc_mark, &check_current_sweep, &check_saved_sweep);
+ bVerifyMember = fgc_should_consider_object(mHeaps[mCurrHeap], mCurrObj.GetAddress(), mSegment,
+ consider_bgc_mark, check_current_sweep, check_saved_sweep);
+
+ if (bVerifyMember)
+ return VerifyObjectMembers(reason, count);
+ }
+ catch(const sos::Exception &e)
+ {
+ BuildError(reason, count, e.GetMesssage());
+ return false;
+ }
+
+ return true;
+}
+
+bool sos::ObjectIterator::Verify() const
+{
+ char *c = NULL;
+ return Verify(c, 0);
+}
diff --git a/src/ToolBox/SOS/Strike/inc/.gitmirror b/src/ToolBox/SOS/Strike/inc/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/ToolBox/SOS/Strike/inc/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/ToolBox/SOS/Strike/inc/dbgeng.h b/src/ToolBox/SOS/Strike/inc/dbgeng.h
new file mode 100644
index 0000000000..73e4d19f99
--- /dev/null
+++ b/src/ToolBox/SOS/Strike/inc/dbgeng.h
@@ -0,0 +1,16122 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+//----------------------------------------------------------------------------
+//
+// Debugger engine interfaces.
+//
+
+//
+//----------------------------------------------------------------------------
+
+#ifndef __DBGENG_H__
+#define __DBGENG_H__
+
+#include <stdarg.h>
+#include <objbase.h>
+
+#ifndef _WDBGEXTS_
+typedef struct _WINDBG_EXTENSION_APIS32* PWINDBG_EXTENSION_APIS32;
+typedef struct _WINDBG_EXTENSION_APIS64* PWINDBG_EXTENSION_APIS64;
+#endif
+
+#ifndef _CRASHLIB_
+typedef struct _MEMORY_BASIC_INFORMATION64* PMEMORY_BASIC_INFORMATION64;
+#endif
+
+#ifndef __specstrings
+// Should include SpecStrings.h to get proper definitions.
+#define __in
+#define __in_opt
+#define __in_bcount(x)
+#define __in_bcount_opt(x)
+#define __in_ecount(x)
+#define __in_ecount_opt(x)
+#define __out
+#define __out_opt
+#define __out_bcount(x)
+#define __out_bcount_opt(x)
+#define __out_ecount(x)
+#define __out_ecount_opt(x)
+#define __out_xcount(x)
+#define __inout
+#define __inout_opt
+#define __reserved
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//----------------------------------------------------------------------------
+//
+// GUIDs and interface forward declarations.
+//
+//----------------------------------------------------------------------------
+
+/* f2df5f53-071f-47bd-9de6-5734c3fed689 */
+DEFINE_GUID(IID_IDebugAdvanced, 0xf2df5f53, 0x071f, 0x47bd,
+ 0x9d, 0xe6, 0x57, 0x34, 0xc3, 0xfe, 0xd6, 0x89);
+/* 716d14c9-119b-4ba5-af1f-0890e672416a */
+DEFINE_GUID(IID_IDebugAdvanced2, 0x716d14c9, 0x119b, 0x4ba5,
+ 0xaf, 0x1f, 0x08, 0x90, 0xe6, 0x72, 0x41, 0x6a);
+/* cba4abb4-84c4-444d-87ca-a04e13286739 */
+DEFINE_GUID(IID_IDebugAdvanced3, 0xcba4abb4, 0x84c4, 0x444d,
+ 0x87, 0xca, 0xa0, 0x4e, 0x13, 0x28, 0x67, 0x39);
+/* 5bd9d474-5975-423a-b88b-65a8e7110e65 */
+DEFINE_GUID(IID_IDebugBreakpoint, 0x5bd9d474, 0x5975, 0x423a,
+ 0xb8, 0x8b, 0x65, 0xa8, 0xe7, 0x11, 0x0e, 0x65);
+/* 1b278d20-79f2-426e-a3f9-c1ddf375d48e */
+DEFINE_GUID(IID_IDebugBreakpoint2, 0x1b278d20, 0x79f2, 0x426e,
+ 0xa3, 0xf9, 0xc1, 0xdd, 0xf3, 0x75, 0xd4, 0x8e);
+/* 27fe5639-8407-4f47-8364-ee118fb08ac8 */
+DEFINE_GUID(IID_IDebugClient, 0x27fe5639, 0x8407, 0x4f47,
+ 0x83, 0x64, 0xee, 0x11, 0x8f, 0xb0, 0x8a, 0xc8);
+/* edbed635-372e-4dab-bbfe-ed0d2f63be81 */
+DEFINE_GUID(IID_IDebugClient2, 0xedbed635, 0x372e, 0x4dab,
+ 0xbb, 0xfe, 0xed, 0x0d, 0x2f, 0x63, 0xbe, 0x81);
+/* dd492d7f-71b8-4ad6-a8dc-1c887479ff91 */
+DEFINE_GUID(IID_IDebugClient3, 0xdd492d7f, 0x71b8, 0x4ad6,
+ 0xa8, 0xdc, 0x1c, 0x88, 0x74, 0x79, 0xff, 0x91);
+/* ca83c3de-5089-4cf8-93c8-d892387f2a5e */
+DEFINE_GUID(IID_IDebugClient4, 0xca83c3de, 0x5089, 0x4cf8,
+ 0x93, 0xc8, 0xd8, 0x92, 0x38, 0x7f, 0x2a, 0x5e);
+/* e3acb9d7-7ec2-4f0c-a0da-e81e0cbbe628 */
+DEFINE_GUID(IID_IDebugClient5, 0xe3acb9d7, 0x7ec2, 0x4f0c,
+ 0xa0, 0xda, 0xe8, 0x1e, 0x0c, 0xbb, 0xe6, 0x28);
+/* 5182e668-105e-416e-ad92-24ef800424ba */
+DEFINE_GUID(IID_IDebugControl, 0x5182e668, 0x105e, 0x416e,
+ 0xad, 0x92, 0x24, 0xef, 0x80, 0x04, 0x24, 0xba);
+/* d4366723-44df-4bed-8c7e-4c05424f4588 */
+DEFINE_GUID(IID_IDebugControl2, 0xd4366723, 0x44df, 0x4bed,
+ 0x8c, 0x7e, 0x4c, 0x05, 0x42, 0x4f, 0x45, 0x88);
+/* 7df74a86-b03f-407f-90ab-a20dadcead08 */
+DEFINE_GUID(IID_IDebugControl3, 0x7df74a86, 0xb03f, 0x407f,
+ 0x90, 0xab, 0xa2, 0x0d, 0xad, 0xce, 0xad, 0x08);
+/* 94e60ce9-9b41-4b19-9fc0-6d9eb35272b3 */
+DEFINE_GUID(IID_IDebugControl4, 0x94e60ce9, 0x9b41, 0x4b19,
+ 0x9f, 0xc0, 0x6d, 0x9e, 0xb3, 0x52, 0x72, 0xb3);
+/* 88f7dfab-3ea7-4c3a-aefb-c4e8106173aa */
+DEFINE_GUID(IID_IDebugDataSpaces, 0x88f7dfab, 0x3ea7, 0x4c3a,
+ 0xae, 0xfb, 0xc4, 0xe8, 0x10, 0x61, 0x73, 0xaa);
+/* 7a5e852f-96e9-468f-ac1b-0b3addc4a049 */
+DEFINE_GUID(IID_IDebugDataSpaces2, 0x7a5e852f, 0x96e9, 0x468f,
+ 0xac, 0x1b, 0x0b, 0x3a, 0xdd, 0xc4, 0xa0, 0x49);
+/* 23f79d6c-8aaf-4f7c-a607-9995f5407e63 */
+DEFINE_GUID(IID_IDebugDataSpaces3, 0x23f79d6c, 0x8aaf, 0x4f7c,
+ 0xa6, 0x07, 0x99, 0x95, 0xf5, 0x40, 0x7e, 0x63);
+/* d98ada1f-29e9-4ef5-a6c0-e53349883212 */
+DEFINE_GUID(IID_IDebugDataSpaces4, 0xd98ada1f, 0x29e9, 0x4ef5,
+ 0xa6, 0xc0, 0xe5, 0x33, 0x49, 0x88, 0x32, 0x12);
+/* 337be28b-5036-4d72-b6bf-c45fbb9f2eaa */
+DEFINE_GUID(IID_IDebugEventCallbacks, 0x337be28b, 0x5036, 0x4d72,
+ 0xb6, 0xbf, 0xc4, 0x5f, 0xbb, 0x9f, 0x2e, 0xaa);
+/* 0690e046-9c23-45ac-a04f-987ac29ad0d3 */
+DEFINE_GUID(IID_IDebugEventCallbacksWide, 0x0690e046, 0x9c23, 0x45ac,
+ 0xa0, 0x4f, 0x98, 0x7a, 0xc2, 0x9a, 0xd0, 0xd3);
+/* 9f50e42c-f1